14

腾讯课堂停课不停学:业务后台总结

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzUxMDk2MTAyMA%3D%3D&%3Bmid=2247485305&%3Bidx=1&%3Bsn=c8bcb5d6b5969315912145f18fded6b6
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

疫情爆发,腾讯发起“停课不停学”专项,腾讯课堂一下子被推到风口浪尖上,2天上线极速版,2周内支持pcu要从5w到600w,2周内接入层qps从1.4w/s到65w/s,对整个后台挑战非常大。整整2个月下来,同合作团队一起,白天7点开始盯监控和开发版本,凌晨12点例行压测和发布扩容,踩过很多坑也取得很多收获,这里拎几个关键点记录下。

一、 腾讯课堂 停课不停学

1.1 项目背景

大年初一,吃着火锅唱着歌,突然收到重庆十一中的求助信:受疫情影响,年后学校无法开学,高三老师学生都很担心影响到高考,问腾讯课堂能否提供线上平台给高三复课,拉开了整个停课不停学专项的序幕。

1.2 极速版的诞生

由于课堂是面向线上培训机构的,这次想把十一中这样的传统线下校园,搬到腾讯课堂内上课才发现困难重重:

  1. 入驻:学校各类资质和机构完全不一样,审核周期长

  2. 发课:机构发课有很多规范约束,而学校用课程表排课,一个个发课成本高

  3. 直播:学校老师转线上上课,普遍说直播工具有上手成本

耗了很多人力才把十一中的入驻发课和老师培训搞完,同时其他学校也陆续找过来了,才发现根本没人力能对接这么多学校。就在这时,小马哥发话了:“把入驻发课全砍掉,快速做个腾讯课堂极速版,老师下载完就能自助上课了”。

随着总办特批QQ推广资源,各省市教育厅陆续宣布使用急速版复课,课堂pcu/dau开始起飞,短短2-3周,pcu从5w到100w,DAU从60w到1000w,AppStore免费类排行榜进Top10,教育类稳居Top1。

JRNjueV.png!web

1.3 疫情期间开发缩影

JZ3IvuI.png!web

1.4 架构挑战

下面是课堂后台架构图,由于之前架构设计和模块部署都是按10w同时在线峰值来算的,突然要在2周内支持量100倍增涨,同时还要开发校园版需求,时间赶且任务重。这里分五个阶段把架构挑战和解决策略介绍下

JjmeeyY.png!web

二、 阶段1:先抗住后优化

面对2周100倍量级增长,重构肯定来不及了,且过大改动仓促上线反而会增加不稳定因素。所以初期思路就是“先抗住后优化”:梳理极速版用户路径,评估路径上各模块容量,快速扩容后,每天凌晨例行全链路压测,持续验证迭代。

2.1 模块梳理和接口裁剪

和产品讨论完用户路径和访问量级后,各页面qps也基本有个数了,就开始梳理每个页面调用接口列表,明确每个接口要支撑的qps:

MB7naeB.png!web

由于课堂svr数有200+,cmd有800+,为了争取时间,需聚焦核心路径减少梳理复杂度,对于非核心体验和风险较大的这2类接口,抛出来和产品讨论做页面接口裁剪。

2.2 系统容量评估

模块和接口梳理清楚后,就开始分负责人对系统做容量评估。要特别关注木桶效应,很多新同学只评估了逻辑Svr这一层,其实从用户端->接入层->逻辑层->存储层全链路都得涉及,不能让任一环节成短板。

2.3 扩容扩容扩容

各模块扩容数算清楚后,剩下的就是申请机器部署了。扩容本该是个很简单的活,但因为历史债务等问题,部分模块费了很多周折,比如:

  1. 容器化和上k8s不彻底

  2. 部分c++模块依赖ShmAgent,扩容流程极其繁琐

  3. 扩容svr导致DB链接数爆炸 ...

针对扩容暴露的问题,从接入->逻辑->存储,沉淀了很多设计经验,后面得彻底改造掉,特别是k8s这块应尽快全面铺开。这里终极目标是:针对量级暴涨的情况,不用花人力做评估和扩容,整个系统就有自动伸缩的能力。

通过1周通宵达旦,抗住首波增长。

