|
上周三下午两点十七分,网站主站首页的排查加载时间突然从1.2秒跳到了8.6秒。运维群里炸了锅,情况我打开监控面板一看,汇报数据库连接池爆了,网站等待队列排到三百多。排查这不是情况第一次了,但这次最狠——持续了整整四十分钟,汇报直到我们手动重启了那台跑着MySQL的网站虚拟机。 排查从日志开始。排查Nginx的情况access.log里,有个IP段在十分钟内发了四千多次请求,汇报全部指向同一个商品详情页。网站这个页面本身没什么特别,排查但它调用了三个外部接口:库存服务、情况价格服务和推荐引擎。库存服务响应正常,价格服务也扛住了,问题出在推荐引擎上——它每次请求都要去查一次用户画像表,而那张表刚被一个运营活动全量更新过,索引失效了。一条慢查询拖垮了整个连接池。 我们做了三件事。第一,把那个IP段封了,临时止血。第二,给推荐引擎的查询加了强制索引,同时把用户画像表拆成了冷热两张表,热表只存最近三十天活跃用户的数据。第三,在Nginx层面对商品详情页做了限流,单IP每分钟最多六十次。这三步做完,首页加载时间回到了1.4秒,比之前还快了一点。 但事情没完。第二天早上九点,后台管理系统的登录接口开始报502。查了一圈,发现是Redis集群里一个节点内存满了,触发了LRU淘汰,把session数据给清了。用户登录状态丢失,前端不停重试,直接把网关打挂了。这个问题的根因是上周上线的那个秒杀功能,它往Redis里写了一个巨大的Hash结构,每个商品一个key,每个key下面存了所有参与用户的倒计时。一万个商品就是一万个Hash,每个Hash里平均五百个字段。内存不炸才怪。 改法很直接:把倒计时从Redis挪到了客户端,用JWT里的过期时间做校验,服务端只存一个简单的布隆过滤器来判断用户是否参与过。Redis内存立刻降了百分之七十。顺便把那个节点的maxmemory-policy从allkeys-lru改成了volatile-lru,避免再误删session。 这两次故障暴露了一个共同问题:我们太依赖“加机器”和“重启”了。监控面板上那些曲线,平时没人细看,出了事才翻。比如数据库连接池的活跃连接数,过去一周一直在缓慢上升,从五十涨到了一百八,没人注意到。推荐引擎的慢查询日志里,那条查用户画像的SQL平均耗时从80毫秒涨到了450毫秒,也没人报警。阈值设得太宽松了。 现在我们把所有核心接口的P99耗时都设了动态基线,偏离基线百分之三十就发企业微信告警。数据库那边加了一个慢查询自动kill的脚本,超过五秒的直接干掉,同时记录下SQL和调用栈。Redis集群加了内存使用率的实时监控,超过百分之七十五就自动扩容。这些改动谈不上优雅,但管用。 还有个小发现:那个刷接口的IP段来自一家云厂商的机房,大概率是某个比价爬虫。我们没有直接拉黑整个段,而是在WAF里加了一条规则——同一IP访问商品详情页超过阈值后,返回一个带验证码的页面。爬虫不会解验证码,正常用户点一下就能过。上线三天,拦截了十二万次异常请求,误杀率不到千分之三。 网站排查这事,说到底就是跟细节较劲。日志里每一行报错、监控上每一个毛刺、用户反馈里每一句“打不开”,背后都可能藏着一条慢查询、一个内存泄漏或者一段没写好的重试逻辑。别指望一次排查能解决所有问题,但每一次都得挖到根上,不然下周同一时间,同样的故障还会再来一遍。 |