$ ls ~yifei/notes/

redis 常见问题

Posted on:

Last modified:

1. 集合操作避免范围过大

使用 sortedset、set、list、hash 等集合类的 O(N) 操作时要评估当前元素个数的规模以及将来的增长规模,对于短期就可能变为大集合的 key,要预估 O(N) 操作的元素数量,避免全量操作,可以使用 HSCAN、SSCAN、ZSCAN 进行渐进操作。集合元素数量过大在使用过程中会影响 redis 的实际性能,元素个数建议尽量不要超过 5000,元素数量过大可考虑拆分成多个 key 进行处理。

2. 合理使用过期时间

如果 key 没有设置超时时间,会导致一直占用内存。对于可以预估使用生命周期的 key 应当设置合理的过期时间或在最后一次操作时进行清理,避免垃圾数据残留 redis。redis 不是垃圾桶。

3. 利用批量操作命令

假设要给一个集合导入 5000 个元素:

方案 1:直接使用 redis 的 HSET 逐个设置

for _ in 0..5000
    HSET hash, k,v

结果:失败。redis ops 飙升,同时接口响应超时

方案 2:改用 redis 的 HMSET 一次将所有元素设置到 hash 中

map<k, v> = 50000 个元素
HMSET hash map

结果:失败。出现 redis 慢日志

方案 3:依然使用 HMSET,只是每次设置 500 个,循环 100 次

map_chunk<k, v> = 500 个元素
for i in 0..100
    HMSET hash map_chunk[i]

结果:成功

MSET/HMSET 等都支持一次输入多个 key,LPUSH/RPUSH/SADD 等命令都支持一次输入多个 value, 也要注意每次操作数量不要过多,建议控制在 500 个以内

4. 合理设置值的大小

String 类型尽量控制在 10KB 以内。虽然 redis 对单个 key 可以缓存的对象长度能够支持的很大,但是实际使用场合一定要合理拆分过大的缓存项,1k 基本是 redis 性能的一个拐点。当缓存项超过 10k、100k、1m 性能下降会特别明显。关于吞吐量与数据大小的关系可见下面官方网站提供的示意图。

在局域网环境下只要传输的包不超过一个 MTU(以太网下大约 1500 bytes),那么对于 10、100、1000 bytes 不同包大小的处理吞吐能力实际结果差不多。

5. 禁用一些命令

keys、monitor、flushall、flushdb 应当通过 redis 的 rename 机制禁掉命令,若没有禁用,开发人员要谨慎使用。其中 flushall、flushdb 会清空 redis 数据;keys 命令可能会引起慢日志;monitor 命令在开启的情况下会降低 redis 的吞吐量,根据压测结果大概会降低 redis50% 的吞吐量,越多客户端开启该命令,吞吐量下降会越多。

keys 和 monitor 在一些必要的情况下还是有助于排查线上问题的,建议可在重命名后在必要情况下由 redis 相关负责人员在 redis 备机使用,monitor 命令可借助 redis-faina 等脚本工具进行辅助分析,能更快排查线上 ops 飙升等问题。

6. 避免大量 key 同时过期

如果大量的 key 过期时间设置得过于集中,到过期的时间点,redis 可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。

7. Redis 如何做持久化

bgsave 做镜像全量持久化,aof 做增量持久化。因为 bgsave 会耗费较长时间,不够实时,在停机的时候回导致大量丢失数据,所以需要 aof 来配合使用。在 redis 实例重启时,优先使用 aof 来回复内存状态,如果没有 aof 日志,就会使用 rdb 来恢复。

如果 aof 文件过大会导致恢复时间过长,不过 redis 会定期做 aof 重写,压缩 aof 文件日志大小。在 redis 4.0 之后还有了混合持久化的功能,将 bgsave 的全量和 aof 的增量做了融合处理,这样既保证了回复的效率有兼容了数据的安全性。

为了避免断电时后丢失数据,还可以设置 aof 日志的 sync 属性,极端情况下,可以每次写入都执行,不过会对性能有影响,一般每秒一次就可以。

