本篇文章的前
比较高效的做法是,在服务内部集成链路追踪,也就是在服务访问外部依赖的出入口,记录下每次请求外部依赖的响应延时。
# 命令执行耗时超过 5 毫秒,记录慢日志
CONFIG SET slowlog-log-slower-than 5000
# 只保留最近 500 条慢日志
CONFIG SET slowlog-max-len 500
获取慢日志中的内容
SLOWLOG get 5
最佳实践
如果一个 key 写入的 value 非常大,那么 Redis 在分配内存时就会比较耗时。同样的,当删除这个 key 时,释放内存也会比较耗时,这种类型的 key 我们一般称之为 bigkey。
BigKey扫描命令
redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 0.01
扫描命令注意事项
最佳实践
Redis 的过期数据采用被动过期 + 主动过期两种策略:
最佳实践:
# 释放过期 key 的内存,放到后台线程执行
lazyfree-lazy-expire yes
最佳实践
你可以在 Redis 上执行 INFO 命令,查看 latest_fork_usec 项,单位微秒。
# 上一次 fork 耗时,单位微秒
latest_fork_usec:59477
这个时间就是主进程在 fork 子进程期间,整个实例阻塞无法处理客户端请求的时间。
最佳实践:
我们都知道,应用程序向操作系统申请内存时,是按内存页进行申请的,而常规的内存页大小是 4KB。
Linux 内核从 2.6.38 开始,支持了内存大页机制,该机制允许应用程序以 2MB 大小为单位,向操作系统申请内存。
应用程序每次向操作系统申请的内存单位变大了,但这也意味着申请内存的耗时变长。
最佳实践:关闭内存大页。
其实,操作系统提供的内存大页机制,其优势是,可以在一定程序上降低应用程序申请内存的次数。
但是对于 Redis 这种对性能和延迟极其敏感的数据库来说,我们希望 Redis 在每次申请内存时,耗时尽量短,所以我不建议你在 Redis 机器上开启这个机制。
幸运的是,Redis 提供了一个配置项,当子进程在 AOF rewrite 期间,可以让后台子线程不执行刷盘(不触发 fsync 系统调用)操作。
这相当于在 AOF rewrite 期间,临时把 appendfsync 设置为了 none,配置如下:
# AOF rewrite 期间,AOF 后台子线程不进行刷盘操作
# 相当于在这期间,临时把 appendfsync 设置为了 none
no-appendfsync-on-rewrite yes
当然,开启这个配置项,在 AOF rewrite 期间,如果实例发生宕机,那么此时会丢失更多的数据,性能和数据安全性,你需要权衡后进行选择。
如果占用磁盘资源的是其他应用程序,那就比较简单了,你需要定位到是哪个应用程序在大量写磁盘,然后把这个应用程序迁移到其他机器上执行就好了,避免对 Redis 产生影响。
当然,如果你对 Redis 的性能和数据安全都有很高的要求,那么我建议从硬件层面来优化,更换为 SSD 磁盘,提高磁盘的 IO 能力,保证 AOF 期间有充足的磁盘资源可以使用。
Redis 在 6.0 版本已经推出了这个功能,我们可以通过以下配置,对主线程、后台线程、后台 RDB 进程、AOF rewrite 进程,绑定固定的 CPU 逻辑核心:
# Redis Server 和 IO 线程绑定到 CPU核心 0,2,4,6
server_cpulist 0-7:2
# 后台子线程绑定到 CPU核心 1,3
bio_cpulist 1,3
# 后台 AOF rewrite 进程绑定到 CPU 核心 8,9,10,11
aof_rewrite_cpulist 8-11
# 后台 RDB 进程绑定到 CPU 核心 1,10,11
# bgsave_cpulist 1,10-1
最佳实践
这里我需要提醒你的是,一般来说,Redis 的性能已经足够优秀,除非你对 Redis 的性能有更加严苛的要求,否则不建议你绑定 CPU。
什么是 Swap?为什么使用 Swap 会导致 Redis 的性能下降?
如果你对操作系统有些了解,就会知道操作系统为了缓解内存不足对应用程序的影响,允许把一部分内存中的数据换到磁盘上,以达到应用程序对内存使用的缓冲,这些内存数据被换到磁盘上的区域,就是 Swap。
问题就在于,当内存中的数据被换到磁盘上后,Redis 再访问这些数据时,就需要从磁盘上读取,访问磁盘的速度要比访问内存慢几百倍!
# 先找到 Redis 的进程 ID
$ ps -aux | grep redis-server
# 查看 Redis Swap 使用情况
$ cat /proc/$pid/smaps | egrep '^(Swap|Size)'
预防实践
最佳实践
# 开启自动内存碎片整理(总开关)
activedefrag yes
# 内存使用 100MB 以下,不进行碎片整理
active-defrag-ignore-bytes 100mb
# 内存碎片率超过 10%,开始碎片整理
active-defrag-threshold-lower 10
# 内存碎片率超过 100%,尽最大努力碎片整理
active-defrag-threshold-upper 100
# 内存碎片整理占用 CPU 资源最小百分比
active-defrag-cycle-min 1
# 内存碎片整理占用 CPU 资源最大百分比
active-defrag-cycle-max 25
# 碎片整理期间,对于 List/Set/Hash/ZSet 类型元素一次 Scan 的数量
active-defrag-max-scan-fields 1000
Redis 的高性能,除了操作内存之外,就在于网络 IO 了,如果网络 IO 存在瓶颈,那么也会严重影响 Redis 的性能。
1:频繁短连接
你的业务应用,应该使用长连接操作 Redis,避免频繁的短连接。
频繁的短连接会导致 Redis 大量时间耗费在连接的建立和释放上,TCP 的三次握手和四次挥手同样也会增加访问延迟。
2:运维监控
3:其它程序争抢资源
最后需要提醒你的是,你的 Redis 机器最好专项专用,只用来部署 Redis 实例,不要部署其他应用程序,尽量给 Redis 提供一个相对「安静」的环境,避免其它程序占用 CPU、内存、磁盘资源,导致分配给 Redis 的资源不足而受到影响。
如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)
RDB:只持久化某一时刻的数据快照到磁盘上(创建一个子进程来做)
AOF:每一次写操作都持久到磁盘(主线程写内存,根据策略可以配置由主线程还是子线程进行数据持久化)
RDB 采用二进制 + 数据压缩的方式写磁盘,这样文件体积小,数据恢复速度也快
AOF 记录的是每一次写命令,数据最全,但文件体积大,数据恢复速度慢
如果你的业务对于数据丢失不敏感,采用 RDB 方案持久化数据
如果你的业务对数据完整性要求比较高,采用 AOF 方案持久化数据
我们可以对 AOF 文件定时 rewrite,避免这个文件体积持续膨胀,这样在恢复时就可以缩短恢复时间了。
Redis 4.0 以上版本才支持混合持久化。
缩短不可用时间:master 发生宕机,我们可以手动把 slave 提升为 master 继续提供服务
提升读性能:让 slave 分担一部分读请求,提升应用的整体性能
当 master 宕机时,我们需要「手动」把 slave 提升为 master,这个过程也是需要花费时间的。
虽然比恢复数据要快得多,但还是需要人工介入处理。一旦需要人工介入,就必须要算上人的反应时间、操作时间,所以,在这期间你的业务应用依旧会受到影响。
哨兵工作流程
多个哨兵的流程
在选举哨兵领导者时,我们可以制定这样一个选举规则:
这个算法还规定节点的数量必须是奇数个,这样可以保证系统中即使有节点发生了故障,剩余超过「半数」的节点状态正常,依旧可以提供正确的结果,也就是说,这个算法还兼容了存在故障节点的情况。
不足:当你的写请求量越来越大时,一个 master 实例可能就无法承担这么大的写流量了。
Redis最佳实践:7个维度+43条使用规范,带你彻底玩转Redis | 附实践清单
控制 key 的长度
避免存储 bigkey
选择合适的数据类型
例如,String、Set 在存储 int 数据时,会采用整数编码存储。Hash、ZSet 在元素数量比较少时(可配置),会采用压缩列表(ziplist)存储,在存储比较多的数据时,才会转换为哈希表和跳表。
String、Set:尽可能存储 int 类型数据
Hash、ZSet:存储的元素数量控制在转换阈值之下,以压缩列表存储,节约内存
把 Redis 当作缓存使用
应用写入到 Redis 中的数据,尽可能地都设置「过期时间」。
实例设置 maxmemory + 淘汰策略
数据压缩后写入 Redis
你的业务应用尽量不要存储 bigkey,避免操作延迟发生。
如果你确实有存储 bigkey 的需求,你可以把 bigkey 拆分为多个小 key 存储。
如果你无法避免存储 bigkey,那么我建议你开启 Redis 的 lazy-free 机制。(4.0+版本支持)
当开启这个机制后,Redis 在删除一个 bigkey 时,释放内存的耗时操作,将会放到后台线程中去执行,这样可以在最大程度上,避免对主线程的影响。
所以,你需要避免执行例如 SORT、SINTER、SINTERSTORE、ZUNIONSTORE、ZINTERSTORE 等聚合类命令。
对于这种聚合类操作,我建议你把它放到客户端来执行,不要让 Redis 承担太多的计算工作。
在查询数据时,你要遵循以下原则:
批量操作相比于多次单个操作的优势在于,可以显著减少客户端、服务端的来回网络 IO 次数。
所以我给你的建议是:
Redis 清理过期 key 是采用定时 + 懒惰的方式来做的,而且这个过程都是在主线程中执行。
你的业务应该使用长连接操作 Redis,避免短连接。
当使用短连接操作 Redis 时,每次都需要经过 TCP 三次握手、四次挥手,这个过程也会增加操作耗时。
同时,你的客户端应该使用连接池的方式访问 Redis,并设置合理的参数,长时间不操作 Redis 时,需及时释放连接资源。
如果你的业务写请求量很大,单个 Redis 实例已无法支撑这么大的写流量,那么此时你需要使用分片集群,分担写压力。
如果对于丢失数据不敏感的业务,我建议你不开启 AOF,避免 AOF 写磁盘拖慢 Redis 的性能。
如果确实需要开启 AOF,那么我建议你配置为 appendfsync everysec,把数据持久化的刷盘操作,放到后台线程中去执行,尽量降低 Redis 写磁盘对性能的影响。
Redis 在做数据持久化时,采用创建子进程的方式进行。
而创建子进程会调用操作系统的 fork 系统调用,这个系统调用的执行耗时,与系统环境有关。
虚拟机环境执行 fork 的耗时,要比物理机慢得多,所以你的 Redis 应该尽可能部署在物理机上。
提升可靠性的第一步,就是「资源隔离」。
你最好按不同的业务线来部署 Redis 实例,这样当其中一个实例发生故障时,不会影响到其它业务。
这种资源隔离的方案,实施成本是最低的,但成效却是非常大的。
如果你只使用单机版 Redis,那么就会存在机器宕机服务不可用的风险。
所以,你需要部署「多副本」实例,即主从集群,这样当主库宕机后,依旧有从库可以使用,避免了数据丢失的风险,也降低了服务不可用的时间。
在部署主从集群时,你还需要注意,主从库需要分布在不同机器上,避免交叉部署。
这么做的原因在于,通常情况下,Redis 的主库会承担所有的读写流量,所以我们一定要优先保证主库的稳定性,即使从库机器异常,也不要对主库造成影响。
而且,有时我们需要对 Redis 做日常维护,例如数据定时备份等操作,这时你就可以只在从库上进行,这只会消耗从库机器的资源,也避免了对主库的影响。
在部署主从集群时,如果参数配置不合理,也有可能导致主从复制发生问题:
主从复制中断
从库发起全量复制,主库性能受到影响
在这方面我给你的建议有以下 2 点:
设置合理的 repl-backlog 参数:过小的 repl-backlog 在写流量比较大的场景下,主从复制中断会引发全量复制数据的风险
设置合理的 slave client-output-buffer-limit:当从库复制发生问题时,过小的 buffer 会导致从库缓冲区溢出,从而导致复制中断
只部署了主从节点,但故障发生时是无法自动切换的,所以,你还需要部署哨兵集群,实现故障的「自动切换」。
而且,多个哨兵节点需要分布在不同机器上,实例为奇数个,防止哨兵选举失败,影响切换时间。
以上这些就是保障 Redis「高可靠」实践优化,你应该也发现了,这些都是部署和运维层的优化。
除此之外,你可能还会对 Redis 做一些「日常运维」工作,这时你要注意哪些问题呢?
执行这些命令,会长时间阻塞 Redis 主线程,危害极大,所以你必须禁止使用它。
如果确实想使用这些命令,我给你的建议是:
SCAN 替换 KEYS
4.0+版本可使用 FLUSHALL/FLUSHDB ASYNC,清空数据的操作放在后台线程执行
不管你是使用 SCAN 扫描线上实例,还是对实例做 bigkey 统计分析,我建议你在扫描时一定记得设置休眠时间。
防止在扫描过程中,实例 OPS 过高对 Redis 产生性能抖动。
你的从库必须设置为 slave-read-only 状态,避免从库写入数据,导致主从数据不一致。
除此之外,从库如果是非 read-only 状态,如果你使用的是 4.0 以下的 Redis,它存在这样的 Bug:
从库写入了有过期时间的数据,不会做定时清理和释放内存。
这会造成从库的内存泄露!这个问题直到 4.0 版本才修复,你在配置从库时需要格外注意。