2.4 全链路压测

在初步扩容完后,为了防止梳理不全、评估不准等情况,例行全链路压测验证非常重要,可以挖掘性能瓶颈和系统隐患。

在测试同学给力支持下,每天例行执行下面流程,持续扩容优化迭代:

  1. 校准压测模型:非常重要,压测用例设计会直接关系到压测效果

  2. 确定压测目标:把每个模块/接口的压测qps确定下来

  3. 执行压测任务:凌晨12点启动整站压测流水线,执行星海用例,输出压测结论

  4. 回归压测结果:压测不达标接口记录doc,尽快暴露隐患,责任人分析原因给解决方案

压测QCI流水线:

JBVZVri.png!web

全链路压测方案:

2EzuQbM.png!web

三、 阶段2:瓶颈最先出在DB数据层

根据先抗住后优化的思路,可扩容的都容易解决,架构瓶颈会最先出现在伸缩性差、不容易扩容的环节上,经常会是数据层,课堂这次也中招了。

3.1 核心DB,一挂全挂

由于之前量较小,课堂大部分模块使用同个DB实例(ip+port),上量前这个核心DB的cpu在20%、qps在1k左右,评估下来风险很大:

  1. 扩展性差:主机没法扩展,从机不建议超5组,且有主备延迟风险

  2. 耦合度高:任一svr链接数或sql没控制好,就算是边缘Svr都可能搞垮DB一挂全挂

  3. 梳理复杂:涉及svr数100+,时间太赶来不及逐个梳理

也是历史设计的坑,后面数据层设计要多考虑吞吐量和可扩展性。但回不去了,硬骨头得啃下来,立了专项找DBA同学一起分析优化,主要有下面几块:

qe2aIbj.png!web

3.2 业务横向拆分

根据压测发现非常明显的28原则,比如top1的写sql占总量82%,是搜索推荐模块定时刷权重用的,这类模块相对独立,和其他模块表关联join等操作少,方便业务拆分。对于这类模块,像搜索推荐、数据分析、评论系统等,快速切独立DB解耦,规避互相影响。

方便业务拆分的切走后,剩下能快速上的就是读写分离扩ro组了。快速扩了4个ro组,把较独立模块sql切过去,规避互相影响,也分摊了主机压力。因为复制模式是半同步的,也需关注主备同步延时,做好监控,特别是一些对延迟敏感的模块。

3.3 慢查询优化

横向能拆的都快速搞了,主DB风险还是很高,除了升级DB机器配置,剩下就只能逐个做慢sql优化了。采用mysqldumpslow对慢查询日志做归并排序,就可很清楚平均耗时/扫描行数/返回记录数top的慢sql,基本优化也是围绕着索引来,比如:

  1. 查询没有走索引

  2. 访问的数据量太大走全表

  3. OR的情况无法使用索引

  4. 在索引上面直接进行函数计算

  5. 没有使用联合索引

  6. 唯一索引没有命中数据

优化效果:主db峰值cpu负载从20%下降到5%左右

3.4 连接数优化

连接数上也出过一些很惊险的case:鉴权svr凌晨扩100台机器,没考虑到对DB连接数影响,svr起来后DB连接数瞬间增长2k+差点爆掉。除了对top的svr减少了连接数外,引入DB代理也是个较快的解决方案,由于之前上云对ProxySql和NginxTcpProxy都有实践过,所以这次刚好也使用上。

优化效果:主db峰值链接数从4.6k下降到3.8k

四、 阶段3:核心模块逐个专项击破

4.1 接入:血的教训

课堂支持学生在pc/web/app/ipad/h5/小程序等多端进行学习,接入模块属于基础组件改动不大,但这次web接入模块却出了2个故障,1次还上了微博热搜,确实不应该。

针对这2个问题也做了专项复盘:

  1. 校准压测用例,更模拟现网流量

  2. nginx动静分离,上报等接口也独立出去

  3. nginx规则精简,接入层尽量打薄,逻辑后移

  4. 统一机器net.core.somaxconn等参数配置,重点监控告警

  5. 压测完要清理战场,关注fd等指标是否恢复

  6. 升级tomcat规避挂死bug

  7. 升级tomcat遵循RFC3986规范,规避特殊字符影响 ......

