redis优化技巧

235 阅读11分钟

1、缩短键值对的存储长度;

image.png

2、使用 lazy free(延迟删除)特性;

在删除时提供异步延时释放键值的功能,把键值释放操作放在 BIO(Background I/O) 单独的子线程处理中,以减少删除对 Redis 主线程的阻塞,可有效地避免删除 big key 带来的性能和可用性问题 lazy free 对应了4 种场景,默认都是关闭(lazy free 特性是 Redis 4.0 新增功能。

lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
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 机制删除。

建议开启 lazyfree-lazy-eviction、lazyfree-lazy-expire、lazyfree-lazy-server-del 等配置,可有效的提高主线程的执行效率

3、设置键值的过期时间;

对键值设置合理的过期时间, Redis 会自动清除过期的键值对,以节约对内存的占用,以避免键值过多的堆积,频繁触发内存淘汰策略。

4、禁用长耗时的查询命令;

Redis 大多数读写命令的时间复杂度都在 O(1) 到 O(N) 之间,官方文档对每个命令都有时间复杂度说明(地址redis.io/commands)

图片O(1) 表示可安全使用的,O(N) 需注意,N 表示不确定,数据越大查询的速度可能会越慢。因为 Redis 只用一个线程来做数据查询,如果这些指令耗时很长,就会阻塞 Redis,造成大量延时。

要避免 O(N) 命令对 Redis 造成影响,可从下面几方面进行考虑:

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

5、使用 slowlog 优化耗时命令;

使用 slowlog 找出最耗时的 Redis 命令进行优化,以提升 Redis 运行速度。 慢查询两个重要配置项:

  • slowlog-log-slower-than :设置慢查询评定时间,即超过此配置项的命令,将会被当成慢操作记录在慢查询日志中
  • slowlog-max-len :配置慢查询日志的最大记录数

根据实际的业务进行配置,其中慢日志是按照插入的顺序倒序存入慢查询日志中,使用 slowlog get n 来获取相关的慢查询日志,再找到慢查询对应的业务进行相关的优化。

6、使用 Pipeline 批量操作数据;

import redis

# 创建连接池获取连接
pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)
r = redis.Redis(connection_pool=pool)

# 创建管道,可以选择开启或关闭事务,这里的事务与Redis事务一样是弱事务型
pipe = r.pipeline(transaction=True)

# 在管道中添加命令
pipe.set('new', '123')
pipe.set('name', 'wyk2')
pipe.set('company', 'csdn2')
pipe.hincrby('hage', 'wyk', 1)

# 执行pipeline里的命令
pipe.execute()

7、避免大量数据同时失效; Redis 过期键值删除使用的是贪心策略,每秒会进行 10 次过期扫描,此配置可在 redis.conf 进行配置,默认值是 hz 10,Redis 会随机抽取 20 个值,删除这 20 个键中过期的键,如过期 key 的比例超过 25% ,重复执行此流程,如下图:

图片

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

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

8、客户端使用优化;

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

9、 限制 Redis 内存大小;

64 位操作系统中 Redis 的内存大小是没有限制的,即配置项maxmemory  是被注释掉的,会导致在物理内存不足时,使用 swap 空间既交换空间,当操作系统将 Redis 所用的内存分页移至 swap 空间时,将会阻塞 Redis 进程,导致 Redis 出现延迟,从而影响 Redis 的整体性能。因此需限制 Redis 的内存大小为一个固定的值,当 Redis 的运行到达此值时会触发内存淘汰策略。

内存淘汰策略在 Redis 4.0 之后有 8 种:

  • noeviction:不淘汰任何数据,当内存不足时,新增操作会报错,Redis 默认内存淘汰策略;
  • allkeys-lru:淘汰整个键值中最久未使用的键值;
  • allkeys-random:随机淘汰任意键值;
  • volatile-lru:淘汰所有设置了过期时间的键值中最久未使用的键值;
  • volatile-random:随机淘汰设置了过期时间的任意键值;
  • volatile-ttl:优先淘汰更早过期的键值。

Redis 4.0 版本中新增 2 种淘汰策略:

  • volatile-lfu:淘汰所有设置了过期时间的键值中,最少使用的键值;
  • allkeys-lfu:淘汰整个键值中最少使用的键值。

其中 allkeys-xxx 表示从所有的键值中淘汰数据,而 volatile-xxx 表示从设置了过期键的键值中淘汰数据。 根据实际的业务情况进行设置,默认的淘汰策略不淘汰任何数据,在新增时会报错

10、使用物理机而非虚拟机安装 Redis 服务;

在虚拟机中运行 Redis 服务器,和物理机共享一个物理网口,并且一台物理机可能有多个虚拟机在运行,因此在内存占用上和网络延迟方面会有很糟糕的表现,可通过 ./redis-cli --intrinsic-latency 100 命令查看延迟时间,如果对 Redis 的性能有较高要求的话,应尽可能在物理机上直接部署 Redis 服务器。

11、检查数据持久化策略;

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

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

  • AOF(Append Only File,文件追加方式),记录所有的操作命令,并以文本的形式追加到文件中;

  • RDB(Redis DataBase,快照方式)将某一个时刻的内存数据,以二进制的方式写入磁盘;

  • 混合持久化方式,Redis 4.0 之后新增的方式,混合持久化是结合了 RDB 和 AOF 的优点,在写入的时候,先把当前的数据以 RDB 的形式写入文件的开头,再将后续的操作命令以 AOF 的格式存入文件,这样既能保证 Redis 重启时的速度,又能减低数据丢失的风险。

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

查询是否开启混合持久化可以使用命令:config get aof-use-rdb-preamble

图片

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

① 通过命令行开启

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

图片

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

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

在 Redis 的根路径下找到 redis.conf 文件,把配置文件中的 aof-use-rdb-preamble no 改为 aof-use-rdb-preamble yes 如下图所示:

图片

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

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

12、禁用 THP 特性;

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

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

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

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

image.png

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

Redis 分布式架构方法:

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

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

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

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

在三个方法中,只需使用一个即可, Redis Cluster 是首选的实现方案,它可把读写压力自动的分担给更多的服务器,并且拥有自动容灾的能力。

建议阅读文章:juejin.cn/post/726922…

14、进阶高级技术:Tair,以下是Tair和redis的对比(tair适合更大并发的场景)

特性TairRedis
数据结构和操作主要支持键值对、Map和List1提供了丰富的数据结构,如字符串、列表、集合、哈希表和有序集合等,并且支持这些数据结构的各种操作1
性能采用了多线程模型,可以更好地利用多核CPU1Redis 6.0开始全面支持多线程1234,可以充分利用服务器CPU资源1234
可用性和可靠性通过多副本和自动故障转移来保证高可用和数据的可靠性1支持主从复制和哨兵模式,可以实现高可用1
持久化提供了更强的数据持久化保证1支持RDB和AOF两种持久化方式1
适用场景更适合不能容忍数据丢失,数据量大,内存放不下的服务1适合需要使用复杂数据结构,延迟敏感的服务1
扩展性支持在线扩容,可以在不停止服务的情况下增加存储容量1支持分片,可以通过增加实例来扩展系统的容量1