Redis 的实践经验
-
缩短键值对的存储⻓度;
在 key 不变的情况下,value 值越⼤操作效率越慢,因为 Redis 对于同⼀种数据类
型会使⽤不同的内部编码进⾏存储,⽐如字符串的内部编码就有三种:int(整数编码)、raw(优化内存
分配的字符串编码)、embstr(动态字符串编码),这是因为 Redis 的作者是想通过不同编码实现效率和
空间的平衡,然⽽数据量越⼤使⽤的内部编码就越复杂,⽽越是复杂的内部编码存储的性能就越低。
这还只是写⼊时的速度,当键值对内容较⼤时,还会带来另外⼏个问题:
内容越⼤需要的持久化时间就越⻓,需要挂起的时间越⻓,Redis 的性能就会越低;
内容越⼤在⽹络上传输的内容就越多,需要的时间就越⻓,整体的运⾏速度就越低;
内容越⼤占⽤的内存就越多,就会更频繁的触发内存淘汰机制,从⽽给 Redis 带来了更多的运⾏负
担。
因此在保证完整语义的同时,我们要尽量的缩短键值对的存储⻓度,必要时要对数据进⾏序列化和压缩再
存储,以 Java 为例,序列化我们可以使⽤ protostuff 或 kryo,压缩我们可以使⽤ snappy。 -
使⽤ lazy free(延迟删除)特性;
lazy free 特性是 Redis 4.0 新增的⼀个⾮常使⽤的功能,它可以理解为惰性删除或延迟删除。意思是在
删除的时候提供异步延时释放键值的功能,把键值释放操作放在 BIO(Background I/O) 单独的⼦线程处
理中,以减少删除删除对 Redis 主线程的阻塞,可以有效地避免删除 big key 时带来的性能和可⽤性问
题。
lazy free 对应了 4 种场景,默认都是关闭的:
2171 lazyfree-lazy-eviction no
2 lazyfree-lazy-expire no
3 lazyfree-lazy-server-del no
4 slave-lazy-flush no
它们代表的含义如下:
lazyfree-lazy-eviction:表示当 Redis 运⾏内存超过 maxmeory 时,是否开启 lazy free 机制删
除;
lazyfree-lazy-expire:表示设置了过期时间的键值,当过期之后是否开启 lazy free 机制删除;
lazyfree-lazy-server-del:有些指令在处理已存在的键时,会带有⼀个隐式的 del 键的操作,⽐如
rename 命令,当⽬标键已存在,Redis 会先删除⽬标键,如果这些⽬标键是⼀个 big key,就会造成
阻塞删除的问题,此配置表示在这种场景中是否开启 lazy free 机制删除;
slave-lazy-flush:针对 slave(从节点) 进⾏全量数据同步,slave 在加载 master 的 RDB ⽂件前,
会运⾏ flushall 来清理⾃⼰的数据,它表示此时是否开启 lazy free 机制删除。
- 设置键值的过期时间;
避免 O(N) 命令对 Redis 造成的影响,可以从以下⼏个⽅⾯⼊⼿改造:
决定禁⽌使⽤ keys 命令;
避免⼀次查询所有的成员,要使⽤ scan 命令进⾏分批的,游标式的遍历;
通过机制严格控制 Hash、Set、Sorted Set 等结构的数据⼤⼩;
将排序、并集、交集等操作放在客户端执⾏,以减少 Redis 服务器运⾏压⼒;
删除 (del) ⼀个⼤数据的时候,可能会需要很⻓时间,所以建议⽤异步删除的⽅式 unlink,它会启动⼀
个新的线程来删除⽬标数据,⽽不阻塞 Redis 的主线程。
-
禁⽤⻓耗时的查询命令;
-
使⽤ slowlog 优化耗时命令;
我们可以使⽤ slowlog 功能找出最耗时的 Redis 命令进⾏相关的优化,以提升 Redis 的运⾏速度,慢查
询有两个重要的配置项:
slowlog-log-slower-than :⽤于设置慢查询的评定时间,也就是说超过此配置项的命令,将会
被当成慢操作记录在慢查询⽇志中,它执⾏单位是微秒 (1 秒等于 1000000 微秒);
slowlog-max-len :⽤来配置慢查询⽇志的最⼤记录数。
我们可以根据实际的业务情况进⾏相应的配置,其中慢⽇志是按照插⼊的顺序倒序存⼊慢查询⽇志中,我
们可以使⽤ slowlog get n 来获取相关的慢查询⽇志,再找到这些慢查询对应的业务进⾏相关的优
化。 -
使⽤ Pipeline 批量操作数据;
Pipeline (管道技术) 是客户端提供的⼀种批处理技术,⽤于⼀次处理多个 Redis 命令,从⽽提⾼整个交互
的性能。 -
避免⼤量数据同时失效;
Redis 过期键值删除使⽤的是贪⼼策略,它每秒会进⾏ 10 次过期扫描,此配置可在 redis.conf 进⾏配
置,默认值是 hz 10 ,Redis 会随机抽取 20 个值,删除这 20 个键中过期的键,如果过期 key 的⽐例
超过 25% ,重复执⾏此流程 -
客户端使⽤优化;
在客户端的使⽤上我们除了要尽量使⽤ Pipeline 的技术外,还需要注意要尽量使⽤ Redis 连接池,⽽不
是频繁创建销毁 Redis 连接,这样就可以减少⽹络传输次数和减少了⾮必要调⽤指令。 -
限制 Redis 内存⼤⼩;
在 64 位操作系统中 Redis 的内存⼤⼩是没有限制的,也就是配置项 maxmemory 是被注释
掉的,这样就会导致在物理内存不⾜时,使⽤ swap 空间既交换空间,⽽当操⼼系统将 Redis 所⽤的内存
分⻚移⾄ swap 空间时,将会阻塞 Redis 进程,导致 Redis 出现延迟,从⽽影响 Redis 的整体性能。因
此我们需要限制 Redis 的内存⼤⼩为⼀个固定的值,当 Redis 的运⾏到达此值时会触发内存淘汰策略, -
使⽤物理机⽽⾮虚拟机安装 Redis 服务;
-
检查数据持久化策略;
Redis 的持久化策略是将内存数据复制到硬盘上,这样才可以进⾏容灾恢复或者数据迁移,但维护此持久
化的功能,需要很⼤的性能开销。
在 Redis 4.0 之后,Redis 有 3 种持久化的⽅式:
RDB(Redis DataBase,快照⽅式)将某⼀个时刻的内存数据,以⼆进制的⽅式写⼊磁盘;
AOF(Append Only File,⽂件追加⽅式),记录所有的操作命令,并以⽂本的形式追加到⽂件中;
混合持久化⽅式,Redis 4.0 之后新增的⽅式,混合持久化是结合了 RDB 和 AOF 的优点,在写⼊的
时候,先把当前的数据以 RDB 的形式写⼊⽂件的开头,再将后续的操作命令以 AOF 的格式存⼊⽂
件,这样既能保证 Redis 重启时的速度,⼜能减低数据丢失的⻛险。
RDB 和 AOF 持久化各有利弊,RDB 可能会导致⼀定时间内的数据丢失,⽽ AOF 由于⽂件较⼤则会影响
Redis 的启动速度,为了能同时拥有 RDB 和 AOF 的优点,Redis 4.0 之后新增了混合持久化的⽅式,因
此我们在必须要进⾏持久化操作时,应该选择混合持久化的⽅式。
查询是否开启混合持久化可以使⽤ config get aof-use-rdb-preamble 命令,
其中 yes 表示已经开启混合持久化,no 表示关闭,Redis 5.0 默认值为 yes
- 禁⽤ THP 特性;
Linux kernel 在 2.6.38 内核增加了 Transparent Huge Pages (THP) 特性 ,⽀持⼤内存⻚ 2MB 分
配,默认开启。
当开启了 THP 时,fork 的速度会变慢,fork 之后每个内存⻚从原来 4KB 变为 2MB,会⼤幅增加重写期
间⽗进程内存消耗。同时每次写命令引起的复制内存⻚单位放⼤了 512 倍,会拖慢写操作的执⾏时间,导
225致⼤量写操作慢查询。例如简单的 incr 命令也会出现在慢查询中,因此 Redis 建议将此特性进⾏禁⽤
- 使⽤分布式架构来增加读写速度
Redis 分布式架构有三个重要的⼿段:
主从同步
哨兵模式
Redis Cluster 集群
Redis 单线程但性能依旧很快的主要原因有以下⼏点:
-
基于内存操作:Redis 的所有数据都存在内存中,因此所有的运算都是内存级别的,所以他的性能⽐较
⾼; -
数据结构简单:Redis 的数据结构⽐较简单,是为 Redis 专⻔设计的,⽽这些简单的数据结构的查找
和操作的时间复杂度都是 O(1),因此性能⽐较⾼; -
多路复⽤和⾮阻塞 I/O:Redis 使⽤ I/O 多路复⽤功能来监听多个 socket 连接客户端,这样就可以
使⽤⼀个线程连接来处理多个请求,减少线程切换带来的开销,同时也避免了 I/O 阻塞操作,从⽽⼤
⼤提⾼了 Redis 的性能; -
避免上下⽂切换:因为是单线程模型,因此就避免了不必要的上下⽂切换和多线程竞争,这就省去了多
线程切换带来的时间和性能上的消耗,⽽且单线程不会导致死锁问题的发⽣。
Redis 4.0 之前⼀直采⽤单线程的主要原因有以下三个:
- 使⽤单线程模型是 Redis 的开发和维护更简单,因为单线程模型⽅便开发和调试;
- 即使使⽤单线程模型也并发的处理多客户端的请求,主要使⽤的是多路复⽤和⾮阻塞 IO;
- 对于 Redis 系统来说,主要的性能瓶颈是内存或者⽹络带宽⽽并⾮ CPU。
为什么需要多线程?
但是单线程也有单线程的苦恼,⽐如当我(Redis)需要删除⼀个很⼤的数据时,因为是单线程同步操作,
这就会导致 Redis 服务卡顿,于是在 Redis 4.0 中就新增了多线程的模块,当然此版本中的多线程主要是
为了解决删除数据效率⽐较低的问题的,他的相关指令有以下三个:
- unlink key 后台删除某个 key
- flushdb async 清空所有数据
- flushall async
在 Redis 6.0 中引⼊了 I/O 多线程的读写,这样就可以更加⾼效的处理更多的任务了,Redis 只是将 I/O 读写变成了多
线程,⽽命令的执⾏依旧是由主线程串⾏执⾏的,因此在多线程下操作 Redis 不会出现线程安全的问题。