商品中台架构设计与技术落地实践——基于Spring Cloud微服务体系的完整解决方案

商品中台架构设计与技术落地实践------基于Spring Cloud微服务体系的完整解决方案

互联网老兵的干货分享


一、建设背景与业务痛点

业务现状

当前公司商品体系面临典型的多渠道、多业态并行问题:

  • 多端并行:电商主站、APP、小程序、线下POS、第三方渠道
  • 多业态并存:自营、平台、分销、预售、组合商品

核心痛点

  1. 烟囱式开发:各业务线独立建设商品系统,重复造轮子
  2. 数据不一致:SPU、SKU、价格、库存多端不同步
  3. 能力不互通:类目、属性、规格、上下架无法统一管控
  4. 迭代效率低:新业务接入周期长,无法快速试错
  5. 运维成本高:多系统独立部署、维护、扩容

结论:必须通过中台化解决以上问题,构建统一、可复用、可扩展的商品能力中心。


二、商品中台定位与核心目标

定位

全公司统一商品能力中心 + 数据枢纽 + 服务网关

  • 向上:支撑各业务前台
  • 向下:屏蔽异构数据源与复杂逻辑

核心目标

目标 说明
能力统一 统一商品核心模型、类目、属性、规格、价格体系
服务化复用 商品核心能力以微服务方式标准化输出
高可用稳定 支撑大促、秒杀、海量商品查询场景
快速接入 前台业务接入成本降低 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("中台暂时不可用,已记录待补偿");
    }
}

七、四阶段平滑迁移策略

这是整个中台迁移的核心策略,确保业务不停服、风险可控。

阶段 写方向 读方向 状态说明
阶段一 仅写老库 仅读老库 初始状态,业务稳定
阶段二 主写老+异步写中台 读老/灰度读中台对账 数据逐步同步,验证中台能力
阶段三 主写中台+同步写老库兼容 双读对账,优先中台 中台成为唯一可信源
阶段四 仅写中台 仅读中台 老库只读/下线,迁移完成

切换主写中台的前提条件(必须全部满足)

  1. 中台核心服务连续稳定运行 ≥ 7 天
  2. 全量 SKU/SPU 数据对账一致率 100%
  3. 全链路压测通过,性能优于/等于老系统
  4. 异常、降级、回滚、补偿流程全部验证通过
  5. 核心业务场景(新增/编辑/上下架/价格修改)全部覆盖
  6. 具备快速回切到老写主的开关能力

八、读链路兼容切换 + 双读对账

读模式设计

模式 说明
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 年业务发展
  • 明确代码清理路径,不留历史包袱

未来展望

  1. 构建商品数据智能中心(AI 选品、智能定价)
  2. 支持全球化多语种商品体系
  3. 服务编排可视化、低代码化
  4. 深度联动库存、订单、营销,形成交易域中台矩阵

说明:本文所有代码基于 Spring Cloud 2023 + Spring Boot 3.x + Nacos 2.x + RocketMQ 5.x 版本验证通过,可直接作为项目落地的参考。


如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、关注。有任何问题欢迎在评论区交流讨论。

相关推荐
人道领域1 小时前
【Redis实战篇】初步基于Redis实现的分布式锁---基于黑马点评
java·数据库·redis·分布式·缓存
雨奔8 小时前
Kubernetes 联邦 Deployment 指南:跨集群统一管理 Pod
java·容器·kubernetes
杨凯凡8 小时前
【021】反射与注解:Spring 里背后的影子
java·后端·spring
buhuimaren_8 小时前
FastDFS分布式存储
分布式
Ares-Wang8 小时前
Flask》》 Flask-Bcrypt 哈希加密
后端·python·flask
小码哥_常9 小时前
Spring Boot项目大变身:为何要拆成这六大模块?
后端
码事漫谈11 小时前
兵临城下:DeepSeek-V4 的技术突围与算力“成人礼”
后端
空中海11 小时前
第四篇:Unity高级阶段(架构级开发能力)
unity·架构·游戏引擎
三水不滴11 小时前
SpringAI + SpringDoc + Knife4j 构建企业级智能问卷系统
经验分享·spring boot·笔记·后端·spring