靠谱 的软件外包伙伴

您的位置:首页 > 新闻动态 > 电商交易系统软件开发的演进之路

电商交易系统软件开发的演进之路

2016-09-09 15:30:18

大家好!我是来自京东商城交易平台的杨超,今天特别高兴能够来给大家做这个分享我是 2011 年加入京东5 年中我经历了不少技术架构的演进,也看到了不少变化这次分享首先介绍京东商城的服务、京东交易结构,然后介绍针对618备战,我们做的一些事情,以及从2011年到现在,京东交易平台经历的变化。

商城服务

如图所示是京东交易平台的一张大的渔网图。从主页面网站开始,到后面提交订单、购物车、结算页、订单中心等的整个生产过程,大体可分为三个部分。第一部分是订单提交前,就是俗称的购物车,结算页。第二部分是订单预处理部分,生成订单之后、到物流之前,还会有一些预处理过程,比如生鲜、大家电、奢侈品、易碎品等商品。第三部分是订单履约部分。今天我讲的主要内容是,交易平台的提交以前和预处理部分。

京东交易平台,包括单品页的价格、库存,购物车、促销,结算页的下单,再到订单中心线。

如下图所示,2011年京东的订单量是30万,2015年订单量就已经到了3000多万,京东的流量每年不断地翻倍。订单从30万到100万是三倍增长,实际上访问流量的翻番,可能是10倍、50倍,甚至上百倍。

比如,用户购买东西从单品页进入,然后查询很多信息,包括价格、评价。商品加入购物车后,用户会不停地比对各类商品。刷新购物车,从前端到后端所有的服务基本上都会刷新。那么,当你刷新一次,调动服务就会承受一次动态的调用。当订单量翻三倍的时候,实际服务访问量最少是要翻20倍。

我见过的京东目前最大的前端流量是,一分钟几千万,一个正常前端服务访问量是在几千万,几亿、几十亿,一天的PV。

那为了应对如此大的调动量,每年的618、双11,京东都做了什么?

下面我会详细讲618、双11备战后面,每一年所做的不同改变。这是一个整体的大概分析,我们从哪些方面做优化,去提高系统的容灾性,提高系统应对峰值流量的能力。

实际上每年京东内部的正常情况是,领导层会给出一个大概的预期值,就是希望当年的大促中,需要达到几百亿,或者几十亿的预期销售额。那么,根据这个销售额,根据客单价(电商的订单的平均价格,称为客单价)换算成订单量。

另外在以往的618、双11中,我们都会统计出订单量和调用量,即前端价格需要访问多少次,购物车需要访问多少次,促销引擎需要访问多少次,整个流程需要多大的量。有了大概的方向之后,就会把具体系统的量换算出来。第一轮会做压测,压测分为线上压测和线下压测两部分。这些都是准备工作,根据一些指标往年的增长量估算出一个预期值。

压测

这是真正进入第一波。首先,每年的大促前,都会经历半年业务迭代期,整个系统会有很多变更。我们会进行第一轮的压测系统,压测之后会知道当前线上真正能够承载的访问量有多大,距离预期有多远。压测分为线上压测跟线下压测。压测场景分为读业务和写业务,压测方案有集群缩减服务、模拟流量、流量泄洪。

讲到压测,先说说压测的来历吧。2011年时候没有线上压测,线下压测也不是很全。真正引入线上压测是在2014年,订单量已经接近2000万。之前的大促备战,是通过组织架构师、优秀的管理人员,优秀的技术人员,一起去评估优化系统,因为在迭代代码的同时,我们会知道系统哪里容易出现问题,然后对数据库、Web或者业务服务做一堆优化。

在2014年,订单量到了上千万,换算成为访问量,每天的PV大涨,集群也更大偏大,如果还是只依靠技术人员去优化,可能会不足。于是就衍生出压测,我们想知道系统的极限值。这样,当系统承受不住访问请求的时候,我们就会知道哪里出现瓶颈,比如,服务器的CPU、内存、连接速度等。我们通过第一轮压测找到第一波的优化点,开始了线上的压测。

当时第一波做线上压测是在凌晨一两点,把整个线上的流量剥离小部分机器上。把集群剥离出来,然后再做压测。所有的服务器、所有的配置就是用线上完全真实的场景去做压测,就能够得到线上服务器在真实情况,再优化。

曾经做redis压测,把进程绑定到单核CPU,redis是单进程程序,当时集群的性能就提升了5%。因为机器的每次CPU切换,都需要损耗资源,当时把进程直接绑定到固定的CPU上,让它高压下不频繁地切换CPU进程。就这样一个改变,性能提升了5%。当量很大的时候,真正底层细节的小改变,整个性能就会有很大的改进。这是我们从2014年引进线上压测和线下压测之后的一个真实感受。

压测完之后得到容量,得到交易系统的购物车、结算页大概承受值,之后会进行一轮优化,包括对NoSQL缓存的优化。京东在2012年的时候自建CDN网络,Nginx层做了很多模块加Nginx+lua的改造。应用程序层也会做很多缓存,把数据存在Java虚拟器里面。数据层的缓存,主要有redis、 NoSQL的使用,另外会剥离出一些独立的数据存储。

缓存压缩

CDN域名切换的问题,原来外部CDN切换IP,需要15-20分钟,整个CDN才能生效。我们的运维做了很多的改进,自建了CDN,内网VIP等等进行缓存压缩。Nginx本身就有介质层的缓存和GZIP压缩功能,把静态js、CSS文件在Nginx层直接拦掉返回,这样就节省了后面服务的服务器资源。

GZIP压缩能压缩传输的文件以及数据,节省了网络资源的开销(GZIP压缩主力损耗CPU,机器内部资源的平衡)。前面就直接压缩返回图片、文件系统等静态资源。流量到部署集群系统时,只需要处理动态资源的计算,这样就将动态静态分离集中处理这些专向优化。

真正的计算逻辑,服务自身的组装、如购物车的促销商品、服务用户,基本上所有资源都耗费在此。比如,连接数都会耗费在跟促销,商品,用户服务之间调用,这是真实的数据服务。如果不分离,你用DOS攻击直接访问JS,然后传一个大的包,就会完全占用带宽,连接和访问速度就会非常慢。这也是一种防护措施,在大促中会做很多缓存、压缩这些防护。

购物车从2010年就开始Java改造,整体结构的划分主体有,促销引擎、商品、用户。系统结构在2012年已经成型。到13年,加入了购物车服务的存储。原来购物车存储的商品是在浏览器端的Cookie里的,用户更换一台设备,之前加入的商品就会丢失掉。为了解决这个需求,我们做了购物车服务端存储,只要登录,购物车存储就会从服务端拿取。然后通过购车服务端存储打通了手机端与PC端等的存储结构,让用户在A设备加入商品,在另外一个设备也能结算,提高用户体验。

异步异构

2013年之后,接入了很多其他业务,如跟腾讯合作,有微信渠道,我们会把存储分为几份,容量就会逐步地放大。这是异步的存储,手机端会部署一套服务,PC端会部署一套服务,微信端会部署一套服务。就会隔离开来,互不影响。

购物车就是这么做的。购物车整个数据异步写的时候都是全量写的。上一次操作可能异步没写成功,下一次操作就会传导都写成功了。不会写丢,但是可能会有一下延时,这些数据还是会同步过来。比如,从PC端加入商品之后没有立即同步到移动端,再勾选下购物车,购物车的存储又会发生变更,就会直接把全部数据同步到移动端。这样,我们的数据很少会出现丢失的情况。

