业务中台目标

  • 目标:
    • 整体目标:高内聚、低耦合,便于开发和维护。
    • 五个方向:扩展性、性能、可用性、伸缩性、安全性。
  • 原因:
    • 单体架构的大泥球会导致业务迭代困难、无法针对性伸缩、故障没有隔离等问题,需要向微服务方向演进。
    • 演进过程中为了复用、抽象,需要沉淀中台能力。
    • 业务中台不仅仅需要关注功能性需求,更需要关注5个方向的非功能性需求,才能高效稳健的支持业务的发展,否则极容易演化成分布式单体。
  • 实现方式:
    • 战略上:
      • 横向分层:分散关注、松散耦合。
        • 应用层:提供具体业务的API和视图,和用户直接交互。
        • 服务层:提供业务编排和业务原子能力。
        • 数据层:提供存储服务,如数据库、缓存、搜索引擎、文件。
      • 纵向分割:通常按照不同业务划分,专人做专事、不同等级保障。
      • 分布式部署:包括服务应用、静态资源、数据存储、计算、配置、锁、文件的分布式。由于通常需要避免单点,需要在C和A之间权衡。
    • 战术上(几种方式可能组合使用):
      • 集群。负载均衡和失效转移提供并发特效和可用性。
      • 缓存。在数据热点不均匀、数据有一定有效期的情况下使用。
      • 异步。堆积消息避开故障时刻提高可用性、减少RT、削峰。
      • 冗余。包括集群备份、冷热备份、灾备数据中心。
      • 自动化。发布过程、代码管理、安全监测、监控、失效转移恢复、降级、分配资源都可以自动化。
      • 安全措施。密码、验证码、加密、过滤、风控等。

下面这张全景图,把「五个方向」逐层拆成可落地的设计手段,后续案例都挂在这棵树上:

业务中台
非功能性需求

扩展性

领域建模DDD

战略层面

问题域/问题解

限界上下文

战术层面

六边形框架

领域模型

流程节点

微服务

内部服务

模块分解

交互协议/方式

外部对接

开放平台

中台扩展点

性能

容量

集群

服务层k8s集群

缓存集群分片

持久化集群分片

代码优化

GC调优

软件版本

堆参数

ZGC

对象复用

ab参数复用

响应时间

异步

消息队列

异步rpc

线程池

并行

多线程

任务分片

存储优化

存储选型

索引调优

容量&响应时间

缓存

分布式缓存

本地缓存

线程缓存

可用性

整体可用性

集群

服务层失效转移

存储层数据备份

多活

同城双活

异地多活

分级隔离

硬件隔离

业务隔离

单点可用性

限流

集群限流

单机限流

熔断

降级

强依赖

无损降级

有损降级

弱依赖

缩短超时

丢弃请求

风险识别

热点流量

业务识别

系统识别

异步

重试

伸缩性

分割

水平方向

API/编排/原子能力/存储分层

垂直方向

新增业务模块

数据分离

冷热分离

动静分离

集群

服务层无状态扩容

存储层有状态降影响

元数据自动迁移

一致性hash

安全性

隔离

PCI域隔离

风控

规则引擎

统计模型

编码安全

校验

接口输入

外部返回

加密摘要

详细理论内容可参考:大型网站架构

具体案例

结合电商商品中台、交易中台、春晚红包的工作经历梳理使用场景

扩展性

取舍:扩展性是用「前期多设计」换「后期少改动」。抽象不是免费的——只在变化频繁的地方投入(商品扩展属性靠 schema 化迭代了数十次需求),稳定的地方别过度设计。

领域建模
战略层面
  1. 通过DDD战略层面拆分各个业务域,先划清问题域/问题解、限界上下文
    1. 交易中台拆分成商品、营销、交易、履约、结算5个业务域,大多数需求可以闭环在各个业务域内。
战术层面
  1. 使用六边形框架搭建微服务。
    1. 交易中台的服务使用DDD构建,切下游模型、接口变更时只需要对rpc适配层进行少量代码改动,无须修改领域层。
  2. 领域模型的通用性,必要时可分离为业务子域或者基础设施子域。
    1. 商品中心的商品扩展属性通过k-v形式存储,shema通过groovy脚本校验,可以做到不发版迭代业务。已经迭代过数十个需求。
    2. 商品详情装修组件也设计为通用的组件+schema+数据的形式,支持业务动态配置详情页。
    3. 商品消息通过配置化每个业务可以单独订阅感兴趣的组合,如只关心商品和SKU的消息。
    4. 通用的能力,如商品黑名单、商品店铺协议、商品操作日志、计数能力单独抽业务子域,其他业务可以复用。
  3. 流程编排节点的抽象。如不同场景的下单流程,都要涉及查询商品、保存订单,均可以抽象成一个节点。
