Redis10大性能优化策略 2

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: Redis10大性能优化策略

10.使用Swap


如果你发现 Redis 突然变得非常慢,每次的操作耗时都达到了几百毫秒甚至秒级,那此时你就需要检查 Redis 是否使用到了 Swap,在这种情况下 Redis 基本上已经无法提供高性能的服务了。


什么是 Swap?为什么使用 Swap 会导致 Redis 的性能下降?


如果你对操作系统有些了解,就会知道操作系统为了缓解内存不足对应用程序的影响,允许把一部分内存中的数据换到磁盘上,以达到应用程序对内存使用的缓冲,这些内存数据被换到磁盘上的区域,就是 Swap。


b237a37e176cb0398d1a9989ad01a5a2.png


牛逼啊!接私活必备的 N 个开源项目!赶快收藏


问题就在于,当内存中的数据被换到磁盘上后,Redis 再访问这些数据时,就需要从磁盘上读取,访问磁盘的速度要比访问内存慢几百倍!


尤其是针对 Redis 这种对性能要求极高、性能极其敏感的数据库来说,这个操作延时是无法接受的。


此时,你需要检查 Redis 机器的内存使用情况,确认是否存在使用了 Swap。


你可以通过以下方式来查看 Redis 进程是否使用到了 Swap:


# 先找到 Redis 的进程 ID
$ ps -aux | grep redis-server
# 查看 Redis Swap 使用情况
$ cat /proc/$pid/smaps | egrep '^(Swap|Size)'

输出结果如下:

Size:               1256 kB
Swap:                  0 kB
Size:                  4 kB
Swap:                  0 kB
Size:                132 kB
Swap:                  0 kB
Size:              63488 kB
Swap:                  0 kB
Size:                132 kB
Swap:                  0 kB
Size:              65404 kB
Swap:                  0 kB
Size:            1921024 kB
Swap:                  0 kB


每一行 Size 表示 Redis 所用的一块内存大小,Size 下面的 Swap 就表示这块 Size 大小的内存,有多少数据已经被换到磁盘上了,如果这两个值相等,说明这块内存的数据都已经完全被换到磁盘上了。


如果只是少量数据被换到磁盘上,例如每一块 Swap 占对应 Size 的比例很小,那影响并不是很大。如果是几百兆甚至上 GB 的内存被换到了磁盘上,那么你就需要警惕了,这种情况 Redis 的性能肯定会急剧下降。


11.碎片整理


Redis 的数据都存储在内存中,当我们的应用程序频繁修改 Redis 中的数据时,就有可能会导致 Redis 产生内存碎片。


内存碎片会降低 Redis 的内存使用率,我们可以通过执行 INFO 命令,得到这个实例的内存碎片率:

# Memory
used_memory:5709194824
used_memory_human:5.32G
used_memory_rss:8264855552
used_memory_rss_human:7.70G
...
mem_fragmentation_ratio:1.45

这个内存碎片率是怎么计算的?


很简单,mem_fragmentation_ratio = used_memory_rss / used_memory。


其中 used_memory 表示 Redis 存储数据的内存大小,而 used_memory_rss 表示操作系统实际分配给 Redis 进程的大小。


如果 mem_fragmentation_ratio > 1.5,说明内存碎片率已经超过了 50%,这时我们就需要采取一些措施来降低内存碎片了。


解决的方案一般如下:


1)如果你使用的是 Redis 4.0 以下版本,只能通过重启实例来解决


2)如果你使用的是 Redis 4.0 版本,它正好提供了自动碎片整理的功能,可以通过配置开启碎片自动整理。


但是,开启内存碎片整理,它也有可能会导致 Redis 性能下降。


原因在于,Redis 的碎片整理工作是也在主线程中执行的,当其进行碎片整理时,必然会消耗 CPU 资源,产生更多的耗时,从而影响到客户端的请求。


所以,当你需要开启这个功能时,最好提前测试评估它对 Redis 的影响。


Redis 碎片整理的参数配置如下:

# 开启自动内存碎片整理(总开关)
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

72f92435e8b8b2941232cf993610cd05.png



二、Redis如何优化

5ca6e03584c97203c3c72a046eb0d1bc.png



1.慢查询优化


1)尽量不使用 O(N) 以上复杂度过高的命令,对于数据的聚合操作,放在客户端做。


