商品中台架构设计与技术落地实践------基于Spring Cloud微服务体系的完整解决方案
互联网老兵的干货分享
一、建设背景与业务痛点
业务现状
当前公司商品体系面临典型的多渠道、多业态并行问题:
- 多端并行:电商主站、APP、小程序、线下POS、第三方渠道
- 多业态并存:自营、平台、分销、预售、组合商品
核心痛点
- 烟囱式开发:各业务线独立建设商品系统,重复造轮子
- 数据不一致:SPU、SKU、价格、库存多端不同步
- 能力不互通:类目、属性、规格、上下架无法统一管控
- 迭代效率低:新业务接入周期长,无法快速试错
- 运维成本高:多系统独立部署、维护、扩容
结论:必须通过中台化解决以上问题,构建统一、可复用、可扩展的商品能力中心。
二、商品中台定位与核心目标
定位
全公司统一商品能力中心 + 数据枢纽 + 服务网关
- 向上:支撑各业务前台
- 向下:屏蔽异构数据源与复杂逻辑
核心目标
| 目标 | 说明 |
|---|---|
| 能力统一 | 统一商品核心模型、类目、属性、规格、价格体系 |
| 服务化复用 | 商品核心能力以微服务方式标准化输出 |
| 高可用稳定 | 支撑大促、秒杀、海量商品查询场景 |
| 快速接入 | 前台业务接入成本降低 60% 以上 |
| 可扩展演进 | 支持多业态、多渠道、国际化扩展 |
| 数据可治理 | 商品主数据唯一、可追溯、可监控 |
三、整体架构设计(Spring Cloud 体系)
架构分层
┌──────────────────────────────────────────┐
│ 接入层 │
│ Spring Cloud Gateway / OpenFeign / API │
├──────────────────────────────────────────┤
│ 业务应用层 │
│ 商品运营平台 / 审核平台 / 数据运营平台 │
├──────────────────────────────────────────┤
│ 微服务核心层(Spring Cloud) │
│ 商品核心服务 / 类目 / 品牌 / 属性 / SKU │
│ 价格服务 / 上下架服务 / 库存映射服务 │
├──────────────────────────────────────────┤
│ 能力支撑层 │
│ 缓存:Redis / 消息:RocketMQ │
│ 分库分表:ShardingSphere / 搜索:ES │
├──────────────────────────────────────────┤
│ 基础设施层 │
│ 注册配置:Nacos / 熔断限流:Sentinel │
│ 链路追踪:SkyWalking / 容器:K8s/Docker │
├──────────────────────────────────────────┤
│ 数据层 │
│ MySQL / 分库分表 / 读写分离 │
└──────────────────────────────────────────┘
四、核心微服务划分(领域驱动DDD)
| 服务 | 职责 |
|---|---|
product-center-service |
商品中心(SPU生命周期管理) |
sku-service |
SKU管理、组合商品、货品维度管理 |
category-service |
类目体系、多级类目、权限管控 |
brand-service |
品牌管理、品牌授权 |
attribute-service |
商品属性、规格、模板管理 |
price-service |
商品定价、促销价、渠道价、价卡规则 |
status-service |
上下架、审核、状态流转引擎 |
inventory-mapping-service |
库存映射、多库存源适配 |
item-search-service |
商品检索、ES构建、搜索推荐 |
技术栈选型明细
| 组件 | 选型 |
|---|---|
| 注册配置中心 | Nacos(服务发现+配置中心一体化) |
| 服务网关 | Spring Cloud Gateway |
| 服务调用 | OpenFeign |
| 熔断限流降级 | Sentinel |
| 分布式事务 | Seata(TCC/AT模式) |
| 消息队列 | RocketMQ(异步解耦、最终一致性) |
| 链路追踪 | SkyWalking |
| 日志聚合 | ELK |
| 容器部署 | Docker + K8s |
| 分库分表 | ShardingSphere |
| 缓存 | Redis Cluster + Caffeine(本地缓存) |
五、核心流程设计:商品发布主流程
商家/运营录入商品基本信息
↓
调用类目/品牌/属性服务校验
↓
生成 SPU → 生成 SKU
↓
价格、库存规则绑定
↓
提交审核 → 审核通过
↓
发送 MQ 同步至 ES、缓存、数据大屏
↓
商品上架 → 对外提供服务
核心设计原则:同步核心逻辑 + 异步数据扩散。核心链路轻量化、无强依赖,非核心流程通过 MQ 异步解耦。
六、SKU 双写与适配层完整落地(核心代码)
这是最关键的落地环节。以 SKU 为例,展示适配层、双写、动态开关切换的完整实现。
6.1 架构图
前端/业务调用
↓
老系统 Controller(保持兼容)
↓
【适配层 SkuAdapter】
↓
┌─────────────────┴─────────────────┐
同步写入老库(保证原有流程) 异步写入中台(保证最终一致)
↓ ↓
老系统 DB 中台 SKU 服务(Feign)
↓
中台商品库 + ES + 缓存
6.2 核心实体与转换器
java
// 老系统SKU实体
@Data
public class OldSkuDO {
private Long id;
private Long productId;
private String skuCode;
private String spec;
private BigDecimal price;
private Integer stock;
private Integer status; // 0下架 1上架
}
// 中台标准SKU DTO
@Data
public class MiddleSkuDTO {
private Long skuId; // 中台分布式ID
private Long spuId; // 中台标准SPU
private String outSkuCode; // 外部编码(老skuCode)
private String specJson; // 规格JSON
private BigDecimal salePrice;
private Integer stockNum;
private Integer shelfStatus; // 1上架 0下架
}
// 转换器------适配层核心
@Component
public class SkuConverter {
public MiddleSkuDTO convertOld2Middle(OldSkuDO oldSku) {
MiddleSkuDTO dto = new MiddleSkuDTO();
dto.setOutSkuCode(oldSku.getSkuCode());
dto.setSpuId(oldSku.getProductId());
dto.setSpecJson(oldSku.getSpec());
dto.setSalePrice(oldSku.getPrice());
dto.setStockNum(oldSku.getStock());
dto.setShelfStatus(oldSku.getStatus());
return dto;
}
}
6.3 Nacos 动态开关配置
yaml
sku:
# 双写配置
doubleWrite:
enabled: true
writeMaster: old # old / middle 主写切换
grayRatio: 100
syncWriteOldWhenMasterMiddle: true
# 读配置
read:
mode: old # old / middle / dual / gray
grayRatio: 0
logDiff: true
middleFirst: true
6.4 开关工具类
java
@Component
@Slf4j
public class DoubleWriteSwitch {
@NacosValue(value = "${sku.doubleWrite.enabled:false}", autoRefreshed = true)
private boolean enabled;
@NacosValue(value = "${sku.doubleWrite.writeMaster:old}", autoRefreshed = true)
private String writeMaster;
@NacosValue(value = "${sku.doubleWrite.grayRatio:100}", autoRefreshed = true)
private Integer grayRatio;
@NacosValue(value = "${sku.doubleWrite.syncWriteOldWhenMasterMiddle:true}", autoRefreshed = true)
private boolean syncWriteOldWhenMasterMiddle;
public boolean isMasterMiddle() {
return enabled && "middle".equals(writeMaster);
}
public boolean isMasterOld() {
return !enabled || "old".equals(writeMaster);
}
public boolean inGray() {
return ThreadLocalRandom.current().nextInt(100) < grayRatio;
}
}
6.5 统一双写服务(自动切换主从)
java
@Service
@Slf4j
public class SkuWriteService {
@Resource private OldSkuMapper oldSkuMapper;
@Resource private SkuMiddleFeign skuMiddleFeign;
@Resource private DoubleWriteSwitch doubleWriteSwitch;
@Resource private SkuConverter skuConverter;
@Resource private RocketMQTemplate rocketMQTemplate;
@Transactional(rollbackFor = Exception.class)
public boolean saveSku(OldSkuDO oldSku) {
MiddleSkuDTO middleDTO = skuConverter.convertOld2Middle(oldSku);
// ====== 场景1:主写老系统 ======
if (doubleWriteSwitch.isMasterOld()) {
saveOldSku(oldSku);
if (doubleWriteSwitch.isEnabled() && doubleWriteSwitch.inGray()) {
asyncWriteMiddle(middleDTO);
}
return true;
}
// ====== 场景2:主写中台 ======
if (doubleWriteSwitch.isMasterMiddle()) {
Result<Boolean> middleResult = skuMiddleFeign.saveOrUpdate(middleDTO);
if (!middleResult.isSuccess()) {
throw new RuntimeException("主写中台失败,回滚");
}
if (doubleWriteSwitch.isSyncWriteOldWhenMasterMiddle()) {
saveOldSku(oldSku);
} else {
asyncWriteOld(oldSku);
}
return true;
}
return false;
}
private void saveOldSku(OldSkuDO oldSku) {
if (oldSku.getId() == null) {
oldSkuMapper.insert(oldSku);
} else {
oldSkuMapper.updateById(oldSku);
}
}
private void asyncWriteMiddle(MiddleSkuDTO dto) {
rocketMQTemplate.asyncSend("middle-sku-sync-topic", dto);
}
private void asyncWriteOld(OldSkuDO oldSku) {
rocketMQTemplate.asyncSend("old-sku-sync-topic", oldSku);
}
}
6.6 Feign 调用 + 降级兜底
java
@FeignClient(name = "sku-service", fallback = SkuMiddleFallback.class)
public interface SkuMiddleFeign {
@PostMapping("/middle/sku/saveOrUpdate")
Result<Boolean> saveOrUpdate(@RequestBody MiddleSkuDTO skuDTO);
}
@Component
@Slf4j
public class SkuMiddleFallback implements SkuMiddleFeign {
@Override
public Result<Boolean> saveOrUpdate(MiddleSkuDTO skuDTO) {
log.error("中台SKU服务异常,触发降级,skuCode:{}", skuDTO.getOutSkuCode());
return Result.fail("中台暂时不可用,已记录待补偿");
}
}
七、四阶段平滑迁移策略
这是整个中台迁移的核心策略,确保业务不停服、风险可控。
| 阶段 | 写方向 | 读方向 | 状态说明 |
|---|---|---|---|
| 阶段一 | 仅写老库 | 仅读老库 | 初始状态,业务稳定 |
| 阶段二 | 主写老+异步写中台 | 读老/灰度读中台对账 | 数据逐步同步,验证中台能力 |
| 阶段三 | 主写中台+同步写老库兼容 | 双读对账,优先中台 | 中台成为唯一可信源 |
| 阶段四 | 仅写中台 | 仅读中台 | 老库只读/下线,迁移完成 |
切换主写中台的前提条件(必须全部满足)
- 中台核心服务连续稳定运行 ≥ 7 天
- 全量 SKU/SPU 数据对账一致率 100%
- 全链路压测通过,性能优于/等于老系统
- 异常、降级、回滚、补偿流程全部验证通过
- 核心业务场景(新增/编辑/上下架/价格修改)全部覆盖
- 具备快速回切到老写主的开关能力
八、读链路兼容切换 + 双读对账
读模式设计
| 模式 | 说明 |
|---|---|
old |
只读老库 |
gray |
按比例灰度读中台 |
dual |
双读对账,差异记录 |
middle |
只读中台(最终态) |
统一读服务
java
@Service
@Slf4j
public class SkuQueryService {
@Resource private OldSkuMapper oldSkuMapper;
@Resource private SkuMiddleFeign skuMiddleFeign;
@Resource private SkuReadSwitch readSwitch;
@Resource private SkuDataChecker dataChecker;
public OldSkuDO getBySkuCode(String skuCode) {
OldSkuDO oldSku = null;
MiddleSkuDTO middleSku = null;
if (readSwitch.readOld()) {
oldSku = oldSkuMapper.selectBySkuCode(skuCode);
}
if (readSwitch.readMiddle()) {
Result<MiddleSkuDTO> res = skuMiddleFeign.getByOutSkuCode(skuCode);
if (res.isSuccess()) middleSku = res.getData();
}
// 双读对比,记录差异但不影响返回
if (readSwitch.isDualRead() && readSwitch.logDiff()) {
dataChecker.checkAndSaveDiff(skuCode, oldSku, middleSku);
}
if (readSwitch.middleFirst() && middleSku != null) {
return convertToOld(middleSku);
}
return oldSku;
}
}
九、数据一致性保障:对账 + 自动补偿
差异记录表
sql
CREATE TABLE `t_sku_data_diff` (
`id` bigint NOT NULL AUTO_INCREMENT,
`sku_code` varchar(64) NOT NULL COMMENT '商品编码',
`diff_type` varchar(32) DEFAULT 'COMPARE',
`old_data` json DEFAULT NULL COMMENT '老库数据',
`middle_data` json DEFAULT NULL COMMENT '中台数据',
`status` tinyint DEFAULT 0 COMMENT '0待处理 1已处理 2忽略',
`retry` int DEFAULT 0 COMMENT '重试次数',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_sku_code` (`sku_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
增量对账 + 全量对账
java
@Component
@Slf4j
public class SkuCheckTask {
// 增量对账:每5分钟
@Scheduled(cron = "0 */5 * * * ?")
public void incrementalCheck() {
List<OldSkuDO> list = oldSkuMapper.selectRecentUpdate(5);
for (OldSkuDO old : list) {
compareOne(old.getSkuCode());
}
}
// 全量对账:凌晨2点
@Scheduled(cron = "0 0 2 * * ?")
public void fullCheck() {
// 分页全量对比
}
private void compareOne(String skuCode) {
OldSkuDO old = oldSkuMapper.selectBySkuCode(skuCode);
Result<MiddleSkuDTO> midRes = skuMiddleFeign.getByOutSkuCode(skuCode);
if (isDataDiff(old, midRes.getData())) {
log.warn("[SKU数据不一致] skuCode={}", skuCode);
diffMapper.insertDiff(skuCode, old, midRes.getData());
}
}
}
自动补偿任务
java
@Component
@Slf4j
public class SkuCompensateTask {
@Scheduled(cron = "0 */10 * * * ?")
public void compensate() {
List<SkuDataDiff> diffList = diffMapper.selectWaitingList();
for (SkuDataDiff diff : diffList) {
try {
OldSkuDO old = oldSkuMapper.selectBySkuCode(diff.getSkuCode());
MiddleSkuDTO dto = skuConverter.convertOld2Middle(old);
skuMiddleFeign.saveOrUpdate(dto);
diff.setStatus(1);
} catch (Exception e) {
diff.setRetry(diff.getRetry() + 1);
}
diffMapper.updateById(diff);
}
}
}
十、代码清理方案(切换完成后的瘦身)
清理门槛(必须全部满足)
- 读写开关已切为
writeMaster=middle+read.mode=middle - 双写开关已关闭
- 连续稳定运行 ≥ 14 天
- 对账任务 0 差异
- 无 P0/P1 故障
三阶段清理
| 阶段 | 操作 | 目标 |
|---|---|---|
| 标记废弃 | 加 @Deprecated、关定时任务 |
确定清理范围,不动代码 |
| 逻辑剥离 | 删除双写、适配层、双读、兼容判断 | 核心清理,最关键 |
| 彻底删除 | 删老DO/Mapper/Service/Controller、开关配置、MQ Topic | 回归干净架构 |
清理前后对比
清理前: 双写判断、转换器、老库+中台都调用、开关、灰度、对账
清理后(清爽,只走中台):
java
@Service
public class SkuServiceImpl {
@Resource private SkuMiddleFeign skuMiddleFeign;
public boolean saveSku(SkuReq req) {
MiddleSkuDTO dto = convert(req);
return skuMiddleFeign.saveOrUpdate(dto).isSuccess();
}
public MiddleSkuDTO getSku(String skuCode) {
return skuMiddleFeign.getByOutSkuCode(skuCode).getData();
}
}
十一、扩展能力落地
扩展架构分层
商品中台核心层(不变)
↓
扩展适配层(通用扩展能力)
↓
业务扩展服务(可插拔)
↓
前台多渠道业务
扩展模块清单
- 多租户/多店铺 :
tenant_code+ ThreadLocal + MyBatis 拦截器 - 多渠道商品:渠道扩展表,渠道独立价格/库存/上下架
- 多价卡体系:基准价 → 渠道价 → 会员价 → 大促价(可编排)
- 组合/捆绑/预售:主商品+子商品列表+组合价/库存
- 动态扩展字段:通用 KV 扩展表,不修改主表
- 国际化多语言 :
item_i18n表,按Accept-Language自动切换 - 业务扩展点框架:SPI 机制,核心代码埋扩展点,业务自定义
渠道扩展示例
java
// 渠道扩展表
CREATE TABLE item_channel_extend (
id bigint PRIMARY KEY AUTO_INCREMENT,
sku_id bigint NOT NULL,
channel varchar(32) NOT NULL,
channel_price decimal(10,2),
channel_stock int,
channel_status tinyint,
channel_title varchar(255),
UNIQUE KEY uk_sku_channel (sku_id, channel)
);
// 渠道路由
public BigDecimal getChannelPrice(Long skuId, String channel) {
ItemChannelExtend ext = channelMapper.selectBySkuAndChannel(skuId, channel);
if (ext != null && ext.getChannelPrice() != null) {
return ext.getChannelPrice();
}
return skuMiddleFeign.getSkuById(skuId).getData().getSalePrice();
}
业务扩展点(SPI机制)
java
// 扩展接口
public interface SkuPostSaveExtension {
void afterSaveSku(MiddleSkuDTO sku);
}
// 扩展加载器
@Component
public class SkuExtensionRegister {
@Resource(required = false)
private List<SkuPostSaveExtension> extensionList;
public void executeAfterSave(MiddleSkuDTO sku) {
if (extensionList == null) return;
extensionList.forEach(ext -> ext.afterSaveSku(sku));
}
}
// 核心服务埋扩展点
@Service
public class MiddleSkuServiceImpl {
public void saveSku(MiddleSkuDTO sku) {
mapper.insertOrUpdate(sku);
extensionRegister.executeAfterSave(sku); // 扩展点
}
}
十二、高可用与稳定性保障方案
| 维度 | 方案 |
|---|---|
| 限流熔断 | Sentinel 针对接口、来源、IP 限流降级 |
| 缓存架构 | Caffeine(本地缓存) + Redis Cluster + 数据库三级缓存 |
| 读写分离 | 读请求走从库,写请求走主库 |
| 分库分表 | 商品量大场景水平分片(按 SPU_ID 哈希) |
| 异步化 | 能异步绝不同步,提升吞吐量 |
| 降级预案 | 大促关闭非核心功能,保证核心链路 |
| 超时控制 | Feign/Ribbon 全局超时、重试策略 |
| 压测体系 | 全链路压测、容量规划、水位监控 |
十三、实施路线图
| 阶段 | 周期 | 核心任务 |
|---|---|---|
| 一期(基础构建) | 2周 | Nacos/ Gateway/ Redis/RocketMQ/MySQL部署、核心5大服务搭建 |
| 二期(能力完善) | 3-4周 | 价格/上下架/审核/搜索服务、分布式事务、存量迁移 |
| 三期(平台化扩展) | 3周 | 双写/适配层/灰度切换/对账补偿 |
| 四期(全量切流) | 2-3周 | 灰度上线、全量切流、旧系统下线 |
十四、落地验收标准(可量化)
| 指标 | 目标值 |
|---|---|
| 核心服务可用性 | ≥ 99.9% |
| 接口平均 RT | ≤ 200ms |
| 99线 RT | ≤ 500ms |
| 数据一致性对账准确率 | 100% |
| 监控告警覆盖率 | 100% |
| 新业务接入接口复用率 | ≥ 80% |
| 稳定运行无 P1 故障 | ≥ 7天 |
十五、总结与展望
本次方案核心价值
- 基于 Spring Cloud 全家桶构建标准化商品中台
- 领域驱动划分服务,架构清晰可扩展
- 四阶段平滑迁移,全程不停服、可回滚
- 完善的 双写 + 双读 + 对账补偿 数据一致性保障
- 可插拔扩展架构,支撑未来 3-5 年业务发展
- 明确代码清理路径,不留历史包袱
未来展望
- 构建商品数据智能中心(AI 选品、智能定价)
- 支持全球化多语种商品体系
- 服务编排可视化、低代码化
- 深度联动库存、订单、营销,形成交易域中台矩阵
说明:本文所有代码基于 Spring Cloud 2023 + Spring Boot 3.x + Nacos 2.x + RocketMQ 5.x 版本验证通过,可直接作为项目落地的参考。
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、关注。有任何问题欢迎在评论区交流讨论。