总结:如果一个key需要设置过期时间,后续修改的操作也应该设置过期时间。
https://redis.io/commands/del/
Time complexity:
O(N) where N is the number of keys that will be removed.
When a key to remove holds a value other than a string, the individual complexity for this key is O(M) where M is the number of elements in the list, set, sorted set or hash.
Removing a single key that holds a string value is O(1).
总结:不同数据结构使用del命令的时间复杂度不同,对于大key不应该执行该命令。谨慎存储大key,对于集合类型数据应该先查询元素数量、判断数量多少选择批量或者del删除。
RANDOMKEY
randomkey
随机获取一个key

总结:由于过期时间的关系,当随机到一个过期的key会做清除的动作然后返回key,如果批量过期的时候执行该命令会导致循环清除过期的key。如果在从库会有更大的问题,因为从库的del命令是主库发过来的。该命令能不用则不用。

总结: 当你在使用 SETBIT 时,也一定要注意 offset 的大小,操作过大的 offset 也会引发 Redis 卡顿。
https://redis.io/commands/monitor/
总结:你需要谨慎使用 MONITOR,尤其在 QPS 很高的情况下。
使用 push 和 pop 命令来生产和消费消息

基于 lpop 或者 rpop 的伪代码如下所示
while true:
msg = redis.rpop("queue")
if msg == null:
continue
handle(msg)
当一段时间内没有新的消费生产的时候,会存在cpu时间片的浪费在无意义的循环上面。
改进方案,当获取不到消息的时候,休眠一小会,例如2s,这样会带来一个新的问题,消息延迟。
至多为2s。如果减小时间就会增大cpu空转的比例。

blpop list1 0 ,参数0代表不设置过期时间,一直阻塞。

工具写入队列值

查看redis-cli

可以看到 成功返回 值2 和阻塞时间。

如果指定时间内没有获取到则返回null

那么现在这种模型的消息队列有什么问题呢?
主要有两点 消息丢失 和 无法重复消费。
list 这一个结构仅仅支持最基本的添加和删除,无法对应多消费者的模型,pop后就从队列中删除了。
执行 pop 后如果程序宕机那么消息就会丢失。