2)执行 O(N) 命令,保证 N 尽量的小(推荐 N <= 300),每次获取尽量少的数据,让 Redis 可以及时处理返回。


2.集中过期优化


一般有两种方案来规避这个问题:


1.集中过期 key 增加一个随机过期时间,把集中过期的时间打散,降低 Redis 清理过期 key 的压力


2.如果你使用的 Redis 是 4.0 以上版本,可以开启 lazy-free 机制,当删除过期 key 时,把释放内存的操作放到后台线程中执行,避免阻塞主线程。


第一种方案,在设置 key 的过期时间时,增加一个随机时间,伪代码可以这么写:


# 在过期时间点之后的 5 分钟内随机过期掉

redis.expireat(key, expire_time + random(300))

第二种方案,Redis 4.0 以上版本,开启 lazy-free 机制:


# 释放过期 key 的内存,放到后台线程执行

lazyfree-lazy-expire yes

运维层面,你需要把 Redis 的各项运行状态数据监控起来,在 Redis 上执行 INFO 命令就可以拿到这个实例所有的运行状态数据。


在这里我们需要重点关注 expired_keys 这一项,它代表整个实例到目前为止,累计删除过期 key 的数量。


你需要把这个指标监控起来,当这个指标在很短时间内出现了突增,需要及时报警出来,然后与业务应用报慢的时间点进行对比分析,确认时间是否一致,如果一致,则可以确认确实是因为集中过期 key 导致的延迟变大。


3.实例内存达到上限优化


1)避免存储 bigkey,降低释放内存的耗时


2)淘汰策略改为随机淘汰,随机淘汰比 LRU 要快很多(视业务情况调整)


3)拆分实例,把淘汰 key 的压力分摊到多个实例上


4)如果使用的是 Redis 4.0 以上版本,开启 layz-free 机制,把淘汰 key 释放内存的操作放到后台线程中执行(配置 lazyfree-lazy-eviction = yes)


4.fork耗时严重优化


1)控制 Redis 实例的内存:尽量在 10G 以下,执行 fork 的耗时与实例大小有关,实例越大,耗时越久。


2)合理配置数据持久化策略:在 slave 节点执行 RDB 备份,推荐在低峰期执行,而对于丢失数据不敏感的业务(例如把 Redis 当做纯缓存使用),可以关闭 AOF 和 AOF rewrite。


3)Redis 实例不要部署在虚拟机上:fork 的耗时也与系统也有关,虚拟机比物理机耗时更久。


4)降低主从库全量同步的概率:适当调大 repl-backlog-size 参数,避免主从全量同步。


从建立同步时,优先检测是否可以尝试只同步部分数据,这种情况就是针对于之前已经建立好了复制链路,只是因为故障导致临时断开,故障恢复后重新建立同步时,为了避免全量同步的资源消耗,Redis会优先尝试部分数据同步,如果条件不符合,才会触发全量同步。


这个判断依据就是在master上维护的复制缓冲区大小,如果这个缓冲区配置的过小,很有可能在主从断开复制的这段时间内,master产生的写入导致复制缓冲区的数据被覆盖,重新建立同步时的slave需要同步的offset位置在master的缓冲区中找不到,那么此时就会触发全量同步。


如何避免这种情况?解决方案就是适当调大复制缓冲区repl-backlog-size的大小,这个缓冲区的大小默认为1MB,如果实例写入量比较大,可以针对性调大此配置。


5.多核CPU优化


那如何解决这个问题呢?


如果你确实想要绑定 CPU,可以优化的方案是,不要让 Redis 进程只绑定在一个 CPU 逻辑核上,而是绑定在多个逻辑核心上,而且,绑定的多个逻辑核心最好是同一个物理核心,这样它们还可以共用 L1/L2 Cache。


当然,即便我们把 Redis 绑定在多个逻辑核心上,也只能在一定程度上缓解主线程、子进程、后台线程在 CPU 资源上的竞争。另外,搜索公众号Linux就该这样学后台回复“猴子”,获取一份惊喜礼包。


因为这些子进程、子线程还是会在这多个逻辑核心上进行切换,存在性能损耗。


如何再进一步优化?


可能你已经想到了,我们是否可以让主线程、子进程、后台线程,分别绑定在固定的 CPU 核心上,不让它们来回切换,这样一来,他们各自使用的 CPU 资源互不影响。


其实,这个方案 Redis 官方已经想到了。


Redis 在 6.0 版本已经推出了这个功能,我们可以通过以下配置,对主线程、后台线程、后台 RDB 进程、AOF rewrite 进程,绑定固定的 CPU 逻辑核心:


Redis6.0 前绑定CPU核

taskset -c 0 ./redis-server

Redis6.0 后绑定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 6.0 版本,就可以通过以上配置,来进一步提高 Redis 性能。


这里我需要提醒你的是,一般来说,Redis 的性能已经足够优秀,除非你对 Redis 的性能有更加严苛的要求,否则不建议你绑定 CPU。


6.查看Redis内存是否发生Swap

$ redis-cli info | grep process_id
process_id: 5332

然后,进入 Redis 所在机器的 /proc 目录下的该进程目录中:

$ cd /proc/5332

最后,运行下面的命令,查看该 Redis 进程的使用情况。在这儿,我只截取了部分结果:

$cat smaps | egrep '^(Swap|Size)'
Size: 584 kB
Swap: 0 kB
Size: 4 kB
Swap: 4 kB
Size: 4 kB
Swap: 0 kB
Size: 462044 kB
Swap: 462008 kB
Size: 21392 kB
Swap: 0 kB


一旦发生内存 swap,最直接的解决方法就是增加机器内存。如果该实例在一个 Redis 切片集群中,可以增加 Redis 集群的实例个数,来分摊每个实例服务的数据量,进而减少每个实例所需的内存量。


7.内存大页


如果采用了内存大页,那么,即使客户端请求只修改 100B 的数据,Redis 也需要拷贝 2MB 的大页。相反,如果是常规内存页机制,只用拷贝 4KB。两者相比,你可以看到,当客户端请求修改或新写入数据较多时,内存大页机制将导致大量的拷贝,这就会影响 Redis 正常的访存操作,最终导致性能变慢。


首先,我们要先排查下内存大页。方法是:在 Redis 实例运行的机器上执行如下命令:


$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never

如果执行结果是 always,就表明内存大页机制被启动了;如果是 never,就表示,内存大页机制被禁止。


在实际生产环境中部署时,我建议你不要使用内存大页机制,操作也很简单,只需要执行下面的命令就可以了:


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

其实,操作系统提供的内存大页机制,其优势是,可以在一定程序上降低应用程序申请内存的次数。


但是对于 Redis 这种对性能和延迟极其敏感的数据库来说,我们希望 Redis 在每次申请内存时,耗时尽量短,所以我不建议你在 Redis 机器上开启这个机制。


8.删除使用Lazy Free


支持版本:Redis 4.0+


1)主动删除键使用lazy free


UNLINK命令

127.0.0.1:7000> LLEN mylist
(integer) 2000000
127.0.0.1:7000> UNLINK mylist
(integer) 1
127.0.0.1:7000> SLOWLOG get
1) 1) (integer) 1
   2) (integer) 1505465188
   3) (integer) 30
   4) 1) "UNLINK"
      2) "mylist"
   5) "127.0.0.1:17015"
   6) ""


注意:DEL命令,还是并发阻塞的删除操作


FLUSHALL/FLUSHDB ASYNC

127.0.0.1:7000> DBSIZE
(integer) 1812295
127.0.0.1:7000> flushall  //同步清理实例数据,180万个key耗时1020毫秒
OK
(1.02s)
127.0.0.1:7000> DBSIZE
(integer) 1812637
127.0.0.1:7000> flushall async  //异步清理实例数据,180万个key耗时约9毫秒
OK
127.0.0.1:7000> SLOWLOG get
 1) 1) (integer) 2996109
    2) (integer) 1505465989
    3) (integer) 9274       //指令运行耗时9.2毫秒
    4) 1) "flushall"
       2) "async"
    5) "127.0.0.1:20110"
    6) ""


2)被动删除键使用lazy free


lazy free应用于被动删除中,目前有4种场景,每种场景对应一个配置参数;默认都是关闭。

lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
slave-lazy-flush no


针对redis内存使用达到maxmeory,并设置有淘汰策略时;在被动淘汰键时,是否采用lazy free机制;因为此场景开启lazy free, 可能使用淘汰键的内存释放不及时,导致redis内存超用,超过maxmemory的限制。此场景使用时,请结合业务测试。(生产环境不建议设置yes)


lazyfree-lazy-expire


