代码改变世界

新东方APP技术架构演进, 分布式系统架构经验分享

2020-07-04 00:20  新东方技术  阅读(4040)  评论(22编辑  收藏  举报

今天的演讲题目是“新东方APP技术架构演进, C端技术经验分享”

作者:张建鑫, 曾任IBM高级软件架构师, 滴滴高级技术专家, 现任新东方集团高级技术总监

古代东西方的思想家都产生过一个终极的追问,世界的本元到底是什么? 老子说,道生一,一生二,二生三,三生万物,天道有常不以尧存不为桀亡。孔子说朝闻道,夕死可矣,孔子把对道的研究从,对人与自然关系的天道,转移到了研究君君臣臣父父子子的人道方向上。古希腊第一个哲学家泰勒斯说世界的本元是水,后来毕达哥拉斯回答说世界的本元是数字,古希腊哲人对道的研究始终聚焦在人与自然关系的天道方向上。对终极追问的不同思考和回答,衍生出了,东西方在文化价值观,思考逻辑和做事方法等方面的巨大差异。

我们在企业里工作,实际上也存在一个终极追问,就是你的终极用户是谁?怎么服务好终极用户。我曾经在2B技术公司IBM工作过12年, 在2C技术公司滴滴工作过三年。 我深切感受到,在这个问题上的不同回答,导致了传统的IT技术公司和互联网技术公司, 在工作价值观,和工作方法等方面的不同。

通常,B端技术重功能不重体验,软件客户每年续签一次合同,软件开发商可以通过各种手段增加用户迁移到其他竞争对手的成本,深度绑架用户,即使客户对软件有一些不满意也会续签合同。 而互联网的C端个人用户是典型的用脚投票,抛弃一个产品的时候,一声再见都不会说。 因此互联网产品技术的竞争是典型的赢者通吃,产品体验稍有不同,短时间内用户就会流失到竞争对手那里。所以互联网产品技术极度关注用户体验,和用户客诉。 IBM解决客户的客诉流程冗长,有所谓的一线二线三线技术支持,解决一个客诉动辄数月之久。 但是互联网C端技术需要时刻关注用户客诉和线上事故,争分夺秒的解决问题,甚至要求几分钟内就把问题解决掉。 所以如何避免和减少客诉, 如何快速处理线上故障,就成为区别2B,2C技术公司不同点之一,对价值导向问题的不同回答,也产生了完全不同的价值评估系,最简单的就是如何去判断一项具体工作的轻重缓急和重要程度。

我刚来新东方的时候, 我的团队里没人关心客诉问题,没有技术值班,上线流程很草率。 互联网公司里重要的技术方法到了IBM那里,就可能变成过度设计,变成不重要的工作,但其实没有毛病, 因为俩者的业务场景和最终用户是完全不同的。所以一定要认识清楚,我们工作的end user是哪些人, 以及如何服务好最终用户。 当最终用户满意时,我们的工作自然就会被公司认可。所以我认为很多事情不是技术问题,而是价值导向问题,价值导向对了,我们的技术规划,架构选型就自然做对了。我是被IBM培养出来的,但到了滴滴后,只要愿意改变,一样也能做好互联网的技术工作。

在互联网产生之前,最大的计算机系统就是银行柜员系统,用户必须去银行网点排队办业务,银行网点人满为患,所有的交易都通过柜员处理, 大家想想看,这实际上就是通过人工排队的消息队列进行了削峰限流,最后所有交易都集中在一台价值几亿人民币的 IBM大型计算机里, 在集中式的DB2或者Oracle数据库里完成交易。 今天互联网电商的交易量要比银行大的多,所有的系统都是分布式系统了,与以前的集中式系统相比,分布式系统最显著的特点就是容易扩容,今天银行的IT系统也都互联网化了,大多数的银行业务都可以足不出户在家自助办理了,所以我们必须看一下什么是分布式系统

分布式系统是由许多计算机集群组成的,但对用户而言,就像一台计算机一样,用户感知不到背后的逻辑。分布式系统最重要的理论是2002年被科学家证明的CAP理论
C是数据一致性, A是可用性, P是分区容错性。 CAP三者之间是互相矛盾,互相影响的关系。

其中,最好理解的是A,A是可用性。 可用性存在两个关键指标。
首先是“有限的时间内”,其次是“返回正常的结果”,如果用户的操作请求返回的是400或者500错误,或者规定时间内没有返回结果发生了超时,那就算是发生了一次不可用。所以并不是说,只有发生了线上大规模事故,全部服务不可用才是不可用,发生400,500错误或者请求超时都属于不可用。

P是分区容错性:当系统发生消息丢失或局部故障时,仍然可以运行
在分布式系统中,当一项数据只在一个节点中保存时,万一放生故障,访问不到该节点的数据,整个系统就是无法容忍的。
如果这项数据复制到多个节点上,某个节点数据访问不了时,系统仍然可以从其他节点上读到数据,系统的容忍性就提高了。
但是多个数据副本又会带来一致性问题。要保证一致性,每次写操作就都要等待全部数据副本写成功,而这种等待会损害系统的可用性。
总的来说就是,数据副本越多,分区容忍性就越高,但要复制更新的数据就越多,一致性就越差。所以CAP就是一种按下葫芦,就起了瓢的感觉。

C是数据一致性, 分布式系统的数据一致,是指所有服务器节点在同一时间看到的数据是完全一样的。 如果成功更新一个数据项后,所有的服务器节点都能读取到最新值,那么这样的系统就被认为是强一致性的。系统如果不能在规定时间内达成数据一致,就必须在C和A之间做出选择。注意规定时间内是很重要的。

现在的系统都是分布式系统了,P是不可能放弃的,但一般来讲,架构设计要尽量减少依赖,系统依赖的基础架构组件和第三方系统越多,如果P太强了,整个系统的C和A,可能都会受到损害。 架构取舍更多的是在C和A之间做出选择。而没有分布式的CA系统就是关系型数据库,就是放弃了分布式,放弃了分布式的扩容能力。 
有些场景要求数据强一致,例如与钱有关的与订单有关的系统,这种场景下,或者舍弃A,或者舍弃P。 关系型数据库是强一致的CA系统, 
但如果mysql引入了从库,就会在一定程度上损害数据一致性, 但同时换来了A可用性或者读写性能的提升。
而TIDB采用raft协议,数据保存在至少三个副本里,同时它要兼容mysql,实现数据的强一致性, 所以既然,TIDB引入了P,就只能在一定程度上要舍弃A,读写性能会相应降低。但是增强了分布式的扩容能力,包括了存储和算力的扩容。 所以CAP理论告诉我们,所有便宜都想占到是不可能的, 只能根据实际业务需求和价值判断体系,做出取舍。

BASE理论是对CAP理论的延伸,是指基本可用、软状态、和最终一致性。基本可用, 比如在规定的1秒内熔断了,没有返回结果, 那我们又重试了两次,结果返回了结果,这个就是基本可用。 假如我重试了两次后,还是失败了, 我们做了一个降级处理,让用户仍然可以继续使用服务,这个也叫做基本可用。

最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。最终一致性是弱一致性的特殊情况。
新东方不同业务系统对数据一致性的要求都不一样, 本质区别就是具体的业务可以容忍在多长的时间限制内,达到最终的一致性。

FLP不可能原理, 请自行查找解释,此定理告诉我们,不要试图设计一个能够容忍各种故障并保持一致的分布式系统, 这是不可能的. 分布式事务也永远无法实现单体应用级别的一致性。即使如paxos协议和raft协议也会出现无法达成共识的情况,只不过出现的可能性很低。所以说paxos是有效的一致性算法,但也不可能做到完美无缺。

集团首席架构师 幺敬国老师在极客时间上做过zookeeper的系列专题讲座, 对raft协议是专家,我纯属外行,大家有任何问题可以请教他。

 

 

 

如果剥离掉具体的业务目标和产品目标,那我们就会发现, 我们C端技术工作的目标,其实正好就是分别对应前面讲的CAP。但是同时满足三个要求是非常难的,只能根据具体业务场景进行取舍。 Tidb的开发商的名字叫PingCap就是一个很有雄心壮志的名字,意在无限的同时逼近这三个目标。

现在C端系统都是分布式的,舍弃P是不可能的,而且业务又会要求我们必须保证系统的可用性,因此事实上我们C端技术能舍弃的也只有规定时间内的数据一致性。
通常只要在数秒钟甚至数分钟,甚至1天后,可以达到最终一致就是可以接受的。 我们牺牲了一致性,换来的就是A的大幅度提升,和性能的提升。

 

接下来我们,结合APP的工作实践分别讲一下分布式系统常用的基础架构组件 

 

 

 

首先讲一下缓存。刚才讲过了,数据拷贝越多, 数据一致性就越差,引入缓存必然会损害规定时间内的数据一致性,因此数据缓存时间通常不宜太久,持久化的缓存数据更是荒唐。Redis对外提供集中式的缓存服务, 只增加了一份临时数据副本。 但Guavar是基于JAVA的本地缓存,每台JAVA服务器在本地保存一份数据副本,会显著损害整个分布式系统的数据一致性,并且造成相关客诉。

除了数据一致性, 使用redis也会涉及到系统可用性问题。 Redis实际上是一个CP系统。对于Redis, value越大,读写访问的延迟就越大。使用缓存时,要避免使用大Value和大Key,否则会降低可用性。大值可以采用序列化后压缩的方法。 与大值相反的另一个应用极端是, 大Key小Value,这种Key可以通过MD5压缩来避免。 通常来讲redis的平均get请求应该是数十微秒级别的,这种高速缓存部署时要和应用服务在统一的IDC, 避免跨机房部署,因为跨IDC通常会有毫秒级的延迟,从而损害系统可用性,使用缓存的意义就不大了。

此外,Redis可以使用pipeline提高批量访问性能,可以想见网络请求数量会因此大幅度减少。

 

 

使用缓存时要思考如何提高缓存命中率,对应的就是如何减少缓存穿透率, 减少对第三方系统或者对数据库的压力。
理想状态下,系统中的热数据应该都存在缓存里。 一个极端是压测, 不好的压测设计是,不停地,频繁地,重复地,请求相同的测试用例,其实第一次就是预取, 数据变热后, 压测快的飞起,但是一到线上真实环境, 可能就不行了,因为线上没有那么多重复请求。但在真实业务场景下,可以跟据用户的真实的操作序列, 预测出来哪些数据将会很快会被用户访问到, 此时,我们就可以采用预先计算和预取数据的方法,在用户后续操作发生时直接从缓存里读取数据。

此外,刚刚发生读写操作的数据,可以认为都是热数据,都可以考虑预取到缓存。

下面这张图是在剥离掉具体业务后的,APP使用缓存的架构方法。

首先讲主动更新缓存,
首先第一个操作是需要更新数据的事务操作,可以考虑主动更新缓存。对一致性要求高的业务,事务主动更新缓存时必须从数据库主库读取数据,否则会因为数据库主从延迟导致缓存数据和数据库里的不一致。 还有一种情况,缓存的值是一个集合set,需要一次更新很多数据项,这种操作没有原子性保障,就必须用事务保障,需要加锁。所以主动更新缓存的方案是不够严谨的,容易导致数据不一致。还有就是发数据更新消息,这时候数据变更MQ必须把所有信息都带上, 典型的如binlog。 如果只是通知一个变更事件,可能就会导致缓存数据和数据库不一致。所以binglog可能是最不容易导致一致性问题的方案。 但有时候我就是拿不到binlog怎么办, 而且严谨的方案显然在技术上会更复杂,落地成本会更高。

我想说,不严谨的方式不一定就不能用, 时刻记住业务场景可以弥补技术上的不足,架构设计时实用主义比完美主义更适用, 更能多快好省地解决业务问题。采用什么方案,首先是要考虑业务场景是啥, 其次是要考虑,万一出问题了后果是否可控,不要追求完美。特别强调,报名续班优惠的同学,千万不要这么玩,如果非要这么玩,出问题后果自负。

第6个操作是主动更新缓存, 缓存快过期时,发生读操作,可以考虑要主动读取源数据,更新缓存,当第三方故障,导致数据源无法读取时,可以主动延长缓存时间。 做一个降级处理,提高系统的可用性。

 

 

 

再讲一下本地缓存Guava。比起集中式的redis缓存,本地缓存的副本更多了,数据一致性也就更差了。如果网关不采用特别的分发策略, 本地缓存时间建议要设置的很短,为的是缩短各节点数据最终一致达成时间。

下面这张图的右边采用了一致性hash的负载均衡策略, 根据用户请求散列,保证各个服务器上的缓存数据没有交集,这样就提升了数据一致性。但这样做等于自己做了一个分布式的redis,严格讲,不可能比redis搞得好,但是好处就是,架构上减少了一层对redis的依赖,系统可用性提高,性能提高。

具体怎么取舍,还是要看业务的实际需求。很多数据库调优的工作,最终变成了玄学。就是因为系统参数太多了。 基于CAP定理,你不可能什么好处都得到,顾此必然失彼。

 

 

 

其他使用缓存的小窍门还有设置缓存时间时加一个随机值,避免缓存集中过期带来的问题。剩下几个前面都涉及到了,就不再讲了

接下来,快速讲一下消息队列。 消息队列可以应用到分布式事务,分段式提交里。 也可以帮助系统架构解耦。 可以用来做削峰限流,既不超过系统承载能力,同时又不丢失用户消息。消息队列服务的SLA是保证消息不丢,但牺牲掉了消息不重。会重复发送同一个消息。所以就要求消费者保证操作的幂等性。

消费者的拉消息模型, 好处是消费者不会过载,超过最大处理能力。坏处是消费者需要专门引入一个单独的服务进程或者线程。
消费者的推模型,消费者构型简单,跟开发一个用户接口一样。坏处是可能会过载。

常见消息队列如kafka和rabbitMQ的特点,我就不多讲了。

 

 

 

下面这张图,我快速讲一下数据库。
数据库的主库从库读写分离,一般会造成规定时间内的数据不一致现象,因此在需要强一致的情况下, 可以先读从库, 如果读不到,再去读主库,一定程度上可以减少主库的压力。 牺牲了一点儿数据一致性,提升了系统的可用性。

分库或者分表可以显著提升数据库的读写性能。 定时归档和分表一样,是减少单表的数据量,提升读写性能。 减少不必要的索引,任何数据库的读写操作,即使没有使用事务, 对数据库而言也都是一个事务。 为了保障数据库的ACID属性, Insert操作必须在所有索引建立完成后才能返回,因此单表建立过多的索引会拖累数据库的性能。

分布式系统里,要尽量避免耗时的数据库操作, 比如数据库事务操作, 联表查询等操作, 因为这些耗时操作,就是让系统在规定时间内的A达不到了。根据业务特点可以采取最终一致性的概念,结合消息队列,使用分段事务提交的方法,适当延长达到一致性的时间,来换取系统可用性的提升。

在新东方APP的作业系统里, 发作业就是一个典型的分布式事务。 此外,由于学生的报、转、退,和插班动作,产生了很多学生收不到作业,作业错乱的客诉。 因此结合消息队列,分段提交。 并且用定时任务扫表,做各种补偿操作,解决了大部分的客诉问题。

结合CAP定理,再说一句, MYSQL是CA系统, 如果mysql增加了从库,也就是引入了CAP里的P, 但是mysql主从分离的分布式属性,比起tidb的分布式属性要差一些,因为这样只是扩容了算力,而不是扩容了存储。数据库主从分离,是引入P,牺牲了C,增强了A,SQL查询变快了。 但是对于很短的,规定时间里, 要达到数据强一致的业务场景, 读写必须都走主库,但还好,这种业务场景是比较少的, 新东方的报名续班业务是一个。

结合CAP定理, Tidb是一个典型的CP系统,至少有三个数据副本,通过raft协议实现三副本数据的强一致。因为引入了P,TIDB的分布式扩展能力比mysql好,无论是存储扩容还是算力扩容都比mysql好。而且tidb兼容mysql,保证数据的强一致性,所以tidb对于订单交易业务,是一个很好的选择。 这些年银行大大小小的不可用事故,发生了不下10起,但没听说谁的钱丢了。 所以涉及到钱和优惠券的场景, 宁可发生服务不可用事故,也不能发生数据一致性事故, 因此CP系统保证了钱不丢,又保证了分布式扩容能力,是一个互联网时代下的很不错的选择,但规定时间内的可用性就会差一点儿。

 

 

新东方APP由我学APP发展而来, 目前也支持新东方的全部业务线的搜课服务,以及K12的学生端与教师端服务

 

 

新东方APP2019年以前的架构可以用一团乱麻来形容,架构没有分层,而且乱拉电线。主要的问题有

1、前端不止做了展现交互, 也做了各个后端系统的数据聚合工作, 前端计算聚合完的数据,还要写到后端。这其实就是10几年前游戏的做法, 黑客可以通过改动魔兽世界的客户端, 就让金币增加,或者获得特殊装备,那就是因为把很多关键的业务计算逻辑放到的客户端完成。整个架构没有符合单一职责,和开闭原则的设计规范,最后就是用APP包了一层皮出来。

2、前后端都没有熔断、降级限流等稳定性设计,听云监控看到一个请求耗时几分钟, 其实用户早都杀掉APP了。

3、没有业务上监控报警, 被动等客诉,代码里基本不写错误日志。前后端都应该有监控报警。前端页面加载失败, JS错误这些都应该上报错误日志。

4、服务器性能很差,单节点只能承载15个并发请求。不是所有问题都可以通过加机器来解决,这么差的性能已经不是加机器可以解决的了。

5、数据不一致现象严重,同一份业务数据, 在多个系统,甚至端上重复计算,分别存储,没有事务保证,不考虑并发设计,导致数据不一致的客诉很多。
系统里存在太多的单点故障。 MQ, NFS存储、定时任务。都曾经爆雷

6、研发效率低,代码里很多地方的注释写着, 此处有巨坑, 不好改, 先这样吧, 以后再说。。。这也说明,以前可能是没有代码review流程,技术方案review流程的。

7、数据库到处是JOIN操作,性能极差,多表写入,并发写入都不考虑事务保护,造成数据不一致现象普遍发生,该用幂等操作设计的地方也没有用。缓存的使用也存在问题。

以上就是新东方APP架构2019年以前的样子。

 

 

 

新东方APP最核心的功能是学生做作业。这张图是以前,新东方APP里趣配音的作业架构,这个架构发生过很多次服务不可用事故。

大家还记得前面的定理描述, 可用性是在规定的时间内要返回正常的应答。而400应答,500应答增多就是系统服务的可用性降低了。不是非要等整个系统已经ping不到才算不可用。
由于作业,无论是上传还是下载,都占用连接池的时间过长,所以导致其他服务都受到了影响, 降低了整个系统的可用性。

用户中心头像服务超时严重, 分析原因也都是因为NFS存储导致的。所以为了保障作业稳定性,提升学生做作业的体验,必须拆除掉对NFS存储的严重依赖

 

 

 

下面这张图是目前新东方APP的趣味配音作业的架构,这个架构比较复杂,第一次分享,没有润色,稍微有点乱。
这个图里的绿色文字步骤都是降级操作。新的架构设计应用了BASE定理, 通过基本可用,最终一致等概念,显著提升了整个作业系统的可用性。

和旧的架构相比,新的架构拆除了整个用户交互过程对NFS存储的的依赖, 使用腾讯对象存储作业主力存储, NFS降级为备份。这个方案还抵挡住了好几次腾讯云的线上事故。
当腾讯云对象存储发生了故障时, NFS会被启用, 短时间内会有一定的数据不一致,但由于NFS被设置成对象存储的回源地址, 因此两边存储最终还是会一致的。

腾讯云CDN和对象存储发生过多次故障, 持续时间从数分钟到10分钟左右不等, 我们的新架构都抗过去了,没有发生任何稳定性问题和客诉, 曾经有一次, 新东方到腾讯云的专线流量激增, 就是因为腾讯云的CDN出了问题,无法访问,APP端发生大面积降级,带附件上传作业的比例显著增加, 查看作业结果视频的降级也同时显著增加。

除了混合云存储, 我们做了自动弹性到腾讯云的混合云计算。 当我们本地的视频合成服务过载时,计算任务会自动弹性到腾讯云的FAAS计算。没有发生弹性时,我们不需要给腾讯云付钱, 发生弹性时, 每个月100万次以下的计算是免费的。所以可以说, 我们以这种方式,基本没花钱,就加强整个系统的可用性。

作业是APP最重要的功能,从我们的价值导向判断,作业稳定性投入再多时间和精力,都是值得的。目前新东方APP的作业系统已经被改造成一个打不死的小强。

 

 

 

 

快速说一下,熔断降级限流, 前面的PPT都提到了。
这里再说一个APP熔断重试的bad case,APP团队以前的系统里,把Redis配成默认1秒熔断,20次重试。 结果有一次,redis发生故障时,拖累整个APP服务故障,全都不可用。大家记住redis是CP系统。

关于限流, 有一次,因为外部的爬虫导致APP对教务的访问增加,流量激增,最后为了系统稳定,在nginx上增加了限流处理, 保护了内部ERP系统的稳定。

 

 

 

快速过一下JVM优化
这一页是APP做JVM优化的实际案例。 我抽象总结一下,就是要尽量避免,一次申请非常大的内存, 例如打开一个10几兆的图片, 例如,不分页,一次查询10几万条数据库记录。
当年轻代内存不够用的时候,这些内存会直接进入老年代,如果这些操作是比较频繁的,就会导致频繁的Full GC,损害整个系统的可用性。 优化之后,full GC频率就明显下降了

再说一下我们做的题库改造工作,
由于B/C两端对产品技术能力模型要求不一样,以前题库跟APP的技术模块是混在一起分不清的,导致工作效率降低, 部门合作成本提高:
C端: 大用户量, 高并发, 影响大, 直接承接外部用户流量
B端: 用户量少, 并发低,如内容系统,题库等, 承接内部用户流量
由于B/C两端支持的用户类型不同, 面对的问题域不同,应该拆开,提高各个部门的研发效率,和跨部门的协同效率

除了研发效率, 以前有些数据比如测评结果数据,学情数据在TPS和泡泡服务器上多处存储, 数据不一致现象非常严重, 由此发生很多非常难解决的客诉,不下定决心做B/C服务拆分,就无法彻底解决相关客诉,和提升整个作业系统的稳定性。

这里说一下去年我们的数据库迁移工作。
我们把数据库从tidb迁移到了mysql,同时做了分库分表,和索引优化,删除了大量没有必要的索引, 查询写入性能提升了十倍以上。
这么做的原因是,我们APP业务尤其作业, 对数据强一致性没那么高的要求,我们需要的就是高可用性。 TIDB是很好, 但它是一个CP系统用到这里,是场景不对。

这页PPT是听云的监控数据。APP团队技术优化工作让用户的体验更流畅,系统更稳定。

通过听云监控的图表可以看到,99百分位的请求应答性能累计提升了163倍。

 

 

 

通过APP客诉统计分析, 可以看到,APP用户量增加一半以上的情况下, 客诉率下降了一倍多。

 

 

 

这个图上是我们自己开发了一套,APP和H5的监控告警机制,当APP的加载超时,访问错误直接上报。
通过APP向H5注入代码, 也实现了对APP上所有的H5页面的监控, H5页面中的JS错误,请求错误,都可以即时上报。

再结合公司的听云监控,运维的监控系统,我们的监控就可以在客诉发生之前发现不少问题,也可以通过监控,发现很多系统稳定性的地雷,即时拆除。

目前APP这边日志都加了TraceID,在一定程度上可以帮助研发快速定位分布式系统的问题原因。

但也有问题,前端后端的traceID没有打通,APP和其他系统的TraceID没有打通,需要公司级的日志监控告警的基础架构组件支持。

 

 

 

下图是,聚合监控告警服务实时分析日志数据,发现预警后, 告警被打倒钉钉群里, 技术值班人员可以通过密切关注钉钉告警,提早发现问题。

 

 

根据APP业务特点, 还做了一些服务化的改造。 对一些业务做了抽象剥离与解耦重构 

 

 

 在新的架构里,我们清晰的引入了服务接入层, 后台做服务化改造。做各种服务拆分与解耦,重构的工作。让整个架构更清晰, 各个模块的职责更明确, 系统的稳定性更高。

 

 

 

新东方集团的主数据的问题有一点类似数据库从库或者是本地缓存带来的的问题,数据副本太多, 数据不一致,数据质量非常差。

各个使用主数据的系统彼此之间调用和对比数据时,也会发现数据不一致。 主数据带来的客诉,和线上故障很多。因此吴强老师下令拆除主数据服务

 

 

 

 为此, 我们启动了C端数据中台OnePiece项目的开发建设工作。

 

 

 

先说一下用户中心的头像服务。请求失败率长期超过50%

在老用户中心的头像服务里, 即使是查询头像URL这种轻量操作,代码里也要用fexist fopen这种很重的IO操作去验证头像图片上,IO耗时不比下载头像少。 此外由于头像没有放到CDN上,因此所有的头像图片下载请求都回源到应用服务器上,应用服务器又是访问本地NFS存储。所以整个系统的可用性极差。

 

 

 

开闭原则大家自行查找, 在这个具体案例里
符合开闭原则,就是当你采用新的数据接口的时候,你也要可以保持老接口继续可用, 同时提供新的数据服务接口
我刚接手用户中心改造工作时,就立刻发现其实只要把新用户中心的数据打平, 就是老用户中心的数据接口,老用户中心的服务不使用stuID数据字段就可以了。

 

 

   

接着上一页PPT讲, 我们在新用户中心里完全接管了对老用户心中的访问。通过打平新用户中心的数据库结构实现对老用户中心服务用户的平滑迁移。
在迁移期间, 数据写入是双写的,为的是出现故障时,快速止损,快速回滚服务,快速回滚数据。迁移期间, 数据查询请求也是两边都请求。 由Apollo做一个开关,决定用那边的请求应答响应用户。 同时也是为了做一个数据对比,及时发现和修复BUG。

数据库主从分离, 本身也是牺牲了数据一致性,换取可用性。但数据库写入操作一般都是强一致的,所以在写入数据之前如果需要读取任何数据库数据,应该是强制读主库(甚至要用事务或锁包读写过程保护起来),而不能读从库,更不能去读缓存。

前面缓存那一页PPT讲了很多了这边就不再赘述。我想强调一下, 我们做老系统的改造和迁移时, 一定要设计好上线计划和回滚计划,在开始动手写代码之前,就写进技术方案里面。 上线之前首先就考虑,上线后出现故障怎么办? 出现故障时,是否会产生脏数据和数据丢失,发生故障时, 及时止损, 不仅仅是服务的回滚恢复, 还有数据的回滚恢复。以及如何快速回滚。

用户中心更新缓存时,由于数据结构非常复杂,有多级列表,因此为了减少代码复杂性,必须要先删除缓存,再重新加载,这期间必须要加锁,有事务性保障。减少数据不一致发生的可能性。但需要数据强一致的操作还是要强制读数据库主库。

用户中心在测试期间要用线上真实流量的回放做一次压测。 多啦A梦,这个是我们团队的测试专家吴兴微老师开发的测试工具。 具体大家可以问他怎么做的。我不做解释

 

 

 

这次我们也重构了教务中心的C端服务。目前教务中心提供的查询服务都是通过ES实现的,C端可用性不高。 这次我们拉了教务的从库来保证C端数据查询的高可用。
由于教务的数据结构复杂,仅仅APP业务需要的查询操作就涉及到20几张表, 因此为了消灭和减少这些耗时的JOIN操作,提高系统可用性, 我们把几个大的维表(最多有12万条记录的)加载到本地缓存里,在服务器的内存里实现JOIN操作。 因为维表数据量大,因此大批量加载数据进redis是不现实的,而且列表的聚合如果走redis,也会增加请求数量,损害redis的可用性。

交易表的聚合依然是在数据库里完成的,根据目前的业务特点聚合结果也是可以短时间缓存的。在引入本地缓存组件后,请求路由上就必须使用一致性Hash,把服务器的本地缓存数据进行分区处理。这里的hash key目前确定是校区ID,班课ID,用户ID等, 以后可能还需要持续优化策略。

 

 

 

最后数据中台Onepiece项目的测试方法,还是准备用吴兴微开发的多啦A流量回放工具,用线上真实流量做测试。 
测试完毕后,准备采用灰度发布加蓝绿发布的方式上线。

OnePiece也是对MDM服务的平滑迁移, 因此兼容主数据服务接口, 在迁移上线期间, 可以用Apollo开关, 一个接口一个接口的, 先10%小流量。 没有问题主键放量到100%。 全量发布后可以通过日志继续对比数据, 及时发现罕见场景下的问题。

整个系统的上线过程持续三周以上,每天都发布上线几个接口,期间研发密切观察监控和客诉,一旦发现问题, 就可以通过apollo开关, 一键快速回滚。有效地,把线上问题和风险控制在很小的范围里。

 

 

 

前面讲的东西其实没有什么技术含量,都是可以快速习得的经验。 我们团队面试时,最看重的是逻辑思维能力,写代码能力,和学习能力等硬核素质。 

 

 

感谢大家! 新东方教育科技集团公司正在加速推动互联网产品技术布局,组建完全互联网化的技术团队,非常欢迎加入, 为中国的教育发展贡献自己的一份力量。