一、背景

2022-11-16 19:31接到线上各系统报警(邮件+报警群),经雷达日志查看,各主要项目(userlbb、courselbb、lbb2c等)存在大量数据库链接丢包问题。

image.png

7分钟内初步定位为公司内一场热点的线上直播培训课程,大量用户通过app端同一时间参与考试,导致线上数据库链接打满,致使整个服务不可用。

image.png

  • 应急处理(12分钟左右)

重启服务,并分批告知用户考试恢复。
服务不可用后,后续请求数大幅降低;伴随服务重启,数据库链接释放,线上响应得以恢复。

image.png

二、排查

后续查看了问题前后(19:31)的数据库sql执行情况

image.png

以及事故期间高频接口调用次数

以resourcelbb为例 2022-11-16 19:30:00 - 19:31:30时段:

IExamQuestionService/getExamQuestionByIdFromCache -- 4213
IExamPaperService/getRelationByQuestionPaperIdFromCache --2079
IExamQuestionService/getExamQuestionAndNextByIdFromCache -- 1441
IVideoPlayRecordService/getLastWatchSection -- 1281
IExamPaperService/getExamPaperDtoByIdFromCache -- 1026
/IVideoPlayRecordService/add -- 439
IExamPaperService/listExamQuestionByExamPaperFromCache --431
IVideoLibraryService/getDto -- 265
/IVideoLibraryService/getPlayUrl4AppLive -- 241

同时app同学梳理了整个培训过程的接口调用流程,作为对照

最终得出结论与之前一致:大量请求导致数据库链接打满,导致其它业务请求无法响应。

此时时间已来到1117中午,而1119日还有一场培训。

我们必须保证19日的培训顺利进行,避免再次发生服务不可用问题!而时间不足48小时……

三、解决方案

细化需求

联系产品和培训讲师,得知19日培训流程依然为直播+考试形式,直播开始时间为上午9:00左右。
好消息在于,预计参与人数会少于16日的培训。

提高响应

本次事故的瓶颈在于数据库,更具体为大量的数据库sql查询(读多写少场景),于是从以下几个方面着手进行优化。

增加缓存

合理的缓存将极大提升查询效率,减少db select

技术选型:为什么是guava cache?
考虑到redis连接数限制(1000?),以及对公司线上redis服务的认知局限(三主三从的具体架构?客户端和主从的交互算法?),本次缓存以本地缓存为主(19日培训的预估内存占用不高)。

最终定位为guava cache,有如下优势:之前在项目中已有使用;上手简单;内存对象个数可控。

缓存内容包括:

  1. 用户:登录、app配置信息
  2. 考试试卷

按当前业务逻辑,一次考试创建并开始后,试卷内容将固定下来,后续不会发生改变。
因此对整张试卷的基本信息、题目、选项进行缓存!(只针对C端; resourcelbb的关键接口增加XXXFromCache方法,lbb2c项目修改rpc调用)

image.png

核心逻辑:
com.liepin.resourcelbb.platform.exam.paper.biz.impl.ExamPaperCacheHandler

EXAM_PAPER_CACHE - 完整的试卷信息,包含基本信息、题目、选项
QUESTION_PAPER_RELATION_CACHE - 题目id-试卷id的KV对应关系
  1. 课程(热门课程)

同时,做好缓存预热,应对缓存建立前的大量并发请求涌入!

table index + 逻辑调整

image.png

故障前的部分慢查询sql如图。
红色部分,可通过添加数据库表的索引进行sql优化;
黄色部分,则可通过逻辑调整规避掉这部分查询!

减少请求

1.h5修改数据

考试答题场景,之前为用户点击一道题的选项,就会提交答案。举个例子:假如一道有着4个选项的多选题,用户ABCD全选就会触发4次http请求。
和前端同学沟通后,最终优化用户点击上一题、下一题、目录或交卷时,才对一道题的答案进行提交。还是以上面的场景举例,用户ABCD全选时无请求,点击下一题时做一次http请求。

2.优化不必要请求

3.临时减少数据埋点

com.liepin.lbb2c.platform.common.filter.UserAuthFilter
为了避免给下游系统造成太大的压力,过滤掉了部分重复高频埋点数据,后续需要在优化为
消费侧(userlbb-platform)批量处理埋点数据。

服务限流

  • 限流背景以及目的

当瞬时请求量远大于系统极限时导致整个服务崩溃,从而造成服务不可用。为了保障服务能够
持续可用,因此需要做一些限流操作。

  • 具体限流措施

请求拦截器中,临时增加了限流com.liepin.lbb2c.platform.common.filter.UserAuthFilter

使用计数器固定窗口限流算法,工具采用Guava RateLimiter

计数器在周期内累加访问次数,当达到设定的限流值时,触发限流策略。下一个周期开始时,进行清零,重新计数,结合定时请求Redis 来实现动态调整限流阈值。

  • 弊端

a.造成部分用户使用体验差
b.限流模块的逻辑代码可能引发新问题

查询分流:数据库多实例

大部分业务主查从库1;主库查主要用于对延迟更敏感的部分,且需要代码层指定;从库2则用来备份,无法直接查询。

理论上讲,如果查询能做到从库1、主库按一定比例分发,将进一步提升db查询性能。

image.png

粗略来看,中间件的getDataSource的策略和延迟情况有关,如果salve延迟高的时候,应该会飘到master上;所以按比例指定,没啥必要

四、一些思考

  1. 能否根据业务模块熔断?如考试崩溃不影响其它业务流程
  2. courselbb服务下沉。(现混杂了过多的业务逻辑,也不该存在courselbb-resourcelbb调用)

为resourcelbb热点5接口增加缓存方法(XXFromCache方法)的过程中,在替换过程耗时较长——考试相关lbb2b、lbb2c调用resourcelbb的方法,想将lbb2c的调用修改为XXFromCache的方法以提升效率,但还存在lbb2b-courselbb-resourcelbb、lbb2c-courselbb-resourcelbb的调用链……

image.png

五、补充

以上种种方案最终保障了19日培训的顺利完成,但不好之处在于未能精确定位到具体的问题sql


青鱼
268 声望25 粉丝

山就在那里,每走一步就近一些


« 上一篇
选人组件