Redis

Redis 相关点

Redis数据结构

  1. String(字符串)
  2. List(列表)
  3. Hash(键-值)
  4. Set(无序集合)
  5. ZSet(有序集合)

String-字符串

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
29
# 设置 key-value 类型的值
> SET name lin
OK
# 根据 key 获得对应的 value
> GET name
"lin"
# 判断某个 key 是否存在
> EXISTS name
(integer) 1
# 返回 key 所储存的字符串值的长度
> STRLEN name
(integer) 3
# 删除某个 key 对应的值
> DEL name
(integer) 1

# 批量设置 key-value 类型的值
> MSET key1 value1 key2 value2
OK
# 批量获取多个 key 对应的 value
> MGET key1 key2
1) "value1"
2) "value2"


# 使用场景
- setnx简单的分布式锁
- 记录用户登录
- 常规计数

List-列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 将一个或多个值value插入到key列表的表头(最左边),最后的值在最前面
> LPUSH key value [value ...]
# 将一个或多个值value插入到key列表的表尾(最右边)
> RPUSH key value [value ...]
# 移除并返回key列表的头元素
> LPOP key
# 移除并返回key列表的尾元素
> RPOP key

# 返回列表key中指定区间内的元素,区间以偏移量start和stop指定,从0开始
> LRANGE key start stop

# 从key列表表头弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
> BLPOP key [key ...] timeout
# 从key列表表尾弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
> BRPOP key [key ...] timeout

#应用场景
// 可以当作简单的 `消息队列`

Hash-键值对

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 存储一个哈希表key的键值
> HSET key field value
# 获取哈希表key对应的field键值
> HGET key field

# 在一个哈希表key中存储多个键值对
> HMSET key field value [field value...]
# 批量获取哈希表key中多个field键值
> HMGET key field [field ...]
# 删除哈希表key中的field键值
> HDEL key field [field ...]

# 返回哈希表key中field的数量
> HLEN key
# 返回哈希表key中所有的键值
> HGETALL key

# 为哈希表key中field键的值加上增量n
> HINCRBY key field n
# 使用场景
可以缓存对象信息、用户购物车

Set-无序键值集合

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
29
30
31
32
33
34
35
36
37
# 往集合key中存入元素,元素存在则忽略,若key不存在则新建
> SADD key member [member ...]
# 从集合key中删除元素
> SREM key member [member ...]
# 获取集合key中所有元素
> SMEMBERS key
# 获取集合key中的元素个数
> SCARD key

# 判断member元素是否存在于集合key中
> SISMEMBER key member

# 从集合key中随机选出count个元素,元素不从key中删除
> SRANDMEMBER key [count]
# 从集合key中随机选出count个元素,元素从key中删除
> SPOP key [count]
Set运算操作:

# 交集运算
> SINTER key [key ...]
# 将交集结果存入新集合destination中
> SINTERSTORE destination key [key ...]

# 并集运算
> SUNION key [key ...]
# 将并集结果存入新集合destination中
> SUNIONSTORE destination key [key ...]

# 差集运算
> SDIFF key [key ...]
# 将差集结果存入新集合destination中
> SDIFFSTORE destination key [key ...]

# 应用场景
- Set 类型可以保证一个用户只能点一个赞
- 共同关注
- 抽奖活动

Zset-有序集合

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
29
30
31
32
33
34
# 往有序集合key中加入带分值元素
> ZADD key score member [[score member]...]
# 往有序集合key中删除元素
> ZREM key member [member...]
# 返回有序集合key中元素member的分值
> ZSCORE key member
# 返回有序集合key中元素个数
> ZCARD key

# 为有序集合key中元素member的分值加上increment
> ZINCRBY key increment member

# 正序获取有序集合key从start下标到stop下标的元素
> ZRANGE key start stop [WITHSCORES]
# 倒序获取有序集合key从start下标到stop下标的元素
> ZREVRANGE key start stop [WITHSCORES]

# 返回有序集合中指定分数区间内的成员,分数由低到高排序。
> ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

# 返回指定成员区间内的成员,按字典正序排列, 分数必须相同。
> ZRANGEBYLEX key min max [LIMIT offset count]
# 返回指定成员区间内的成员,按字典倒序排列, 分数必须相同
> ZREVRANGEBYLEX key max min [LIMIT offset count]
# Zset 运算操作(相比于 Set 类型,ZSet 类型没有支持差集运算):