微服务
内部服务
  1. 内部服务通过微服务分解业务模块。是DDD的技术实现。
    1. 各个服务可以根据业务场景选型最合适的技术栈。
      1. 如存储选型、语言框架选型
    2. 微服务之间可以通过多种方式交互
      1. 同步的:rpc、http调用;异步的:消息中间件、异步rpc。
      2. 交互协议是可扩展的,json, hessian,thrift等序列化方式;tcp,udp等协议。
外部对接

SPI

  1. 开放平台。注重对外部开发者的扩展性。
    1. 交易中台会定义一套三方下单的接口,并公开在开平文档中,saas根据供应商提供client_key路由到指定的url
  2. 中台扩展点。交易中台通过在编排层流程节点定义接口,业务侧实现接口的方式,将业务定制逻辑剥离。

性能

取舍:性能基本是「拿资源换时间」——加机器、内存、CPU 换吞吐和延迟。但每种手段都有反向代价:缓存提速要牺牲一部分一致性,ZGC 降停顿要多耗 CPU。所以先看值不值:热点接口(商品列表 300w qps)值得堆缓存集群,冷接口够用就好。

系统性的性能提升手段可参考:性能优化

容量

提高数量、提高质量

集群
  1. 服务层k8s实例的集群化。商品列表场景单机房上千实例可抗300w+ qps,并在10ms内返回。单机压力大会导致cpu飙升的长尾。
  2. 缓存的集群化。商品列表的缓存集群抗300w+ qps的批量查询流量,并保证RT在1ms左右。
  3. 持久化存储的集群化。
    1. mysql。
      1. 商品256库256表,可以支持百万qps的查询,日常流量下商详场景的redis雪崩,也可以基本支持业务。
      2. 春晚预热的全局预算累计拆分到32个库,可支持几十万的写入。
    2. hbase。商品列表缓存下的持久化存储,支持百万qps的查询。
代码优化

提高单机质量:相同资源下让每台机器扛更多、更快。系统性手段见JVM/GC优化

  1. GC调优。升级版本、调堆参数、换收集器,降低GC对响应时间的拖累。
    1. 软件版本。JDK8升级到JDK11后GC次数减半;连接池从c3p0换成Tomcat-jdbc,GC次数也降约50%。
    2. 堆参数。按内存调-Xms/-Xmx(如4G→8G)使GC频次降50%(单次耗时略增),并按核数调ConcGCThreads/ParallelGCThreads线程数。
    3. 更换收集器。商品敏感数据服务需用本地缓存、GC压力大,上游50ms经常超时;换ZGC后损失一点CPU换来1-2ms的GC停顿,极大降低了上游超时率。
  2. 对象复用。
    1. ab参数复用。下单页对大ab参数只解析一次、后续复用解析后的对象——解析大json可能占用机器将近一半的CPU耗时。
响应时间
异步
  1. 消息队列。
    1. 消息中间件。春晚红包抢红包实时链路db只记录中奖流水,后续发奖通过kafka异步。抢红包需要支持600w tps,下游发奖等无法支持,并且异步也可以减少用户等待时间能再次摇奖。
    2. 事件驱动。用户下单后的预约流程需要商家接单,接单等待时间较长。实现为用户申请落库后直接放回,后续通知商家流程通过可靠领域事件驱动,商家接单之后回调。
  2. rpc异步。商品列表聚合素材、标签等数据使用异步dubbo,并行调用减少列表渲染时间。
  3. 线程池。商品列表的缓存回写通过线程池异步,减少为命中缓存时用户请求等待的时间。
并行
  1. 多线程。春晚红包下单回滚库存、限购等资源时并行回滚,减少用户等待时间。
  2. 任务分片。商品中心同步数据任务,使用64个pod对账6w+张表,实现分钟级别增量补偿。
