前言
随着双 11 进入千亿时代, 电商平台正在向 "全球化, 娱乐互动化, 无线化, 全渠道" 发展.
为实现全民互动, 电商平台会进行低价预售, 狂欢红包, 购物券, 红包雨, 商品半价, 满 n 减 1 等多种促销方式.
核心交易链路设计
每笔剁手操作都会经历一系列核心系统处理, 如图:
如此眼花缭乱的玩法, 底层是多个核心系统的支撑, 整个系统要保证在交易高峰下的海量订单有序, 准确, 顺滑.
预算扣减及红包领取
红包发放要保证精确预算控制, 预算发放的红包总金额不能超过预算的金额.
每条预算在数据库中是一条记录, 在高并发场景下, 可能会成为单热点瓶颈, 维护过多的记录表, 可能造成数据倾斜.
预算控制和用户无关, 无需实现单元化. 用户的红包信息则需要实现单元化, 发放流程涉及到预算扣减, 扣减操作后, 用户需要尽快收到红包.
因单元化和非单元化的数据处于不同数据中心, 可能造成跨机房调用, 也就引入了不确定性.
所以红包系统和预算系统需要解决高并发场景下的预算扣减和用户收取红包的一致性.
分桶方案
通过分析历史数据, 我们将预算拆分为多个子预算, 瓶颈分配到多个数据库中, 根据红包发放请求的 userId 进行路由. 在子预算余额不足时, 路由到主预算中. 每个子预算的扣减并不相等, 肯定有一部分子预算会出现余额不足的情况, 所以主预算要比子预算多分配一些金额. 当多个子预算余额非常少时, 可以对子预算进行回收, 避免预算分片的碎片化.
数据库优化
为提升记录表性能, 需要对数据库操作进行梳理及优化. 设想这个场景主要有 3 条 sql:
两条插入语句
一条更新语句
更新语句是造成热点的瓶颈, 为减少更新导致的独占锁, 可以将 3 条 sql 合在一起, 通过一次网络传输到达数据库服务器, 同时在更新语句中设置余额大于等于 0 的条件, 这样可以避免在扣减之前再查询一次余额, 而仅仅通过判断 sql 错误码就能识别余额是否足, 减少了对数据库的压力.
更新语句添加 commit_on_success 标签, 保证事务成功后立即提交事务, 不用等到客户端获取更新结果再发起 commit, 减少了一次网络交互, 同时记录的独占锁可以立即释放.
更新语句添加 target_affect_row 1 标签, 保证如果满足条件的记录不存在, 事务应该失败, 而不是返回影响行数为 0.
可以将多个事务封装成一个数据库的写入单位. 整体优化后系统 qps 可以摸高到 30w.
红包展示
用户领取红包后需要在多个系统终端中进行红包展示, 比如领了多少红包, 金额是多少等. 统计这些会比较消耗数据库性能, 同时展示红包也是比较高频的需求. 采用缓存可以解决这个问题, 但是随之而来的问题是缓存的失效处理. 红包本身涉及多个生命周期, 到底在哪个缓解设置缓存失效是合理的呢?
在用户的红包每次进行状态变更时都会更新 modify_time, 所以采用读时更新缓存:
采用事务内一致性读取缓存, 将当前时间设置为缓存生效时间, 如果用户没有红包, 则生效时间为 2000 年 1 月 1 日.
当查询到用户用红包时, 会同时查询出红包的最后更新时间, 然后和缓存生成时间做比较, 当用户红包数据更新时间大于缓存生成时间, 则判断缓存失效.
这样可以利用数据库索引, 同时减少返回信息, 对数据库的消耗比较少.
红包使用
在业务规则角度进行了红包使用控制, 每次只能使用 10 个红包, 红包使用场景 qps 也很高放大到红包系统 qps 是 10 倍. 每次红包使用需要更新 10 个红包状态, 产生 10 条红包使用的流水, 还需要产生至多 10 条红包相关的业务单据.
一次红包使用场景涉及到大量 CPU 资源进行 sql 解析, 一次下单涉及到多个 sql, 对网络消耗较大. 我们采用 batch insert 语法优化插入性能, 更新语句采用多条方式提升更新性能. 在业务系统中生成一个大 sql 发送给数据库服务器, 减少网络交互.
比如这次下单操作涉及到 5 个红包, 可以通过一个 sql 将 5 个红包余额更新为 0, 同时加入金额锁保证红包的并发更新. 设置语句的 target_affect_row 5 标签, 如果某个红包已经被其他订单并发下单使用, 事务会提交失败, 可以通过数据库返回的错误码识别出这一情况.
可靠性保障
上面的情况只是处理非单元化场景预算, 系统需要在预算扣减之后写入单元化的用户数据中. 两种数据处于不同数据库, 需要保证操作的一致性. 同时在红包领取后, 在 1s 内展示用户红包, 这种情况一般采用跨库事务框架来解决. 但跨库事务不能做到严格的事务一致性, 严格的事务一致会造成性能的极大下降, 于是采用内部的一致性消息 jbus 实现.
jbus
jbus 思想是业务在事务中插入一条消息记录, 建立一套消息订阅和分发系统对消息进行处理. 消息的记录和业务记录在一个数据库中, 可以做到事务一致性. 多个消息订阅者可以共享一条消息记录, 因此不会增加过多的数据库性能损耗. 做到 1s 内消息消费, 则可以保证用户看到自己领取的红包.
同时建立监控系统对消息挤压进行监控, 可以及时发现消息的积压问题, 同时在消费出现问题时进行流程完整性的保障.
流程前置处理
下单系统涉及到访问物流系统获取运费模版, 计算运费价格, 之前的架构会调用远程服务, 获取计算结果, 这种方式会将下单峰值带到下游依赖的系统中, 需要下游系统具备同样的峰值承载能力, 提高了整个核心链路的成本, 同时稳定性也带来了复杂和挑战.
流程前置处理后, 下单系统不再需要请求物流系统, 而是直接访问运费模板缓存服务器, 通过前置下单运费计算模块在本地计算出运费, 减少了对于远程服务的调用和依赖, 提升了系统性能, 增强了系统稳定性.
提升开发效率 (TMF2)
随着业务玩法的越来越丰富, 参与的团队越来越多, 多个团队在一套平台开发造成的效率浪费越来越多.
为提升开发效率架构进行了升级, 业务级代码和平台级代码分离, 平台级代码对交易相关能力进行分类抽取, 抽象提取对外提供支撑服务, 业务方根据团队能力自助式定制逻辑开发, 无需平台团队介入, 大幅提高开发效率.
为达到业务和平台分离的架构目的, 主要通过能力模型, 配置模型, 所生成对配置数据贯穿业务配置主线和业务运行主线来实现.
通过对交易建模, 抽象, 收敛, 形成了交易基础能力层, 采用功能域 -> 能力 -> 扩展点方式进行表达.
在下单环节中归纳十几个功能域, 优惠, 支付, 交付, 价格, 订单, 结算, 税费等, 针对这些域进行能力扩展, 同时开放给业务方进行定制, 适应不同业务场景需求.
同时引入产品概念, 将多个域能力进行整合, 对业务方提供满足业务功能的能力包, 进一步提高业务研发效率.
整个架构中, 业务能力, 产品, 场景属于平台能力, 业务方定制功能都在业务包中, 这样可以做到业务和平台分离, 业务之间隔离, 使得平台开发人员和业务开发人员之间互不干扰, 提升整体协作和交付效率.
业务技术挑战
营销平台核心系统:
优惠计算系统能力:
来源: http://www.bubuko.com/infodetail-2947248.html