Contents

浅记Redis知识点

参考资料

基础概念 & 面试要点

什么是 Redis?

Remote Dictionary Server(远程字典服务器)
Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存亦可磁盘持久化的 Key-Value 数据库
提供多种语言的 API,属于 非关系型数据库(NoSQL)

传统关系型数据库遵循 ACID(原子性、一致性、隔离性、持久性)
NoSQL 多采用 CAP 定理(分区容错、可用性、一致性)进行权衡

Redis 支持的数据类型

Redis 可以存储键和不同类型的值之间的映射
键只能是 二进制安全的字符串(binary‑safe string)
值支持 5 大核心数据结构:StringHashListSetZSet,以及高级结构如 HyperLogLogBitmapStream

类型 命令示例 适用场景
String SET key valueGET keyINCR key 缓存、计数器、会话 ID
Hash HSET user name TomHGETALL user 对象/用户信息、字段级查询
List LPUSH list v1 v2LRANGE list 0 -1 队列、任务列表、时间线
Set SADD set a bSMEMBERS setSINTER set1 set2 去重集合、共同好友、标签交叉
ZSet ZADD zset 10 aZRANGE zset 0 -1ZREVRANK zset a 排行榜、带权重的集合
HyperLogLog PFADD hll a bPFCOUNT hll 大基数去重统计(近似计数)
Pub/Sub SUBSCRIBE chPUBLISH ch "msg" 消息广播、实时推送
事务 MULTI … EXECWATCH key 原子批处理

什么是 Redis 持久化?

持久化就是 把内存的数据写到磁盘中去,防止服务宕机了导致内存数据丢失
Redis 提供了两种持久化方式

  • RDB(默认)

    RDB 是 Redis DataBase 的缩写
    优点是启动快、文件紧凑、适合灾备,但是可能丢失最近几秒数据

    • 功能核心函数

      rdbSave 生成 RDB 文件
      rdbLoad 从文件加载内存

      rdbSave 将内存快照写入 .rdb 文件,rdbLoad 从文件恢复

  • AOF
    AOF 是 Append-only File 的缩写

    每执行写命令都会将 aof_buf 中的缓存写入到 AOF 文件,然后定期调用 fsync 或 fdatasync 函数进行持久化,将 AOF 文件保存到磁盘中

    优点是数据完整性最高(几乎不丢失),但是文件会随时间增长,需要 BGREWRITEAOF 重写压缩

Redis 的用途

Redis 的数据是存在内存中的,所以读写速度非常快
每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value 数据库
故 redis 被广泛应用于缓存

Redis 支持 主从复制和分布式部署,可以轻松扩展以支持更多的用户和更高的负载
主要有以下的应用场景:

  • 缓存

    将热点数据放到内存中,设置 内存的最大使用量 以及 淘汰策略(LRU/LFU) 来保证缓存的命中率

  • 计数器

    可以对 String 进行自增自减运算,从而实现计数器功能
    Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量

  • 会话缓存

    可以使用 Redis 来统一存储多台应用服务器的会话信息

  • 全页缓存(FPC)

    除基本的会话 token 之外,Redis 还提供很简便的 FPC 平台
    Magento、WordPress 插件可以使用 Redis 作为全页缓存后端,帮助你以最快速度加载你 曾浏览过的页面

  • 查找表

    查找表和缓存类似,也是利用了 Redis 快速的查找特性
    可以用于存储 DNS 记录、商品 SKU

    但是查找表的内容不能失效,而 缓存的内容可以失效,因为缓存不作为可靠的数据来源

  • 分布式锁实现

    在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步
    可以使用 Redis 自带的 SET key value NX PX <ms> 命令实现分布式锁

  • 其它
    Set 可以实现 交集、并集 等操作,从而实现 共同好友 等功能
    ZSet 按分数排序、范围查询,可以实现 排行榜 等功能

配置

bind 127.0.0.1
绑定 ip:如果需要远程访问,可将此行注释掉,或绑定⼀个真实 ip

port 6379
端⼝,默认为 6379

daemonize yes
以守护进程运⾏(不会在命令⾏阻塞,类似于服务)
如果以非守护进程运⾏,则当前终端被阻塞

database 16
数据库数量,默认有 16 个,SELECT 0~15 切换

appendonly no
开启 AOF 持久化

appendfilename appendonly.aof
AOF 文件名

appendfsync everysec
AOF 刷盘频率

maxmemory
设置内存上限

maxmemory-policy noeviction
内存淘汰策略(volatile‑lrul, allkeys‑lru、…)

生产环境一般 appendonly yesappendfsync everysecmaxmemory <size> + maxmemory-policy allkeys-lru

常用命令

服务器端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 启动/停止/查看服务
redis-server --service-start
# 指定配置文件启动
redis-server /etc/redis/redis.conf

# 使⽤ help 查看帮助⽂档
redis-server --help

# 管理 redis 服务器进程
ps aux | grep redis
sudo kill -9 <pid进程号>

客户端

1
2
3
4
5
6
7
# 启动客户端
redis-cli -h 127.0.0.1 -p 6379

# 基础交互
ping                     # 检查是否存活
select 2                 # 切换数据库(0-15)
info                     # 查看服务器信息

连接 redis 默认选择第一个数据库

数据操作

redis 是 key-value 的数据结构,每条数据都是⼀个键值对
键的类型是字符串
值的类型分为五种,字符串 string、哈希 hash、列表 list、集合 set、有序集合 zset

注意:键不能重复

  • 数据操作行为
    保存
    删除
    修改
    获取

string

字符串类型是 Redis 中最为基础的数据存储类型
它在 Redis 中是 二进制安全 的,这便意味着该类型可以接受任何格式的数据
如 JPEG 图像数据或 Json 对象描述信息等

在 Redis 中,字符串类型的 Value 最多可以容纳的数据长度是 512M

如果设置的键不存在则为添加,如果设置的键已经存在则修改

  • 设置键为 name,值为 Tom 的数据

    SET name Tom

  • 获取键 name 的值,如果不存在此键则返回 nil

    GET name

  • 设置键为 tmp,值为 Hello,过期时间为 3 秒的数据

    SETEX tmp 3 Hello

  • 设置键为 a1 值为 python、键为 a2 值为 java、键为 a3 值为 c

    MSET a1 python a2 java a3 c

  • 获取键 a1、a2、a3 的值

    MGET a1 a2 a3

  • 向键为 name 中追加值 cat

    APPEND name cat

  • 删除键 a2、a3 及对应的值

    DEL a2 a3

  • 设置键 a1 的过期时间为 3 秒

    如果没有指定过期时间则⼀直存在,直到使⽤ DEL 移除
    EXPIRE a1 3

  • 查看键 bb 剩余的有效时间

    TTL bb

  • 查看键 a1 的值类型,为 redis ⽀持的五种类型中的⼀种

    TYPE a1

  • 查看键

    • 查看所有键(生产环境慎用)

      KEYS *

    • 查看名称中包含 a 的键

      KEYS a*

  • 判断键 a1 是否存在

    EXISTS a1

hash

键存储的是对象,该对象可以包含多个属性
对应的值为 string 类型

  • 设置键 user 的属性 name 为 Tom

    HSET user name Tom

  • 设置键 user2 的属性 name 为 Jerry、属性 age 为 6、属性 date 为 2024

    HMSET user2 name Jerry age 6 date 2024

  • 获取键 user2 属性 name 的值

    HGET user2 name

  • 获取键 user2 属性 name、age 的值

    HMGET user2 name age

  • 获取键 user2 的所有属性

    HKEYS user2

  • 获取键 user2 所有属性的值

    HVALS user2

  • 删除键 user2 的属性 age

    HDEL user2 age

  • 删除整个 hash 键及值

    DEL user2