存储优化
  1. 存储选型。商品操作日志这种写多读少的场景,使用hbase这种追加式的存储,做到毫秒级写入。
  2. 索引调优。
    1. 添加索引减少每次扫描的数据行数,从而降低响应时间。
      1. 商品的sku包含删除和未删除的sku,热门商品修改频繁,删除的sku较多,导致每次可能需要查到上万sku,但实际生效的只有几十上百个。添加商品Id+SKU状态的索引解决这一问题。
    2. 添加查询条件引导优化器走扫描数据量少的索引。
      1. 春晚红包的索引优化
容量&响应时间
缓存
  1. 分布式缓存。主流的redis的3种集群,是电商商品中心各种场景的缓存主力。
    1. 主从redis。client 路由。
      1. 一致性hash路由。商品主库上的缓存,用于商详等商品单查。已经成功支持过200+ redis实例,抗500w+ qps。
        1. 通常取模算法是按服务器数量取模,而一致性哈希算法是对固定值如2^32取模
      2. 多副本路由。上一条的二级缓存,存放热点数据,防止单redis和主库单分片被打挂。50个 redis实例可抗100w+ qps。
    2. codis。proxy路由。用于C端列表大流量的批量查询,通常多集群部署。单集群256主从server+100proxy可抗近千万qps,并且可以通过节点的方式最多支持到3000w qps。
    3. redis cluster。前两者的替代品,扩容更加方便。集群规模受限,建议最多200 server和2T内存,可以通过多集群分片的方式扩展集群的规模。
  2. 本地缓存。主流Caffeine,用于列表场景大内存(56G)实例抗突发热点,保护分布式缓存。
  3. 线程缓存。ThreadLocal,用于aop带出上下文,如商品B端编辑在aop中校验商品状态,并把商品数据带到业务逻辑中修改,减少查库的次数。

可用性

取舍:可用性是花成本买确定性。强一致和高可用不可兼得时就退一步——商品库用半同步复制折中,账户走读容灾做「几乎无损」降级,核心链路绝不为非核心依赖陪葬(弱依赖超时砍到 200ms)。

分布式场景判断是否可用的一般方式:超时

整体可用性
集群
  1. 服务层的无状态化k8s集群。可负载均衡+重试做到失效转移。
    1. dubbo调用支持配置retry属性,也可二次开发健康度lb,从而使得请求最终落到下游的正常实例上。
  2. 存储层的数据备份。可失效转移。
    1. 单主写入
      1. 同步备份。mysql同步复制
      2. 异步备份。mysql异步复制,redis主从复制
      3. 半同步备份。商品库采用mysql半同步复制,主备同步复制,主从异步复制,主到灾备异步复制,是C和A的折中。
    2. 无主写入。
      1. 商品中心的缓存采用单机房三集群,同步时串行写入,异常时跨集群降权重试。
多活
  1. 同城双活,异地、跨云多活。
    1. 商品中心、交易中心等核心服务均部署两个以上的机房,同时提供服务,并且定期做切流量演练。
分级隔离
  1. 高优先级的服务占用更好的硬件资源,部署在不同的宿主机上,甚至异地容灾。
    1. 商品服务独立宿主机,和其他服务隔离,并做多机房部署和异地灾备。
  2. 高优先级的场景和其他场景隔离,避免被影响。
    1. 商品详情的缓存使用独立的redis集群,和其他缓存独立。
    2. 电商核心的盈利部门广告从单独的商品集群查询商品。
单点可用性
限流
  1. 保证有效流量不超过自身的处理能力。分为集群限流和单机限流。几种限流算法
    1. 集群限流。用存储如redis计数,适合流量小、精度高的场景,如1w qps左右。
    2. 单机限流。本地内存限流,适用于流量大精度不高的场景。
      1. 商品服务均使用单机限流 。dubbo在filter中用sentinel的滑动窗口,每个窗口默认0.5s。
熔断
  1. 抛弃下游请求。下单页服务对下游的所有依赖如果超过50%,会被mesh层熔断。
降级
  1. 强依赖降级:
    • 无损降级。如读容灾存储。
      1. 交易支付流程如果查询账号服务失败,则从容灾的redis中获取账户信息。账号信息每次查询账号服务后更新,由于账户变更不频繁,读容灾存储基本等于无损降级。
    • 有损降级。如异步补偿写。
      1. 交易下单流程中如果限购服务异常,可以降级成读历史订单后补偿扣限购,而不是同步扣限购。可能造成超卖。
  2. 弱依赖降级:可以失败
    • 缩短超时时间。交易下单页对于历史留资等若依赖超时时间设置成200ms而不是框架默认的1s
    • 支持丢弃请求。以上的留资服务可以手动降级,不去请求。
