2016.03.22 丨 壹佰案例
1号店交易系统架构如何向「高并发高可用」演进
2016.03.22 丨 壹佰案例
张立刚:1号店架构部-OMS订单管理平台负责人,负责1号店订单、库存、拆单、运费、第三方平台订单等电商核心交易系统。2012年7月加入1号店,作为负责人及项目经理,主导并参与了1号店SOA治理、订单Service化、订单水平拆库&去Oracle迁Mysql、无线性能优化及拆pool、运费体系重构、库存准确率优化等重要项目,负责1号店与Tmall、百度、当当、B2B2C平台等第三方平台订单业务。
轻量级电商的架构和痛点
大家看上图,一个轻量级的电商网站应用架构就是这样的,比如说你现在想做一个电商网站,你是创业公司,两三个人开始做,估计架构就是这样的。前端有PC、App和H5,有表现层、业务逻辑层和数据访问层等。
重量级的电商网站应用架构是怎么重的呢?很简单,随着业务的扩展,业务量多了、代码量多了、数据量大了、并发量高了。1号店是一家电商网站,但严格来讲并不是百分之百的互联网公司,我们更多是业务驱动型而不是技术驱动型公司。因为技术是为业务服务的,反过来说,技术也可以驱动业务,如果我们的技术能力支持不到1号店的业务体量的增长,支持不了那么高的并发量的话,网站很容易就挂了。
一些轻量级电商网站的架构痛点是什么?
首先说它的特点,业务高速发展,业务形式多样,人员规模爆增。这里所说的人员更多是技术人员,一开始是两三个人,可能是面对面的,后来变成几十人、几百人甚至是几千人上万人的规模。当然它的请求量、并发量、数据存储量都非常的高。
痛点也很多,首先是代码耦合,一开始可能就一个人就全部搞定了。因为一开始就一、两个人,不需要那么多。但从业务端的增长来看,一、两个人进行业务支持就有问题了,业务响应慢,业务互相影响。还有在出现问题时定位很难,有的时候还会责任不清,遇到一些互相扯皮的情况,表现出来的就是系统不稳定。
表结构也非常混乱,今天来一个业务加一个,明天来个业务又加一个。数据库单点,也是我们非常大的痛点,一旦数据库挂了系统就彻底崩溃了。
还有监控预警的问题,这是一个非常大的痛点。我们知道类似支付宝红包、微信红包等的监控预警甚至是秒级的,分钟级确实是不够用,不可能说发一个红包,1分钟还没有抢到,用户会一直不停地戳屏幕,对系统带来的压力反而更大。
所以,这个时候我们就需要做一些扩展性的工作。最简单的就是按业务扩展,一个小公司大概到十个、几十个人左右,大概会做这样的工作。如网站上的表现层,一开始页面代码都耦合在一起,然后我们会把详情页、搜索页、团购区分,包括域名也会区分开。慢慢的业务多了之后,会把普通购物、团购、虚拟业务等剥离开来,从数据库层来说,慢慢会把产品、用户、订单及其他一些业务数据等都进行分拆,主要是从物理上隔离开来。这个时候总体来说,架构本身变化不大,只是简单的切分一下。当然这个时候产品、用户、订单等这个层面的划分要有一定的规则和边界。
电商网站演进之路
一个小网站变成一个大网站是一个架构演进的过程。举例子来说,现在有一个老师,他讲课的时候有很多学生都在同一个教室,这边是一年级的,这边是二年级的,这边是三年级的,先给一年级讲课二三年级的自己复习做功课,再给二年级的讲课一三年级的复习做功课,在前期还能忙过来。但到了一定的时候,学生开始多了,教室坐不下了。业务也多了,我不光教语文、数学,我还要教体育、音乐、美术了,这个时候学校的规模大了。学校的规模大了之后,相关的配套设施就要跟着上去。这个时候要有校长、主任、班主任、音乐英语老师...有后勤的,甚至是保洁阿姨,甚至还有食堂了,和大学一样了。
这个例子可以类比我们的架构变化。首先是业务变化,这相当于课程的变化;第二,架构变化,就相当于学校的规模大了之后,相关的配套设施都要跟上;第三个就是人员扩大了,就是我们的开发维护人员多了,这个时候我们不可能还像以前那样,几百号人在那里弄一个工程,你必须要进行分割剥离。
那么我们怎么把它做大?没有别的路,就是拆分扩展,而且扩展不能是简单的横向扩展,不能说来一个业务就简单地按业务来拆。因为可能一个业务量就非常的大,比如说我们的团购体量就非常大,仅仅团购业务的订单量已经很大了,自己本身不能再扩了那怎么办?从我的总结来说就是拆,把大的拆成小的,不断地拆。包括现在提到的微服务,当然现在微服务还没有明确的定义。这和我们的SOA架构是分不开的,只不过是一种形式而已。
现在IT界有很多的概念,就好像敏捷一样,两个星期做一个迭代,以前是一个项目做好几个月。一开始我不太理解什么是敏捷,有什么变化呢?我觉得大概就是把大的拆成小的,以前是做两个月的,我们把它分成N个两个星期的,在两个星期的迭代当中,你该做的还是逃不了,你还是要做需求分析,做设计,做开发,做测试,做上线。这个过程没有变,只是说把任务给拆分小了。
在拆的时候,从表现层来看,可以从UI展示和UI逻辑上进行拆分。在逻辑层上,比如说你要提交订单,在提交订单后面的操作包括有订单的服务、接口,这是我们的后端业务逻辑控制层。拆的时候,原来业务逻辑和数据访问都在一层,大家知道,以前在JSP上可以在页面上直接去连数据库进行DB操作。 我们从业务逻辑层进行拆分,拆分成控制层和Service层及数据库操作层。Service层就涉及到刚刚讲到的微服务,而Service层也分为复杂的Service和简单的Service。基础Service层相对来说业务逻辑比较单一,但是又相对比较完整,聚合Service则包括了完整的业务逻辑。
我们的技术团队也是从一个小团队发展到上千人的规模,网站也经历这样一个变革,刚开始的时候就是一个简单的MVC架构,后来这个架构不适应业务的发展和人员规模了。在三五百人的时候我们还是那个架构,很多人在维护一个大的工程项目,经常出问题。
而且刚刚开始的时候是没有无线端的,无线端也是近几年才开始做起来的。
后来要做无线端,怎么办?把PC端的代码包复制一份给无线端用,这个时候问题就不断的来了,因为PC端的逻辑不可能完全符合无线端的要求。
架构演进的准备工作
这样我们开始做比较大的架构层面的规划,最大的一个就是业务逻辑层的拆分-SOA服务化,另一个是DB层的水平拆库。拆分之后,理论上它就具有了无限的扩展能力,比如说订单库,我可以把它按照一定的维度,去拆成很多的订单库。
拆分和解耦是分不开的,一方面是代码和业务的解耦,另一方面对人员和工作来说也是一种解耦。同样还要做一些异步工作,因为以前业务特别重,下单流程非常长,操作步骤非常多,但是很多东西并不是用户都要马上关注的。比如说下单给用户送积分,这个积分并不是说下单的时候必须马上就要给到客户的,可以稍微延迟几秒甚至是几分钟,它不应该影响下单业务。但是如果因为积分出现问题,导致下单出现问题那就是本末倒置了。因此我们做了异步,它挂了,我们可以做补偿。
当然有一些是不能做异步的,比如说积分兑换商品的订单,下单时要去扣积分,因为积分就是钱,我们还有礼品卡支付,这个是不能做异步的。我记得以前有过一个例子,好像是一个网站用积分可以充话费,结果话费充成功了,积分没扣,就变成了可以无限的充。现在是互联网时代,信息散布的很快,据说一两个小时就是几个亿的损失。还有读写分离,读和写是可以完全分开的。为了保证下单的流畅,我们把读和写分开,在不同的库里进行读和写,这样可以很大地减轻下单压力。
核心Service规划
这张图是我们核心Service的规划。大家想象一下一个电商网站有哪些基本特点,哪怕你的网站上只有一个页面一个商品,比如说你只卖苹果手机或者是小米手机,网站就一个页面显示这个商品,你点商品就可以直接进行购物。首先它要有商品的描述信息,第二是商品的价格,第三是商品的库存,当然库存数字你可以不显示,有就卖,没有就不卖,以上这些是商品基本的信息。
有了商品的信息,如果你想买这个商品,你要先注册用户,注册用户之后要登陆,这是用户信息;然后你就可以下单了,生成一个订单,订单之后是对订单进行相应的物流信息跟踪。同时也可以做线上支付,当然你也可以只做货到付款不做线上支付。
这是一个电商网站最基本的核心要素。一个是产品,第二个是用户&支付,再就是订单。这三大块构成了一个电商网站最核心的三个部分,这就是我们核心Service的架构规划,抓住核心是服务化的重要理念。从Service角度来说,产品服务、价格服务相对来说比较简单和单一,对产品的价格来说用户主要是查;但是对订单来说,我们把库存放到订单这个层面来,而不是放到产品上,是因为库存和订单是息息相关的,生成订单的时候要扣库存的,库存不足的话,订单是无法完成的。
刚开始的时候我们没有服务化,订单有两个问题:第一个是写,生成订单。写的业务是很多的,比如说加购物车生成订单,充值是一种业务的订单,电子卡是一种业务的订单,都在写;第二个是读,订单写了之后,用户要来读、后端的客服商家也在读。
读不是简单的仅仅读订单表,还有很多其他的关联表。比如说抽奖有抽奖系统有自己的很多表,抽完奖给用户发一个奖品,就生成一个订单,但抽奖系统要知道哪一个用户是通过什么样的方式获得这个订单、订单里有什么东西,必然要关联查询。所以订单查询是非常复杂的,有无数的点可以查,而且这个查询一定是有无数张表可以关联查的,甚至是跨表、跨库的。
那么Service怎么做呢?如果说我把所有的订单相关表都关联起来,都纳入到Service范围的话,基本上可以把70%-80%的业务都纳入其中了。因为几乎所有的业务都要围绕订单来转,所以Service化一定要有一个边界。边界是什么?比如说我刚刚说的,你抽奖的信息我关不关心呢?如果说我关心的话,你的抽奖业务就纳入进来了。这就是一个边界,所以我只能只关心我的订单,这就是订单的边界。
上图这句话我觉得说的非常好。一个出色的演讲一定要很短,一定也要很长。这对我们来说是非常有意义的。这一块来讲怎么样很短,怎么样又很长?
回到核心Service的规则。既要很短,又要很长,比如说订单的生成,它有很多的业务,要给客户积分,要生成订单表数据,要把抵用券扣掉,还有相关的支付信息,这些业务是不能剥离开来,必须要融合在一起,这当中要包含所有的能想到的业务场景,因此在订单生成业务上,我们要做到足够长,把所有的业务都包含进来。有一些是需要同步的,有一些是需要异步的,但是无论是同步还是异步的,我们都要纳入进来。
但对查询来说,业务是可以分开的,比如商品详情页,可能先是调商品的基本信息Service,然后再去调价格信息Service,然后再调用库存服务Service,生成一个页面需要很多的服务,这些服务可以是各自独立的,所以在查询Service上,我们可以做的很短。价格、库存等都可以做成独立的业务Service单元。
当然独立并不一定是最简单,它也要有自己完整的业务逻辑。比如库存并不是简单地看库存表里的数字是大于0还是小于0的问题,比如是说某一个地方销售不销售,或者说我们尽管有库存,我们现在是不卖它的,这些都是库存,库存不是一个简单的数字,如果说这个商品暂时不卖,我就显示说无库存,但是我的仓库里是有实物库存的。
我理解所谓的微服务,在底层这一块更多像微服务,微服务是不是拆分的越多越好,也不一定。比如说库存,如果看这个商品有没有库存,首先你调一个服务看这个商品是不是在这个区域里卖,再调一个服务看商品是不是上架,再调一个服务看库存是不是大于0,那就太多了。
举个例子,打开一个详情页会调用很多的服务,类目信息、商品信息、价格信息、库存信息、评论等等。这么多服务,怎么保证性能呢,如果拆的非常细的话,仅一个库存服务就可以拆成7、8个子服务,这样的话服务就要调七八次,网络交互也是七八次,单个Service性能再好又怎么样呢?哪怕你的服务性能达到1毫秒,够快了吧,你调用10次要10毫秒,调用100次要100毫秒,你的性能还是在下降,所以并不是越微越好,这个长和短的粒度要划分好。记住两个关键词:边界和粒度。
订单水平拆库
接下来我们谈谈数据库。我们最早用的Oracle,很庞大,支持的量也很大,一般的业务量是没有问题的。但是什么情况会出问题呢?一个是单点故障,数据库一挂了,整个网站就全挂了,另外不支持水平扩展,包括它的存储、性能、数据量,Oracle再厉害,它不可能几百亿、几千亿的数据都放进去。所以我们后来选择了Mysql,对数据库进行了水平拆分,这样的话单点故障率会小一点,这么多的物理数据库,挂一个,其他的还可以运行,不至于影响全局。同时做了水平拆分之后,扩展能力非常强,从理论上来说可以无限扩展,因为它无非就是加服务器,你只要加一些硬件就可以了。
那么水平拆分怎么拆?要考虑哪些因素?
比如说订单,你第一要考虑业务场景,查询订单是哪些用户:其一是前端的用户;其二是后端的用户商家和客服。
第二,它的存储量,订单的数据量是非常大的。但对商品和库存来说,它是有一定的范围的,不会无限的大,因为一个网站或者一个商店,你卖的SKU数量是有限的。一个大超市可能是几万个SKU,一个小门店可能是几百个,它不会无限扩展的。
数据增量也是如此,一个大超市卖的SKU也就是几万个,电商平台可能是百万级千万级,但是它也不是无限增长的,这更多取决于商家的体量,所以它的数据量即使有增长也是非常缓慢的。这和订单不一样,订单是几何式的增长。
再看读和写,订单、库存的读和写频率都很高。但是对于商品、价格来讲,读肯定是很高的,因为不停地在浏览,但是写是很少的,改价格的机率很低,不停地改商品信息的机率也是很低的。
另外是事务的一致性。对于订单和库存一定是要保持一致性,商品信息写的话比较少,不太涉及到事务,除非是批量修改,相对来说事务性一致性稍微弱一些。
还有缓存,库存可以有缓存,但是缓存的时间是很短的,库存的缓存时效不可能是以天、以小时为级别的,几分钟级别已经是不错了。很多时候前端显示还是有库存,后面可能已经没有了,所以库存有时效性的要求。但为了减轻数据库压力,在前端展示会有库存的缓存,比如有时候大家会遇到,在浏览的时候发现它是有库存的,但是下单就没有了,那就是因为前端是缓存的,但是下单的是实时的库存,已经没有了。但对商品和价格信息来说,缓存时效就可以长一些,可以通过缓存技术减轻数据库的压力。
热点数据也是一样的。数据库的水平拆分怎么拆,从哪些维度去拆,比如说订单,可以有几个维度,你可以根据订单号去拆,根据产用户、商家去拆。对响应速度来说,用户要求响应速度是最高的;而对商家来说,用户下完订单之后,稍微延迟一会儿他也能接受。
如果按照用户去拆,热点数据的概率就很低,很难出现一个用户一下子出现几千个几万个订单;但是如果按对商家来拆,有一些大的商家,一个双十一可能几个小时就有上千万的单,这个量就非常大。
而对商品信息来说,如果说你的量没有像天猫、淘宝级别的话,并且主要是靠缓存来读,一般的电商网站,是不需要拆分的。
拆库时怎么做压测
在做订单水平拆库的时候,不可能网站停下来去做这个项目,所以我们说是飞机开的时候换发动机,在汽车跑的时候换轮胎。我们在做数据库拆库的时候要做压测。怎么做压测呢?
Oracle改Mysql的时候,当时我们对性能是没有绝对的信心的,因为Mysql的健壮性没有Oracle强大,有一次一个badsql直接把我们的一个mysql数据库给搞挂了,对性能要求特别高,但是在业务层,我们很难去模拟。我们可以在Service或sql上一步一步的分析,一步一步的优化,但是毕竟有很多业务场景是模拟不出来的。
当时我们做了Tcpcopy压测,原理就是把线上请求的包抓下来,放到测试环境中,测试的数据库尽量保持和线上一致,保持环境一致。压测会动态调整流量,把原来的流量比如说一小时千万级的提升到亿级的,提升了很多倍,主要是测试看能不能把数据库压垮,会不会出现问题。
当然这个场景也不可能完全覆盖我们的现实应用场景,因为在线上抓包的时候,我们抓了一天,但这一天中数据库的数据是不断变化,不断有insert和update,而线下的测试数据是一个静态的数据,所以还有一些业务场景我们是模拟不到的。因此模拟结果和线上还是有一定的差距,但还是给我们吃了一颗很大的定心丸。
SOA中间件
我刚刚说了两个,一个是Service化做了技术架构上的拆分,一个是做了数据库的水平拆分。这是刚刚提到的准备工作,Service化和水平拆库的同时,我们的很多中间件技术也发展起来了,因为你的量上来了、架构调整了,配套设施也要上来,不是说简单的教室一拆分就完了,学校没有保安,要上体育课没有操场是不行的,因此没有相应的中间件没有是不行的。
SOA中间件本身也是一个有意思的发展,包括分布式服务SOA中间件、数据库中间件、缓存平台、消息中间件、任务调度中间件和全局配置中心等。日志和监控系统也非常有必要,这都是系统稳定的基础。
还有实时的分析系统,比如说双十一,大家都关注着淘宝的数字,那个数字是怎么出来的,一定是实时出来的,你不能说到了第二天才告诉人家前一天晚上1点的时候是什么样的数据,一定是刚过1点就马上就都出来了。
同样还要做灰度发布,什么叫高可用,就是不出问题系统一直处于可用状态。但我们还要发布啊,发布的时候怎么办,所以灰度发布的价值就体现出来了,有了它我们的系统就有了100%可用的理论可能。
这是我们的一个简单的架构图。提交订单的时候,可以同步也可以异步提交,异步走的是秒杀系统,它不是提交之后马上生成订单,而是要有排队系统进行排队的。我刚刚在前面还说过负载均衡,我们开发了自己的SOA中间件做负载均衡,它有自己的逻辑控制,购物流程到到订单服务是通过SOA中间件做负载均衡和调用的。
同时我们还有数据库中间件,我们和数据库的交互怎么办?一个订单查询,如何定位到它所在的数据库。如果是根据用户维度拆库,用户来查询马上可以定位到相应的数据库,但是商家来查询怎么办?他的订单可能是覆盖所有的数据库,这个时候需要做一些聚合、排序。这就要通过数据库中间件,它对前端是透明的,它去做一些排序等,应用层只须常规的写自己的sql就行了。
同样,我们还有消息中间件,比如前面提到的下单后送积分,就可以通过消息异步处理。
这是我们的核心交易架构,我们如何让它更完美一些,怎么让它的稳定性更高一些?我们有前台用户,前台用户作为普通消费者去下单、查询,同时也有很多后台的操作。
比如对消费者来说,下完单后要做支付,当然他可能会订单取消,要把订单变成取消状态;再一个他会修改收货地址,也就是这些简单的几个update操作。而后端的,运营也好,客服也好,对这个订单是有很多的操作的。可能还有审核系统,还有发货、出库等等的系统都要对订单进行操作,因此我们后端的反而是更复杂的。后端的操作必然会影响到数据库,如果不注意也会出现很多的问题,把数据库夯住了,影响了前端的交易。
Service层可以不断的拆,但从用户层角度来说,还是要考虑前端和后端的拆开。比如代码可以前端一套,后端一套,把它物理隔离开。我们主要目的是保前端的交易,后端系统稍微延迟一点没有问题的,但是大家看到,前后端代码物理上虽然隔开了,但是DB还是在一起,后端写的代码把数据库搞挂了,前端还是照样挂,这是一个很大的问题。
多活机房架构
最后说一下多活,多活的架构比较大,可以专门作为一个主题来,我这里只是给大家引申一下。
刚刚讲到,前端用户、后端用户尽管代码什么的可以隔开,但是DB这一层还是在一起。因此我们也要想办法把DB分开,但这个时候,两个DB的数据要保持同步,当然我今天说的只是一个思路而不是解决方案。
这是我在网上搜来的一张双活的图,想想双活和多活其实是一样的。我们可以有不同的机房,也可以在一个机房内部有多个独立的单元,多个机房或单元物理独立。这样的话,一定要有一个统一的数据中心,这两个数据要同步,因为前端用户下完单之后,商家在看订单的时候,不可能要看这边的订单到这个系统,要看那边订单到另外一个系统去看,因此必须要有数据中心。如果说我们有多个机房,可能是三个五个,像淘宝的级别就是非常多的机房。我理解它一定要有一个数据中心把数据汇总起来。
当然我这是从应用层来说,从应用隔离的角度去看的。多活的目的不是简单的隔离,它考虑的是一旦发生地震、灾害等如何保证不出问题,这个时候数据中心对多活也是必要的。
但数据中心我是不是可以作为后端应用来使用呢?后端的应用走数据中心,因为它对数据的实时性要求相对不是特别高,而前端只保证核心交易业务,后端保证非核心交易业务,这是多活应用架构拆分的思路。
本文根据TOP100Summit旗下技术沙龙品牌into100沙龙第17期录音整理原创首发,转载或节选内容前需获授权。同时,也欢迎更多企业、社区与TOP100公众账号展开内容合作,更欢迎您成为原创作者。更多内容合作请发邮件至wow@top100summit.com,我们期待认识你:)