剁手了,ovh mystery box
看来百业萧条,ovh居然也出盲盒了,基准配置是 22.99o/月,E5-1650v3 64g 2*480ssd 1g。
本着愚人精神,我赌把手气,还不错,v3 -> v4, 2*480 ssd -> 2*1.2t nvme, 64g -> 128g。
卖了溢价几十百把块也没意思,还不如自己拿来乐乐吧,这么高性价比的机器真的难找。
1ip的博客现在挂在上面了,也不套cf了,直连吧。
让我再好好想想怎么创造一点需求出来。
看来百业萧条,ovh居然也出盲盒了,基准配置是 22.99o/月,E5-1650v3 64g 2*480ssd 1g。
本着愚人精神,我赌把手气,还不错,v3 -> v4, 2*480 ssd -> 2*1.2t nvme, 64g -> 128g。
卖了溢价几十百把块也没意思,还不如自己拿来乐乐吧,这么高性价比的机器真的难找。
1ip的博客现在挂在上面了,也不套cf了,直连吧。
让我再好好想想怎么创造一点需求出来。
前因后果不扯了,我前几篇贴文都在说这个事,简单说就是上周系统大并发下炸了。
今天的流量比上周还大一倍,但数据库和服务器完全是在低负荷下运行。(数据库有的那个尖峰,是因为数据库缩容后比例尺变化显示原因,不是真的大负荷)
开心。
无语
程序在处理请求读取非实时数据时,通常会先从 Redis 缓存中获取数据。如果缓存失效或过期,程序会转而从数据库读取最新数据,然后将结果写回 Redis。听起来没问题,对吧?确实,这是标准的缓存更新流程,我最初也没觉得有什么不对。
但问题来了:在高并发场景下,事情就没那么简单了。假设缓存恰好失效,从缓存过期到新数据写入 Redis 的这段时间,哪怕只有几十毫秒就可能有无数个请求同时涌入。这些请求发现缓存没了,都会去查数据库、更新 Redis,轻则系统负荷升高,重则引起缓存混乱系统失效。我脑子不好使没转过弯,想不出啥好办法解决。后来一问ai,才发现,哎,这早就是个成熟的算法了,简单又巧妙,赶紧写下来备忘!
var dupMu sync.Mutex //互斥锁,用来锁定缓存失效请求
func heavyLoadFunc() {
err:=LoadFromRds(keyname, param, &list) //读redis
if err==nil && list!=nil { //缓存命中直接返回数据
....
return list
}
dupMu.Lock() //双重缓存锁。缓存未命中,锁定,只允许一个个排队进入
defer dupMu.Unlock()
err:=LoadFromRds(keyname, param, &list) //第二次读取redis。即,排队的未命中请求,第一个请求会缓存再次失败进入数据库更新redis数据,但在第一个完成后,其余请求即可从redis获得数据,防止再次更新。太巧妙和简单了。
if err==nil && list!=nil {
....
return list
}
list,err=getDataFromDb() //从数据库更新
return list
}
接上回。
宝塔waf挂了后继续排查。通过对比宝塔的日志以及mysql日志,结论是,后端负荷过高引起宝塔waf回源超时,因为宝塔在回源时使用了较短的生存探测超时时间,在大负荷后端响应慢的情况下,所有后端节点全部被宝塔剔除,于是,从响应缓慢变成了502彻底挂了。
解决办法,第一点,当然是把宝塔去掉了,生产环境不敢再用这种免费的玩具了。
第二点,增强mysql的处理能力。以前没注意到云服务商其实提供了mysql的弹性伸缩产品,以前的mysql固定2c,用这个产品可以最高拉到32c,按小时计费而已。下次活动前拉满了。不做广告,不说具体谁了。
第三点,也是最关键的,限流。再优化再高的qps能力,也不过是让外挂们抢得更快并发更高而已。系统处理能力提高1倍,他们可以轻松的提高10倍抢单能力。所以,崩溃是永恒的,不管怎么优化。限流就唯一的办法了。
这几天,每天都是与ai肉搏。毕竟是成熟的算法,想来ai写得会比自己写更好。从固定窗口到滑动窗口到令牌桶,让几个ai每个都写一次,然后它们同行评测一次然后再服务器实测。
参与的有 chatgpt,元宝(deekseek),grok, claude。比较意外,它们都写得并不是太好,交叉评议就更搞笑,都是说对方是垃圾到处是bug,自己是100%可靠,然后一实测它的改进版,很可能根本就逻辑错误实现不了需求。因为牵涉到工作的细节,我就不截图等了,我只说结论,这次的测试,grok>元宝>chatgpt>claude。claude平时一直都不错,这次不知道为什么,它写不出来一个可以正常运行的版本,只能排最后。因为测试结果是可以量化的,所以,这个比较也算是量化的结果。是不是有点意外。。。
测试是类似这样的结果,就看谁的数字最接近理论允许的数值。
测试时长: 10 秒
并发数: 300
预期: 10秒内150次请求返回200,其余返回429 (2秒30次)
------------------------------------
等待5秒边界...
开始时间: Tue Mar 18 10:10:25 PM CST 2025
------------------------------------
测试结果统计:
总请求数: 1500
允许的请求 (HTTP 200): 150
限流的请求 (HTTP 429): 1350
每秒平均请求数: 150
每秒平均成功数: 15
老规矩,发代码,有需要的人可以看看。虽然不是我的代码,不过是我在ai写的几十个代码里选出来的,也算是辛苦了。真的是大家说ai那样的,写代码5秒钟,debug代码5小时。
redis lua:
-- from blog.lostshit.com
-- 移除时间窗口外的记录
redis.call('ZREMRANGEBYSCORE', KEYS[1], '-inf', ARGV[1])
-- 获取当前窗口内请求数
local current = redis.call('ZCOUNT', KEYS[1], ARGV[1], '+inf')
if current >= tonumber(ARGV[4]) then
return 0
end
-- 添加新请求记录
redis.call('ZADD', KEYS[1], ARGV[2], ARGV[5])
-- 更新过期时间(毫秒精度)
if redis.call('TTL', KEYS[1]) < (ARGV[3] - ARGV[2])/1000 then
redis.call('PEXPIREAT', KEYS[1], ARGV[3])
end
return 1
不解释了。懂的自然懂,不懂的也不需要懂。