风险识别
  1. 热点流量。
    1. 业务识别。热点商品通过定时任务拉秒杀服务获取,并刷到多副本缓存中,用于商品详情页和下单,避免数据库被打崩。
    2. 系统识别。
      1. 商品列表使用caffeine本地缓存,通过tiny-lfu识别热点并缓存到本地,保护分布式缓存。
      2. 商品详情、下单服务使用改造之后的sentinel,发现db访问热点,并刷到多副本缓存。
异步
  1. 方式同上一节中的mq,dubbo。
    1. 下单流程生成交易快照使用mq+异步dubbo双通道,不影响核心的下单链路。
重试
  1. 需要注意:
    1. 下游保证幂等。
    2. 重试的错误类型。网络超时可以重试,业务异常需要具体问题具体分析,有些异常重试没有意义。
      1. 某分布式事务组件的思路是一阶段提交,二阶段无限重试,但是二阶段调下游的入参有问题被拦截,无限重试卡住。
    3. 重试次数限制。过多的重试会造成读或写扩散,打挂下游。

伸缩性

取舍:伸缩性是用「拆分的复杂度」换「各部分独立伸缩」——按技术特点拆:读多写少的商品用缓存横向扩、写为主的库存堆存储,无状态服务随便扩、有状态存储靠分片迁移。拆得越细越能各自伸缩,但跨边界的一致性和运维成本也越高。

分割
  1. 水平方向:按照业务处理流程分离,比如API层、编排层、原子能力层、中间件层、存储层。

    1. 交易中台的服务层无状态,可以快速扩容。有状态的存储层相对伸缩性较差。
  2. 垂直方向:

    1. 新增业务模块分离。
      1. 交易中台新建设预约能力,和正向、履约独立,不用再对现有流程进行修改。
    2. 数据分离。
      1. 动静分离。商品中心的库存服务单独拆分,和商品数据动静分离。商品可以往大流量支持场景,使用缓存伸缩,库存往小流量写场景堆存储伸缩。
      2. 冷热分离。商品库会hive表捞删除1年的商品,通过kafka推给业务删除商品库的相关数据并存储到hbase中,hbase提供只读能力。
集群
  1. 服务层的无状态化k8s集群。可扩容支持大促流量,负载均衡业务无感知。
    1. 双11等大促时,商品中台需要两倍以上的k8s实例做压测和保障,可扩容并依赖dubbo的负载均衡支持业务。
  2. 有状态的存储集群可以扩容,并通过2种方法识别到数据在哪个分片上。
    1. 元数据服务器。
      1. 商品中心使用hbase,依赖meta节点获取rowkey的分布,扩容无须业务感知。
      2. redis cluster的槽迁移,依赖槽的路有扩容同样无须业务感知
    2. 路由算法。商品中心客户端路由缓存使用一致性hash,扩容通过平台配置,业务无须修改代码

安全性

取舍:安全是用一点体验和性能换风险下降。全量加密、强校验、PCI 隔离都有成本,所以按数据敏感度分级——持卡人数据进 PCI 域严管,普通数据够用即可。

隔离
  1. 把敏感数据收拢到独立的安全域,缩小泄漏面和合规范围。
    1. PCI 域隔离。国际支付的卡号、CVV 等持卡人数据落在独立的 PCI-DSS 合规域:独立机房/网段、最小化访问权限,普通业务只拿脱敏后的 token,碰不到明文。
    2. 注意和「可用性·分级隔离」区分:那是为了可用性切硬件和流量,这里是为了安全合规、缩小敏感数据的暴露面。
风控
  1. 规则引擎+统计模型识别黑产。
    1. 交易中台下单前过风控拦截,下单后、支付前后通知风控分析用户行为。
编码安全
校验
  1. 接口输入强校验。比如不拼接字符串作为sql入参
    1. 下单流程对手机号会进行正则校验,防止sql注入。
  2. 外部返回强校验。
加密摘要
  1. 单向hash加密(MD5、SHA),对称加密(DES、RC),非对称加密(RSA)等。
    1. 订单的用户信息属于用户隐私,需要通过AES加密之后存储在DB,防止泄漏。
  2. 对外报文摘要。如hash。
    1. 对外支付渠道为了防止报文被篡改,对http body hash之后发给外部。
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