Redis性能优化的13条建议

Redis是基于单线程模型实现的,也就是Redis是使用一个线程来处理所有的客户端请求的,尽管Redis使用了非阻塞式IO,并且对各种命令都做了优化(大部分命令操作时间复杂度都是O(1)),但由于Redis是单线程执行的特点,因此它对性能的要求更加苛刻。

1. 缩短键值对的存储长度

键值对的长度是和性能成反比的,比如我们来做一组写入数据的性能测试,执行结果如下。
在这里插入图片描述
从以上数据可以看出,在key不变的情况下,value值越大操作效率越慢,因为Redis对于同一种数据类型会使用不同的内部编码进行存储,比如字符串的内部编码就有三种,int(整数编码)、raw(优化内存分配的字符串编码)、embstr(动态字符串编码),这是因为Redis的作者是想通过不同的编码实现效率和空间的平衡,然而数据量越大使用的内部编码就越复杂,而越是复杂的内部编码存储的性能就越低。

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

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

因此在保存完整语义的同时,我们要尽量的缩短键值对的存储长度,必要时要对数据进行序列化和压缩再存储,以Java为例,序列化我们可以使用protostuffkryo,压缩我们可以使用snappy

2. 使用lazy free特性

lazy free特性是Redis 4.0新增的一个非常实用的功能,它可以理解为惰性删除或延迟删除。意思是在删除的时候提供异步延时释放键值的功能,把键值释放操作放在BIO(Background I/O)单独的子线程处理中,以减少删除对Redis主线程的阻塞,可以有效地避免删除big key时带来的性能和可用性问题。

lazy free对应了4中场景,默认都是关闭的。

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

它们代表的含义如下:

  • lazyfree-lazy-eviction:表示当Redis运行内存超过maxmemory时,是否开启lazy free机制删除;
  • lazyfree-lazy-expire:表示设置了过期时间的键值,当过期之后是否开启lazy free机制;
  • lazyfree-lazy-server-del:有些指令在处理已存在键时,会带一个隐式的del键的操作,比如rename命令,当目标键已存在,Redis会先删除目标键,如果这些目标键是一个big key,就会造成阻塞删除的问题,此配置表示在这种场景中是否开始lazy free机制删除;
  • slave-lazy-flush:针对slave(从节点)进行全量数据同步,salve在加载master的RDB文件前,会运行flushall来清理自己的数据,它表示此时是否开启lazy free机制删除。

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

3. 设置键值的过期时间

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

4. 禁用长耗时的查询命令

Redis绝大多数读写命令的时间复杂度都在O(1)到O(N)之间,在官方文档对每个命令都有时间复杂度说明,其中O(1)表示可以安全使用的,而O(N)就应该当心了,N表示不确定,数据越大查询的速度可能会越慢。因为Redis只用一个线程来做数据查询,如果这些指令耗时长,就会阻塞Redis,造成大量耗时。

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

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

5. 使用slowlog优化耗时命令

我们可以使用slowlog功能找出最耗时的Redis命令进行相关的优化,以提升Redis的运行速度,慢查询有两个重要的配置项:

  • slowlog-log-slower-than:用于设置慢查询的评定时间,也就是说超过此配置项的命令,将会被当成慢操作记录在慢查询日志中,它执行单位是微秒;
  • slowlog-max-len:用来配置慢查询日志的最大记录数 。

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

6. 使用Pipeline批量操作数据

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

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

publicclass PipelineExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        // 记录执行开始时间
        long beginTime = System.currentTimeMillis();
        // 获取 Pipeline 对象
        Pipeline pipe = jedis.pipelined();
        // 设置多个 Redis 命令
        for (int i = 0; i < 100; i++) {
            pipe.set("key" + i, "val" + i);
            pipe.del("key"+i);
        }
        // 执行命令
        pipe.sync();
        // 记录执行结束时间
        long endTime = System.currentTimeMillis();
        System.out.println("执行耗时:" + (endTime - beginTime) + "毫秒");
    }
}

以上程序直接结果为:

执行耗时:297毫秒

普通的操作代码如下:

publicclass PipelineExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        // 记录执行开始时间
        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < 100; i++) {
            jedis.set("key" + i, "val" + i);
            jedis.del("key"+i);
        }
        // 记录执行结束时间
        long endTime = System.currentTimeMillis();
        System.out.println("执行耗时:" + (endTime - beginTime) + "毫秒");
    }
}

以上程序的执行结果为:

执行耗时:17276毫秒

从以上的结果可以看出,管道的执行时间是297毫秒,而普通命令执行时间是17276毫秒,管道技术要比普通的执行大约快了58倍。

7. 避免大量数据同时失效

Redis过期键值删除使用的是贪心策略,它每秒会进行10次过期扫描,此配置可在redis.conf进行配置,默认是hz 10,Redis会随机抽取20个值,删除这20个键中过期的键,如果过期key的比例超过25%,重复执行此流程,如下图所示:
在这里插入图片描述
如果在大型系统中有大量缓存在同一时间同时过期,那么会导致Redis循环多次持续扫描过期字典,直到过期字典中过期键值被删除的比较稀疏为止,而在整个执行过程中会导致Redis的读写出现明显的卡顿,卡顿的另一种原因是内存管理器需要频繁回收内存页,因此也会消耗一定的CPU。

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

8. 客户端使用优化

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

9. 限制Redis内存大小

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

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

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

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

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

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

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

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

11. 检查数据持久化策略

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。如果是其他版本首先需要检查下是否已经开启了混合持久化,如果关闭的情况下,可以通过以下两种方式开启:

(1)通过命令行开启
使用命令config set aof-use-rdb-preamble yes,执行结果如下:
在这里插入图片描述
命令设置配置的缺点是重启Redis服务之后,设置的配置就会失效。

(2)通过修改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 nerver > /sys/kernel/mm/transparent_hugepage/enabled

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

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

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

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

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

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

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

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

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

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值