list

  • 从键为 user 的列表左侧分别加⼊数据 Tom、Jerry

    LPUSH user Tom Jerry

    1
    2
    
    1) "Jerry"
    2) "Tom"
    
    • 右侧分别加⼊数据 Tom、Jerry

      RPUSH user Tom Jerry

      1
      2
      
      1) "Tom"
      2) "Jerry"
      
  • 在键为 user 的列表中,元素 Tom 前加⼊ Spike

    LINSERT user BEFORE Tom Spike

    1
    2
    3
    
    1) "Jerry"
    2) "Spike"
    3) "Tom"
    
    • 元素 Tom 后面加⼊ Spike

      LINSERT user AFTER Tom Spike

      1
      2
      3
      
      1) "Jerry"
      2) "Tom"
      3) "Spike"
      
  • 获取键为 user 的列表指定范围内的元素

    LRANGE user 0 2
    下标索引从 0 开始,用法类似于 python

    1
    2
    
    1) "Jerry"
    2) "Spike"
    
    • 获取所有元素

      LRANGE user 0 -1

  • 修改键为 user 的列表中下标为 1 的元素值为 Tuffy

    LSET user 1 Tuffy

    1
    2
    3
    
    1) "Jerry"
    2) "Tuffy"
    3) "Tom"
    
  • 删除指定元素

    从键为 alist 的列表左侧分别加⼊数据 a b a b a b
    LPUSH alist a b a b a b

    1
    2
    3
    4
    5
    6
    
    1) "b"
    2) "a"
    3) "b"
    4) "a"
    5) "b"
    6) "a"
    

    从右往左删除两个 b 元素

    1
    2
    
    LREM alist -2 b
    LRANGE alist 0 -1
    
    1
    2
    3
    4
    
    1) "b"
    2) "a"
    3) "a"
    4) "a"
    

set

无序集合,无法修改
键对应的元素具有唯一性,为 string 类型
集合没有修改操作

  • 向键 user 的集合中添加元素 Tom、Jerry、Spike、Tuffy

    SADD user Tom Jerry Spike Tuffy

  • 获取键 user 的集合中所有元素

    SMEMBERS user

  • 删除键 user 的集合中元素 Tuffy

    SREM user Tuffy

zset

有序集合,特性与 set 相同
不同之处在于,每一个元素会关联一个 double 类型的 score
表示权重,用于从小到大排序

  • 向键 user 的集合中添加元素 Tom、Jerry、Spike、Tuffy

    权重分别为 4、5、6、3
    ZADD user 4 Tom 5 Jerry 6 Spike 3 Tuffy

  • 获取键 user 的集合中所有元素

    ZRANGE user 0 -1

  • 获取键 user 的集合中权限值在 5 和 6 之间的元素

    ZRANGEBYSCORE user 5 6

  • 获取键 user 的集合中元素 Tom 的权重

    ZSCORE user Tom

  • 删除键 user 的集合中元素 Tuffy

    ZREM user Tuffy

  • 删除键 user 的集合中权重在 5 和 6 之间的元素

    ZREMRANGEBYSCORE user 5 6

HyperLogLog

比如数据集 {1, 3, 5, 7, 5, 7, 8}
那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为 5
基数估计就是在误差可接受的范围内,快速计算基数

1
2
3
4
5
PFADD myhll redis
PFADD myhll redis
PFADD myhll mongodb
PFADD myhll mysql
PFCOUNT myhll

发布订阅

发送者(在 Redis 术语中称为发布者)发送消息,而接收者(订阅者)接收消息
需要开启两个客户端

  • 第一个作为接收者:

    SUBSCRIBE mychannel

  • 第二个作为发布者:

    PUBLISH mychannel "test"
    PUBLISH mychannel "hello world"
    PUBLISH mychannel "thank you"

事务

MULTI 用来组装一个事务
EXEC 用来执行一个事务
DISCARD 用来取消一个事务
WATCH 用来监视一些 key,一旦这些 key 在事务执行之前被改变,则取消事务的执行

与 Python 交互

先创建虚拟环境,然后在虚拟环境下安装依赖

1
2
3
python -m venv .venv
source .venv/bin/activate
pip install redis rq flask flask-session flask-rq2
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from redis import StrictRedis


# 创建 StrictRedis 对象
# 与 redis 服务器建立连接
sr = StrictRedis(
  host = 'localhost',
  port = 6379,
  db = 0
)

增删改查 CRUD

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from redis import StrictRedis


if __name__ == '__main__':
    try:
        sr = StrictRedis()
        # 查询键列表
        result = sr.keys()
        print(result)

        # 添加键值对
        result = sr.set('name', 'Tom')
        print(result)  # 若添加成功,则返回 True

        # 获取键对应的值
        result = sr.get('name')
        print(result)  # b'Tom'
        # 若键不存在,则返回 None

        # 键存在则进行修改,否则添加
        result = sr.set('name', 'Jerry')
        print(result)    # 若操作成功,则返回 True

        # 删除键值对
        result = sr.delete('name')
        print(result)    # 若删除成功,则返回受到影响的键数,否则返回 0
    except Exception as e:
        print(e)

作为任务队列 RQ

https://www.showapi.com/news/article/66d96abf4ddd79f11a0a9667

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from flask import Flask
# 引入 Redis Queue 的核心组件 Queue
from rq import Queue
import redis


app = Flask(__name__)
redis_url = 'redis://localhost:6379'
redis_conn = redis.Redis.from_url(redis_url)
q = Queue(connection=redis_conn)

@app.route('/perform_task')
def perform_task():
    def my_task(x, y):
        return x + y
    job = q.enqueue(my_task, 1, 2)
    return f'Task {job.id} enqueued'

作为缓存

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from flask import Flask
import redis


app = Flask(__name__)
redis_client = redis.Redis(host='localhost', port=6379)

@app.route('/cache_example')
def cache_example():
    cached_data = redis_client.get('my_data')
    # 获取键 my_data 对应的缓存数据,若不存在再写入
    if not cached_data:
        cached_data = 'Expensive data processing'
        redis_client.set('my_data', cached_data)
    return cached_data

会话管理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from flask import Flask
from flask_session import Session
import redis


app = Flask(__name__)
app.config.update(
    SESSION_TYPE='redis',
    SESSION_PERMANENT=False,
    SESSION_USE_SIGNER=True,
    SESSION_REDIS=redis.from_url('redis://localhost:6379')
)
Session(app)

允许开发者将用户的会话数据存储在服务器端,而不是在客户端的 cookie 中
这样可以减少 cookie 的大小,提高安全性
用户可以在不同的浏览器或设备上保持会话状态,因为会话数据存储在服务器端
可以通过设置 SESSION_PERMANENT 为 True 来持久化会话
SESSION_USE_SIGNER 启用签名,可以确保会话数据在传输过程中未被篡改,增强安全性

异步任务处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from flask import Flask
from flask_rq2 import RQ


app = Flask(__name__)
redis_url = 'redis://localhost:6379'
rq = RQ(app, connection(redis_url))

@app.route('/enqueue')
def enqueue():
    # 从队列中获取数据并异步处理
    job = rq.get_queue().enqueue(my_background_task, 'hello', 'world')
    return f'Job {job.id} enqueued'

搭建主从和集群

主从复制

一个 master 可以有多个 slave
一个 slave 又可以有多个 slave
这样就形成了强大的多级服务器集群架构
master 用来写数据
slave 用来读数据

下面在同一个主机上配置 master 和 slave 节点

  • 配置 master

    修改 redis 的配置文件,将 ip 绑定为本机的 ip ifconfig

    执行 sudo vi redis.conf

    1
    2
    3
    
    # 编辑 /etc/redis/redis.conf
    bind 127.0.0.1          # 若需外部访问改为本机 IP 或注释
    port 6379
    

    重启服务

    1
    2
    
    sudo service redis stop
    redis-server redis.conf
    
  • 配置 slave

    将 master 的配置文件复制一份为 slave.conf
    并配置 slave 的端口

    1
    2
    
    sudo cp /etc/redis/redis.conf ./slave.conf
    sudo vi slave.conf
    
    1
    2
    3
    4
    5
    
     # 编辑 slave.conf
    port 6378
    slaveof <master_ip> 6379   # 指定 master
    # 启动
    redis-server ./slave.conf
    
  • 验证

    查看配置结果

    redis-cli -h ip地址 info Replication

    进入 master 的客户端并进行写数据

    1
    2
    
    redis-cli -h ip地址 -p 6379
    set name Tom
    

    进入 slave 的客户端并进行写数据

    1
    2
    
    redis-cli -h ip地址 -p 6378
    get name
    

集群概述

https://www.cnblogs.com/wuxl360/p/5920330.html

如果同一时间访问量过大,或者遇到自然灾害,主服务器出现宕机,会导致数据服务挂掉
集群是一组相互独立的、通过高速网络互联的计算机
可以进行负载均衡,提高可用性和可缩放性

Redis Cluster 通过水平分片(slot)把键划分到 16384 个槽中,每个槽分配给不同节点,实现自动路由、故障转移和线性扩容

  • 适用场景:单机内存上限(数十 GB)无法满足、需要读写分担高可用

  • 部署要点

    • 至少 3 主 + 3 从(保证多数派决议)
    • 每节点 cluster-enabled yescluster-config-file nodes.confcluster-node-timeout 5000
    • 使用 redis-cli --cluster create <ip1:6379> <ip2:6379> … --cluster-replicas 1 初始化