异步写的数据是进行了很多的压缩的。第一层压缩从前端开始,整个前端是一个接口串,到后面购物车服务,先把它压缩为单个字母的接口串,后面又会压缩成字节码,使字节流真正存储到redis层里面。当存储压缩得很小的时候,性能也会提高。

缓存压缩只是为提升纵向性能做的改进。后面还会进行横向异步异构的改进,购物车把移动端存储剥离出去,移动端的存储在一组redis上,PC端的存储在另外一组上。PC端和移动端是异步去写,这样相互不影响,虽然它们的数据是同步的。这是针对多中心用户所做的一些改进。

外层的异步,是做一些不重要的服务的异步,就从购物车前端看到的地址服务、库存状态服务。库存状态服务在购物车只是一些展示,它不会影响主流层、用户下单。真正到用户提交的时候,库存数据才是最准确的。这样,我们会优先保证下单流程。

接下来讲讲接单的异步。提交订单,提交一次订单原来需要写10多张表。当订单量提高到一分钟10万的时候,系统就无法承受。我们就把整个提交订单转成XML,这样只写一张表,后面再去做异步。接单的第一步,先是把整个订单所有信息存储下来,然后再通过状态机异步写原来的10多张表数据。

关于订单中心的异步异构,订单中心原来都是从订单表直接调出的。随着体量增大,系统无法承载访问,我们异构出订单中心的存储,支付台帐存储等。 异构出来数据都具有业务针对性存储。数据体量会变小,这样对整体的优化提升提供很好的基础。

这样的存储隔离,对订单状态更新压力也会减小,对支付的台帐、对外部展示的性能也会提升。大家会疑问,这些数据可能会写丢。我们从第一项提交开始,直接异步写到订单中心存储,到后面订单状态机会补全。如果拆分不出来,后面就生产不了。也就是说,到不了订单中心,数据生产不了,一些异步没成功的数据就会在这个环节补全。

然后是商品的异步异构。2013年,商品团队面临的访问量,已经是几十亿。如何去应对这个情况呢?很多商品数据贯穿了整个交易,包括交易的分析、各个订单的系统都会调商品系统。我们会针对系统优化。

比如,针对促销系统调用,促销系统主要调用特殊属性,我们把这些属性存到促销系统的特有存储。库存系统也类推。调用的特殊属性的方法也不一样。譬如大家电的长宽高这些特有属性,不像前端商品页里只是基本属性。这样就把所有的属性异构处理,针对商品纬度、商品ID等所有数据会异构一份到库存、促销、单品页,后面进行改造的时候,又将数据分A包、B包、C包。

京东的业务很复杂,有自营,又有平台数据,A包可能是基础数据,B包可能是扩展数据,C包可能是更加偏的扩展数据。这样,促销系统可能调用的是B包的扩展属性,也有可能调用的是A包的基础属性。单品页访问A包、B包,调的集群是不一样的。这样存储的容量就可以提高两倍,系统的容灾承载力也会提高。

商品原来是一个单表,后来慢慢发展成为了一个全量的商品系统,包括前端、后端整个一套的流程。异步异构完了之后,系统可进行各方面的优化,这样系统的容量也会慢慢接近预期值。然后找到系统容量的最大值,如果超过这个值,整个系统就会宕机。那么,我们会做分流和限流,来保证系统的可用性。否则,这种大流量系统一旦倒下去,需要很长的时间才能恢复正常,会带来很大的损失。

分流限流

在618、双11时候,手机、笔记本会有很大力度的促销,很多人都会去抢去刷。有很多商贩利用系统去刷,系统流量就不像用户一秒钟点三四次,而是一分钟可以刷到一两百万。怎样预防这部分流量?我们会优先限掉系统刷的流量。

  • Nginx层: 通过用户IP、Pin,等一下随机的key进行防刷。

  • Web 层: 第一层,Java应用实列中单个实列每分钟,每秒只能访问多少次;第二层 ,业务规则防刷,每秒单用户只能提交多少次,促销规则令牌防刷。