针对设置有TTL的键,达到过期后,被redis清理删除时是否采用lazy free机制;此场景建议开启,因TTL本身是自适应调整的速度。


lazyfree-lazy-server-del


针对有些指令在处理已存在的键时,会带有一个隐式的DEL键的操作。如rename命令,当目标键已存在,redis会先删除目标键,如果这些目标键是一个big key,那就会引入阻塞删除的性能问题。此参数设置就是解决这类问题,建议可开启。


slave-lazy-flush


针对slave进行全量数据同步,slave在加载master的RDB文件前,会运行flushall来清理自己的数据场景, 参数设置决定是否采用异常flush机制。如果内存变动不大,建议可开启。可减少全量同步耗时,从而减少主库因输出缓冲区爆涨引起的内存使用增长。


3)lazy free的监控


lazy free能监控的数据指标,只有一个值:lazyfree_pending_objects,表示redis执行lazy free操作,在等待被实际回收内容的键个数。并不能体现单个大键的元素个数或等待lazy free回收的内存大小。所以此值有一定参考值,可监测redis lazy free的效率或堆积键数量;比如在flushall async场景下会有少量的堆积。

# info memory
# Memory
lazyfree_pending_objects:0

注意事项:unlink命令入口函数unlinkCommand()和del调用相同函数delGenericCommand()进行删除KEY操作,使用lazy标识是否为lazyfree调用。如果是lazyfree,则调用dbAsyncDelete()函数。


但并非每次unlink命令就一定启用lazy free,redis会先判断释放KEY的代价(cost),当cost大于LAZYFREE_THRESHOLD(64)才进行lazy free.


释放key代价计算函数lazyfreeGetFreeEffort(),集合类型键,且满足对应编码,cost就是集合键的元数个数,否则cost就是1。


举例:


一个包含100元素的list key, 它的free cost就是100


一个512MB的string key, 它的free cost是1 所以可以看出,redis的lazy free的cost计算主要时间复杂度相关。


9.AOF优化


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运行在独立的机器上。


10.Swap优化


1)增加机器的内存,让 Redis 有足够的内存可以使用


2)整理内存空间,释放出足够的内存供 Redis 使用,然后释放 Redis 的 Swap,让 Redis 重新使用内存


释放 Redis 的 Swap 过程通常要重启实例,为了避免重启实例对业务的影响,一般会先进行主从切换,然后释放旧主节点的 Swap,重启旧主节点实例,待从库数据同步完成后,再进行主从切换即可。


预防的办法就是,你需要对 Redis 机器的内存和 Swap 使用情况进行监控,在内存不足或使用到 Swap 时报警出来,及时处理。


c4e7179abc7f51b27152748b30857b8d.png


三、Redis变慢了排查步骤


5acfbfa09689882d580af2251af22f5a.png


1.获取 Redis 实例在当前环境下的基线性能。


2.是否用了慢查询命令?如果是的话,就使用其他命令替代慢查询命令,或者把聚合计算命令放在客户端做。


3.是否对过期 key 设置了相同的过期时间?对于批量删除的 key,可以在每个 key 的过期时间上加一个随机数,避免同时删除。


4.是否存在 bigkey?对于 bigkey 的删除操作,如果你的 Redis 是 4.0 及以上的版本,可以直接利用异步线程机制减少主线程阻塞;如果是 Redis 4.0 以前的版本,可以使用 SCAN 命令迭代删除;对于 bigkey 的集合查询和聚合操作,可以使用 SCAN 命令在客户端完成。


5.Redis AOF 配置级别是什么?业务层面是否的确需要这一可靠性级别?如果我们需要高性能,同时也允许数据丢失,可以将配置项 no-appendfsync-on-rewrite 设置为 yes,避免 AOF 重写和 fsync 竞争磁盘 IO 资源,导致 Redis 延迟增加。当然, 如果既需要高性能又需要高可靠性,最好使用高速固态盘作为 AOF 日志的写入盘。


6.Redis 实例的内存使用是否过大?发生 swap 了吗?如果是的话,就增加机器内存,或者是使用 Redis 集群,分摊单机 Redis 的键值对数量和内存压力。同时,要避免出现 Redis 和其他内存需求大的应用共享机器的情况。


7.在 Redis 实例的运行环境中,是否启用了透明大页机制?如果是的话,直接关闭内存大页机制就行了。


