Contents

高并发优化方案

High Concurrency Solution

Contents

前言

  • XXX系统是公司分销系统数字化的重要载体,提高客户下单、门店要货、销量上报、零售退回、盘亏/盘盈、调拨、发货/入库、报表管理等功能。对手机、物料、IOT等产品的库存、销售状态、代理商数字化管理,同事实现精细化生产高效协同、数据透明、共享以及多维度分析,为管理者提高智慧决策。为了更好地实现业务目标,普惠更多的代理和用户,XXX系统逐步在代理商推广开,用户基数越来越大,对系统的性能、可扩展、数据准确性以及稳定性要求越来越高,为了使XXX系统推广能够平稳、顺利的进行,并达到预期的效果,某云团队和公司IT技术团队就XXX应用架构现状做了深入的交流和讨论,设计如下高并发优化方案,供XXX系统后续业务建设参考

1高并发优化方案

1.1 分布式事务和锁

  • 通过前期调研,XXX分销系统过度依赖分布式事务(大事务)进行控制,导致性能无法达到预期目标,结合公司某部门同事的测试结论<br> 优化掉分布式事务和锁后,部分操作异步化&代码优化,核心订货TPS理论等达到100左右,基本能够满足短期内v-vork的业务发展需求。

1.1.1 优化思路

  • 核心优化思路为尽可能去掉分布式事务&锁控制,考虑在业务模型上采用更有内聚性的设计,将必须要用的部分逻辑(如:流水和扣库存)</br> 抽离出来加本地事务控制,避免在入口处加大段锁控制;详细如下:
  • 1 业务流程分解(权限、订单、扣库存、扣款、出库);将大事务拆解为小事务,每个小事务内部关注事务内的补偿,参见代码优化章节
  • 2 库存扣减添加乐观锁,库存表添加version字段,每次更新对比字段,并判断减去当前售卖量库存是否为0,来防止超卖。
  • 3 拆小锁,合并数据库操作,优先使用本地事务。
  • 4(工厂版)针对业务层实现的TCC分布式事务,建议完整实现整个流程,在Try阶段进行资源锁定(具体实现上可以在要操作的记录行上用状态位做标识,事务结束前阻止其他线程并发修改),在Confirm和Cancel阶段做实际的提交或回滚。Confirm和Cancel操作做幂等处理。
  • 尽量减少分布式事务的使用,考虑在业务模型上采用更有内聚性的设计。在可以接受异步的场景下,建议采用Sega模式的设计,利用消息解耦,充分发挥异步的性能优势。在sega设计时注意识别可补偿事务、关键性事务和可重复性事务。对于隔离性问题可采用<b>语义锁<b>的方式来解决。

1.1.2 详细优化点

1.1.2.1 订货流程去掉分布式锁
  • 订货下单即支付场景中,涉及到写操作有如下几个部分
  • a 供货方库存占用insert inventoryOccupy表
  • b 占用订货方可拿货金额
  • c 回写层级分货DistributionHierarchy,java面向过程的方式计算结果,并发场景会导致数据异常,update set a = a+y 的方式。
  • d 插入DistributionHierarchyOccupy记录
  • e 订货方库存已订未发 update FreeInventoryQuanity,没有改记录则insert,不在分布式锁控制范围内,但是occupySellerInventory方法添加有锁,该方法存在和C步骤同样的问题,代码计算结果
  • f 保存发货计划单
  • g 插入订货方金额占用表记录
  • h 插入订货方金额流水记录
  • i 删除购物车记录
  • 去掉分布式锁的改造点
  • a 层级分货替换为 update set a= a + y
  • b 订货方库存占用替换为 update set a= a + y (高并发环节,加减必须改为自增减)
  • c 库存预占时扣减库存,插入库存占用表操作本地事务进行控制
1.1.2.2 出入库去掉分布式锁
  • 订货、调拨、出库时涉及的写操作:
  • a 扣减发货方冻结库存
  • b 扣减发货方主库库存(前置到下单时扣减)
  • c 更新串码为在途,变更串码归属方为收货方
  • d 出库状态更新
  • c 生成入库单
  • 去掉分布式锁的改造点
  • a 冻结库存和扣减发货方主库库存用本地事务进行控制
  • b 将不同类型的库存操作抽离出来,针对不同的订单类型封装出库方法