# 并集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积
> ZUNIONSTORE destkey numberkeys key [key...]
# 交集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积
> ZINTERSTORE destkey numberkeys key [key...]

# 应用场景
- 排行榜
- 电话姓名排序

Redis持久化

Redis持久化方式

  1. AOF日志(快照 + 追加命令)
  2. RDB快照(二进制文件)

AOF日志 (Appen Only File)

AOF 只会记录 写操作 ,读操作不会被记录,因为没意义

三种写回策略

如果每次修改都写入磁盘,会造成极大的资源浪费,所以redis 创建了 AOF 缓冲区,并提供了三种写回策略:

  • Always ,每次执行完写操作,同步 AOF 日志数据回硬盘
  • Everysec ,每次执行完写操作,先将命令写到 AOF 内核缓冲区,每秒将缓冲区里内容写回硬盘
  • No ,将写回时机交给操作系统决定

这三种策略知识在控制 fsync() 函数调用时机

AOF 重写机制

为了避免 AOF 文件越写越大,提供了 AOF 重写机制

AOF 重写机制是在重写时,读取当前数据库中所有键值对记录到[新的 AOF 文件] ,然后将新的 AOF 文件替换旧的 AOF 文件,从而实现 AOF 文件的压缩。

为何用新文件替换旧文件? 这样确保如果重写失败了,也不会对现有 AOF 文件造成污染。

AOF 重写过程由后台子进程 bgrewriteaof 来完成

主进程会复制一份 [页表](记录虚拟地址与物理地址映射关系) 给子进程 ,这样做 节约物理内存资源

写时复制

当父进程或子进程对内存发起写操作时, CPU 会触发 写保护中断 ,操作系统会进行 物理内存复制,并重新设置映射关系,将父进程的内存读写权限设置为 可读写,最后才对内存进行写操作,这个过程叫 写时复制(Copy On Write)

主进程修改已经存在的 key-value ,就会发生 写时复制 , 只会复制主进程修改的物理内存数据,没修改的内存还是与子进程共享

为了解决 写时复制主线程修改的值与 AOF 文件中数据不一致问题, redis 设置了 AOF 重写缓冲区

当重写 AOF 期间,当 redis 执行完一个写命令,会 同时将这个命令写到 [AOF 缓冲区] 和 [AOF 重写缓冲区]

当子进程完成 AOF 重写工作后,会向主进程发送一条信号,主进程收到信号会做以下事情

  • 将 AOF 重写缓冲区的数据写入 AOF 文件
  • 覆盖原有 AOF 文件

RDB

AOF 记录的写操作命令, RDB记录的是某一瞬间的数据快照

因此在进行数据恢复时, RDB 优于 AOF

RDB 生成时机

RDB 提供 savebgsave,区别在于是否在[主线程执行]

RDB通过配置文件来实现每隔一段时间自动执行一次 bgsave 命令

1
2
save 900 1
save 300 10

选项名叫save ,实际执行的是 bgsave

  • 900 秒内,对数据库进行了至少 1 次修改
  • 300 秒内,对数据库至少进行了 10 次修改

注: RDB 快照是全量快照

RDB 的写时复制

当在进行 bgsave 时,主进程依旧能 继续处理操作命令

如果这时主线程要 修改共享数据里的某一块数据 (如 A),就会发生写时复制,这块数据的 物理内存会被复制一份(A·) ,然后 主线程操作数据副本(A·) ,此时 bgsave 子进程可以继续把原来的数据(A) 写入到 RDB 文件中

注意,发生写时复制时, RDB 快照保存的是原本的内存数据 ,而修改的数据是没办法写入本次 RDB 文件的, 主线程刚修改的数据交由下次处理

aof-use-rdb-preamble yes

混合使用 AOF 和 RDB ,进行 混合持久化

前半部分是 RDB 的全量数据,后半部本是 AOF 格式的增量数据

这样 加载的时候速度会很快

并且 加载完 RDB 内容后,才会加载后半部分的 AOF 内容, 这里的内容是 redis 后台子进程重写 AOF 期间,主线程处理的操作命令,使 数据丢失更少

过期删除策略和内存淘汰策略

过期删除策略

redis 可以对 key 设置过期时间,相对应的机制将过期的键值对删除,这个机制就是过期键值删除策略

设置过期时间

一般使用 expire <key> <n> , 表示 key 在 n 秒后过期

也可以在创建时对 key 设置过期时间 set <key> <value> ex <n> setex <key> <n> <value> ,表示设置键值对时,同时设置过期时间(单位 )