8.是否运行了 Redis 主从集群?如果是的话,把主库实例的数据量大小控制在 2~4GB,以免主从复制时,从库因加载大的 RDB 文件而阻塞。


9.是否使用了多核 CPU 或 NUMA 架构的机器运行 Redis 实例?使用多核 CPU 时,可以给 Redis 实例绑定物理核;使用 NUMA 架构时,注意把 Redis 实例和网络中断处理程序运行在同一个 CPU Socket 上。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
NoSQL Redis
Redis的数据淘汰策略有哪些 ?
Redis 提供了 8 种数据淘汰策略,分为淘汰易失数据和淘汰全库数据两大类。易失数据淘汰策略包括:volatile-lru、volatile-lfu、volatile-ttl 和 volatile-random;全库数据淘汰策略包括:allkeys-lru、allkeys-lfu 和 allkeys-random。此外,还有 no-eviction 策略,禁止驱逐数据,当内存不足时新写入操作会报错。
59 16
|
12天前
|
NoSQL 算法 Redis
redis内存淘汰策略
Redis支持8种内存淘汰策略,包括noeviction、volatile-ttl、allkeys-random、volatile-random、allkeys-lru、volatile-lru、allkeys-lfu和volatile-lfu。这些策略分别针对所有键或仅设置TTL的键,采用随机、LRU(最近最久未使用)或LFU(最少频率使用)等算法进行淘汰。
27 5
|
12天前
|
NoSQL 安全 Redis
redis持久化策略
Redis 提供了两种主要的持久化策略:RDB(Redis DataBase)和AOF(Append Only File)。RDB通过定期快照将内存数据保存为二进制文件,适用于快速备份与恢复,但可能因定期保存导致数据丢失。AOF则通过记录所有写操作来确保数据安全性,适合频繁写入场景,但文件较大且恢复速度较慢。两者结合使用可增强数据持久性和恢复能力,同时Redis还支持复制功能提升数据可用性和容错性。
33 5
|
1月前
|
存储 NoSQL Redis
Redis的数据过期策略有哪些 ?
Redis 采用两种过期键删除策略:惰性删除和定期删除。惰性删除在读取键时检查是否过期并删除,对 CPU 友好但可能积压大量过期键。定期删除则定时抽样检查并删除过期键,对内存更友好。默认每秒扫描 10 次,每次检查 20 个键,若超过 25% 过期则继续检查,单次最大执行时间 25ms。两者结合使用以平衡性能和资源占用。
45 11
|
1月前
|
存储 缓存 监控
利用 Redis 缓存特性避免缓存穿透的策略与方法
【10月更文挑战第23天】通过以上对利用 Redis 缓存特性避免缓存穿透的详细阐述,我们对这一策略有了更深入的理解。在实际应用中,我们需要根据具体情况灵活运用这些方法,并结合其他技术手段,共同保障系统的稳定和高效运行。同时,要不断关注 Redis 缓存特性的发展和变化,及时调整策略,以应对不断出现的新挑战。
64 10
|
1月前
|
缓存 监控 NoSQL
Redis 缓存穿透及其应对策略
【10月更文挑战第23天】通过以上对 Redis 缓存穿透的详细阐述,我们对这一问题有了更深入的理解。在实际应用中,我们需要根据具体情况综合运用多种方法来解决缓存穿透问题,以保障系统的稳定运行和高效性能。同时,要不断关注技术的发展和变化,及时调整策略,以应对不断出现的新挑战。
51 4
|
2月前
|
缓存 分布式计算 NoSQL
大数据-47 Redis 缓存过期 淘汰删除策略 LRU LFU 基础概念
大数据-47 Redis 缓存过期 淘汰删除策略 LRU LFU 基础概念
80 2
|
2月前
|
存储 缓存 NoSQL
【redis】数据量庞大时的应对策略
【redis】数据量庞大时的应对策略
45 2
|
2月前
|
NoSQL Redis
redis 的 key 过期策略是怎么实现的(经典面试题)超级通俗易懂的解释!
本文解释了Redis实现key过期策略的方式,包括定期删除和惰性删除两种机制,并提到了Redis的内存淘汰策略作为补充,以确保过期的key能够被及时删除。
61 1
|
3月前
|
存储 缓存 NoSQL
Redis 过期删除策略与内存淘汰策略的区别及常用命令解析
Redis 过期删除策略与内存淘汰策略的区别及常用命令解析
81 0