1.1.2.3 零售商退货去掉分布式事务控制
  • 零售退货涉及的写操作:
  • a 退货销量上报
  • b 保存退货单信息
  • 改造点:
  • 保存退货订单核心函数saveOrUpdate 添加了分布式事务控制,从业务逻辑上看,此处事务控制意义不大,理论上可以去掉
1.1.2.4 零售商换货去掉分布式事务控制
  • 同 1.1.2.3 零售商退货去掉分布式事务控制

1.2 业务代码优化

1.2.1 优化思路

  • 1 库存扣减加乐观锁,维护可售库存字段
  • 2 业务逻辑分块,高内聚低耦合
  • 3 部分高频率读取操作加缓存
  • 4 容本地事务控制优化部分写操作,确保数据一致性

1.2.2 详细优化点

1.2.2.1 库存设计&扣减优化
  • 乐观锁:
  • 代理版已上线该方案,最终方案中,乐观锁可以被优化掉,直接判断扣减后库存是否小于0
  • 例子: uddate set a = a +1 ,version = old_version +1 from inventory where account = 1 and version = old_version
  • 可售字段:
  • 库存表中维护可售库存字段(避免超卖),inventoryQuantity、freezeInventoryQuantity ,reportQuantity(上报销量)、可售数量、预扣数量、占用数量(预留,主要用于标记出库和极速退货等)
1.2.2.2 订货流程代码优化
  • 代码结构优化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
订货流程 -> 权限:1 权限校验
权限 --> 订货流程:
订货流程 -> 购物车:2 查询购物车
购物车 --> 订货流程:
订货流程 -> 库存:3 库存预占
库存 -> 库存:4 层级分货
库存 -> 订货流程:
订货流程 -> 价格:5 价格计算
价格 --> 订货流程:
订货流程 -> 支付:6 生成支付订单\n or立即支付
支付 --> 订货流程:
订货流程 -> 履约:7 各类单据生成
履约 --> 订货流程:
订货流程 -> 购物车:8 删除购物车
购物车 --> 订货流程:
  • 将订货流程代码按照伊桑时序图(权限、购物车、…… 履约)进行重新组织梳理,提高代码的可维护性。
  • 代码分包的通用原则:
  • 核心业务流程,独立接口: 比如创建订单,查看订单,退订单,改订单都作为独立接口提供,不打包接口。
  • 公共实体单独分包: 比如非核心业务流程实体,则可以单独分包,目的时达到公用的,避免重复制定相同接口。
1.2.2.3 调拨流程代码优化
  • 外部调拨单大致流程:
  • 1 提交,待审核 2 库存校验 3 串码去重 4 保存调拨数据 5 调出方申请生成发货通知单/出库单 6 冻结库存,此处需要判断冻结库存结果 7 串码状态为冻结 8 非对接wms场景,需要保存到IDC中间表 9 启动工作流
  • 优化改造点:
  • 库存占用步骤和冻结串码状态步骤需要判断异常,并进行异常补偿,避免引入脏数据
  • 库存占用和冻结串码状态步骤用本地事务控制
1.2.2.4 余额冻结/扣款代码优化
1.2.2.5 报表&主数据查询
  • 报表查询
  • a 查询结果添加缓存,减少对数据库直接请求次数
  • b 多join查询场景,将中间结果存储到结果表,减少数据库的压力
  • c 长期: cannal + ES 进行数据聚合分析查询处理
  • d 强烈建议使用SQL生态的BI工具,便于维护,开发效率高,对程序员友好
  • 主数据查询
  • a globalConfig 等高频查询添加缓存

1.3 异常补偿机制

  • 对订货、退换货、盘盈、盘亏代码流程中增加异常补偿机制和废单逻辑(解决短期的数据一直性问题);另外,目前代码中对于异常原因状态没有留痕(单据和日志),只是简单的抛出Exception,导致问题排查困难。
  • A 订单单据中增加order_status 字段,抛出 exception时,需要将状态回写回数据库
  • B 增加业务流水日志
  • C 异步或者同步对异常单据进行补偿,回补库存你、金额等。

1.4 业务流水日志

  • 流水日志是指将业务每一个关键点的详细状态都打印出来&持久化,便于问题的排查和追踪,如在订货过程中:A,B,C,D四个步骤,我们需要在日志中将这四个步骤状态打印出来。这样对于监控线上状态也成为可能,如:
  • a) 执行步骤A
  • b) 打印A的结果信息
  • c) 如果程序退出,需要将A的结果持久化到单据状态。
  • 推荐的埋点日志格式:
分类埋点字段优先级描述使用
黄金指标场景名称必须一个SOA服务是一个场景GroupBY
是否成功必须本次请求在业务上是否成功监控数据
RT必须本次SOA执行RT监控数据
错误码必须业务失败唯一识别码监控数据
功能维度traceId必须本次业务请求唯一标识监控采样
压测标必须是否压测流量过滤
重试次数必须重试次数过滤
业务信息业务身份必须区分不同的业务身份GroupBY
其他业务信息必须其他业务信息监控采样
其他错误信息推荐对于错误原因的描述监控采样
  • 常见日志埋点技术实现方式,方法维度可以使用spring aop方式自动生成;业务流水日志需要主动埋点打印,强烈建议核心业务流程增加流水日志,提高问题排查效率。

1.5 权限优化

  • 分销系统的权限较为复杂,涉及到业务系统的各个流程,比较难有通用的解决方案,核心技术关键点在于如何优化和构造权限树;推荐的优化思路:
  • 1 权限和业务解耦,充分利用缓存,提前预处理优化,提升整体性能;
  • 2 涉及大量数据查询,统计的功能从主业务流程中剥离,重新组织数据模型,可考虑混合存储模式进行数据持久化,实时性要求高的可引入实时计算平台来解决。
  • 3 使用高效的数据结构存储+缓存方式
  • 数据库存储树类型的数据常见的方式:邻接表、路径枚举、嵌套集、闭包表
  • 主表(冗余一代,二代,区域等其他必须且改动较小的属性字段)
  • 高频查询引入redis
  • 权限变更(大量写),建议在业务低峰操作
  • 批量数据join查询和分析,使用ES等系统进行旁路,避免阻塞主业务流程
  • 路径枚举是当前采用的方式,但不便于数据变更和维护,层级也有限

2 业务架构优化方案

2.1 库存能力中心

2.1.1 库存业务模型

/posts/architecture/high-concurrency-solution/xxx高并发优化方案.drawio.png

  • 交易库存中心,主要负责交易库核心库的查询和写入,同时put库存记录数据到前端缓存系统,所有的交易系统的读库操作都读取缓存数据,提升性能并减少核心库的压力。
  • 商家接入提供库存修改入口
  • 库存超时中心则负责对拍下未付款的订单进行释放(库存回补)
  • 库存任务调度,辅助交易核心库做一些定时或者消息处理的工作,例如:库存释放,关单消息监听回补库存等
  • 交易库存对账及超卖审计,会通过消息及消息拉取binlog的方式收集库存变更数据和交易订单数据,进行对账和审计,如果需要做到实时对账的功能,可以按需引入流计算平台作为支撑
  • 说明
  • 1 核心库存读写分离,读流量前置缓存的模式为后期减少核心库qps压力而设计,前期核心库压力不大的情况,可以暂缓建设。
  • 2 核心库达到容量瓶颈时,可采用根据商品ID进行分部分表的扩展方案。
  • 3 课程对账与审计相关:一 库存初始值记录(可以用统计周期开始时间作为初始值) 2 交易订单(含退换货,调拨)与库存变更记录(订货方,供货方)对账。

2.1.2 库存扣减模式与状态变化

  • 库存数量表达: inventory_quantity 可售库存(qu) inventory_occupy 占用库存(oc) free_quantity 冻结(待发、在途) 库存(fr)
  • 订货模式:订货方(供方出库: fr+, 入库:qu+,fr-); 供货方(下单:qu-,oc+,支付:oc-,fr+,出库: fr-)
  • 退换货模式:订货方(下单:qu-,oc+,出库:oc-, fr+,入库 qu+),供货方(入库 qu+)
  • 销量上报扣减:qu-

2.1.3 库存类型设计

/posts/architecture/high-concurrency-solution/xxx高并发优化方案-库存类型设计.drawio.png

  • 商家销售的商品,消费者可以看到的商品我们称之为前端商品,仓库中的商品为后端商品。
  • 库存分前端后端商品的模式可以根据业务实际情况进行选择,目前来看以后端商品的模式作为建设重点即可,所以后续将以后的商品库存的模式为主进行讲解。
2.1.3.1 区域销售
  • 不同地区有不同的库存,背后是仓库有自己的覆盖范围和库存。从库存中找到覆盖该地区的有库存的仓库,如果有多个仓库,再根据仓库优先级,库存最大值等路由策略的选择一个,然后显示该仓库的库存。
2.1.3.2 共享库存
  • 由外部系统保留可共享的仓库存映射关系,在进行库存查询时由商品或商家(订货方、供货方)属性代入特性值,由库存查询系统进行聚合和选择。
2.1.4 渠道库存
  • 渠道库存就是专门为渠道预留的,或限制渠道最大可售数量的库存,也可以叫做活动库存,用一个code标识渠道ID。并使用渠道库存的业务需要对商品或交易的特性加入渠道库存标识,称之为渠道库存标识。
  • 物理库存就是ITEM过程,sku库存,后端商品库存,仓库库存,门店库存的这种有实体对应的库存。
  • 逻辑库存是一个概念,建造的渠道库存就是逻辑。库存它可以限制在item sku后端,仓库上等它必须和物理库存一起出现。
  • 渠道库存无法独立存在,所以操作渠道库存的时候,正常情况下渠道库存要和物理库存一起扣减,一起回补。
  • 渠道库存分类:
  • 1)共享渠道:仅限做渠道,最大可收数量,但不为渠道预留出渠道,所需的库存数量的方式为共享的渠道库存模式。
  • 2)互斥渠道:既渠道最大可收数量,又为渠道预留出渠道所需的库存数量的方式。
  • 3)专享渠道:只存在一个渠道,且只能从这个渠道售出的为独享的渠道库存模式。
  • 渠道库存业务应用:
  • 1)营销渠道库存:外部指定(比如优惠系统),支持三个维度(item/SKU/storeCode纬度上挂库存)和物理库存所在的维度可以不同。
  • 2)商家(经销商、门店)维度渠道库存):是指商家a可以使用商家b的库存,但是商家b的库存供给多个人,划分出一部分隔商家A专享,商家a可以使用商家b划分出来的商家a专享渠道加共享渠道的库存,为了避免临界点大量失败,如果单渠道满足购买量,优先使用单渠道库存。
  • 3) 订货方维度渠道库存: 供货方根据下级代理及零售商的多方面因素,划分各下级订货方的专享渠道。订货方可以使用的库存,是专享渠道加共享渠道,要求优先使用专享渠道。

2.1.4 库存查询领域模型

  • 在库存查询中,按照领域实体和业务范围划分如下3个子域:供货品,仓,库存。
  • 供货品聚合,聚合根是销售商品。一个销售商品可以对应多个供货品,其他如下图
  • 供货品子域:解决库存从哪个品出的问题。主要有:自供货,供应链后端供货,共享库存供货模型
  • 仓子域:解决哪些仓可以供货的问题。主要有2大模型:地址路由(全国覆盖路由,区域销售路由,经纬度路由等)和供给关系路由(货–货/商–商/仓–仓 关系建立的路由模型,商家–商家–门店-仓 , 货品–货品–仓–仓供给关系等模式)
  • 库存子域:围绕在仓库存、渠道库存等实体展开,最终确定最优的库存结果。在仓库存属于物理库存(现货)

/posts/architecture/high-concurrency-solution/xxx高并发优化方案-库存查询领域模型.drawio.png

2.1.5 库存数据存储结构参考

2.1.5.1 SKU库存
  • 对于同一个商品的库存是树形结构的存储,比如商品1001有若干sku,那么库存记录里面商品ID 1001有一条库存记录,对应的每个sku也有自己的库存记录,且商品item库存等于所有sku的库存总和。
  • 库存中心要实时维护sku的db库存,缓存,发送SKU的库存变更消息,同事维护item的db库存,缓存,发item库存变更消息
2.1.5.2 仓库存
  • 普通后端货品的库存存储是树形结构的存储,具体位2层。根节点是货品总库存数量,叶子节点是货品在每个仓库的库存数量
  • db中数据可分为 可售库存、占用库存、冻结库存。
iditme_idsku_idtypeinventory_quantityinventory_occupyfreeze_quantityroot_idparent_iddist_id
11100120010010001002011100102001
1110022001027002010111001111001701
1110032001023008010111001111001702
  • 货品仓 type = 0 ,dist_id为货品id,实仓 type = 2 ,dist_id为仓库storeCode对应的id
2.1.5.2 渠道库存
  • 渠道库存 type = 3,dist_id为渠道Code对应的id
iditme_idsku_idtypeinventory_quantityinventory_occupyfreeze_quantityroot_idparent_iddist_id
11100120010010000011100102001
11100220010250000111001111001701
11100320010250000111001111001702
11100420010310000111001111001801
11100520010310000111001111001801

2.1.6 库存策略&营销策略

  • 库存策略主要解决的是仓路由查询和库存管理的问题,其中仓路由部分的重点是供给关系。路由策略也就是分销层级关系的相关策略逻辑,库存管理部分主要是考虑利用渠道库存来实现层级分伙和指定分销的逻辑,并依次为基础,延展出更多的库存策略实现方案,更灵活的适应未来的业务发展需要。

2.2 交易能力中心

/posts/architecture/high-concurrency-solution/xxx%E9%AB%98%E5%B9%B6%E5%8F%91%E4%BC%98%E5%8C%96%E6%96%B9%E6%A1%88-%E4%BA%A4%E6%98%93%E4%B8%9A%E5%8A%A1%E6%A8%A1%E5%9E%8B.drawio.png

2.2.1 交易能单据模型

  • 主订单模型与主子订单模型;主子订单模式能够适配多SKU,多优惠,分批发货等不同场景,扩展性强。主子订单模型在order表中增加 is_mian和parent_id 进行区分 ,其他和主订单模式一致。TDOD : 新增ER图例

2.2.2 单据个性化:

  • 可预留几个扩展字段(阿里内部预留6个),维护成本低,数据结构化不好(比如直接村json,不利于搜索场景),扩展有限
  • 个性化场景较为复杂,需要较多字段,可以考虑使用扩展属性表

2.2.3 单据状态流转:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@startuml
scale 350 width
hide empty description
 
state 初始状态: status=-1
state 待支付 : status=1
state 待审核 : status=2
state 待出库 : status=3
state 待入库 : status=4
state 支付失败 : status=-2
state 拒绝 : status=-3
state 出库失败 : status=-4
state 入库失败 : status=-5
state 成功 : status=0
 
[*] -->   初始状态: status=-1
初始状态 --> 待支付: 成功
待支付 --> 待审核: 成功
待支付 --> 支付失败
待审核 --> 待出库: 成功
待审核 --> 拒绝
待出库 --> 待入库: 成功
待入库 --> 入库失败
待入库 --> 成功:成功
成功 --> [*]
待出库 --> 出库失败
支付失败 --> [*]
拒绝 --> [*]
出库失败 --> [*]
入库失败 --> [*]
@enduml
  • 对于失败的状态,统一进行数据订正和回补操作,避免脏数据;这样不同类型的订货场景可以通过状态机进行操作的异步解耦,代码逻辑清晰,提升整体性能

2.2.4 单据存储方案

  • xxx会对单据有一类偏分析类请求(AP请求),AP类型请求对资源消耗可能会很大,一个简单的AP请求可能会小号完系统资源,从而影响现有的TP类线上业务请求,因此需要对TP/AP进行隔离;业界开源方案:引入一个ETL系统将TP数据复制一份给AP使用;商用解决方案:mysql+tablestore
2.2.4.1 Canal + ES 方案
  • 为了解决MySQL 在复杂查询场景下的查询性能瓶颈,通常引入ElasticSearch 或 Solr 等搜素引擎作为查询引擎,用作查询加速。问题:用户需要自己维护数同步,保证数据一致性。
  • 直接监听MySQL的binlog异步写数据到ES,但是会有消费乱序,消费失败,数据不一致等问题。推荐引入Canal开源工具,将数据同步到ES;对于报表强一致性诉求场景可考虑引入rocketMq,通过mq削峰错谷来避免大压力场景下的数据不一致,在adaptor层面对于数据写入失败场景也需要增加补偿,架构图如下: /posts/architecture/high-concurrency-solution/xxx高并发优化方案-交易业务-复杂查询方案.drawio.png
  • 单据会持续累积,需要归档,避免MySQL单表数据过大(根据阿里经验不要超过500万),数据归档工具使用pt-archiver工具(开源)。另外单表数据出现瓶颈后,可考虑分库分表,或者在建设之初就进行分库分表设计。
  • 大批量写场景(代理权限变更等),可以在业务低峰进行(推荐)。或可考虑HBASE开源产品或商业化商品ADB,TABLESTORE产品。

2.3 调度能力中心

  • 包含两部分:单据状态推进能力,超时控制中心(TOC)