4.2 资料:坑爹的Agent

由于课堂这几年课程/老师数都维持在10万量级,历史上就把这些数据全量Load到1块1G的多阶hash共性内存上,部分C++模块依赖这个ShmAgent获取课程/老师数据,可减少Rpc调用提高性能和可用性。但随着极速版普及,课程/老师数不到2周就增长到100万量级,不仅1G内存放不下了,扩容流程也极其繁琐:

aQZJju3.png!web

作为历史的坑专项讨论多次,由于时间太赶来不及重构,所以暂时也只能对数据裁剪后铺人力凌晨扩容。对于一些非核心且工作量小的,把ShmApi换成RpcApi调后端资料Svr。

这块没什么技术点,主要告诫团队后面少用SHM,微服务往云原生靠齐(12-Factor):

b2IVzyI.png!web

4.3 直播:站在云的肩膀上

课堂最核心的模块就是音视频,直播的进房成功率/首帧延迟/卡顿率/音画同步时延/分辨率等指标直接影响用户核心体验。由于直播模块之前已全部切云,这次站在云的肩膀上,业务不仅直接使用了云的多种直播模式,云音视频团队在整个疫情期间也提供非常给力的质量保障。

下面是具体的直播架构,业务通过流控Svr来控制各端走哪种直播模式:

qYfQjae.png!web

4.4 消息:走推走拉?

随着极速版普及,各学校对单房间同时在线人数要求也越来越高,从3w->6w->30w->150w。对于大房间消息系统而言,核心要解决消息广播风暴的问题(30w人房间,有100人在同1秒发言,会产生30w*100=3kw/s消息),随着扩散因子的变大,之前纯推的方案已不能满足需求,参考了业内直播间的一些IM方案:

vIjE32q.png!web

结合对比方案,也针对课堂产品特性,在原来纯推架构上新迭代一版推拉结合的架构

对于IM系统的设计,推荐一下这个文章:《新手入门一篇就够:从零开发移动端IM》 http://www.52im.net/thread-464-1-1.html

五、 阶段4:做好核心路径的防过载和柔性降级

在非常有限的时间里,逻辑层根本来不及重构,容量评估也不一定精准,过载保护和柔性降级就显得尤其重要。因为没时间全盘优化,在量突然暴涨的情况下,某个模块过载或者爆bug的概率会变大,所以在用户登录->查课表->上课的核心路径上,必须增加足够容错能力来提高可用性。

5.1 雪崩来得猝不及防

疫情初期课堂就遇到一个雪崩的case:2月12号早上,直播间拉取成员列表接口有失败毛刺,因为web没做异常保护,失败直接把循环拉取间隔时间置0,导致接口调用量越滚越大,B侧拉取涨了10倍后雪崩超时。由于没预埋开关等控制策略,得回滚web版本才解决。

这个case暴露了过载保护的缺失,一方面web没对异常返回做合理处理保护后端,一方面svr没有识别雪崩请求做限频,除此之外,也缺少一些配置开关可快速控制web循环间隔时间,导致雪崩来得猝不及防。

5.2 过载保护策略

针对这类badcase,对核心路径的服务做了很多过载保护和柔性降级策略,这里把一些典型方案记录下:

7vyeQ3v.png!web

5.3 限流和熔断

高并发场景为了规避过载的级联传递,防止全链路崩溃,制定合理的限流和熔断策略是2个常见的解决方案。以这次疫情互动直播限流场景为例,互动直播默认只部署最多支撑600w同时在线的接口机资源,如果哪天突发超过了600w学生:

u2AVfq6.png!web

限流算法选择上,最常见就是漏桶和令牌桶。不是说令牌桶就是最好的,只有最合适的,有时简单的计数器反而更简单。golang拓展库 golang.org/x/time/rate 就提供了令牌桶限流器,3个核心api:

除了算法外,怎么把限流集成到框架、分布式限流实现、限流后请求优先级选择等问题,可以做得更深入,但很遗憾这次没时间搞,后面继续实践。