如果想查看某个 key 剩余存活时间,可以使用 TTL <key> 命令

1
2
3
4
5
6
7
# 设置键值对,同时指定过期时间 60 秒
> setex key1 60 value1
ok

# 查看 key1 过期时间
> ttl key1
(integer) 56

如果反悔,取消 key 的过期时间,可以用 persist <key> 命令

1
2
3
4
5
6
7
8
# 取消 key1 的过期时间
> persist key1
(integer) 1

# 查看 key1 过期时间
# -1 则表明永不过期
> ttl key1
(integer) -1

Redis 如何判定过期

当我们设置过期时间时, redis 会把该 key 带上过期时间存储到一个 过期字典
(实际上时 哈希表 ,时间复杂度 O(1))

过期删除策略

  • 定时删除
  • 惰性删除
  • 定期删除

定期删除: 在设置 key 的过期时间时,同时创建一个定时事件,当时间到达,由该事件处理器自动执行 key 的删除

惰性删除: 不主动删除过期 key , 每次从数据库访问时进行检测,如果过期则删除 key

定时删除: 每隔一段时间 [随机(默认20个)] 从数据库取出一定量的 key 进行检查,并删除其中的 过期 key

redis 过期删除策略

Redis 选择 [惰性删除 + 定期删除] 配合使用

惰性删除: 对访问的 key 判断是否过期,如果过期删除 key ,并且返回 null

定期删除: redis 默认每秒进行 10 次过期检查,通过 redis.conf.hz 进行设置;默认每次抽取 20 个 key,检查并删除过期的 key;如果本轮过期的数量超过 25% ,则重复执行,直至过期的 key 小于 25%,则停止继续删除过期 key , 等待下一轮检查

内存淘汰策略

过期删除策略是删除过期的 key ,而 Redis 内存淘汰策略是 当 Redis 运行内存超过 Redis 设置的最大内存后,用内存淘汰策略删除符合条件的 key , 以此来保障 Redis 高效运行

如何设置 Redis 最大运行内存

在 redis.conf 中,通过参数 maxmemory <bytes> 来设置

  • 在 64 位操作系统中,这个默认值为 0 ,表示没有内存大小限制
  • 在 32 位操作系统中,这个默认值为 3GB,因为 32 位的机器最大支持 4GB 内存

Redis 内存淘汰策略

  1. 不进行数据淘汰策略
    • noevication(redis3.0后, 默认的内存淘汰策略):不进行数据淘汰,拒绝写入
  2. 进行数据淘汰
    • volatile-random:随机淘汰,只淘汰设置了 expire 的 key
    • volatile-ttl:优先淘汰更早过期的 key
    • volatile-lru:淘汰所有设置了过期时间中,最久未使用的 key
    • volatile-lfu:淘汰所有设置了过期时间中,最少使用的 key
    • allkeys-random:随机淘汰
    • allkeys-lru:淘汰整个 key 中最久未使用的
    • allkeys-lfu:淘汰整个 key 中最少使用的

使用 config get maxmemory-policy 查看当前内存淘汰策略

设置内存淘汰策略方式

  1. 通过命令 config set maxmemory-policy <策略> 设置
  2. 通过 redis.conf 设置 maxmemory-policy <策略>

LRU 和 LFU

LRU:Least Recently Used, 最久未使用 , 维护一个额外字段,记录此数据最后一次访问时间; 无法解决缓存污染

LFU:Least Frequently Used,使用频率最少 ,记录使用次数

布隆过滤器

布隆过滤器(Bloom Filter) 是一种空间效率高的概率数据结构,用于快速判断一个元素是否再一个集合中

布隆过滤器遵循 存在的不一定存在,不存在的一定不存在

基本原理

一个位数组和多个哈希函数来实现

流程

  1. 初始化
    1. 创建一个大小为m大小的 位数组
    2. 对要添加的元素使用 k 个哈希函数分别计算,得到 k 个哈希值。
  2. 将这 k 个哈希值对应到位数组中的位置,并将这些位置设置为 1
  3. 查询元素,对要检查的元素分别进行哈希,然后 将哈希值对应位置与位数组进行比较,如果对应位置都为1,则表示 该元素可能 存在

优点

  • 空间效率高
  • 查询速度快
  • 简单易用

缺点

  • 误判
  • 无法删除元素
  • 需要合理选择参数

高可用

todo