去哪儿网第三方支付系统架构构演进

实战案例解析:去哪儿网支付系统架构演进全历程
  作者|吕博
  编辑|小智
  本文将为你详细讲述去哪儿网的支付系统架构演进的全历程,这中间的设计思路是怎样的,踩过怎样的坑,做过哪些可供参考的改进,各种经验分享都在这个实战案例里!
去哪儿支付系统自2011年搭建以来,在五年的时间里逐渐从一个高耦合的单一系统发展为众多子系统组成的高并发、高可用、支持多种交易支付业务的分布式系统。业务从最初的非代收到现在多种非代收、代收场景的支持,B2B业务的从无到有,支付方式从单一网银支付到现在银行卡、拿去花、代金券、红包、立减、积分、趣游宝等多种的组合,订单从单笔支付到多个订单同时支付和多次付款。下面对整体的演变过程进行简单的介绍。
  支付系统1.0
新的业务系统初建时,业务逻辑相对简单,业务量也比较小,为了能够快速实现功能,发布上线,大多数团队都会把所有的逻辑都耦合在一个系统。这对于初期业务的快速迭代是有一定好处的。毫不例外,支付交易系统也采用了这样的方式。如下图所示。
一个支付系统不例外包括几个重要组成部分:收银台、交易、支付、网关、账务。
收银台:用于展示支付详情、提供各种多样支付方式的选择
交易:收单规则和交易规则处理
支付:处理各种组合的支付方式,如银行卡、用户余额、信用付、拿去花、红包、代金券、立减、积分等
账务:用来记录所有交易、资金往来的明细,财务会计记账
网关:用于对接银行通道、第三方支付通道(微信、支付宝)
在业务量不大的情况下,这样的系统结构没有问题。随着更多业务的接入,各种复杂的功能逻辑加入,系统处理起来有点吃力,主要表现以下几个方面:
系统容灾能力:所有的功能都集中在一起,一但某个功能出问题,直接影响全局
系统扩容:在一个分布式系统中,决定系统性能的取决于最差的部分,整体扩容效果差
开发成本高:团队成员的增加,功能的复杂,多个项目并行时,开发效率极低
更多更复杂业务:结构不合理,不能满足业务发展需要
系统职责混乱:如收银台只是简单维护银行列表
在这样的一些背景下,2.0系统应运而生。
  支付系统2.0
2.0时代是支付交易系统快速发展的一个重要时段。在此过程中,不仅要从系统架构上进行服务化的拆分,而且需要支持更复杂的业务。
  服务化拆分
  网关拆分
首先对相对比较独立的网关进行拆分,网关在整个支付系统中属于底层基础服务,是比较重要的基础设施。对外能够提供怎么样的支付交易服务,很多都取决于网关能力的建设。
网关有一些显著特征,它是一个可高度抽象的业务。对外可以抽象到支付、退款、查询这些标准的服务。因此优先将这部分拆分,一是为了能够更好的打好基础,二是其能够独立的发展,三是这部分也相对好实施。
网关的拆分路由系统起到至关重要的作用,对于多通道支付的支持和智能化选择发挥着巨大作用。
  账务系统的拆分
做交易支付业务,重要的一件事要记清楚账。记账可以很简单的记录来往流水,也可以更加专业的记财务会计账。在拆分前系统只是记录了交易流水,拆分后实现了更加专业和复杂的复式记账。
新账务系统的一个简单流程图:
  会员系统的独立
会员系统与交易系统本身只是一个依赖关系,在交易支付系统看来只是一个业务系统。比如会员充值业务可以看做是一笔支付交易。为了摆正各自角色,对于会员部分从原有系统中独立出来。这样一来各自定位更加清晰明了,也方便了各自独立发展。现在的会员系统不仅仅只有一个余额,而且引入实名服务、各种资产管理、交易管理等。
  基础服务的拆分
更多的系统拆分独立后,原有公用的某些功能会多次复制重复。为方便集中管理维护,通过对各系统公用逻辑更能的统一,提供集中的基础服务,如安全服务、加验签服务、通知服务、基础信息查询等,如下图中talos系统。
上述几个服务的拆分更多是为从业务方面或者技术驱动来考虑。而典型的交易支付过程是有一个时序过程的。比如下单-&交易-&收银台-&支付-&网关-&银行。这样一个先后时序也是一个比较好的系统拆分方案。根据这样的一个时序,我们针对性的对每个阶段做了拆分(排除网关和银行部分),如下过程:
1、交易核心(Apollo)
关注于收单方式和交易类型。
收单方面系统已经支持单笔订单支付、批量订单支付。交易类型目前支持直接交易、担保交易、直接分账交易、担保分账交易、预授权交易等。在批量订单支付时各种交易类型可以进行混合。且分账交易同时支持多个账户。交易类型除了上面正向交易外,系统还支持很多后续流程交易、如预授权确认、预授权撤销、退款、担保撤销、二次分账交易等。
多种多样的交易源于各事业部业务的复杂性,比起标准化的支付系统,我们提提供了更多灵活方便的业务来支持。
2、支付核心(minos)
关注于支付方案的组合和执行。
支付方式:银行卡、支付宝、微信、拿去花、趣游宝、余额、积分、红包、代金券、会员红包、立减等多种方式支付。
支付组合:可以单一使用,也可以进行组合使用。组合场景区分资金类型,如银行卡、支付宝、微信每次只能选择一个,其它类资金可多个同时使用。
在有上面基础的支持下,对于同一批次交易订单可也进行多次的组合支付扣款,如酒店信用住付款、拿去花还款等业务场景。下图是支付核心(minos)在系统中的位置:
收银台直接面向用户,因此支付体验至关重要。据统计在支付环节放弃的订单占比还比较大。因此一个方便、简洁易用的收银台对于订单转换是有很大帮助的。目前系统支持的收银台主要有app(native)、app前置收银台、touch、PC预授权收银台、PC多单收银台、PC英文版收银台、PC标准收银台等。收银台在系统中的位置如下图所示。
无线端收银台:
PC端收银台:
4、API接入层
交易系统更多的服务是通过后台接口来完成的,这部分占到整体系统很大的业务比重。如支付后期的资金流转、逆向操作退款等。但也有一些是用来查询一些交易订单相关性的信息。在此背景下,对于api接入层采用读写分离方式处理。如下图ares系统,将底层的各dubbo服务包装提供各种查询类服务。Odin系统是可读写,更多的关注跟核心业务相关的写,如解冻、退款、撤销等。
截止目前,整体系统的一个大体结构如下图所示:
  服务化拆分带来的挑战
服务化拆分后,在系统结构上更加清晰了,但对于整体系统的开发管理和日常运营带来更大的挑战。比如下几个方面:
  如何提高开发效率
系统拆分后主要提供dubbo服务和对外http(https)服务
1.针对Dubbo服务的约定
接口定义:粒度控制、边界控制。一个接口不能存在模棱两可的情况,只做其一
参数标准:复杂接口使用对象做参数(避免map)、统一父类、支持扩展属性透传、提供create/builder构造合法参数、使用枚举限制参数范围。有效避免调用端参数错传
返回值:统一QResponse封装、错误码管理(非数字形式含义明确、按业务区分避免重复等)
业务模板:定义标准业务处理流程、标准化异常处理
接口文档化:定义好接口后,通过注解动态生成接口文档
2.针对http服务的约定
a)接口参数:command、校验器、参数类型配置化。
command中定义接口信息,包括请求返回参数、每个参数的参数类型、参数的校验器、参数类型的校验器。校验器可以组合使用,也可以自定义实现扩展。如下示例:
  Command定义:&commands&&command name="forex_queryExchangeRate"& &cnName&汇率查询接口&/cnName& &version&&/version& &desc&查询本币和目标币种汇率&/desc& &request& &param name="localCurrType" required="true"& &validator id="CURID"/& &/param& &param name="targetCurrType" required="true"& &validator id="CURID"/& &/param& &/request& &!-- 返回参数部分 --& &response& &param name="localCurrType"& &cnName&本币&/cnName& &required&true&/required& &/param& &param name="targetCurrType"& &cnName&目标币种&/cnName& &required&true&/required& &/param& &param name="sellingPrice"& &cnName&卖出价&/cnName& &required&true&/required& &/param& &param name="buyingPrice"& &cnName&购买价&/cnName& &required&true&/required& &/param& &param name="rateTime"& &cnName&汇率时间&/cnName& &required&true&/required& &/param& &/response&&/command&&/commands&校验器:&validators&&validator id="CURID" type="Regex"& &pattern&^[A-Z]{3}$&/pattern& &/validator&&/validators&参数类型:&paramTypes&&paramType name="merchantCode"& &cnName&商户号&/cnName& &desc&用来区分不同商户&/desc& &type&java.lang.String&/type& &example&testbgd&/example& &validator type="Regex"& &pattern&^[A-Za-z0-9]{1,20}$&/pattern& &/validator& &/paramType&&/paramTypes&
b)并发控制
在某些操作场景下,对于并发写会有一些问题,此时可以通过依赖cache加锁来控制。比如通过在接口增加注解来启用。可以指定接口参数来作为锁的lockKey ,指定锁失效时间和重试次数,并指定异常时(lockGotExIgnore )的处理方案。
  @RequestLock(lockKeyPrefix = "combdaikoupay:", lockKey = "${parentMerchantCode}_${parentTradeNo}", lockKeyParamMustExists = true, lockKeyExpireSecs = 5, lockUsedRetryTimes = 0, lockUsedRetryLockIntervalMills = 500, lockGotExIgnore = false)
c)流量控制
流控目前分两种:qps、并行数。
qps分为节点、集群、接口节点、接口集群。通过对每秒中的请求计数进行控制,大于预设阀值(可动态调整)则拒绝访问同时减少计数,否则通过不减少计数。
行数主要是为了解决请求横跨多秒的情况。此时qps满足条件但整体的访问量在递增,对系统的吞吐量造成影响。大于预设阀值(可动态调整)则拒绝访问。每次请求结束减少计数。
d)安全校验
接口权限:对接口的访问权限进行统一管理和验证,粒度控制到访问者、被访问系统、接口、版本号
接口签名:避免接口参数在传递过程中发生串改
e)统一监控
包括接口计数、响应时长和错误码统计三个维度
f)接口文档化
依赖前面command、校验器、参数类型配置进行解析生成
  如何管理多个系统?
接口监控模板化:http、Dubbo多系统统一模板,集中展示管理。
组件可监控化:Redis/Memcache、Mybatis 、Lock 、QMQ 、 EventBus 、DataSource 、JobScheduler
监控面板自动化生成:Python自动化生成脚本,新创建系统只需要提供系统名称和面板配置节点即可生成标准监控面板
系统硬件资源、tomcat、业务关键指标可视化监控
  如何高效日常运营?
对于各个场景的关键流程进行格式化日志输出,集中收集处理。如orderLog、userLog、cardLog、binlog、busilog、tracelog、pagelog...
  服务化拆分过程中DB处理
随着业务量增加,单表数据量过大,操作压力大。因此分表势在必行。常用的分表策略如按照时间来分表,如月表,季表,按照某个key来hash分表,也可以将两种结合起来使用。分表的好处是可方便将历史数据进行迁移,减少在线数据量,分散单表压力。
  分库、多实例
多库单实例,多业务单库。部分业务存在问题会影响全局,从而会拖垮整个集群。因此在业务系统拆分后,db的拆分也是重要的一个环节。举一个例支付库拆分的例子。支付交易的表都在同一个库中,由于磁盘容量问题和业务已经拆分,因此决定进行拆库。稳妥起见,我们采用保守方案,先对目前实例做一个从库,然后给需要拆分出来的库创建一个新的用户U,切换时先收回U的写权限,然后等待主从同步完成,,确定相关表没有写入后将U切到新的实例上。然后删除各自库中无关的表。
  读写分离、读负载均衡
很多业务读多写少,使用MMM结构,基本上只有一台在工作,不仅资源闲置且不利于整体集群的稳定性。引入读写分离、读负责均衡策略。有效使用硬件资源,且降低每台服务器压力。
a)读写负载均衡
b)多动态源
c)多库动态源读负载均衡
  异步化使用
servlet3异步:释放出http线程提高系统整体吞吐量,可隔离开不同业务的工作线程
qmq:使用最广泛也更灵活的异步
dubbo:对于服务提供者响应比较慢的情况
servlet异步和qmq结合的场景如下图所示。流程为http服务接到组合扣款请求,然后向后端交易系统下单并发起扣款,此时http服务进入轮询等待,根据轮询间隔定时发起对放在cache中的扣款结果查询。交易系统则根据扣款规则以qmq的方式驱动扣款,直至走完所有流程为止(成功,失败,部分支付)。每次扣款结束将结果放入cache中供http服务查询。
轮询式场景如上图中使用,关键在于确定轮询间隔
  监控&报警
  Java监控模块
嵌入在应用中,指标数据可灵活配置发送方式到多个地方。也支持api接口直接拉取数据
  离线监控框架
python监控脚本框架,从db、java模块api、redis等获取数据,计算指标并发送
整体架构可插件化、有通用标准功能、也可定制化开发
指标可直接推送至watcher(dashboard)系统添加监控页
报警方式有mail、sms、qtalk
python监控脚本框架主要包含四个重要组件:
metric_manager:指标管理器
graphite_sender:指标推送
Dbpool:数据库链接池管理
Scheduler:调度器,定时执行指标数据获取
  数据流系统
采用xflume、kafka、storm、hdfs、hbase、redis、hive对业务日志、binlog等实时收集并处理。提供业务日志、订单生命周期日志、各种格式化日志的查询和一些监控指标的计算存储和报警。整体大致流程如下图所示:
业务和系统结构复杂后报警尤为重要。甄别哪些指标是必须报警的和报警阀值的确定是个很复杂的问题。一般有两种情况:一种是明确认为不能出现的,另一种是需要一定计算来决定是否要报警。当然有些基础层的服务出现问题,可能会导致连锁反应,那么如何甄别最直接的问题来报警,避免乱报影响判断是比较难的事情。目前针对这种情况系统会全报出来,然后人工基本判断下,比如接口响应慢报警,此时又出现了DB慢查询报警,那基本可以确认是DB的问题。
A、明确失败报警
日志NPE、业务FAIL、系统ERROR、Access (4xx5xx)、接口异常、dubbo超时、fullgc、DB慢查询等
B、计算类报警
调用量特别小,波动明细,没有连续性,不具有对比性
期望值:如下图所示,当前值与期望值偏差加大
  写在最后
截止目前交易支付系统从收银台、交易、支付、网关、账务、基础服务、监控等各个模块的拆分并独立完善发展,针对高复杂业务和高并发访问的支撑相比以前强大很多。但还有很多不足的地方有待提高和完善。
继续期待交易支付3.0……
本文系Qunar技术沙龙原创文章,已经授权InfoQ公众号转发传播。
  作者介绍
吕博,去哪儿网金融事业部研发工程师,毕业于吉林大学,2012年加入去哪儿网。致力于支付平台研发和支付环节的基础服务建设。
  今日荐文
  点击下方图片即可阅读
  腾讯集团副总裁姚星谈AI:真实的希望与隐忧
责任编辑:
声明:本文由入驻搜狐号的作者撰写,除搜狐官方账号外,观点仅代表作者本人,不代表搜狐立场。
今日搜狐热点2009人阅读
架构(62)
轻量级电商的架构和痛点
大家看上图,一个轻量级的电商网站应用架构就是这样的,比如说你现在想做一个电商网站,你是创业公司,两三个人开始做,估计架构就是这样的。前端有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,而线下的测试数据是一个静态的数据,所以还有一些业务场景我们是模拟不到的。因此模拟结果和线上还是有一定的差距,但还是给我们吃了一颗很大的定心丸。
我刚刚说了两个,一个是Service化做了技术架构上的拆分,一个是做了数据库的水平拆分。这是刚刚提到的准备工作,Service化和水平拆库的同时,我们的很多中间件技术也发展起来了,因为你的量上来了、架构调整了,配套设施也要上来,不是说简单的教室一拆分就完了,学校没有保安,要上体育课没有操场是不行的,因此没有相应的中间件没有是不行的。
SOA中间件本身也是一个有意思的发展,包括分布式服务SOA中间件、数据库中间件、缓存平台、消息中间件、任务调度中间件和全局配置中心等。日志和监控系统也非常有必要,这都是系统稳定的基础。
还有实时的分析系统,比如说双十一,大家都关注着淘宝的数字,那个数字是怎么出来的,一定是实时出来的,你不能说到了第二天才告诉人家前一天晚上1点的时候是什么样的数据,一定是刚过1点就马上就都出来了。
同样还要做灰度发布,什么叫高可用,就是不出问题系统一直处于可用状态。但我们还要发布啊,发布的时候怎么办,所以灰度发布的价值就体现出来了,有了它我们的系统就有了100%可用的理论可能。
这是我们的一个简单的架构图。提交订单的时候,可以同步也可以异步提交,异步走的是秒杀系统,它不是提交之后马上生成订单,而是要有排队系统进行排队的。我刚刚在前面还说过负载均衡,我们开发了自己的SOA中间件做负载均衡,它有自己的逻辑控制,购物流程到到订单服务是通过SOA中间件做负载均衡和调用的。
同时我们还有数据库中间件,我们和数据库的交互怎么办?一个订单查询,如何定位到它所在的数据库。如果是根据用户维度拆库,用户来查询马上可以定位到相应的数据库,但是商家来查询怎么办?他的订单可能是覆盖所有的数据库,这个时候需要做一些聚合、排序。这就要通过数据库中间件,它对前端是透明的,它去做一些排序等,应用层只须常规的写自己的SQL就行了。
同样,我们还有消息中间件,比如前面提到的下单后送积分,就可以通过消息异步处理。
这是我们的核心交易架构,我们如何让它更完美一些,怎么让它的稳定性更高一些?我们有前台用户,前台用户作为普通消费者去下单、查询,同时也有很多后台的操作。
比如对消费者来说,下完单后要做支付,当然他可能会订单取消,要把订单变成取消状态;再一个他会修改收货地址,也就是这些简单的几个update操作。而后端的,运营也好,客服也好,对这个订单是有很多的操作的。可能还有审核系统,还有发货、出库等等的系统都要对订单进行操作,因此我们后端的反而是更复杂的。后端的操作必然会影响到数据库,如果不注意也会出现很多的问题,把数据库夯住了,影响了前端的交易。
Service层可以不断的拆,但从用户层角度来说,还是要考虑前端和后端的拆开。比如代码可以前端一套,后端一套,把它物理隔离开。我们主要目的是保前端的交易,后端系统稍微延迟一点没有问题的,但是大家看到,前后端代码物理上虽然隔开了,但是DB还是在一起,后端写的代码把数据库搞挂了,前端还是照样挂,这是一个很大的问题。
多活机房架构
最后说一下多活,多活的架构比较大,可以专门作为一个主题来,我这里只是给大家引申一下。
刚刚讲到,前端用户、后端用户尽管代码什么的可以隔开,但是DB这一层还是在一起。因此我们也要想办法把DB分开,但这个时候,两个DB的数据要保持同步,当然我今天说的只是一个思路而不是解决方案。
这是我在网上搜来的一张双活的图,想想双活和多活其实是一样的。我们可以有不同的机房,也可以在一个机房内部有多个独立的单元,多个机房或单元物理独立。这样的话,一定要有一个统一的数据中心,这两个数据要同步,因为前端用户下完单之后,商家在看订单的时候,不可能要看这边的订单到这个系统,要看那边订单到另外一个系统去看,因此必须要有数据中心。如果说我们有多个机房,可能是三个五个,像淘宝的级别就是非常多的机房。我理解它一定要有一个数据中心把数据汇总起来。
当然我这是从应用层来说,从应用隔离的角度去看的。多活的目的不是简单的隔离,它考虑的是一旦发生地震、灾害等如何保证不出问题,这个时候数据中心对多活也是必要的。
但数据中心我是不是可以作为后端应用来使用呢?后端的应用走数据中心,因为它对数据的实时性要求相对不是特别高,而前端只保证核心交易业务,后端保证非核心交易业务,这是多活应用架构拆分的思路。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:153318次
积分:2262
积分:2262
排名:第17253名
原创:27篇
转载:328篇
(1)(9)(15)(14)(49)(1)(7)(9)(13)(3)(38)(186)(10)
(window.slotbydup = window.slotbydup || []).push({
id: '4740881',
container: s,
size: '200,200',
display: 'inlay-fix'

我要回帖

更多关于 支付宝的系统架构 的文章

 

随机推荐