Redis性能优化的13条军规

一、前言

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

二、Redis操作最佳实践

我们通过以下手段来提升Redis的运行速度:
1、缩短key/value的存储长度
2、使用lazy free(延迟删除)特性
3、设置key/value的过期时间
4、禁用长耗时的查询命令
5、使用slowlog优化耗时命令:怎样使用?
6、使用pipeline批量操作数据
7、避免大量数据同时失效
8、客户端使用优化
9、限制Redis内存大小
10、使用物理机而非虚拟机安装Redis服务
11、检查数据持久化策略
12、禁用THP特性
13、使用分布式架构来增加读写速度

2.1、缩短key/value的存储长度

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

上述还只是写入时的速度,当key/value内容较大时,会带来另外几个问题:

  • 内容越大,需要的持久化时间就越长,请求hang住的时间就越长
  • 内容越大,在网络上传输需要的时间久越多
  • 内容越大,会频繁触发内存淘汰机制

因此在保证完整语义的同时,要尽量缩短key/value的存储长度,必要时对数据进行序列化和压缩后再存储,以Java为例,序列化可以用PB,压缩可以用snappy。

2.2、使用lazy free特性

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

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

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

他们代表的含义:

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

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

2.3、设置key/value的过期时间

设置合理的过期时间,Redis会帮你自动清除过期的key/value对,以节约对内存的占用,避免key/value过多,频繁的触发内存淘汰策略(如LRU)。

2.4、禁用长耗时的查询命令

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

扫描二维码关注公众号,回复: 12678351 查看本文章

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

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

2.5、使用slowlog优化耗时命令

可使用slowlog功能找出最耗时的redis命令进行优化,慢查询有两个重要的配置项:

  • slowlog-log-slower-than:用于设置慢查询的评定时间,及超过此项设置的值就会被当成慢操作,被记录到慢查询日志中,它的执行单位是微妙(1秒等于100万微妙)
  • slowlog-max-len:用来配置慢查询日志的最大记录数

我们可以使用slowlog get n 来获取相关的慢查询日志,再找到这些慢查询对应的业务,进行相关的优化。

2.6、使用pipeline批量操作数据

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

public class 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) + "毫秒");
    }
}

以上程序,执行耗时:297ms

若普通操作:

public class 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) + "毫秒");
    }
}

执行耗时17276ms。

2.7、避免大量数据同时失效

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

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

所以要防止大量的缓存在同一时刻过期,最简单的解决办法就是在过期时间的基础上,添加一个指定范围的随机数。

2.8、客户端使用优化

在客户端要尽量使用连接池与pipeline技术,尽量减少网络IO次数,并减少非必要的调用指令。

2.9、限制Redis内存的大小

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

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

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

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

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

2.10、使用物理机而非虚拟机

在虚拟机上运行Redis服务器,因为和物理机共享一个物理网口,并且一台服务理解可能有多个虚拟机在运行,因此在内存占用上和网络延迟上,都会不如物理机。
我们可以通过./redis-cli --intrinsic-latency 100 命令查看延迟时间,如果对Redis性能有较高要求的话,最好使用物理机而不是虚拟机。

2.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命令
Redis 5.0默认开启该功能。

2.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

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

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

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

使用主从同步功能,我们可以把写放入主库上执行,把读放到从库上,以此来提升性能

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

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

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

这三个方案,选一个就行,毫无疑问首选Redis Cluster, 他可以把读写压力分担给更多的服务器,并且拥有自动容灾的能力。

猜你喜欢

转载自blog.csdn.net/shijinghan1126/article/details/108881469