⼀、背景

  在介绍背景前先了解⼏个名词概念
课程权利:指的是⽤户领有哪些课程(e.g. 语⽂体验课L1级别48周)
交融项⽬:英语、语⽂、思维三科各⾃保护的项⽬重构的新项⽬
预排课:推算的课表信息
延期调级:批改⽤户的开课时间和课程级别
  去年3⽉份,交融英语、交融语⽂、思维三科进⾏业务交融,参加了交融项⽬从零到⼀的过程。业务交融的同时,零碎架构也要进⾏交融降级。从三科各⾃保护⾃⼰的零碎到交融,涉
及到技术栈的统⼀、资源的交融、数据的迁徙等等。
  我次要负责课中排课中⼼的架构波及和开发⼯作,下⾯就具体说下这块的设计。
  

⼆、整体架构

  下⾯是排课中⼼⽐较具体的架构图
  
从上⾯的架构图能够看到排课的次要性能

  • 权利(治理订单、⽩名单、流动等起源的课程权利)
  • 排课(课表的⽣产和输入)
  • 课程的延期、调级、退费
  • B端的⼀些查问和告诉机制

2.1 业务的时序性设计

  从上⾯的架构的图中能够看到零碎⽤到了kafka,kafka在这⾥次要是⽤来做异步解耦的,包含:

  • 订单音讯的生产
  • 告诉其余模块课表的变更
  • ⾯向辅导侧的业务告诉(结课、课程降级)

从业务⻆度看排课中⼼表演的⻆⾊是上下游业务的中转站:
  

⼀个⽤户残缺的购课流程是,⾸先从排课中⼼获取⽤户以后的权利信息(⽐如在读还是新购⽤户)来决定售卖什么样的商品;⽤户下单⽀付实现后,订单告诉排课中⼼,排课中⼼生产到音讯后进⾏权利的⽣成;排课中⼼⽣产权利后会转发音讯到另外⼀个队列,上游服务会依据⽤户的课表信息(如结课工夫、权利等)进⾏物流发货、流动命中等解决。
  能够看到整个流程上游⼀些业务是依赖于⽤户的课表的,所以不能间接接管订单的音讯,要先等排课中⼼解决实现之后再进⾏生产。通过⼀层音讯转发就解决可业务的时序问题。

2.2 排课的设计和⽅案抉择

  在旧的业务零碎设计中,交融英语是下单实现后间接⽣成全副课表,语⽂思维是每周定期去排课。两种⽅式的优缺点都和显著

⼀次全排长处:

  • 课表所⻅即所得,⽤户将来进度⼀⽬了然
  • 对⼀些圈定⽤户的业务查问⽅便
  • 不依赖脚本

毛病:

  • 校历变更(即新增了复课周)须要重排
  • 课包变更(⽤户将来上的课包发⽣了变动)须要重排

定期排课和⼀次全排刚好相同。业务交融之后,校历和课包的会常常变更,如果是⼀次性全排可能⼀次变更就要更新⽤户所有的课表数据,自身课表数据量较⼤、增⻓也快,不适宜做
频繁的变更,更适宜使⽤定期排课。
预排课,上⾯说到定期排课的毛病就是没方法间接读取数据库⾥⽤户残缺的课表,然而咱们晓得⽤户买了多少课、什么时候开课、怎么调整了。有了这些咱们就能推送出⼀个在校历和课包不变的前提下的⼀个准确性的课表,这⾥称他为预排课。

2.3 B端和C端

  C端次要是⾯向的群体是购课⽤户,场景是课表和权利的输入,通常是⽐较简略的数据查问,⼀般是按⽤户维度来的;
  B端次要的⽤户群体是辅导⽼师和经营⽼师,还有B端也会进⾏全量⽤户扫描的⼀些业务逻辑,包含被动缓存、结课告诉、圈定⽤户等;通常是有⼀些简单的查问逻辑;
  要齐全做到互不影响就要做资源的隔离:

  • B端和C端别离部署在不同的服务器(⽬前曾经在逐渐上云,人造的隔离)。
  • 数据层⾯的隔离(C端⽤DRDS,B端⽤ADB,通过DTS同步数据)。
对于数据库的选型

  DRDS是阿⾥云的分布式数据库,适宜做分库分表,C端按uid hash进⾏分库分表,升高单表的数据量
  ADB是列式存储,适宜⼤量数据简单sql的查问   

B端也⽤于服务C端

  上⾯说到C端通常是⼀些单个⽤户维度的业务场景,然而有时也可能波及到全表扫描的业务场景。⽐如⽤户插班报的场景:
  ⽤户A上完了语⽂零碎课L1级别24周课程(共48周),过了很久没续费后再次续费(这时候⽤户曾经没有班级了),须要给⽤户安顿⼀个合乎进度的班级(实践上必定存在)。这个时候就须要查找是否与之匹配进度的⼀批学⽣,如果有就找最近的⼀期给学⽣排课。
  这个场景就须要扫描课表了,显然不可能间接在C端库这么⼲。所以先在B端把第2~48周(因为第⼀周不算插班报)进度的开课时间间接缓存起来,C端间接读取缓存即可。

2.4 数据⼀致性怎么保障的

  从上⾯的业务时序图能够看到排课中⼼作为上下游的中转站,在数据流转的过程中必定会存在数据⼀致性的问题。针对这种问题咱们与上下游共建了数据核验平台来保证数据的最终⼀致性。

如上图所示:上游(订单会定时检测10分钟内的订单)⾸先会进⾏⾃我⽀付核验,订单模块核验实现之后根据订单信息触发排课中⼼权利核验、及其他上游核验。如果排课中⼼核验失败(可能是生产订单音讯时发送了不可⾃动复原的谬误),订单向上游会尝试从新下发音讯;只有上游各个模块做好幂等性,那么就能够从新依照上⾯的业务时序重新处理。

三、将来的⼀些优化

类标签零碎的设计来解决圈⽤户问题
  上⾯的预排课机制解决了⽤户将来课表的展现,然而对于圈定⽤户的需要还是难以满⾜;⽐如须要给⼀批⽤户推送⼀场直播, ⽤户的圈定范畴是:学科-英语、2021-10-01残余课时为1周的⽤户 ;⼀般来说咱们设计数据表的时候都着重于拆,然而拆分的结果就是查问简单,上⾯说到使⽤ADB进⾏简单查问,然而在数据量过⼤的状况下使⽤join也⽆法解决,这⾥有⼀个构想就是把拆分的属性进⾏聚合。
 
解决⽅案:

  • 咱们把⽤户的学科、进度、残余课时等条件作为⽤户的属性给他打上标签。假如存储抉择MongoDB,能够设计以下⽂档:

    {  "user_id":1,  "subject_list":[      {          "subject_type":2,//英语          "surplus_duration":1//残余1周      },      {          "subject_type":1,//语⽂          "surplus_duration":2//残余2周      }  ]}

    查问条件则为

    { "subject_list": { $elemMatch: { "subject_type": 2, "surplus_duration": 1} } }
  • 怎么触发更新
    对于单个⽤户:利⽤下单、退课、调级、延期等课程权利变更时触发的队列音讯进⾏实时变更
    对于校历和课包变更:变更时也采⽤告诉机制,异步执⾏⽤户标签的变更
      综上所述:定期排课保障了⽤户课表的准确性,防止C端数据⼤量变更,仅在每周⼀定期更新当周课程。⽤户标签数据量⼩,且数据变更不影响c端上课⽤户