跨越篱笆:蘑菇街每秒订单数25倍提升历程

xuti3567 8年前
   <p>本文根据白辉在2016ArchSummit全球架构师(深圳)峰会上的演讲整理而成。ArchSummit北京站即将在12月2日开幕,更多专题讲师信息请到 北京站官网 查询。</p>    <p>今天分享的内容来自于去年我们做的事情,题目用了一个关键词是“篱笆”,篱笆的英文是Barrier,是指2015年蘑菇街面临的问题和艰巨的困难。我们越过了这些篱笆,取得了很好的成果。</p>    <h2>引言</h2>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/11d30dd048b2400f9bb65737e9754fd5.jpg"></p>    <p>今天分享的内容主要分为五部分。第一部分,概述电商系统发展中期面临的一般性问题。第二部分,如何解决面临的问题,主要的策略是做拆分、做服务化。第三、四部分,服务化之后业务的大增长、网站流量飞速的增加、“双11”大促等的挑战很大,我们做了服务的专项系统优化以及稳定性治理。第五部分,进行了总结和展望。</p>    <h2>电商系统发展中期面临的一般性问题</h2>    <p>我们先看第一部分的内容。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/1d6ddfc2764b1b4573b3a0754e036d49.jpg"></p>    <p>我总结了一下,一般电商系统发展到中期都会面临三个方面的问题(如图)。第一方面是业务问题。比如,一开始做业务的时候可能很随意,一是并不考虑业务模型、系统架构,二是业务之间的耦合比较严重,比如交易和资金业务,有可能资金和外部第三方支付公司的交互状态耦合在交易系统里,这些非常不利于业务发展。第二方面是系统问题。2014年我们面临单体应用,400人开发一个大应用,扩展性很差,业务比较难做。第三方面是支撑问题,比如关于环境、开发框架和质量工具等。这些是电商系统发展到中期都会面临的问题,中期的概念是用户过了千万,PV过了1亿。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/bc93a824df71c23212384170d1c75404.jpg"></p>    <p>我们来看一下蘑菇街2015年初面临的问题。蘑菇街2015年用户过亿,PV过10亿,业务在超高速发展,每年保持3倍以上的增长。电商促销、交易、支付等业务形态都在快速膨胀,我们需要快速支持业务发展,而不是成为业务的瓶颈。那么就是要去做系统的拆分和服务化。</p>    <h2>系统拆分与服务化过程</h2>    <p>第二部分的内容,是关于蘑菇街系统拆分与服务化的历程。</p>    <p>按照如下几条思路(见图),我们进行系统拆分以及服务化。最开始,大家在同一个应用里开发一些业务功能,都是选择速度最快的方式,所有的DB和业务代码都是在一起的。首先我们将DB做垂直拆分。第二步是做业务系统垂直拆分,包括交易、资金等。第三步是在系统拆完了之后要考虑提供什么样的API来满足业务的需求?这里我们要做数据建模+业务建模,数据建模方面包括数据表的设计和扩展支持,数据模型应该非常稳定;业务建模方面,使用标准和灵活的API,而且尽量不用修改代码或者改少量代码就能支持业务需求。第四步是需要将业务逻辑下沉到服务,Web层专注于展示逻辑和编排,不要涉及过多业务的事情。然后用SOA中间件建设服务化系统。最后会做一些服务的治理。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/9f02a01526e0934ef42ed975b4bd1e0d.jpg"></p>    <p>来看一个API服务化的例子,在做服务化之前和做服务化之后,交易创建下单业务有什么不一样。服务化之前我们面临的问题有:入口分散,如果要在底层做任何一个微小的改动,十几个入口需要几十个人配合修改,这是非常不合理的一种方式;多端维护多套接口,成本非常高;还有稳定性的问题,依赖非常复杂,维护很难。我刚到蘑菇街的时候,一次大促活动就导致数据库崩溃,暴露了系统架构很大的问题和总量上的瓶颈。按照上面提到几条思路去做服务化,看看有了哪些改善?首先是API统一,多个端、多个业务都用统一的API提供;其次是依赖有效管理起来,大事务拆分成多个本地小事务;最后降低了链路风险,逻辑更加清晰,稳定性更好。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/47964c33f9f9abb44585f7c2601952bf.jpg"></p>    <p>2015年3月我来到蘑菇街之后,先制订了服务化的规范,探讨了到底什么是标准的服务化。在做服务化的过程中,发现大家代码风格完全不一样,所以制定编码规范非常重要。2015年8月,我们完成了各个模块的改造,包括用户、商品、交易、订单、促销、退款等,然后有了服务化架构1.0的体系。在此基础之上,我们进一步做了提升流量和稳定性等更深度的建设。2015年9月,我们实施了分库分表和链路性能提升优化,2015年10月做了服务治理和服务保障。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/797f2dc132e507850530c23cca5f86a3.jpg"></p>    <p>接下来,以服务架构和服务体系建设为主线,讲一下去年整个网站架构升级的过程。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/a8c86c2076bdb1e5a9418b091214eaf1.jpg"></p>    <p>在服务化1.0体系完成之后,我们得到了一个简单的体系,包含下单服务、营销服务、店铺服务、商品服务和用户服务,还有简单的RPC框架Tesla。当时,我们并没有做很多性能优化的事情,但是通过业务流程化简和逻辑优化,每秒最大订单数从400提升到1K,基础服务也都搭建了起来。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/83ecae6953410ddc03c678ec1fc3c3af.jpg"></p>    <p>有了1.0初步的服务化体系之后,更进一步,我们一是要继续深入网站如资金等的服务化,二是要做服务内部的建设,比如容量、性能,这也是接下来要讲的内容。</p>    <h2>购买链路的性能提升</h2>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/2e580158116093102749dad455847b65.jpg"></p>    <p>这个链路(见图)是比较典型的电商链路,有商品页、下单、支付、营销和库存等内容。一开始每个点都有瓶颈,每个瓶颈都是一个篱笆,我们要正视它,然后翻越它。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/c6d41df433d0a441897673b941bdcf59.jpg"></p>    <p>我们先来看第一个篱笆墙:下单的瓶颈。</p>    <p>2015年“3.21”大促的时候,DB崩溃了,这个瓶颈很难突破。下一个订单要插入很多条数据记录到单DB的DB表。我们已经用了最好的硬件,但是瓶颈依然存在,最主要的问题就是DB单点,需要去掉单点,做成可水平扩展的。流量上来了,到DB的行写入数是2万/秒,对DB的压力很大。写应该控制在一个合理的量,DB负载维持在较低水平,主从延时也才会在可控范围内。所以DB单点的问题非常凸显,这座大山必须迈过去,我们做了一个分库分表组件TSharding来实施分库分表。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/ea3d1b693c797d89c28874084113ab39.jpg"></p>    <p>将我们写的分库分表工具与业界方案对比,业界有淘宝TDDL Smart Client的方式,还有Google的Vitess等的Proxy方式,这两种成熟方案研发和运维的成本都太高,短期内我们接受不了,所以借鉴了Mybatis Plugin的方式,但Mybatis Plugin不支持数据源管理,也不支持事务。我大概花了一周时间写了一个组件——自研分库分表组件TSharding,然后快速做出方案,把这个组件应用到交易的数据库,在服务层和DAO层,订单容量扩展到千亿量级,并且可以继续水平扩展。TSharding上线一年之后,我们将其开放出来。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/d98e872ee661829bb8fc954e9acc6162.jpg"></p>    <p>第二个篱笆墙就是营销服务RT的问题。促销方式非常多,包括各种红包、满减、打折、优惠券等。实际上促销的接口逻辑非常复杂,在“双11”备战的时候,面对这个复杂的接口,每轮链路压测促销服务都会发现问题,之后优化再压测,又发现新的问题。我们来一起看看遇到的各种问题以及是如何解决的。首先是压测出现接口严重不可用,这里可以看到DB查询频次高,响应很慢,流量一上来,这个接口就崩溃了。那怎么去排查原因和解决呢?</p>    <p>首先是SQL优化,用工具识别慢SQL,即全链路跟踪系统Lurker。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/2ae4395bfb8ed11b38e7efac4f1bcefc.jpg"></p>    <p>这张图我简单介绍一下。遇到SQL执行效率问题的时候,就看是不是执行到最高效的索引,扫表行数是不是很大,是不是有filesort。有ORDER BY的时候,如果要排序的数据量不大或者已经有索引可以走到,在数据库的内存排序缓存区一次就可以排序完。如果一次不能排序完,那就先拿到1000个做排序,然后输出到文件,然后再对下1000个做排序,最后再归并起来,这就是filesort的大致过程,效率比较低。所以尽量要走上索引,一般类的查询降低到2毫秒左右可以返回。</p>    <p>其次是要读取很多优惠规则和很多优惠券,数据量大的时候DB是很难扛的,这时候我们要做缓存和一些预处理。特别是查询DB的效率不是很高的时候,尽量缓存可以缓存的数据、尽量缓存多一些数据。但如果做缓存,DB和缓存数据的一致性是一个问题。在做数据查询时,首先要看本地缓存有没有开启,如果本地缓存没有打开,就去查分布式缓存,如果分布式缓存中没有就去查DB,然后从DB获取数据过来。需要尽量保持DB、缓存数据的一致性,如果DB有变化,可以异步地做缓存数据失效处理,数据百毫秒内就失效掉,减少不一致的问题。</p>    <p>另外,如果读到本地缓存,这个内存访问比走网络请求性能直接提升了一个量级,但是带来的弊端也很大,因为本地缓存没有办法及时更新,平时也不能打开,因为会带来不一致问题。但大促高峰期间我们会关闭关键业务数据变更入口,开启本地缓存,把本地缓存设置成一分钟失效,一分钟之内是可以缓存的,也能容忍短暂的数据不一致,所以这也是一个很好的做法。同样的思路,我们也会把可能会用到的数据提前放到缓存里面,做预处理。在客户端进行数据预处理,要么直接取本地数据,或者在本地直接做计算,这样更高效,避免了远程的RPC。大促期间我们就把活动价格信息预先放到商品表中,这样部分场景可以做本地计价,有效解决了计价接口性能的问题。</p>    <p>再就是读容量问题,虽然缓存可以缓解压力,但是DB还是会有几十K的读压力,单点去扛也是不现实的,所以要把读写分离,如果从库过多也有延时的风险,我们会把数据库的并行复制打开。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/d7c02ce697b6c935919c8eb7ba54680a.jpg"></p>    <p>我们来看一下数据。这是去年“双11”的情况(如图)。促销服务的RT得到了有效控制,所以去年“双11”平稳度过。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/e3945fda6d22869b5f3168d9e6c8716a.jpg"></p>    <p>接下来讲一个更基础、更全局的优化,就是异步化。比如说下单的流程,有很多业务是非实时性要求的,比如下单送优惠券,如果在下单的时候同步做,时间非常长,风险也更大,其实业务上是非实时性或者准实时性的要求,可以做异步化处理,这样可以减少下单对机器数量的要求。另外是流量高峰期的一些热点数据。大家可以想象一下,下单的时候,一万个人竞争同一条库存数据,一万个节点锁在这个请求上,这是多么恐怖的事情。所以我们会有异步队列去削峰,先直接修改缓存中的库存数目,改完之后能读到最新的结果,但是不会直接竞争DB,这是异步队列削峰很重要的作用。还有,数据库的竞争非常厉害,我们需要把大事务做拆分,尽量让本地事务足够小,同时也要让多个本地事务之间达到一致。</p>    <p>异步是最终达到一致的关键,异步的处理是非常复杂的。可以看一下这个场景(见图),这是一个1-6步的处理过程,如果拆分成步骤1、2、3、4、end,然后到5,可以异步地做;6也一样,并且5和6可以并行执行。同时,这个步骤走下来链路更短,保障也更容易;步骤5和6也可以单独保障。所以异步化在蘑菇街被广泛使用。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/9200cbf7203b1c3af97b0a1c3fc73111.jpg"></p>    <p>异步化之后面临的困难也是很大的,会有分布式和一致性的问题。交易创建过程中,订单、券和库存要把状态做到绝对一致。但下单的时候如果先锁券,锁券成功了再去减库存,如果减库存失败了就是很麻烦的事情,因为优化券服务在另外一个系统里,如果要同步调用做券的回滚,有可能这个回滚也会失败,这个时候处理就会非常复杂。我们的做法是,调用服务超时或者失败的时候,我们就认为失败了,就会异步发消息通知回滚。优惠券服务和库存服务被通知要做回滚时,会根据自身的状态来判断是否要回滚,如果锁券成功了券就回滚,减库存也成功了库存做回滚;如果库存没有减就不用回滚。所以我们是通过异步发消息的方式保持多个系统之间的一致性;如果不做异步就非常复杂,有的场景是前面所有的服务都调用成功,第N个服务调用失败。另外的一致性保障策略包括Corgi MQ生产端发送失败会自动重试保证发成功,消费端接收ACK机制保证最终的一致。另外,与分布式事务框架比起来,异步化方案消除了二阶段提交等分布式事务框架的侵入性影响,降低了开发的成本和门槛。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/240024441e291fc9dd951d86bfbc7f6b.jpg"></p>    <p>另一个场景是,服务调用上会有一些异步的处理。以购物车业务为例,购物车列表要调用10个Web服务,每一个服务返回的时间都不一样,比如第1个服务20毫秒返回,第10个服务40毫秒返回,串行执行的效率很低。而电商类的大多数业务都是IO密集型的,而且数据量大时还要分批查询。所以我们要做服务的异步调用。比如下图中这个场景,步骤3处理完了之后callback马上会处理,步骤4处理完了callback也会马上处理,步骤3和4并不相互依赖,且处理可以同时进行了,提高了业务逻辑执行的并行度。目前我们是通过JDK7的Future和Callback实现的,在逐步往JDK8的Completable Future迁移。这是异步化在网站整体的应用场景,异步化已经深入到我们网站的各个环节。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/6e7674d2922ba7589096faaaca83f2c9.jpg"></p>    <p>刚才我们讲了链路容量的提升、促销RT的优化,又做了异步化的一些处理。那么优化之后怎么验证来优化的效果呢?到底有没有达到预期?我们有几个压测手段,如线下单机压测识别应用单机性能瓶颈,单链路压测验证集群水位及各层核?系统容量配比,还有全链路压测等。</p>    <p>这是去年“双11”之前做的压测(见图),达到了5K容量的要求。今年对每个点进一步深入优化,2016年最大订单提升到了10K,比之前提升了25倍。实际上这些优化可以不断深入,不仅可以不断提高单机的性能和单机的QPS,还可以通过对服务整体上的优化达到性能的极致,并且可以引入一些廉价的机器(如云主机)来支撑更大的量。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/19c5d2faa257c8b3e66ea4b8e651674b.jpg"></p>    <p>我们为什么要做这些优化?业务的发展会对业务系统、服务框架提出很多很高的要求。因此,我们对Tesla做了这些改善(见图),服务的配置推送要更快、更可靠地到达客户端,所以有了新的配置中心Metabase,也有了Lurker全链路监控,服务和服务框架的不断发展推动了网站其他基础中间件产品的诞生和发展。2015年的下半年我们进行了一系列中间件的自研和全站落地。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/d4a00473ba6b8571589239984bfad1e5.jpg"></p>    <p>我们得到了服务架构1.5的体系(见图),首先是用户服务在最底层,用户服务1200K的QPS,库存250K,商品服务400K,营销200K,等等。</p>    <p>接下来我们看一下这一阶段,Tesla开始做服务管控,真正成为了一个服务框架。我们最开始做发布的时候,客户端、服务端由于做的只是初级的RPC调用,如果服务端有变更,客户端可能是几秒甚至数十秒才能拉到新配置,导致经常有客户投诉。有了对服务变更推送更高的要求后,我们就有了Matabase配置中心,服务端如果有发布或者某一刻崩溃了,客户端马上可以感知到,这样就完成了整个服务框架连接优化的改进,真正变成服务管控、服务治理框架的开端。</p>    <h2>购买链路的稳定性提升</h2>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/e45881919f1da3b73d488e56bae8e679.jpg"></p>    <p>有了上面讲到的服务化改进和性能提升之后,是不是大促的时候看一看监控就行了?其实不是。大流量来的时候,万一导致整个网站崩溃了,一分钟、两分钟的损失是非常大的,所以还要保证服务是稳的和高可用的。只有系统和服务是稳定的,才能更好地完成业务指标和整体的经营目标。</p>    <p>下面会讲一下服务SLA保证的内容。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/c76ccffa2f2a8b4539527a2990f16d5c.jpg"></p>    <p>首先SLA体现在对容量、性能、程度的约束,包括程度是多少的比例。那么要保证这个SLA约束和目标达成,首先要把关键指标监控起来;第二是依赖治理、逻辑优化;第三是负载均衡、服务分组和限流;第四是降级预案、容灾、压测、在线演练等。这是我们服务的关键指标的监控图(见上图)。支付回调服务要满足8K QPS,99%的RT在30ms内,但是图中监控说明SLA未达到,RT程度指标方面要优化。</p>    <p>服务的SLA保证上,服务端超时和限流非常重要。如果没有超时,很容易引起雪崩。我们来讲一个案例,有次商品服务响应变慢,就导致上层的其他服务都慢,而且商品服务积压了很多请求在线程池中,很多请求响应过慢导致客户端等待超时,客户端早就放弃调用结果结束掉了,但是在商品服务线程池线程做处理时拿到这个请求还会处理,客户都跑了,再去处理,客户也拿不到这个结果,最后还会造成上层服务请求的堵塞,堵塞原因缓解时产生洪流。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/3fee6e858b886e920ed9a88f6423839a.jpg"></p>    <p>限流是服务稳定的最后一道保障。一个是HTTP服务的限流,一个是RPC服务的限流。我们服务的处理线程是Tesla框架分配的,所以服务限流可以做到非常精确,可以控制在服务级别和服务方法级别,也可以针对来源做限流。</p>    <p>我们做了这样一系列改造之后,服务框架变成了有完善的监控、有负载均衡、有服务分组和限流等完整管控能力的服务治理框架。服务分组之后,如果通用的服务崩溃了,购买链路的服务可以不受影响,这就做到了隔离。这样的一整套服务体系(如图)就构成了我们的服务架构2.0,最终网站的可用性做到了99.979%,这是今年6月份的统计数据。我们还会逐步把服务的稳定性和服务质量做到更好。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/1b8ea70b258945762a7cf1e5cedecab1.jpg"></p>    <h2>总结及下一步展望</h2>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/1da2537c4be0b1246c84f8719d8d56dc.jpg"></p>    <p>最后总结一下,服务框架的体系完善是一个漫长的发展过程,不需要一开始就很强、什么都有的服务框架,最早可能就是一个RPC框架。服务治理慢慢随着业务量增长也会发展起来,服务治理是服务框架的重要组成部分。另外,Tesla是为蘑菇街业务体系量身打造的服务框架。可以说服务框架是互联网网站架构的核心和持续发展的动力。选择开源还是自建,要看团队能力、看时机。我们要深度定制服务框架,所以选择了自研,以后可能会开源出来。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/16c7e465c8d266775536a43fdf649b34.jpg"></p>    <p>服务框架是随着业务发展不断演变的,我们有1.0、1.5和2.0架构的迭代。要前瞻性地谋划和实施,要考虑未来三年、五年的容量。有一些系统瓶颈可能是要提前解决的,每一个场景不一样,根据特定的场景选择最合适的方案。容量和性能关键字是一切可扩展、Cache、IO、异步化。目前我们正在做的是服务治理和SLA保障系统化,未来会做同城异地的双活。</p>    <p> </p>    <p>来自:http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance</p>    <p> </p>