1.主要服务K12人群,涵盖从幼儿园小班到高中的在校学生。业务模型比较复杂,线下教学有教学大纲编写、教材设计与印刷,有报名、分班、排课、课中教学、课后作业、考试等环节。与之对应,在线教育也有课件设计、课程生产、报名分班、虚拟直播教室等。与线下不同的是,一名老师可能会给几万人进行在线授课,为此演变成双师模式:主讲老师负责课中教学,班主任老师为学员提供辅导、答疑等服务。业务流程如图所示:
2.与线下课外辅导班类似,网校的业务流量有明显的周期性:每年暑期、寒假是流量高峰,每周五晚、周六和周日也是流量高峰,是平时流量的数倍。今年受疫情影响,整个在线教育行业高速发展,在读学员量也快速增长,而面对的技术挑战尤为显著。
3.网校的每位用户都属于大额消费用户(简称大R),他们对稳定性更敏感,对系统要求更高。
面临的挑战
从2019年暑期后,吕本伟带领团队开始进行新系统开发。当时,它们有4套按学科拆分的系统,面临着一些问题:
1.功能复用性比较差
2.业务耦合严重
据悉,多次因为非核心业务,甚至其他部门的微小变更导致学员上课环境出现问题,造成非常不好的影响。
3.性能与稳定性状况频出
同时上课的学员有几倍的增长,在一个教室同时上课的学员也有几倍的增长,老系统在某些场景的性能跟不上,小问题频发,甚至出现多次线上事故,最终也需要人员花费大量的精力值班来解决线上客户投诉问题。
直播课堂架构升级实践
通过整合多套单体应用,他们想整合为能支持未来3年的系统。具体来说,有三个目标:
为此从2019年下半年开始,吕本伟带领团队启动对多套单体应用的整合,首先基于DDD(领域驱动设计)的理念进行领域拆分、解耦。
吕本伟提到,行业内做架构设计一般精简为以下三个层面:
DDD的核心理念是深入理解业务痛点,面向业务演进构建清晰合理的业务架构,并映射到系统架构;后续迭代中,追求系统架构低成本随业务架构进行演进。微服务架构追求设计出来的系统架构和业务一致,达到系统层面的功能复用。工程实现时,系统模块可以按需灵活选择技术架构,遵循职责单一的原则高度自治,去中心化地治理数据;模块间“高内聚、低耦合”,最小依赖降低风险。
具体来讲,主要是从几个方面实施:
领域拆解
在整合之初,组织层面已按照需要解决的问题领域拆分成不同的部门:
但是由于历史原因,在系统层面几大领域依然"藕断丝连"。
课中互动环节也存在类似的问题,学员作答互动题,需要了解诸如课件、讲义、自传互动题等等信息。
系统解耦
面对上述问题,他们采取了三个措施:
1.统一领域语言
梳理自身业务上下游依赖,进行走访、串讲,搞清楚各自团队的核心术语,形成一套“交互原语”。团队内部各系统实现、wiki记录均遵守该原语。
跨部门之间,按照最小依赖的原则,减少要暴露的信息。
2.问题分解——子域拆分,界定核心域、支撑域、通用域
按照5W1H原则讲述用户故事(userstory),明确系统要解决的核心问题(即核心域),要解决这一核心问题的关键依赖(即支撑域)以及其他周边依赖(即通用子域)。如下图所示
以进教室这一业务场景为例,我们划分的核心域为“课堂教学”,支撑域为“班课”与“互动”,诸如用户、信令、课件等均有行业通用解决方案或可以通过外包等方式获得的模块归于我们的通用子域。
(班课:即上课日程管理系统,包括班级、课程、上课直播场次等数据)
3.划分限界上下文及上下文映射
梳理出核心概念对象和关键行为,基于此进行人为的限界上下文的拆分。
我们抽象出虚拟教室这一上下文作为系统的核心,由它对外部依赖(课件、直播流等)进行集成。一方面明确上下文之间的依赖关系;另一方面提前思考将来的架构演进,尤其对于会扩展或替代的依赖(比如班课),我们参考DDD的防腐层设计进行影响隔离。
平台化——进一步抽象
前文提到,期望打造的系统将来可以更好的赋能多业务线、可以低成本扩展新功能,基于通用子域的思考,让每个非核心依赖具备可替代的能力;基于防腐层的设计可以很好的提升单个系统扩展能力,开了个好头,按照平台化的方式进一步思考:
如图所示,课堂的六要素都是可变的,每个互动功能是相对稳定的,为此抽象为:
基于对直播教室和互动的定位思考,团队将服务拆分为:
在每个服务集内,进一步按照“高内聚、低耦合”以及“职责单一”的原则进一步拆分成独立的微服务,比如大班直播教室类比现实世界,需要从外部对接班课、课件、学员、老师等信息,为此抽象出“课堂中心”与“用户中心”等核心服务,完成班、课、场、人的信息封装,以及外部系统的适配工作。最终沉淀出下图中所示业务架构中的“后端服务层”。
从单体到微服务
在整合前,大家的共识是采用微服务架构,但具体的实施经验不多。因此,分成三个阶段:
1.打底基础设施。统一语言(Golang)、框架(Gin/RPCX),自研脚手架、日志等类库,统一解决微服务注册发现、日志监控、CI/CD等基础问题。
2.微服务开发
结合前文提到的上下文映射关系,按需采用RPC或MQ进行通信;
工程组织分为两层:
3.基础设施升级。统一解决Trace、全链路压测、流量录制与回放、建设放火平台模拟故障、搭建预案平台等工作。
解决性能瓶颈
随着在线教育的快速发展,用户量不断攀升,他们不断面临性能上的压力。据吕本伟介绍,他们逐步去优化性能瓶颈。
1.持续完善基础设施,比如链路追踪、影子库、全链路压测、流量录制与回放等;
2.上游依赖数据自治,异步化处理,解耦外部依赖
比如,课堂环节对班课系统是强依赖,但班课数据变更频次较低,为此,他们采取预热缓存+事件驱动更新的方式解耦数据层面的强依赖,独立自治。一方面提高系统稳定性,另一方面也获得更好的性能。
3.业务上支持小班模式,优化并发模型解决教师端性能瓶颈。
在小班模式下,教师端可能需要同时给几百甚至几千个班级同时发送指令,原来遍历发送的方式变得不适用,因此需要调整发送模型来解决类似的性能瓶颈。
4.热Key问题
前面有提到在线教育的流量有较强的潮汐性,大量的学员会在几分钟内暴涨。另外,某些互动场景会导致“惊群”现象,老师一个信令大量学员同时请求后端,造成所谓的热Key现象。尤其对于10万级学员同时上课的情形,可能会达到Redis单实例的性能上限,影响是灾难性的。目前,对于这种情况,我们采用前端机本地缓存的方式解决读场景;对于写场景,有些是在客户端进行指令级削峰,有些是在服务端进行削峰处理。
稳定性提升
之前多次线上事故是依赖“人”的反馈:学员遇到问题->反馈给家长->家长反馈给客诉->反馈给研发,效率低下。为此,从事前预防、事中干预、事后规避几个维度进行稳定性建设,逐步夯实稳定性。
从单体到微服务,业务架构的升级不仅让系统的复用带来人效的提升,而且资源隔离,提升了功能模块扩容的便利性。并且,核心与非核心的资源隔离,避免了交叉影响,稳定性得到很大提升。
架构领域的新尝试
其次,他们还在尝试不断提高智能化。在他看来,课中学员与老师之间的亲密度建设至关重要,但是老师的精力是有限的。所以,他们正在探索结合大数据和AI的能力赋能课堂教学。