这是个人第 43 篇原创文章。
Redis 是基于单线程模型实现的,也就是 Redis 是使用一个线程来处理全部的客户端请求的,尽管 Redis 使用了非阻塞式 IO,而且对各类命令都作了优化(大部分命令操做时间复杂度都是 O(1)),但因为 Redis 是单线程执行的特色,所以它对性能的要求更加苛刻,本文咱们将经过一些优化手段,让 Redis 更加高效的运行。
本文咱们将使用如下手段,来提高 Redis 的运行速度:面试
键值对的长度是和性能成反比的,好比咱们来作一组写入数据的性能测试,执行结果以下:
从以上数据能够看出,在 key 不变的状况下,value 值越大操做效率越慢,由于 Redis 对于同一种数据类型会使用不一样的内部编码进行存储,好比字符串的内部编码就有三种:int(整数编码)、raw(优化内存分配的字符串编码)、embstr(动态字符串编码),这是由于 Redis 的做者是想经过不一样编码实现效率和空间的平衡,然而数据量越大使用的内部编码就越复杂,而越是复杂的内部编码存储的性能就越低。
这还只是写入时的速度,当键值对内容较大时,还会带来另外几个问题:redis
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-lazy-flush no
它们表明的含义以下:缓存
咱们应该根据实际的业务状况,对键值设置合理的过时时间,这样 Redis 会帮你自动清除过时的键值对,以节约对内存的占用,以免键值过多的堆积,频繁的触发内存淘汰策略。
4.禁用长耗时的查询命令安全
Redis 绝大多数读写命令的时间复杂度都在 O(1) 到 O(N) 之间,在官方文档对每命令都有时间复杂度说明,地址:https://redis.io/commands
以下图所示:
其中 O(1) 表示能够安全使用的,而 O(N) 就应该小心了,N 表示不肯定,数据越大查询的速度可能会越慢。由于 Redis 只用一个线程来作数据查询,若是这些指令耗时很长,就会阻塞 Redis,形成大量延时。
要避免 O(N) 命令对 Redis 形成的影响,能够从如下几个方面入手改造:性能优化
咱们可使用 slowlog 功能找出最耗时的 Redis 命令进行相关的优化,以提高 Redis 的运行速度,慢查询有两个重要的配置项:服务器
Pipeline (管道技术) 是客户端提供的一种批处理技术,用于一次处理多个 Redis 命令,从而提升整个交互的性能。
咱们使用 Java 代码来测试一下 Pipeline 和普通操做的性能对比,Pipeline 的测试代码以下:网络
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) + "毫秒"); } }
以上程序执行结果为:执行耗时:297毫秒
普通的操做代码以下:
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) + "毫秒");
}
}
以上程序执行结果为:
执行耗时:17276毫秒
从以上的结果能够看出,管道的执行时间是 297 毫秒,而普通命令执行时间是 17276 毫秒,管道技术要比普通的执行大约快了 58 倍。
7.避免大量数据同时失效架构
Redis 过时键值删除使用的是贪心策略,它每秒会进行 10 次过时扫描,此配置可在 redis.conf 进行配置,默认值是 hz 10,Redis 会随机抽取 20 个值,删除这 20 个键中过时的键,若是过时 key 的比例超过 25% ,重复执行此流程,以下图所示:app
若是在大型系统中有大量缓存在同一时间同时过时,那么会致使 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 种:
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。若是是其余版本的 Redis 首先须要检查一下,是否已经开启了混合持久化,若是关闭的状况下,能够经过如下两种方式开启:
经过命令行开启
经过修改 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。
13.使用分布式架构来增长读写速度
Redis 分布式架构有三个重要的手段:
往期推荐
Java14发布!Switch竟如此简单?Lombok也不须要了?来用Idea搭建Java14吧!
阿里巴巴Java开发手册建议设置HashMap的初始容量,但设置多少合适呢?
由于我说:volatile 是轻量级的 synchronized,面试官让我回去等通知!
关注下方二维码,订阅更多精彩内容