赣州新闻网

首页 / 科技

大厂内部资料 | Redis 性能优化的 13 条军规

2020-03-25 · 赣州新闻网 · 科技


作者 | 老王

来源 | Java 中文社区

Redis 是基于单线程模型实现的,也就是 Redis 是使用一个线程来处理所有的客户端请求的,尽管 Redis 使用了非阻塞式 IO,并且对各种命令都做了优化(大部分命令操作时间复杂度都是 O(1)),但由于 Redis 是单线程执行的特点,因此它对性能的要求更加苛刻,本R S ] ;文我们将通过一些优化手段,让 Redis 更加高效地运E + : 8 Z v p v e行。

本文我们将使用以下手段,来提升 Redis 的运行速度:m e l ( ; A G f Q

  1. 缩短键值对的存储长度;
  2. 使用 lazy free(延迟删除)特性;
  3. 设置键值的过期时间;
  4. 禁用长耗时的查询命令;
  5. 使用 sv 7 8 Y O ;lowlog 优化耗时命令;
  6. 使用 PiT R & / xpeline 批量操作数据;
  7. 避免大量数x o H D z 5据同时失效;
  8. 客户端使用优化;
  9. 限制 Redis 内存大小W 2 _ B { H;
  10. 使用物理机而非虚拟机安装 R3 4 b = I V H K Eedis 服务;
  11. 检4 a { h查数据持久化策略^ T m Y ;;
  12. 禁用 Ta ] THP 特性;
  13. 使用分布式架构来增加读R G K M写速度。


大厂内部资料 | Redis 性能优化的 13 条军规

缩短键值对的存储长度

键值对的长度是和性能成反比的,a ` - = L l 比如我们来做一组写入数据的性能测试,执行结果如下:

大厂内部资料 | Redis 性能优化的 13 条军规

从以上数据可以看出,在 key 不变的情况下,value 值越大操作效率越慢,因为 Redis 对于同? $ _ B一种数据类型会E t $ 9 } V *使用不同的J # p内部编D @ H X n 6码进行存储,比如字符串的内部编码就有三种:int(整数编t A s码)、raw(优化内存分配的字符串编码) S 6 ] 、embstr(动态字符串编码),这是因为 Redis 的作者是想通过不同编码实现效率和空间的平衡,然而数据量越大使用的内部编码就越复杂,而越是@ p h j复杂的内部编码存储的性能就越低。

这还只是写入时的速度,当键值对内容较大时,还会带来另外几个问题:

  • 内容越大需要的持久化时间就越长,需要挂起的时间越长,Redis 的性能就会越低;
  • 内容越大在网络上传输的内容就越多,需要的时间就越长,整体的运行速度就越低;
  • 内容越大占用的内存就越多,就会更频繁的触发内存淘汰机制,从而给 Redis 带来了更多的运行负担。

因` h o K e此在保证完整语义的同时,我们要尽量的缩短键值对的存储长度,必要时要对数据进行序列化和压缩再存储,以 Java 为例,序列化我们可I ) b V b * D C以使用 protostuff 或 kryo,压缩我们可以使用 snappy。

大厂内部资料 | Redis 性能优化的 13 条军规

使用 lazy freeh , { v 特性

lazy free 特性是 Redis 4.0 新增的一个非常使用的功能,它可以理解为惰性删除或延( L 4 ) 迟删除。意思是在删除的时候v f G e f p i提供异步延时释放键值的功能,把键值释放操作放在 BIO(Background I/O) 单独的子线程处理中,N X G { P + X以减少删除删除对 Redis 主W 6 B线程的阻塞,可以有效地避免删O 1 5 e除 big key 时带来的性能和可用性问题。

lay d : g xzy free 对应了 4 种场景,默认都是关闭的:


1lazyfree-lazy-eviction no
2. t &lazyfree-la. G t mzy-expire no
3lazyfree-lazy-server-del no
4slD X c $ $ C t wave-lazy-flush no

它们代表的含义如下:

  • lazyfree-lazy-eviction:表示当% ` $ J 8 h K Q Redis 运行内存超过 maxmeory 时,是否开启 lazyy % Y free 机制删除;
  • lazyfree-lazy-expire:表示设置了过期时间的键值,当过期之后是否开启 lazyi h Q P & , p free 机制删除;
  • lazyfree-lazy-server-del:有些指令在处理已存在的键时,会带有一个隐式的 del 键的操S a b 9作,比如 rename 命令,当目标键已存在,Redis 会 t p先删除目标键,如果这些目标键是一个 big key,就会造成阻塞删除的问题,此配置表示在这种场景中是否开启 lazy freeA 1 ] g y 机制删除;
  • slave-lazy-flush:针对 slave(从节点) 进行全量数据同步,slave 在加载 master 的 RDB 文件前,会运行 f! $ h L Y ] 3 k X , _ Razy-evica G z P ; 2tion、lazyfree-lazy-exe e H # a & |pire、lazyfree-lazy-serv1 j h D # O Der-del 等配置,这样就可以有效的提高主线程的执行效率。w 5 Q O _ S

    大厂内部资料 | Redis 性能优化的 13 条军规

    设置键值的过期时间

    我们应该根据实际的业务情况,对键值设置合理的过期时间,这样 Redis 会帮你自动清除过期的键值对,以节约对内存的占用3 b d B C b,以避免键值过多的堆积,频繁的触发内存3 j 7 & g w n .淘汰策略。

    大厂内部资料 | Redis 性能优化的 13 条军规

    禁用长耗时的查询命令

    Redis 绝大多数读写命令的时间复杂度都在 O(1) 到 O(N) 之间,在官方文档对每命令都有O r V E | n U [时间复杂度说明,地址:httv w 6 o M K S H Tps://redis n Y Q f.io/J J 7 )commands,如下图所示:

    大厂内部资料 | Redis 性能优化的 13 条军规

    其中 O(1) 表示可以安全使用的,而 O(N) 就应该当心了,N 表示不确定,数据越大查询的速度可能会越慢。因为m + N 1 Y Redis 只用一个线程来做数据查询,如果这些指令耗时很长,就会阻塞 Redis,造成大量延时。

    要避Z : 2 e 4 M ^ 免 O(N) 命令对 Redis 造成的影响,可以从以下几个方面入手改造:

    • 决定禁止使用 keys| V m q 8 b 命令;
    • 避免一次查询所有的成员,要使用 scan 命令进行分批的,游标式的遍历;
    • 通过机制严格控制 Hash、Set、Sorted Set 等结构的数据大小;
    • 将排序、并集、交集等操作放在客h # W Y , ` Q户端执行,以减少 Redis 服务器运行R U , J n压力;
    • 删除 (del) 一个大数据的时候,可能会需要很长时间,所以建议用异步删除的方式 unlink,它会启动一个新的线程来删除目标数据,而不阻塞 Redis 的主线a Y B N J 7程。


    大厂内部资料 | Redis 性能优化的 13 条军规

    使1 T ~ % v k,其中慢日志是按照插入的顺序倒序存入慢查询日志中,我们可以使用 slowlog get n 来获取相关的慢查询日志,再找到这些慢查询对应的业务进行相关的优化。

    大厂内部资料 | Redis 性能优化的 13 条军规

    使用 Pipeline 批量操作数据

    Pipeline (管道技术N ( 9 k M) 是客户端提供的一种批处理T [ R k (技术,用于一次处理多个 Redis 命令,从而提高整个交互的性能。

    我们使用 Java 代码来测试一下 Pipelj o tine 和普通操作的性能对比,Pipeline 的测试代码如下:

     1public class PipelineExample {
    2 public static void main(String args) {
    3 Jedis jeds A I h w K Li# q ^ ! R j w a ?s = new Jedis(\"127.0.0.1\"^ j G n, 6379);
    4 // 记录r ( j执行开始时间
    5 l6 N R 5 `ong beginTime = System.currentTimeMillis;
    6 // 获取 PS s . & 0 ipeline 对象
    7 Pipeline pipe = jedis.pipelined;
    8 // 设置多个 Redis 命令
    9 for (int i = 0; i < 100; iw v | 4 C L++) {
    10 pipe.set(n F ; A )\"key\" + i, \e d P U j b ! V"val\" + i);
    11 pipe.del(\"key\"+i);
    12 }
    13 // 执行命令
    14 pipe.sync;
    15 // 记录执行结束时间
    16 long endTime = System.currentTimeMillis;
    17 System.out.println(\"执行耗时:\" + (endTime - beginTime) + \"毫秒\");
    18 }
    19}

    以上程序执b Y 3 0 !行结果为:

    执行耗时:29m j r X . k h :7毫秒

    普通的操作代码如下:


    1public class Pipel_ 5 - x E ] 6 K pineExample {

    2 public static void main(s l d 1 $ r #String args) {

    3 Jedis jedis = new Jedis: z $(\"127.0.0.1\", 6379);

    4 // 记录执行开始时间

    5 long beginTime = Sys( & f 2 y { htem.cB [ vurrentTimeMillis;

    6 for (int i = 0; i < 100; i++) {c Y F

    7 jedis.set(\"key\" + i, \"val\" + i);

    8 jev 0 C p J E ydis.del(\"key\"+i);

    9 }

    10 // 记录执行结束时间

    11 long endTime = System.currentTimeMilr f f 5 H o @ S uli& * V [ ) % +s;

    12 System.out.priy t H 1 + 8 Untln(\"执行耗时:\" + (endTime - beginTime) + \"毫秒\");

    13 }

    14}

    以上程序执行结果为:

    执行耗时:172766 @ 8 [ + 1 F毫秒

    从以上的结果可以看出,管道的执行时间是 297 毫秒,而普通命令执行时间是 1727J & ^ v Y6 毫秒g x Q ( H = I,管道技术要比普通的执行大约快了 58 倍。

    大厂内部资料 | Redis 性能优化的 13 条军规

    避免大量数据同时失效

    Redis 过期键值删除使用的是贪心策略,它每秒会进行 10 次过期扫描,此配置可在 redis.conf 进行配置,默认值是 hz 10,Redis 会随机抽取 20 个值,删除这 20 个键中过期的键,如果过期 key 的比例超过 25% ,重复执行此流程,如下图所示:

    大厂内部资料 | Redis 性能优化的 13 条军规

    如果在大型系统中有大量缓存在同一时间同时过期,那么会导致 Redis 循环多次持续扫描删除过h c 3 & i期字典,直到过期字典中过期键值被删除的比较稀疏为止,而在整个执行过程会导致 Redis 的读写出现明显的卡顿,卡顿的另一种原因是内存管理器需要频繁回收内存页,因此也会消耗一定的 CPU。

    为了避免这种卡顿现象的产生,我们需要预防大量的缓存在同一时刻一起过期,就简单的解决方案就是在过期时间的基础上添加一个指定范围的随机数。

    大厂内部资料 | Redis 性能优化的 13 条军规

    客户端4 y [ 7使用优化

    在客户端Z Z Q x / + l的使用上我们除了要尽量使用 Pipeline 的技术外,还需要注意要尽量使用 Redis 连接池,而不是 9 y频繁创建销毁 Redis 连接,这样就可以减少i e k Q z ^网络传输次数和减少了非必要调用指令。

    大厂内部资料 | Redis 性能优化的 13 条军规

    限制 Redis 内存大小

    在 64 位操作系[ r F 统中 Redis 的内存大小是没有限制的,也就是配置项R Y b v / j e h 淘汰整个键值中最少使用的键值。

  • 其中 allkeys-xxx 表示从所有的键值中淘汰数据,而 volatile-xxx 表示从设置了过期键的键值中淘汰数据。

    我E j S 0 3 = P z们可以根据实际的业务情况进行设置,默认的淘汰策略不淘汰任何数据,在新增时会报错。

    大厂内部资料 | Redis 性能优化的 13 条军规

    使用物理机而非Q & i D v k Z虚拟机

    在虚拟机中运行 Redis 服务器,因为和物理机共享一个物理网口,并且一台物理机可能有多个虚拟机在运行,因此在内存占用上和网络延迟方面都会? z G | a有很糟糕的表现,我们可以通过 ./redis-cli --ia ` ; * !ntrin* ^ R W bsic-latency 100 命令查看延迟时间,如果对 Redis 的性能有较高要求的话,F s 5 r = c U ] c应尽可能在物理机上直接部署 Redi) L & p @ x . Rs 服务器。

    大厂内部资料 | Redis 性能优化的 13 条军规

    检查数据持久化策略

    Redis 的持久化策略是将内存数据复制到硬盘上,这样才可以进行容灾恢复或者数据迁移,但维护此持久化的功能,需要很大的性能开销。

    在 Redis 4.0 之后,Redis 有 3 种持久化的方式:

    • RDd h O j K d nB(Redis DataBase,快照方式)将某一个时刻的内存数据,以2 K I K f二! 7 j ? f ; 6 H 3进制的方式写入磁盘;
    • AOF(Append Only File,文件追加方式),记录所有的操作命令,并以文本的形式追加到文件中;
    • 混合持久化方式,Redis 4.0 之后新增的方式,混合持久化是结合了 RDB 和 AOF 的优点,在写入的时候,先把4 y E ! Z +当前的数据以 RDB 的形式写入文件的开头,再将后续的操作命令以 AOF 的格式存入文件,这n p p样既能保证 Redis 重启时的速度,又能减低数据丢失的j s & s c风险。

    RDB 和 AOF 持久化各- # l J G v有利弊,RDB 可能会导致一定时间内的数据丢失,而 AOF 由于文件较大则会影响 Redis 的启动c [ ! 6速度,为了能同时拥有 RDB 和 AOF 的优点,Redis 4.0 之后新增了混合持久化| k l的方式,因此我们在必须要进行持久化操作时,应该选择混合持久化的方式。

    查询是否开启混合持久S d 2 G K B ~ a化可以使用 config get aof-usej T { { _ $-rdb-preamble命令,执行结果如下图所示:

    大厂内部资料 | Redis 性能优化的 13 条军规

    其中 yes 表示已经开启混合持久化,no 表示关闭,Redis 5.0 默认值为 yes。如果是其他版本的 Redis 首先需要检查一下,是否已经开启了混合持久化,如果关闭的情况下,可以通过以下两种方式开启:

    • 通过命令行开启
    • 通过修改 Redis 配置文件开启

    ① 通过命令行开启

    使用命令 config set aof-use-rdb-preamble yes 执行结果如下图所示:

    大厂内部资料 | Redis 性能优化的 13 条军规

    命令行设置配置的缺点是重启 Redis 服务之后,设置的配置就会失效。

    ② 通过修改 Redis 配置文件开启

    在 Redis 的根路径下找到 redis.M ) L ` Oconf 文件,把配置文件中的 aof-use-rdb-preamble no 改为 aof-use-rdb-preamble yes 如下图所示:o H 6 / ~ 7

    大厂内部资料 | Redis 性能优化的 13 条军规

    配置完成之后,需要重启 Redis 服务器,配Y U ~置才能生效,但修改配置文件的方式,在每次重启 Redis 服务之后,配置信息不会丢失。

    需要注意的是,在非必须进行持8 q 6 h久化的业务中,可以关闭持久化,这样可以有效的提升 Redis 的运行速度,不N P u o 9 z ) 会出现间歇性卡顿的困扰。

    大厂内部资料 | Redis 性能优化的 13 条军规

    禁用 THP 特性

    Linux kernel 在 2.6.38 内核增_ z L / |加了 Transparent Huge Pages (THP) 特性 ,支持大内存页 2MB 分配,默认开启。

    当开启了 THP 时,fork 的速度会变慢,fork 之后每个内存} j K 1 ( !页从原来 4KB 变为 2MB,会大幅增加重写期间父进程内存消耗。同时每次写命令引起的复制内存页单位放大了 512 倍,会拖慢写操作的执行时间,导致大量写操作慢查z 2 u k询。例如简单的 incr 命令也会出现在慢查询中,因此 Redis; l J Z H z 建议将此特性进行禁用,禁用方法如下:

    echo never > /sys/kernel/mm/transparent_hugepage/enabled

    为了使机器重启后 THP 配置依然生效,可以在 /etc/rc.local 中追加 echo never > /sys/kernel/mm/transparent_hugepage/enabled。

    大厂内部资料 | Redis 性能优化的 13 条军规

    使用分布式架构来增加读写速度

    Redis 分布式架构有三个重要的手段:

    • 主从同步
    • 哨兵模式
    • Redis Cluster 集群

    使用主从同步功能我们可以把写入放到主库上执行,把读功能转移到从服务上,因此就可以a F F n d M c Y在单位时间内处理更多的请求,从而提升的 Redis 整体的运行速度o k X k I。

    而哨兵模式是对于主从功能 ] h c d C [力从一台服务器,分散给多台服务器了,v E Z C &因此性能会有很大的提升。

    在这三个功能中W T y Z 1 j J # @,我们只需要使用一个就行了,毫无疑问 Redis Cluster 应该是首选的实现方案,它可以把读写压力自动的分担给更多的服务器,并且拥有自动容灾的能力。

测试请求设置代表维护内容性能信丰优化