redis set 超时_记一次redis读取超时的排查过程(SADD惹的祸)

本文详细记录了一次由于SADD命令引发的Redis读取超时问题的排查过程。通过测试发现,尽管SADD操作在理论上具有O(1)的复杂度,但在大量数据下可能会导致执行缓慢,甚至触发过期删除操作,从而影响性能。解决方案包括分片处理集合键和利用Redis 4.0的延迟删除特性来减少阻塞。
摘要由CSDN通过智能技术生成

记一次redis读取超时的排查过程(SADD惹的祸)

问题背景

在业务使用redis过程中,出现了read timeout 的异常。

问题排查

直接原因

运维查询redis慢查询日志,发现在异常时间节点,有redis慢查询日志,执行sadd 命令花费了1秒钟。但由于redis是单线程应用,执行单条命令的阻塞,会造成其他命令的排队等候,导致read timeout。

深入排查-为什么sadd这么慢呢

为什么sadd这么慢呢?查阅redis文档看到,sadd操作的复杂度是O(1)的,实际使用本机docker搭建redis进行测试,使用脚本进行sadd,直到800W以上的量级才偶尔出现100毫秒以上的情况。(测试过程详见后面)

搭建redis环境

偷懒在本机就行测试,使用docker跑起了redis应用,过程如下:docker pull redis # 使用redis3.x版本docker run  -itv ~/redis.conf:/redis.conf -p 32768:6379 --name myredis5 -d redis redis-server /redis.conf

测试脚本#coding=utf-8import timeimport redisimport random

r = redis.Redis(host='x.x.x.x', port=xxxx, decode_responses=True)

k = 'key4'tarr = []

st = time.clock()

st2 = time.clock()

r.sadd(k, 1) # 创建连接也会有耗时for i in range(1, 1600000):

t1 = time.clock() * 1000

rn = random.randint(100000000000, 20000000000000)

r.sadd(k, rn)

t2 = time.clock() * 1000

c = t2 - t1

tarr.append(str(c))    if c > 100:        print i, cprint time.clock()

s = "\n".join(tarr)with open('./result.txt', 'w') as f:

f.write(s)

测试结果

到达800W的时候开始偶尔出现sadd需要100ms的现象。

问题分析

查询了很多资料,无意中看到redis del操作复杂度为O(n),这里补充一下超时的更多背景,举例如下:

慢查询日志时间:16号00点00分01秒,命令为sadd prefix_20180215, 且key有过期时间。

看到这里答案已经呼之欲出,是不是sadd触发了redis是过期删除操作,同时由于del命令的复杂度为O(n),时间花在了删除过期数据上。

测试重现for i in range(1, 1000000):

t1 = time.clock() * 1000

rn = random.randint(100000000000, 20000000000000)

r.sadd(k, rn)

t2 = time.clock() * 1000

c = t2 - t1

tarr.append(str(c))    if c > 100:        print i, c

x = int(time.time())

x += 10 #延时10每秒过期r.expire(k, 10)while True:

y = time.time()

t1 = time.clock() * 1000

rn = random.randint(1, 1000000000)

r.sadd(k, rn)

t2 = time.clock() * 1000

tarr.append(str(c))    if c > 100:#复现sadd慢查询的情况

print i, c    if y > x + 5: # 超时时间就break

breakprint time.clock()

重现的步骤很简单,给某个key sadd上足够的数据(百万级)

给key设置一个相对过期时间。

持续调用sadd命令,记录调用时间。

最后观察redis的慢查询日志。

如猜想一样,慢查询日志中出现了SADD命令,耗时1秒。

解决方案与总结

由于redis 对于集合键的del操作复杂度均为O(n),所以对于集合键,最好设置通过分片,避免单个key的值过大。另外,redis4.0已经通过配置支持延时删除,可以通过lazyfree_lazy_expire/azyfree_lazy_eviction/lazyfree_lazy_server_del 来实现异步删除的操作,避免异步阻塞

延伸阅读

最后,让我们来看看redis3.x和4.x处理删除key的源码吧。

redis 有三种淘汰key的机制,分别为del命令被动淘汰(当请求命令对应的键过期时进行删除)

主动删除(redis主动对键进行淘汰,回收内存)

我们先看看redis3.x版本中上面三种淘汰机制的入口代码。

del命令 - delCommandvoid delCommand(client *c) {

int deleted = 0, j;    for (j = 1; j argc; j++) {

expireIfNeeded(c->db,c->argv[j]);        if (dbDelete(c->db,c->argv[j])) {

signalModifiedKey(c->db,c->argv[j]);

notifyKeyspaceEvent(NOTIFY_GENERIC,                "del",c->argv[j],c->db->id);

server.dirty++;

deleted++;

}

}

addReplyLongLong(c,deleted);

<
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值