一、前言
初入职场,总是会经历从一开始的不知所措,到后来职业路上的不断选择。知识浩如烟海,更迭如黑夜白昼。有限的时间里,如何在业务迭代和自我提升间平衡,如何快速积累和记住有效知识。我的方法是多关注各类解决方案模型,先只理解模型结构和基础思路,忽略掉很多容易忘掉的细节,多花时间去记住能记住的部分,快速让自己的知识全面。等到真正用到的时候才去专研实现。
下面结合实践中的一些例子谈谈模型积累在工作中如何提高工作效率,为工作提供一些解决思路。
二、scorm课件——殊途同归的静态资源访问
scorm是教育产品2017年最新支持的课件形式,从得知要做完成设计思路,基本一天内就搞定了。
我的整个思考过程是这样的:
第一次听说这课件,必然是一脸懵逼的。肯定先去了解一下概念和demo,主要就是看下前后端网络交互过程。对于新的事物,我都喜欢先看下网络层交互,网络是互联网一切资源连接关系的入口。是如http、websocket等点对点,还是如zeromq、MQTT等支持点对多的协议,深刻影响着系统的架设方向。后发现scorm和普通网页的资源访问逻辑如出一辙,只是定了一些格式规范。
这个时候基本就知道该怎么做,无非就是网页渲染 + 静态资源访问,业内服务端已经有非常成熟的解决方案,了解过视频访问的
模型,基本可以套用了。总体设计如下:
因为不是纯视频,是以各类静态资源组合形式的,所以cdn采取静态资源加速模式。大型静态资源访问基本就是这个结构。
网易的各类服务都已很齐全,本次要做的实际就是转码。转码服务一般有几个要点,可以说是所有转码服务都差不多:
1、转码状态要可跟踪,越细越好。
2、重启或者宕机能够快速恢复现场
3、计算能力可横向扩展
4、任务优先级支持,允许紧急任务先执行
因为之前整理过视频转码和杭研nts上的模型设计,实际上仍然可以高度复用到scorm转码中。
整体流程如下:
数据库表结构:
从流程可以看出,比较耗时的部分就是解压、上传,这些都是可以通过加机器扩展能力的。
任务核心就是状态转移记录,把执行的每一个步骤都记录到数据库中,重启或宕机时就能快速从执行步骤
开始。
三、组织架构同步——抽象的链式任务模型
组织架构同步是企业云一项较为复杂的任务,需要定期同步大量企业组织架构信息。第一版的想法是认为这是个顺序执行任务。
因此有了以下抽象:
这种任务结构,核心要解决几个问题:
1、按照一定顺序执行,后一个任务依赖前一个任务执行结果
2、对外进度视图统一,对内子任务有自己的执行进度
3、子任务可能耗时较长,需要同时支持异步和同步模式
4、无单点故障
整体架构如下:
核心是利用mq做实例高可用,并且维护任务执行阶段状态。异步任务通过回调
progressManager方法进行进度更新,父类会帮忙更新整个状态。
第一版本缺陷在于实际上任务并不是严格线性的,并且异步任务实际没做流控,在异步的情况下存在隐患。
进一步抽象成V2方案:
实际上更大部分的场景是支持并行的,部门和岗位同步是没有互相依赖关系的,仅仅是员工同步依赖前者。
这种情况需要维护一个任务组内并行任务的执行状态。当时想了很多并行任务的执行执行状态如何维护比较
好,能减少对外部环境的依赖。顺序读取能否解决?想了很多顺序读取状态的情况,必然会存在多任务间的阻塞,
并且会引入很大的管理困难。这时候想到了storm中是如何实现的,storm针对并行的任务是通过
Spout类里面就维护的Map<messageId,Tuple>对的集合,
来缓存各节点执行情况
。这种实现方式,
会存在任务积压导致内存不足的风险,不过从这里可以看出,对于并行任务的状态存储,随机存储方案是比较可行的。
而系统本身就依赖于redis存任务进度,因此在redis中存储并行任务子任务状态也不会引入特别多依赖。唯一的缺陷是
redis不是完全可靠的,不过基本可以认为百分之99.9可靠。剩下的千分之一概率,直接超时出错处理就ok了,简单省事。而且redis容错处理应该交给存框架做,不需要在每个业务迭代都要考虑redis什么时候出错,除非对数据极度敏感
的关键业务,一般不用操心太多。
和非并行区别在于redis会额外维护一个任务组状态,异步任务执行时,taskExecutor的消息不会直接ack,会
阻塞定期检查任务状态,做异步限流。
四、单题提交统计——熟悉的高并发场景
单题提交类的特点是高并发、实时统计计算、和多产品。多产品首先想到的是应用分流,关于同步场景,教育已经继承了hystrix做这件事。而针对高并发又设计复杂 计算的场景,必然采用的是异步方式。教育产品目前没有固定的异步分流方案,爱多思可根据scope动态,创建队列来进行任务分流。scope是爱多思划分业务场景的基本单位,利用
RabbitAdmin和
SimpleMessageListenerContainer
以scope为单元声明和绑定队列,确保不同场景下的任务互不影响。
单题提交最常用的是直播场景,会有较大并发,因此需要大部分前台请求不走任何数据库操作,缓存标准答案,根据用户和题目维度做非阻塞锁,避免重复提交。
做完判分逻辑确保消息发送后马上返回结果,所有的数据库操作异步化。
后续的处理包括两部分,答题记录插入以及多维度的信息统计,如下图所示:
统计数据一张试卷只有一条记录,多线程更新一条记录会有锁竞争问题,效率反而不如单线程。
因此采取了双缓冲队列,一个队列多线程插入,一个做消息批量处理。如下图所示:
rabbitmq原生不支持批量消费消息,虽然可强行通过人工ack方式for循环拉取消息最好再ack,不过效率低下,并且数据一致性处理复杂。因此可以用kafka或者数据库来做二级缓存冲队列。kafka仍然会有数据一致性问题,需要引入第三方管理平台保证提交记录insert成功和消息发送的唯一性,并且目前在教育应用并不成熟。因此暂时先采用数据库作为队列。用数据库做队列,首先能确保业务表操作和消息发送在同一个事务内,避免业务和统计消息数据不一致问题。在普通消息队列中,消费者和生产者的连接关系一般是通过心跳维护的,数据库中如果类似方式只能定期扫表,百分之99时间这种扫表是没有意义的。因此统计任务采取响应式+低频补偿方式进行队列消费,响应式是只有发生插入的时候,才会去触发扫表任务一段时间,同时redis中记录一个下次触发时间,扫表的过期时间略大于下次触发时间,避免消息触发扫表临界点可能会丢失触发消息情况。消息采取单机器单线程消费(根据questionId批量读取数据),多线程分批计算(单机结果多线程计算汇总),最后根据情况看是否合并为单线程更新(计算线程不会很多,并且已经几百一批计算,不会存在大量竞争)。通过批量处理,可以减少几百甚至上千倍的io操作和锁竞争,让统计不会成为性能瓶颈。上面这个模型很多高并发场景都适用,看一遍我相信就能记住,思路有了,实现我觉得都不是难事。
五、总结
时光飞逝的洪流中,往往会发现记忆总是容易模糊,无论曾经多么努力,就如背了无数遍的英语单词、写烂的数学公式,现在依然记忆深刻的会有多少?我慢慢发现,深植于心的永远不是琐碎的细节,而是沉淀后的思维方式。我注重各类模型的积累和改进,持续基于此锻炼自己的抽象思维。当然,作为技术人员,自己领域基础知识永远是需要不断巩固和复习,没有根基,一切都是空谈。
本文来自网易实践者社区,经作者扬海瀚授权发布。