熔断是另一个重要防过载策略,其中熔断器Hystrix最为著名,github.com/afex/hystrix-go 就提供了其golang版本实现,使用也简单。其实L5就包含了熔断能力,包括熔断请求数阈值、错误率阈值和自动恢复探测策略。

5.4 Apollo配置中心

好的组件都是用脚投票,这次疫情期间,很多策略开关和阈值控制都是用Apollo配置中心来做,实现配置热更新,在高可用上实践也不错。很多时候多预埋这些配置就是用来保命的,当监控发现趋势不对时,可快速调整规避事故发生,简单列些例子:

  1. 后端限流阈值大小,后端要过载时可调小

  2. Cache缓存时间,数据层负载高时可调大

  3. 非核心路径后端调用开关,必须时关闭调用补上降级默认值

  4. 前端定时调用的间隔时间,后端要过载时可调大 ......

当然,如果可做到系统自动触发调整配置就更进一步了,当时有想过但时间太赶没实践,有兴趣同学可思考实践下。

Apollo是携程开源的分布式配置中心,能够集中化管理应用不同环境配置,实现配置热更新,具备规范的权限、流程治理等特性,适用于微服务配置管理场景。补个架构图推荐下:

6JZ3Mjv.png!web

六、 阶段5:服务性能优化实战

在抗住前2周最猛的流量增长后,下来很长一段时间都是在优化服务的性能和稳定性、处理用户反馈和打磨产品体验上。这里沉淀3个服务性能优化上印象较深刻的点

6.1 分析利器 pprof+torch

在性能分析上,对比c++,golang提供了更多好用的工具,基本每次性能分析都是先用pprof+torch跑一把。通过框架中嵌入net/http/pprof并监听http遥测端口,OA管理后台就可随时得到svr协程/cpu/内存等相关指标。

6.2 缓存设计和踩坑

回过头看,大部分服务性能瓶颈还是在数据层或Rpc调用上,很多时候数据一致性要求没那么高,加缓存是最简单的首选方案。关于缓存的设计,无论是本地缓存、分布式缓存、多级缓存,还是Cache Aside、Read/Write Through、Write Behind Caching等缓存模式,就看哪种更适合业务场景,这里也不累赘,核心说下这次实践中踩的2个坑:

1、缓存击穿

  • 案例:高频访问的缓存热key突然失效,导致对这个key的读瞬间压到DB上飙高负载

  • 方案:使用异步更新或者访问DB加互斥锁

2、缓存穿透

  • 案例:访问DB中被删除的key,这些key在缓存中也没有,导致每次读直接透到DB

  • 方案:把这些key也缓存起来,但要关注恶意扫描的影响

6.3 为啥qps压不上去?

疫情期间,有一个现象很奇怪但又经常出现:压测时cpu很低,pprof+torch看不出什么异常,数据层返回也很快,但吞吐量就是上不去。一开始思路较少,后面也慢慢知道套路了,这里列几个真实的case供参考:

  1. 锁竞争:如死锁、锁粒度太大等,关注锁时间上报

  2. 打日志:日志量过大等导致磁盘IO彪高,在高并发场景尤其要注意精简日志量

  3. 进程重启:如panic或oom导致进程被kill,重启过程请求超时,要补齐进程重启监控

  4. 队列丢包:如请求缓存队列设置过小等,要关注队列溢出监控 ......

比如最后这点,就遇过这样的case:一次凌晨压测,其他机器都正常,就2个新机器死活一直超时,业务指标也看不出区别,折腾了好一阵,才发现monitor上 [监听队列溢出(ListenOverflows)] 这个值有毛刺异常。继续深挖下去,证明请求在tcp队列就溢出了,tcp的accept队列长度 =min(backlog,SOMAXCONN),查看新机器内核配置 [net.core.somaxconn=128],确实比其他机器小,神坑。

所以后续也增加了服务器tcp的半连接和全连接队列相关监控:

3IrInaR.png!web

最后挂个招聘

在这次疫情的推动下,在线教育越来越普及,各大互联网公司都持续加码,教育也是个有温度的事业,百年大计,教育为本。团队聚焦golang和云原生,还有大量后台HC希望大家推荐或自荐,欢迎随时勾搭。

YJjInai.jpg!web

aUrQZrU.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK