查看原文
其他

首长,Redis性能优化十三条军规立好了,请过目~

无聊 Java后端面试官 2021-11-29

前言

Redis作为高性能的内存数据库,在大数据量的情况下也会遇到性能瓶颈,日常开发中只有时刻谨记优化铁则,才能使得Redis性能发挥到极致。

本文将会介绍十三条性能优化军规,开发过程中只要按照执行,性能必能质的飞跃。

1. 避免慢查询命令

慢查询命令指的是执行较慢的命令,Redis自身提供了许多的命令,并不是所有的命令都慢,这和命令的操作复杂度有关,因此必须知道Redis不同命令的复杂度。

如说,Value 类型为 String 时,GET/SET 操作主要就是操作 Redis 的哈希表索引。这个操作复杂度基本是固定的,即 O(1)。但是,当 Value 类型为 Set 时,SORTSUNION/SMEMBERS 操作复杂度分别为 O(N+M*log(M))O(N)。其中,NSet 中的元素个数,MSORT 操作返回的元素个数。这个复杂度就增加了很多。Redis 官方文档中对每个命令的复杂度都有介绍,当你需要了解某个命令的复杂度时,可以直接查询。

当你发现 Redis 性能变慢时,可以通过 Redis 日志,或者是 latency monitor 工具,查询变慢的请求,根据请求对应的具体命令以及官方文档,确认下是否采用了复杂度高的慢查询命令。

如果确实存在大量的慢查询命令,建议如下两种方式:

  1. 用其他高效的命令代替:比如说,如果你需要返回一个 SET 中的所有成员时,不要使用 SMEMBERS 命令,而是要使用 SSCAN 多次迭代返回,避免一次返回大量数据,造成线程阻塞。

  2. 当你需要执行排序、交集、并集操作时,可以在客户端完成,而不要用 SORTSUNION、SINTER 这些命令,以免拖慢 Redis 实例。

2. 生产环境禁用keys命令

keys这个命令是最容易忽略的慢查询命令,因为keys命令需要遍历存储的键值对,所以操作延时很高,在生产环境使用很可能导致Redis阻塞;因此不建议在生产环境中使用keys命令

3. keys需要设置过期时间

Redis作为内存数据库,一切的数据都是在内存中,一旦内存占用过大则会大大影响性能,因此需要对有时间限制的数据需要设置过期时间,这样Redis能够定时的删除过期的数据。

4. 禁止批量的给keys设置相同的过期时间

默认情况下,Redis 每 100 毫秒会删除一些过期 key,具体的算法如下:

  1. 采样 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 个数的 key,并将其中过期的 key 全部删除;
  2. 如果超过 25%key 过期了,则重复删除的过程,直到过期 key 的比例降至 `25%`` 以下。

ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 是 Redis 的一个参数,默认是 20,那么,一秒内基本有 200 个过期 key 会被删除。这一策略对清除过期 key、释放内存空间很有帮助。如果每秒钟删除 200 个过期 key,并不会对 Redis 造成太大影响。

但是,如果触发了上面这个算法的第二条,Redis 就会一直删除以释放内存空间。注意,删除操作是阻塞的(Redis 4.0 后可以用异步线程机制来减少阻塞影响)。所以,一旦该条件触发,Redis 的线程就会一直执行删除,这样一来,就没办法正常服务其他的键值操作了,就会进一步引起其他键值操作的延迟增加,Redis 就会变慢。

频繁使用带有相同时间参数的 EXPIREAT 命令设置过期 key 将会触发算法第二条,这就会导致在一秒内存在大量的keys过期。

因此开发中一定要禁止批量的给keys设置过期时间。

5. 谨慎选择数据结构

Redis 常用的数据结构一共有五种:stringhashlistsetzset(sorted set)。可以发现,大多数场景下使用 string 都可以去解决问题。但是,这并不一定是最优的选择。下面,简单说明下它们各自的适用场景:

  1. string:单个的缓存结果,不与其他的 KV 之间有联系
  2. hash:一个 Object 包含有很多属性,且这些属性都需要单独存储。注意:这种情况不要使用 string,因为 string 会占据更多的内存
  3. list:一个 Object 包含很多数据,且这些数据允许重复、要求有顺序性
  4. set:一个 Object 包含很多数据,不要求数据有顺序,但是不允许重复
  5. zset:一个 Object 包含很多数据,且这些数据自身还包含一个权重值,可以利用这个权重值来排序

另外Redis还提供了几种的扩展类型,如下:

  1. HyperLogLog:适合用于基数统计,比如PV,UV的统计,存在误差问题,不适合精确统计。
  2. BitMap:适合二值状态的统计,比如签到打卡,要么打卡了,要么未打卡。

6. 检查持久化策略

Redis4.0之后使用了如下三种持久化策略:

  1. AOF日志:一种采用文件追加的方式将命令记录在日志中的策略,针对同步和异步追加还提供了三个配置项,有兴趣的可以查看官方文档。
  2. RDB快照:以快照的方式,将某一个时刻的内存数据,以二进制的方式写入磁盘。
  3. AOFRDB混用:Redis4.0新增的方式,为了采用两种方式各自的优点,在RDB快照的时间段内使用的AOF日志记录这段时间的操作的命令,这样一旦发生宕机,将不会丢失两段快照中间的数据。

由于写入磁盘有IO性能瓶颈,因此不是将Redis作为数据库的话(可以从后端恢复),建议禁用持久化或者调整持久化策略。

7. 采用高速的固态硬盘作为日志写入设备

由于AOF日志的重写对磁盘的压力较大,很可能会阻塞,如果需要使用到持久化,建议使用高速的固态硬盘作为日志写入设备。

8. 使用物理机而非虚拟机

由于虚拟机增加了虚拟化软件层,与物理机相比,虚拟机本身就存在性能的开销,可以使用如下命令来分别测试下物理机和虚拟机的基线性能

./redis-cli --intrinsic-latency 120

测试结果可以知道,使用物理机的基线性能明显比虚拟机的基线性能更好。

9. 增加机器内存或者使用Redis集群

物理机器的内存不足将会导致操作系统内存的Swap

内存 swap 是操作系统里将内存数据在内存和磁盘间来回换入和换出的机制,涉及到磁盘的读写,所以,一旦触发 swap,无论是被换入数据的进程,还是被换出数据的进程,其性能都会受到慢速磁盘读写的影响。

Redis 是内存数据库,内存使用量大,如果没有控制好内存的使用量,或者和其他内存需求大的应用一起运行了,就可能受到 swap 的影响,而导致性能变慢

这一点对于 Redis 内存数据库而言,显得更为重要:正常情况下,Redis 的操作是直接通过访问内存就能完成,一旦 swap 被触发了,Redis 的请求操作需要等到磁盘数据读写完成才行。而且,和我刚才说的 AOF 日志文件读写使用 fsync 线程不同,swap 触发后影响的是 Redis 主 IO 线程,这会极大地增加 Redis 的响应时间。

因此增加机器的内存或者使用Redis集群能够有效的解决操作系统内存的Swap,提高性能。

10. 使用 Pipeline 批量操作数据

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

11. 客户端使用优化

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

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

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

  1. 主从同步
  2. 哨兵模式
  3. Redis Cluster 集群

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

而哨兵模式是对于主从功能的升级,但当主节点奔溃之后,无需人工干预就能自动恢复 Redis 的正常使用。

Redis ClusterRedis 3.0 正式推出的,Redis 集群是通过将数据分散存储到多个节点上,来平衡各个节点的负载压力。

Redis Cluster 采用虚拟哈希槽分区,所有的键根据哈希函数映射到 0 ~ 16383整数槽内,计算公式:slot = CRC16(key) & 16383,每一个节点负责维护一部分槽以及槽所映射的键值数据。这样 Redis 就可以把读写压力从一台服务器,分散给多台服务器了,因此性能会有很大的提升。

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

13. 避免内存碎片

频繁的新增修改会导致内存碎片的增多,因此需要时刻的清理内存碎片。

Redis提供了INFO memory可以查看内存的使用信息,如下:

INFO memory
# Memory
used_memory:1073741736
used_memory_human:1024.00M
used_memory_rss:1997159792
used_memory_rss_human:1.86G

mem_fragmentation_ratio:1.86

这里有一个 mem_fragmentation_ratio 的指标,它表示的就是 Redis 当前的内存碎片率。那么,这个碎片率是怎么计算的呢?其实,就是上面的命令中的两个指标 used_memory_rssused_memory 相除的结果。

mem_fragmentation_ratio = used_memory_rss/ used_memory

used_memory_rss 是操作系统实际分配给 Redis 的物理内存空间,里面就包含了碎片;而 used_memory 是 Redis 为了保存数据实际申请使用的空间。

那么,知道了这个指标,我们该如何使用呢?在这儿,我提供一些经验阈值:

  1. mem_fragmentation_ratio 大于 1 但小于 1.5。这种情况是合理的。这是因为,刚才我介绍的那些因素是难以避免的。毕竟,内因的内存分配器是一定要使用的,分配策略都是通用的,不会轻易修改;而外因由 Redis 负载决定,也无法限制。所以,存在内存碎片也是正常的。

  2. mem_fragmentation_ratio 大于 1.5 。这表明内存碎片率已经超过了 50%。一般情况下,这个时候,我们就需要采取一些措施来降低内存碎片率了。

一旦内存碎片率过高了,此时就应该采用手段清理内存碎片了,具体如何清理,参考文章:Redis清理内存碎片

总结

本文着重介绍了13条性能优化军规,在开发过程中还是需要针对性的具体问题具体分析,希望作者这篇文章能够帮助到你。


: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存