8. 保存失败

redis 报错:Can't save in background: fork: Cannot allocate memory。

原因是 redis 在后台保存的时候会直接 fork 一下,然后保存。由于数据库过大,就会 fork 失败,但是实际上由于 copy-on-write 机制的存在,并不会产生问题。所以可以直接更改系统的配置,允许 fork。

/etc/sysctl.conf 文件修改如下:

vm.overcommit_memory=1

然后重新加载:

sysctl -p /etc/sysctl.conf

糟粕

redlock sentinel cluster 都是垃圾。完全没有遵循分布式系统的基本概念。

主从复制

使用 slaveof ip port 就可以作为副本。

主节点首先执行一次 bgsave,然后发送 RDB 文件到从节点实现一次全量复制。

主节点和从节点分别维护一个复制偏移量(offset),代表的是主节点向从节点传递的字节数;主节点每次向从节点传播 N 个字节数据时,主节点的 offset 增加 N;从节点每次收到主节点传来的 N 个字节数据时,从节点的 offset 增加 N。

一些实现细节和比较好的地方

redis 的 string 是 binary-safe 的,可以存储任意的二进制数据(bytes),甚至可以把图片存储在 redis 中

经常用到的场合

  1. 用作缓存

  2. 最基础的,最经典的应用场合,当查询数据库或者 ES 等存储代价比较高的时候,直接用查询的语句做 key,查询结果用作缓存

  3. 用做队列。Redis 5.0 之后最好用 Redis Stream,不要用 list。

  4. 用做集合,也就是存储一批数据的池子。用作有序集合

经常遇到的问题

过期之间只能指定到键级别,而不能指定到集合的键级别

pipeline

imporve performance by combining multi command into one and reduce TCP times

>>> p = r.pipeline()        # 创建一个管道
>>> p.set("hello","redis")
>>> p.sadd("faz","baz")
>>> p.incr("num")
>>> p.execute()
[True, 1, 1]
>>> r.get("hello")
"redis"

or

>>> p.set("hello","redis").sadd("faz","baz").incr("num").execute()

默认的情况下,管道里执行的命令可以保证执行的原子性,执行 pipe = r.pipeline(transaction=False) 可以禁用这一特性。

key 的命名

colon sign : is a convention when naming keys. Try to stick with a schema. For instance "object-type:id:field" can be a nice idea, like in "user:1000:password". I like to use dots for multi-words fields, like in "comment:1234:reply.to".

使用方法

Redis 是个好东西,提供了很多好用的功能,而且大部分实现的都还既可靠又高效(主从复制除外)。所以一开始我们犯了一个天真的用法错误:把所有不同类型的数据都放在了一组 Redis 集群中。

  • 长生命周期的用户状态数据
  • 临时缓存数据
  • 后台统计用的流水数据

导致的问题就是当你想扩分片的时候,客户端 Hash 映射就变了,这是要迁移数据的。而所有数据放在一组 Redis 里,要把它们分开就麻烦了,每个 Redis 实例里面都是千万级的 key。

根据数据性质把 Redis 集群分类;我的经验是分三类:cache、buffer 和 db

  • cache:临时缓存数据,加分片扩容容易,一般无持久化需要。
  • buffer:用作缓冲区,平滑后端数据库的写操作,根据数据重要性可能有持久化需求。
  • db:替代数据库的用法,有持久化需求。

规避在单实例上放热点 key。

同一系统下的不同子应用或服务使用的 Redis 也要隔离开

参考

  1. https://stackoverflow.com/questions/11752544/redis-bgsave-failed-because-fork-cannot-allocate-memory
  2. https://www.cnblogs.com/kismetv/p/9236731.html
  3. https://mp.weixin.qq.com/s/vS8IMgBIrfGpZYNUwtXrPQ

© 2016-2022 Yifei Kong. Powered by ynotes

All contents are under the CC-BY-NC-SA license, if not otherwise specified.

Opinions expressed here are solely my own and do not express the views or opinions of my employer.