Redis 几乎每小时都出现大量超时,求助

68 天前
 drymonfidelia

报错信息是 Timeout awaiting response (outbound=1KiB, inbound=0KiB, 5728ms elapsed, timeout is 5000ms), command=SET, next: EVAL, inst: 0, qu: 0, qs: 15, aw: False, bw: SpinningDown, rs: ReadAsync, ws: Idle, in: 76, in-pipe: 0, out-pipe: 0, serverEndpoint: 127.0.0.1:6379, mc: 1/1/0, mgr: 10 of 10 available, clientName: AppProductionEnvServer1(SE.Redis- v2.5.43.42402), IOCP: (Busy=0,Free=10000,Min=9000,Max=10000), WORKER: (Busy=236,Free=32531,Min=10000,Max=32767), POOL: (Threads=236,Queueditems=50,Completeditems=8751117254), v: 2.5.43.42402 (Please take a look at this article for some common client-side issues that can cause timeouts: https://stackexchange.github.io/StackExchange.Redis/Timeouts)

出错的时候看了下也就三百多并发,比较怀疑是下面这段代码引起的:

   while (!await redis.GetDatabase().LockTakeAsync($"PlaceOrder:{user.UserId}", "1", TimeSpan.FromSeconds(180)))
        {
            await Task.Delay(1);
        }

作用是确保同一用户只有一个订单未写入数据库(系统下单逻辑涉及几十个函数,全是一些莫名奇妙的判断逻辑,混淆后可读性大幅提升的那种(当然是开玩笑的)),屎山作者已经跑路了,没人能看懂他代码,一个用户下多个订单数据会混乱。更牛逼的这套系统除了性能极差,运行 3 年没出错一次。一次调用 API 只能下一单,客户端随硬件交付,已经写死了,不能更新,然后客户端一次多少个订单就多少并发调用 API 提交,没有队列功能。目前要求 500 订单 10 秒内全部下单完成返回订单号(单独提交的话每个订单 0.01 秒左右能写入完数据拿到订单号)。

预分配订单号行不通,不运行一遍这部分屎山代码不能确定这个订单能不能提交,返回订单号就代表这个订单提交成功了,不能取消。目前打算改造成 Sub/Pub ,不知道能不能提升性能,或者 V 友有没有更好的改造方案?只要能让这屎山跑起来就行,代码多脏都没关系,改动需要尽可能小,不能把系统改炸。目前加硬件到 256GB 内存都没解决。

5839 次点击
所在节点    Redis
67 条回复
chenqh
68 天前
@drymonfidelia 你要不加个 log 看一下 lock 的耗时时间?
sagaxu
68 天前
“客户端一次多少个订单就多少并发调用 API 提交”

如果都是同一个 userId ,或者有锁其它共同的 id ,并发高的时候,可能会出现饥饿的情况,每次 unlock 都被别的 lock 抢去了,如果下单性能稳定,超时时长改成 10 秒,从头饿到尾也轮到了
dynastysea
68 天前
@chenqh 因为作者分析不出原因,我的意思是这种也可以叫云厂商的帮忙协助定位
chenqh
68 天前
@dynastysea 云厂商还帮忙做这个的吗?我无知了。
lsk569937453
68 天前
1.不要换成 Sub/Pub,性能上不会有提升,架构更复杂了。
2.提高 redis 的超时时间。

redis 的 client 超时时间可能是全局线程池繁忙导致的,也可以是 redis 服务端导致的。redis 可以加一下监控,看有没有大 key/热 key 查询。
chunworkhard
68 天前
学习下
drymonfidelia
68 天前
@sagaxu 但是这个 timeout 似乎是 redis 被卡死了,一次加锁尝试一直没返回结果
drymonfidelia
68 天前
@lsk569937453 redis 服务端不是单线程运行的么,客户端从上面的报错信息看线程池还有很大空闲
lsk569937453
68 天前
@drymonfidelia 客户端从上面的报错信息看线程池还有很大空闲

你的报错信息展示的都是.net 的线程池信息,并没有 redis 服务端的监控信息。
8355
68 天前
@dynastysea #23 这是应用问题,是代码写成这样,遇到屎山问题我没脸提工单。我怕人家让我提供代码我截出来丢老脸。
edward1987
68 天前
delay + random(1,20)试试? 可以少试 10 倍请求,随机过后不容易有空闲或堵塞
sagaxu
68 天前
300 个并发,每个并发每秒 1000 次请求,你算算看 QPS
gaogang
68 天前
循环里面 delay 的带短了吧
拿 redis 锁之前 加个本地锁 应该会好点
drymonfidelia
68 天前
@sagaxu 不能直接乘吧,拿到锁的请求就不会继续申请锁了
drymonfidelia
68 天前
@drymonfidelia 另外 1ms 也不一定能拿到锁
i8086
68 天前
这个错误信息,没什么问题,毕竟都用了异步 IOCP 也是空闲。

如果有监控且是单机 redis ,那就查查 redis 当时的连接数是不是爆了,首行提示 Timeout awaiting response 。
8355
68 天前
我猜测了一下你的代码上下文,通过首行业务加并发锁,并且在执行到最后一个业务的时候内部有一个解锁指令。
加了个 180 秒的锁,执行到最后应该执行 del 解锁。

可以把你的订单内容,产品+数量之类的编个字符串算个 hash 加入到 rediskey 中,这样的话相同的产品并发下单才会触发锁,而不同产品下单不会受到影响。

这样改的前提是你需要把整个执行流程看一遍确认只有 2 个位置(也有可能只有一个)有锁操作,如果其他位置有锁检查一定要再看下代码逻辑。
我觉得这是最低成本改动,不牵扯到原有逻辑,可以通过搜索去检查该 key 的应用次数。
sagaxu
68 天前
@drymonfidelia 300 个只有一个拿到了锁,其它的都拿不到啊
sunjiayao
68 天前
加锁和解锁的地方都加下日志看看 应该是死锁了
zhy0216
68 天前
增加重试时间啊 1ms 这瓶颈是 cpu 了 你加内存什么用
redis 单线程还不能利用多核优势

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1034586

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX