Redis时延问题,慢查询优化
通过连接池的方式实现查询优化
使用普通jedis对象和jedis连接池之间的差别
关于Redis中比较耗时的命令,禁止在线上环境使用
keys、sort,exists等命令
keys [pattern]模式查询 O(n) 禁用
通过scan模式查询 SCAN cursor [MATCH pattern] [COUNT count]
sort 主要对List,Set,Zset来进行排序。
同样,既然需要使用keys这些耗时的操作,那么我们就将它们剥离出去,比如单开一个redis slave结点,专门用于keys、sort等耗时的操作,这些查询一般不会是线上的实时业务,查询慢点就慢点,主要是能完成任务,而对于线上的耗时快的任务没有影响
exists key_name:查询key是否存在
redis本身是key-value的形式,时间复杂度本来是O(1),但是为什么会超时呢?
我们发现在EXISTS命令处理函数中实现了清除过期key的主动策略,会先调用expireIfNeeded函数检查要访问的key是否过期,如果过期就delete掉这个key。del命令在删除元素很多的复合数据类型(list、hash、zset、set)时是一个很耗时的操作。由于存在元素很多的zset,和ZADD一样,在删除zset时需要一个一个遍历所有元素,时间复杂度是大O(n)。由于这个删除操作在EXISTS命令的处理函数中执行,所以导致EXISTS耗时过长。
上面主要的原因还是我们处理过期key的方式的问题,我们处理过期key:
1.定期删除:redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载!
2.惰性删除 :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。然后我们在做每一个操作的时候,都会执行exists keyname 的操作,如果遇到了过期key,我们会在这里面执行删除key的操作,所以非常花时间。
针对删除大key这个问题:
1.我们可以选择适当增加过期时间,但不能从根本上解决问题
2.redis作者提供了解决方案,具体就是使用异步线程对大key进行删除操作,避免阻塞主线程。
smembers,hgetall,lrange,smembers,zrange,mget, mset等命令:时间复杂度O(n),如果集合本身数据量非常大也会影响查询
解决方案:
和sort,keys等命令不一样,smembers可能是线上实时应用场景中使用频率非常高的一个命令,这里分流一招并不适合,我们更多的需要从设计层面来考虑;
在设计时,我们可以控制集合的数量,将集合数一般保持在500个以内;
比如原来使用一个键来存储一年的记录,数据量大,我们可以使用12个键来分别保存12个月的记录,或者365个键来保存每一天的记录,将集合的规模控制在可接受的范围;
如果不容易将集合划分为多个子集合,而坚持用一个大集合来存储,那么在取集合的时候可以考虑使用SRANDMEMBER key [count];随机返回集合中的指定数量,当然,如果要遍历集合中的所有元素,这个命令就不适合了;
生成RDB快照文件时,save命令会带来阻塞:
当然我们采用bgsave来fork()一个子进程来做数据持久化的bgsave,虽然在redis底层我们采用写时复制策略copy-on-write(为子进程创建虚拟空间结构,复制父进程的虚拟空间结构,不分配物理内存,就有点像只复制地址,这样可以极大的提高redis性能,但如果对父进程有写入操作了,那么我们还是要对子进程复制父进程的物理内存,这是非常耗时的,所以在bgsave命令的时候不要对父进程写入)。
在极端的情况下,父进程内存空间特别大,它的页表大小也会有点大,即使不复制物理内存,也可能很耗时哦。
redis慢查询分析
对于一个redis命令生命周期:
慢查询的配置:
1.慢查询的语句会被放在一个队列里面
slowlog-max-len这个慢查询队列的长度,这个队列放在内存中不会被持久化(需要定期持久化慢查询)
2.慢查询阈值
slowlog-log-slower-than(微秒)当大于这个时间的时候会被放在慢查询队列里面
慢查询命令:
slowlog get [n]:获取慢查询队列,获取前n条慢查询的数据。
slowlog len:获取慢查询队列长度
slowlog reset:清空慢查询队列
对于AOF阻塞定位:
info persistence
有aof_delayed_fsync:100可以看到进行了多少个这样的命令。
pipeline:解决耗时操作的最简单的操作:(简单来说就是减少了客户端发送请求的数量,可以在一定程度上帮助提高性能)
Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应,简单描述就是一次性可以发送多个请求。
1.pipeline不是原子操作,其中的所有请求还是按原来的顺序,但中间可能插入其他的请求。
2.pipeline每次只能作用在一个Redis节点上
没有pipline
Jedis jedis = new Jedis("127.0.0.1",6379);
for(int i=0;i<1000;i++) {
jedis.hset("hashkey:"+i,"field"+i,"value"+i);
}
使用pipline
Jedis jedis = new Jedis("127.0.0.1",6379);
for(int i=0;i<100;i++) {
Pipeline pipeline = jedis.pipelined();
for(int j=i*100;j<(i+1)*100;j++) {
pipeline.hset("hashkey:"+i,"field"+i,"value"+i);
}
pipeline.syncAndReturnAll();
}
我们也可以通过pipline高效插入:方在2.6版本推出了一个新的功能-pipe mode,即将支持Redis协议的文本文件直接通过pipe导入到服务端。
1.新建文本文件,创建redis命令
SET Key0 Value0
SET Key1 Value1
...
SET KeyN ValueN
2. 将这些命令转化成Redis Protocol。
因为Redis管道功能支持的是Redis Protocol,而不是直接的Redis命令。
3.利用管道插入:
cat data.txt | redis-cli --pipe
本文来自博客园,作者:LeeJuly,转载请注明原文链接:https://www.cnblogs.com/peterleee/p/10701376.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义