2.3.1 单据状态推进

  • 基于事件的单据状态推进的方式,能够对单据核心逻辑进行异步解耦,各个步骤之间不依赖,又提升整体性能,提升个模块代码的维护效率。
  • 单据状态:下单、审核、支付、出库、入库。 /posts/architecture/high-concurrency-solution/xxx高并发优化方案-调度-单据状态流程.drawio.png
  • 调度中心订阅消息推进单据后续步骤运行
  • 失败/废单:根据创建失败、支付失败 …… 进行单据状态和数据回补
  • 对账,超时控制(超时则发送超时消息,调度中心调用废单模块进行数据回补),后门管理模块(用于补发单据状态信息,另外会定时扫描当前为结束顶顶那是否存在异常或状态丢失情况,如有异常则进行订正和告警通知)

2.3.2事件驱动机制方案

  • 有三种方案实现:
2.3.2.1 直接发送
  • 业务数据创建或更新后,业务系统发送消息,消息体包含事件内容和状态。
  • 如何保证数据库与消息队列之间的数据一致性
  • 最大努力通知:系统重试机制,和定时单据状态扫描回补机制,避免系统异常导致消息状态丢失,订单无法推进。
  • 事务消息: RocketMq事务消息,使用方式是在数据库事务之前先向MQ的Broker 发送一条prepare消息,在数据库事务之后再commit消息。如果再数据库事务之后没有commit,RocketMQ Broker会定期回调,业务系统提供回调接口,用来判断数据一致性。
2.3.2.2 事件表
  • 核心思想是利用本地事务完成业务数据更新和事件发布的一致性,具体实现如下:
  • 使用一个与订单表位于同一个数据库的事件记录表记录事件数据,利用本地数据库事务保障数据一致性。订单服务在一个事务内完成订单创建或更新操作和事件创建操作后,操作即完成,不直接操作消息队列。 事件引擎轮询事件表,发现新事件后,将事件发布到消息队列后更新事件表中的事件状态,从而完成整个业务数据更新和事件发布的流程,并保证数据一致性。
  • 优点:事件表将一部分时间发布动作和保证数据一致性的功能从业务系统转移到一个专门的事件引擎服务。从而降低了业务服务的复杂性,提升了业务服务的稳定性。
  • 缺点:需要额外研发事件引擎和设计额外的事件表,除了需要实现正常事件发布功能外,还需要实现历史事件归档或清理的逻辑。事件发布时效性取决于轮询频次,与直接发送方式相比,实时性会有所降低。
2.3.2.3 事务日志
  • 同上一种方法事件表一样,事务日志也是利用数据库本地事务保证业务数据和事件数据一致性的设计。不同点在于事务日志方法时将数据库事务日志(如 MySQL的binlog)作为事件表的来源。
  • 在具体实现时,可使用开源组件如阿里开源的canal接收事务;该方案相对于事件表方式而言提升了时效性,但是整体实现方案较复杂,适合大规模海量并发场景。
2.3.2.4 总结

结合xxx系统单据量来看,相对较适合方案一:直接发送方式。开发和维护成本小,时效性也高。

2.3.3 超时控制中心(TOC)

  • 对于xxx单据系统中,直接单据生成时,如配置有超时扩展点,那么会生成超时任务任务,xxl-job定时扫描超时任务表,将任务表数据存入内存,超时控制中心会订阅支付状态消息,支付成功,则将对应的超时任务失效。
  • /posts/architecture/high-concurrency-solution/xxx高并发优化方案-超时控制中心.drawio.png

3 数据一致性方案

3.1 订单&库存能力对账

3.1.1 库存对账及超卖审计

  • 库存对账系统结构图 /posts/architecture/high-concurrency-solution/xxx高并发优化方案-库存对账系统结构图.drawio.png
  • 参考实现架构:
  • 数据源 :上图左侧都是数据源,库存binlog,交易数据库binlog,左侧最下方为库存变更日志,对账系统分别从上述数据源拉取原始数据。
  • 商品维度超卖审计需要的数据:初始库存、商家调整的库存数量、交易数量、订单关闭返回库存数量
  • 流式计算框架: 核心组件运行在JStorm上的。对账Bolt时最为核心的组件,数据写入MySQL,而商品维度的Bolt和交易统计的Bolt数据写入Hbase.
  • 复检系统:对账是初步检验,已经发现了部分异常的订单。为了确保万无一失,复检系统会查询其他系统,等到真正异常的订单。
  • 对账系统核心处理逻辑: /posts/architecture/high-concurrency-solution/xxx高并发优化方案-对账系统核心处理逻辑.drawio.png

3.1.2 通用业务对账

/posts/architecture/high-concurrency-solution/xxx高并发优化方案-通用业务对账.drawio.png

  • 数据源
  • 通过消息实时驱动和binlog同步方式获取源数据
  • 规则脚本 :
  • 通过groovy/java编写校验规则,可以接入分布式服务和缓存等第三方服务
  • 报警与报表:
  • 异常数据产生后,事件可通过邮件、钉钉群、短信等途径报警。详细的执行情况可通过报表展现
  • 对账类型
  • 涉及的商业订单可分为不同场景的对账。
  • 1 实时对账:可用于解决下单创建订单后交易消息丢失等系统问题,在一定延迟发起订单核对和目标数据进行核验。
  • 2 定时对账:每日或每周进行定时对账,校验历史订单的状态
  • 3 数据变动对账:在当前订单数据DB字段发生改变时主动拉取目标数据进行对账,主要用于校验一些状态修改是否合规及准确性。
  • 以上场景可以通过不同的订阅方式进行触发,场景1和2可用消息或binlog进行监听数据的变动,场景3可用binlog消费来触发对账事件。

3.2 异常数据监控

  • 按照1.4 业务流水日志章节提到的标准日志埋点后,用Logstash将打印在本地流水日志采集到ElasticSearch进行分析监控展示,第一时间发现系统异常。
1
2
3
4
@startuml
:业务流水日志;
:ELK;
@enduml

4 业务架构演进

4.1 应用架构

  • 容器化、OAM方向演进

4.2 业务中台

4.2.1 业务模型图

  • 业务分身: 物料、终端零售、IOT,手机分销
  • 解决方案: 终端零售、通用区域手机分销、海外手机分销、IOT、物料解决方案
  • 商业能力 交易能力(订货、退换货等)、结算能力(预付款、费用核销等)、分销能力(商品投放、渠道营销等);商业能力,商业能力扩展点,业务流程,业务活动
  • 业务域:分销、交易、库存、商品、支付。业务域,业务域服务,业务域服务扩展点
  • 业务视角

业务身份是对不同的业务方的标识信息,用于界定和区分系统为业务方分配哪些商业能力的依据,针对不同业务方需求进行逻辑隔离的依据。技术视角:通过业务身份对系统的程序逻辑和配置信息进行虚拟分区(APP包),从而使得每个使用系统的业务身份,能使用到一个单独的虚拟实例,并且可以对这个虚拟实例进行定制化。

  • 商业能力

商业能力是为了达到某个特定的商业的目的而提供的一组聚合的业务活动、流程、规则的集合,是实现业务需求的模块化组件,业务方通过快速复用商业能力的方式,保证了快速实现业务需求,提升系统质量。

  • 业务活动

业务活动通常指的是特定角色为了达到某一个特定的目的业务动作的集合,业务活动以信息对象为粒度构成的基础单元,通过业务活动构建商业能力,业务活动比商业能力的粒度更小,因此具有更高的抽象性和稳定性。

  • 业务活动

业务流程是描述商业能力为了达到特定的业务目标,按照一定时序锁执行的各种业务活动的过程。业务流程中描述了各环节的IPO(输入、输出、处理),通过这种方式能够让软件开发过程中的各种角色清晰直观的了解整体业务需求,避免协作过程中理解的不一致。

  • 商业能力扩展点

商业能力扩展点为了实现业务方差异化、灵活性的业务逻辑、规则,同时避免导致系统的复杂性和不稳定,设计的个性化参数配置点。扩展点应用范围包括:属性扩展、流程扩展、业务规则扩展。

  • 业务域

业务域是领域建模过程中,针对目标领域中涉及到所有业务对象及其服务,按照业务对象高内聚、低耦合的原则进行的抽象、归类的一组系统服务,目的是降低耦合性,提高复用性。

  • 系统域服务

系统域服务指各个系统域对外提供可供系统流程编排的服务接口。系统域服务需要对外屏蔽内部的具体实现的依赖关系。例如:库存功能域提供的库存查询服务、外部调用方不需要关心库存你管理的具体是和哪个第三方库存中心绑定的。

  • 系统域服务扩展点

系统域对外提供的可扩展能力,一次系统服务调用的逻辑中,允许第三方进行扩展实现的节点。根据扩展点的粒度不同,可以分为类级和方法级。系统域服务扩展点是对商业能力扩展点的技术实现。

4.2.2 技术架构图

/posts/architecture/high-concurrency-solution/xxx高并发优化方案-技术架构图.drawio.png

  • 交易中心:订单、支付、购物车能力
  • 库存中心:库存生命周期管理,支持各类型的商品库存,如渠道库存等
  • 营销中心:营销策略中心,如限购、上下限、折扣、满减等
  • 商品中心:商品品类管理,上下架、区域销售等
  • 能力中心提供核心愿你在能力,业务按照业务场景进行组合和编排,对外提供服务。

4.3 微服务设计(暂略去)

4.3.1 架构模型

  • 在我们传统的代码里面,我们一般都很注重每个外部依赖的实现细节和规范,但是如果我们抛弃掉原有的理论,重新审视代码结构,抛弃掉具体实现细节,我们会发现每一个对外部的抽象类其实就是输入或输出,类似于计算机系统的I/O节点。这个管对岸在CQRS架构中也同样适用,将所有接口分为Command(输入)和Query(输出)两种。除了I/O之外其他的内部逻辑就是应用的核心逻辑。基于这个基础,架构的组织关系可以变成一个二维的内外关系,而不是传统的一维的上下关系。同时我们可以发现UI层、DB层和各层中间件层实际上没有本质区别的,都只是数据的输入和输出,而不是在传统架构中的最上层和最下层。Hex架构、洋葱架构、干净架构也都是极为类似的思想,整体架构都是基于一个二维的内外关系。

4.3.2 分层模型

4.3.3 依赖解耦

  • 防腐层:应用不要直接依赖外域信息,要把外域的信息转换成自己领域上下文的实体再去使用,从而实现本域和外域的解耦。

5 其他问题

  • 1 现在主数据权限在common服务中,在分销业务或报表中匹配权限只能是先从common库查询出来权限列表,然后再拼SQL,如果有些权限较大有上千客户的账号,可能拼接出来长达几千行的SQL,针对这个点有其他更好的方案吗?

    • 长期方案(只针对报表):查询能力旁路,如:将相关数据同步(canal)到ES,通过ES聚合查询
    • 短期方案:java层面对查询进行分批控制,分批查询,查询必须要有分页,避免慢SQL将数据库拖死;如果是前端的直接查询,可以改为服务端增量同步给端侧,降低对数据库的压力。(增量加载)
  • 2 线上分销(DPS)如果重构,基于我们目前的服务模块,做在哪里更好(包含代理版,工厂版,南京做的几个中心等)

    前提 : DPS 默认都是一代,不会有层级分货等逻辑,不考虑项目周期 纯从技术角度 : 工厂版合适,如果是DPS业务场景会有层级分货、调拨之类的,且项目周期较短,那估计代理版落地可能会很快,但后续维护时问题(与工厂版整合)

    工厂版优势: a . 符合整合趋势(各中心) b . 代码维护成本相对低(无需关注历史业务逻辑)等同于新开发

    工厂版劣势: a . 项目周期可能会比较长,工厂版目前不支持手机分销(库存、单据等)

    代理版优势: a . 开发周期会短一些

    代理版劣势: a . 不符合将来都整合到工厂版的趋势

    关键点: 需要参考DPS业务需求,确认是否可以服用到各业务中心的能力

  • 3 报表,我们想着数据应该落地一个DB,但是针对服务不确定要不要做一个统一的,或者水这个全国性的报表落地到哪块?

    建议先按照统一做。手机、IOT、终端的报表数据维度理论都一致,都是抽数到DB,无外乎时多抽了几个数据库的数据。后面万一要过渡到分析型数据库或者SQL生态BI工具,成本也相对低。

  • 4 VRS 搬迁

  • 5 自定义报表

  • 6 多时区

    推荐方案(落地快,改动小)

    • A 统一前端改,包含转换;后端不动(建议后端存储时区,方便排查问题)
    • B 需要梳理现有DB中是否有使用DATE说着STRING 表示时间的写法,需要统一订正为DATETIME或者INSTANT表示

    标准方案(改动量大,短期内难落地)

    • 后端接口支持多时区,需要将所有对时区敏感结果修改为都市区的接口

6 参考资料