新东方APP技术架构演进
原创
张建鑫
新东方技术
新东方技术
微信号
gh_4f54cbcacb95
功能介绍
互联网开发的实践经验,技术分享、沉淀和前瞻性思考。
前言
古代东西方的思想家都产生过一个终极的追问,世界的本元到底是什么?
老子说,道生一,一生二,二生三,三生万物,天道有常不以尧存不为桀亡。
孔子说朝闻道,夕死可矣,孔子把对道的研究从,对人与自然关系的天道,转移到了研究君君臣臣父父子子的人道方向上。
古希腊第一个哲学家泰勒斯说世界的本元是水,后来毕达哥拉斯回答说世界的本元是数字,古希腊哲人对道的研究始终聚焦在人与自然关系的天道方向上。
对终极追问的不同思考和回答,衍生出了,东西方在文化价值观,思考逻辑和做事方法等方面的巨大差异。
我们在企业里工作,实际上也存在一个终极追问,就是你的终极用户是谁?怎么服务好终极用户。我曾经在2B技术公司IBM工作过12年,在2C技术公司滴滴工作过三年。我深切感受到,在这个问题上的不同回答,导致了传统的IT技术公司和互联网技术公司,在工作价值观,和工作方法等方面的不同。
通常,B端技术重功能不重体验,软件客户每年续签一次合同,软件开发商可以通过各种手段增加用户迁移到其他竞争对手的成本,深度绑架用户,即使客户对软件有一些不满意也会续签合同。而互联网的C端个人用户是典型的用脚投票,抛弃一个产品的时候,一声再见都不会说。因此互联网产品技术的竞争是典型的赢者通吃,产品体验稍有不同,短时间内用户就会流失到竞争对手那里。所以互联网产品技术极度关注用户体验,和用户客诉。IBM解决客户的客诉流程冗长,有所谓的一线二线三线技术支持,解决一个客诉动辄数月之久。但是互联网C端技术需要时刻关注用户客诉和线上事故,争分夺秒的解决问题,甚至要求几分钟内就把问题解决掉。所以如何避免和减少客诉,如何快速处理线上故障,就成为区别2B,2C技术公司不同点之一,对价值导向问题的不同回答,也产生了完全不同的价值评估系,最简单的就是如何去判断一项具体工作的轻重缓急和重要程度。
我刚来新东方的时候,我的团队里没人关心客诉问题,没有技术值班,上线流程很草率。互联网公司里重要的技术方法到了IBM那里,就可能变成过度设计,变成不重要的工作,但其实没有毛病,因为俩者的业务场景和最终用户是完全不同的。所以一定要认识清楚,我们工作的end user是哪些人,以及如何服务好最终用户。当最终用户满意时,我们的工作自然就会被公司认可。所以我认为很多事情不是技术问题,而是价值导向问题,价值导向对了,我们的技术规划,架构选型就自然做对了。我是被IBM培养出来的,但到了滴滴后,只要愿意改变,一样也能做好互联网的技术工作。
在互联网产生之前,最大的计算机系统就是银行柜员系统,用户必须去银行网点排队办业务,银行网点人满为患,所有的交易都通过柜员处理,大家想想看,这实际上就是通过人工排队的消息队列进行了削峰限流,最后所有交易都集中在一台价值几亿人民币的 IBM大型计算机里,在集中式的DB2或者Oracle数据库里完成交易。今天互联网电商的交易量要比银行大的多,所有的系统都是分布式系统了,与以前的集中式系统相比,分布式系统最显著的特点就是容易扩容,今天银行的IT系统也都互联网化了,大多数的银行业务都可以足不出户在家自助办理了,所以我们必须看一下什么是分布式系统。
分布式系统
CAP原则
分布式系统是由许多计算机集群组成的,但对用户而言,就像一台计算机一样,用户感知不到背后的逻辑。分布式系统最重要的理论是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理论
BASE理论是对CAP理论的延伸,是指基本可用、软状态、和最终一致性。基本可用,比如在规定的1秒内熔断了,没有返回结果,那我们又重试了两次,结果返回了结果,这个就是基本可用。假如我重试了两次后,还是失败了,我们做了一个降级处理,让用户仍然可以继续使用服务,这个也叫做基本可用。
最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。最终一致性是弱一致性的特殊情况。
新东方不同业务系统对数据一致性的要求都不一样,本质区别就是具体的业务可以容忍在多长的时间限制内,达到最终的一致性。
FLP不可能原理
FLP不可能原理,请自行查找解释,此定理告诉我们,不要试图设计一个能够容忍各种故障并保持一致的分布式系统,这是不可能的. 分布式事务也永远无法实现单体应用级别的一致性。即使如paxos协议和raft协议也会出现无法达成共识的情况,只不过出现的可能性很低。所以说paxos是有效的一致性算法,但也不可能做到完美无缺。
如果剥离掉具体的业务目标和产品目标,那我们就会发现,我们C端技术工作的目标,其实正好就是分别对应前面讲的CAP。但是同时满足三个要求是非常难的,只能根据具体业务场景进行取舍。Tidb的开发商的名字叫PingCap就是一个很有雄心壮志的名字,意在无限的同时逼近这三个目标。
现在C端系统都是分布式的,舍弃P是不可能的,而且业务又会要求我们必须保证系统的可用性,因此事实上我们C端技术能舍弃的也只有规定时间内的数据一致性。
通常只要在数秒钟甚至数分钟,甚至1天后,可以达到最终一致就是可以接受的。我们牺牲了一致性,换来的就是A的大幅度提升,和性能的提升。
新东方App中的C端技术
接下来我们,结合APP的工作实践分别讲一下分布式系统常用的基础架构组件
缓存
首先讲一下缓存。刚才讲过了,数据拷贝越多,数据一致性就越差,引入缓存必然会损害规定时间内的数据一致性,因此数据缓存时间通常不宜太久,持久化的缓存数据更是荒唐。Redis对外提供集中式的缓存服务,只增加了一份临时数据副本。但Guava是基于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缓存,本地缓存的副本更多了,数据一致性也就更差了。
如果网关不采用特别的分发策略,本地缓存时间建议要设置的很短,为的是缩短各节点数据最终一致达成时间。
具体怎么取舍,还是要看业务的实际需求。
很多数据库调优的工作,最终变成了玄学。
就是因为系统参数太多了。
基于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系统保证了钱不丢,又保证了分布式扩容能力,是一个互联网时代下的很不错的选择,但规定时间内的可用性就会差一点儿。
熔断降级限流
快速说一下,
熔断降级限流,前面的PPT都提到了。
这里再说一个APP熔断重试的bad case,APP团队以前的系统里,把Redis配成默认1秒熔断,20次重试。结果有一次,redis发生故障时,拖累整个APP服务故障,全都不可用。大家记住redis是CP系统。
关于限流,有一次,因为外部的爬虫导致APP对教务的访问增加,流量激增,最后为了系统稳定,在nginx上增加了限流处理,保护了内部ERP系统的稳定。
JVM优化
快速过一下JVM优化。
这一页是APP做JVM优化的实际案例。我抽象总结一下,就是要尽量避免,一次申请非常大的内存,例如打开一个10几兆的图片,例如,不分页,一次查询10几万条数据库记录。
当年轻代内存不够用的时候,这些内存会直接进入老年代,如果这些操作是比较频繁的,就会导致频繁的Full GC,损害整个系统的可用性。优化之后,full GC频率就明显下降了。
新东方App口语配音作业重构
新东方APP最核心的功能是学生做作业。
这张图是以前,新东方APP里趣配音的作业架构,这个架构发生过很多次服务不可用事故。
大家还记得前面的定理描述,可用性是在规定的时间内要返回正常的应答。而400应答,500应答增多就是系统服务的可用性降低了。不是非要等整个系统已经ping不到才算不可用。
由于作业,无论是上传还是下载,都占用连接池的时间过长,所以导致其他服务都受到了影响,降低了整个系统的可用性。
用户中心头像服务超时严重,分析原因也都是因为NFS存储导致的。所以为了保障作业稳定性,提升学生做作业的体验,必须拆除掉对NFS存储的严重依赖。
下面这张图是目前新东方APP的趣味配音作业的架构,这个架构比较复杂,第一次分享,没有润色,稍微有点乱。
这个图里的绿色文字步骤都是降级操作。新的架构设计应用了BASE定理,通过基本可用,最终一致等概念,显著提升了整个作业系统的可用性。
和旧的架构相比,新的架构拆除了整个用户交互过程对NFS存储的的依赖,使用腾讯对象存储作业主力存储,NFS降级为备份。这个方案还抵挡住了好几次腾讯云的线上事故。
当腾讯云对象存储发生了故障时,NFS会被启用,短时间内会有一定的数据不一致,但由于NFS被设置成对象存储的回源地址,因此两边存储最终还是会一致的。
腾讯云CDN和对象存储发生过多次故障,持续时间从数分钟到10分钟左右不等,我们的新架构都抗过去了,没有发生任何稳定性问题和客诉,曾经有一次,新东方到腾讯云的专线流量激增,就是因为腾讯云的CDN出了问题,无法访问,APP端发生大面积降级,带附件上传作业的比例显著增加,查看作业结果视频的降级也同时显著增加。
除了混合云存储,我们做了自动弹性到腾讯云的混合云计算。当我们本地的视频合成服务过载时,计算任务会自动弹性到腾讯云的FAAS计算。没有发生弹性时,我们不需要给腾讯云付钱,发生弹性时,每个月100万次以下的计算是免费的。所以可以说,我们以这种方式,基本没花钱,就加强整个系统的可用性。
作业是APP最重要的功能,从我们的价值导向判断,作业稳定性投入再多时间和精力,都是值得的。目前新东方APP的作业系统已经被改造成一个打不死的小强。
通过一年多的技术改造,整个2020年暑期,新东方APP没有发生一起线上事故,平稳的度过过了一个暑期。
预览时标签不可点
微信扫一扫
关注该公众号
继续滑动看下一个
轻触阅读原文
新东方技术
向上滑动看下一个
知道了
微信扫一扫
使用小程序
取消
允许
取消
允许
:
,
。
视频
小程序
赞
,轻点两下取消赞
在看
,轻点两下取消在看
分享
留言