京东返利app的多数据源整合策略:分布式数据同步与一致性保障

京东返利app的多数据源整合策略:分布式数据同步与一致性保障

大家好,我是阿可,微赚淘客系统及省赚客APP创始人,是个冬天不穿秋裤,天冷也要风度的程序猿!

在京东返利app的业务场景中,需整合京东开放平台API数据、用户本地行为数据、第三方支付数据等多类数据源,这些数据分散在不同存储介质(MySQL、Redis、MongoDB)和物理节点中,面临数据格式不统一、同步延迟、一致性难保障等问题。基于此,我们设计了"分层同步+一致性校验"的多数据源整合方案,通过分布式同步组件与一致性算法,实现数据高效整合与可靠流转。以下从数据源分类、同步架构设计、核心代码实现三方面展开说明。

一、京东返利app数据源分类与核心诉求

1.1 数据源类型划分

根据数据来源与用途,将系统数据源分为三类:

  1. 业务核心数据源:用户账户数据(MySQL)、订单返利数据(MySQL分库分表);
  2. 外部依赖数据源:京东商品数据(通过开放平台API获取)、第三方支付流水(MongoDB);
  3. 缓存与日志数据源:热点商品缓存(Redis)、用户操作日志(Elasticsearch)。

1.2 核心业务诉求

  • 实时性:京东商品价格、库存变更需在10秒内同步至app前端;
  • 一致性:用户下单后,订单数据与返利计算结果需保证最终一致;
  • 可靠性:同步过程中出现网络波动或节点故障,需支持数据重试与断点续传。

二、多数据源分布式同步架构设计

采用"采集-转换-同步-校验"四层架构实现数据整合,各层职责与技术选型如下:

  1. 数据采集层:通过定时任务(Quartz)拉取京东API数据,基于Canal监听MySQL binlog获取本地数据变更;
  2. 数据转换层:使用Flink SQL统一数据格式,将不同数据源的JSON、XML格式转换为系统标准DTO;
  3. 数据同步层:基于RocketMQ实现跨节点数据投递,Redis Cluster用于缓存同步结果;
  4. 一致性校验层:通过TCC事务补偿与定时对账机制,保障数据最终一致性。

三、核心组件代码实现

3.1 京东API数据采集组件

基于Quartz定时拉取京东商品数据,封装API调用与数据解析逻辑,代码如下:

java 复制代码
package cn.juwatech.jdrebate.collector;

import cn.juwatech.jdrebate.config.JdApiConfig;
import cn.juwatech.jdrebate.dto.JdProductDTO;
import cn.juwatech.jdrebate.utils.HttpClientUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;

/**
 * 京东商品数据采集任务
 */
@Component
public class JdProductCollector implements Job {

    @Autowired
    private JdApiConfig jdApiConfig;
    @Autowired
    private DataTransferService dataTransferService;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        try {
            // 1. 构建京东API请求参数
            Map<String, String> params = new HashMap<>();
            params.put("appKey", jdApiConfig.getAppKey());
            params.put("timestamp", String.valueOf(System.currentTimeMillis()));
            params.put("pageNum", "1");
            params.put("pageSize", "50");
            // 签名生成(省略具体签名逻辑)
            params.put("sign", generateSign(params));

            // 2. 调用京东商品列表API
            String response = HttpClientUtils.doGet(jdApiConfig.getProductListUrl(), params);
            JSONObject result = JSON.parseObject(response);

            // 3. 解析API返回数据
            if ("0".equals(result.getString("code"))) {
                JSONArray productArray = result.getJSONArray("data");
                for (int i = 0; i < productArray.size(); i++) {
                    JSONObject productJson = productArray.getJSONObject(i);
                    // 4. 转换为系统标准DTO
                    JdProductDTO productDTO = dataTransferService.convertJdProduct(productJson);
                    // 5. 发送至同步队列
                    sendToSyncQueue(productDTO);
                }
            }
        } catch (Exception e) {
            throw new JobExecutionException("京东商品数据采集失败", e);
        }
    }

    /**
     * 生成京东API签名
     */
    private String generateSign(Map<String, String> params) {
        // 省略签名算法实现(按京东API文档要求拼接参数并加密)
        return "";
    }

    /**
     * 发送数据至同步队列
     */
    private void sendToSyncQueue(JdProductDTO productDTO) {
        // 后续章节实现
    }
}

3.2 MySQL binlog数据监听组件

基于Canal监听用户订单表变更,实时捕获订单创建、修改事件,代码如下:

java 复制代码
package cn.juwatech.jdrebate.collector;

import cn.juwatech.jdrebate.dto.OrderDTO;
import cn.juwatech.jdrebate.service.SyncService;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;
import java.util.List;

/**
 * MySQL binlog监听组件(监听订单表变更)
 */
@Component
public class MysqlBinlogCollector {

    @Value("${canal.server.host}")
    private String canalHost;
    @Value("${canal.server.port}")
    private Integer canalPort;
    @Value("${canal.destination}")
    private String destination;
    @Value("${canal.username}")
    private String username;
    @Value("${canal.password}")
    private String password;

    @Autowired
    private SyncService syncService;

    private CanalConnector canalConnector;

    @PostConstruct
    public void init() {
        // 1. 创建Canal连接
        canalConnector = CanalConnectors.newSingleConnector(
                new InetSocketAddress(canalHost, canalPort),
                destination, username, password
        );
        // 2. 启动监听线程
        new Thread(this::startListen).start();
    }

    /**
     * 启动binlog监听
     */
    private void startListen() {
        try {
            canalConnector.connect();
            // 订阅订单表(数据库.表名)
            canalConnector.subscribe("jd_rebate.order_info");
            while (true) {
                // 3. 获取binlog消息(每次拉取100条)
                Message message = canalConnector.getWithoutAck(100);
                long batchId = message.getId();
                if (batchId == -1 || message.getEntries().isEmpty()) {
                    Thread.sleep(1000);
                    continue;
                }
                // 4. 解析binlog条目
                parseBinlogEntries(message.getEntries());
                // 5. 确认消息消费
                canalConnector.ack(batchId);
            }
        } catch (Exception e) {
            e.printStackTrace();
            // 异常重试
            try {
                Thread.sleep(5000);
                startListen();
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * 解析binlog条目,转换为订单DTO
     */
    private void parseBinlogEntries(List<CanalEntry.Entry> entries) {
        for (CanalEntry.Entry entry : entries) {
            if (entry.getEntryType() != CanalEntry.EntryType.ROWDATA) {
                continue;
            }
            CanalEntry.RowChange rowChange;
            try {
                rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
                throw new RuntimeException("解析binlog条目失败", e);
            }
            // 处理INSERT/UPDATE事件
            if (rowChange.getEventType() == CanalEntry.EventType.INSERT ||
                rowChange.getEventType() == CanalEntry.EventType.UPDATE) {
                for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
                    OrderDTO orderDTO = buildOrderDTO(rowData.getAfterColumnsList());
                    // 发送至同步服务
                    syncService.syncOrderData(orderDTO);
                }
            }
        }
    }

    /**
     * 从binlog列数据构建订单DTO
     */
    private OrderDTO buildOrderDTO(List<CanalEntry.Column> columns) {
        OrderDTO orderDTO = new OrderDTO();
        for (CanalEntry.Column column : columns) {
            switch (column.getName()) {
                case "order_id":
                    orderDTO.setOrderId(column.getValue());
                    break;
                case "user_id":
                    orderDTO.setUserId(column.getValue());
                    break;
                case "product_id":
                    orderDTO.setProductId(column.getValue());
                    break;
                case "rebate_amount":
                    orderDTO.setRebateAmount(new BigDecimal(column.getValue()));
                    break;
                // 其他字段映射(省略)
            }
        }
        return orderDTO;
    }
}

3.3 分布式数据同步服务

基于RocketMQ实现跨数据源同步,封装同步逻辑与重试机制,代码如下:

java 复制代码
package cn.juwatech.jdrebate.service;

import cn.juwatech.jdrebate.dto.JdProductDTO;
import cn.juwatech.jdrebate.dto.OrderDTO;
import cn.juwatech.jdrebate.mapper.ProductMapper;
import cn.juwatech.jdrebate.mapper.OrderMapper;
import cn.juwatech.jdrebate.utils.RocketMQUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 分布式数据同步服务
 */
@Service
public class SyncService {

    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private RocketMQUtils rocketMQUtils;

    /**
     * 同步京东商品数据至本地MySQL
     */
    public void syncProductData(JdProductDTO productDTO) {
        try {
            // 1. 检查商品是否已存在(幂等处理)
            if (productMapper.selectById(productDTO.getProductId()) != null) {
                // 存在则更新
                productMapper.updateById(productDTO);
            } else {
                // 不存在则插入
                productMapper.insert(productDTO);
            }
            // 2. 同步至Redis缓存
            syncProductToRedis(productDTO);
        } catch (Exception e) {
            // 同步失败,发送至重试队列
            rocketMQUtils.send("jd_product_retry_topic", productDTO);
            throw new RuntimeException("商品数据同步失败", e);
        }
    }

    /**
     * 同步订单数据(含TCC事务补偿)
     */
    @Transactional(rollbackFor = Exception.class)
    public void syncOrderData(OrderDTO orderDTO) {
        // 1. 本地事务:保存订单数据
        orderMapper.insert(orderDTO);
        // 2. 发送事务消息,触发返利计算(TCC Try阶段)
        rocketMQUtils.sendTransactionMsg("jd_order_topic", orderDTO, orderDTO.getOrderId());
    }

    /**
     * 同步商品数据至Redis
     */
    private void syncProductToRedis(JdProductDTO productDTO) {
        // 省略Redis SET逻辑(使用RedisTemplate)
    }
}

3.4 数据一致性校验组件

通过定时对账与TCC补偿机制保障一致性,代码如下:

java 复制代码
package cn.juwatech.jdrebate.service;

import cn.juwatech.jdrebate.mapper.OrderMapper;
import cn.juwatech.jdrebate.mapper.RebateMapper;
import cn.juwatech.jdrebate.dto.OrderDTO;
import cn.juwatech.jdrebate.dto.RebateDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;

/**
 * 数据一致性校验组件(定时对账+补偿)
 */
@Component
public class ConsistencyCheckService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private RebateMapper rebateMapper;
    @Autowired
    private RebateService rebateService;

    /**
     * 每小时执行一次订单-返利对账(0 0 * * * ?)
     */
    @Scheduled(cron = "0 0 * * * ?")
    public void checkOrderRebateConsistency() {
        // 1. 查询近1小时已完成但未返利的订单
        List<OrderDTO> unRebateOrders = orderMapper.selectUnRebateOrders(
                System.currentTimeMillis() - 3600000L,
                System.currentTimeMillis()
        );
        // 2. 对账并补偿
        for (OrderDTO order : unRebateOrders) {
            RebateDTO rebate = rebateMapper.selectByOrderId(order.getOrderId());
            if (rebate == null) {
                // 返利记录缺失,触发补偿
                rebateService.compensateRebate(order);
            }
        }
    }
}

四、关键技术优化

  1. 分库分表路由:采用Sharding-JDBC对订单表按用户ID哈希分片,解决单表数据量过大问题;
  2. 缓存一致性保障:商品数据更新时,先更新MySQL再删除Redis缓存(避免缓存脏读);
  3. 失败重试策略:基于RocketMQ重试队列,设置3次重试,失败后存入死信队列人工处理。

本文著作权归聚娃科技省赚客app开发者团队,转载请注明出处!

相关推荐
失散132 小时前
分布式专题——10.3 ShardingSphere实现原理以及内核解析
java·分布式·架构·shardingsphere·分库分表
Cloud Traveler2 小时前
第3天-Jenkins详解-3
运维·分布式·jenkins
在未来等你3 小时前
Elasticsearch面试精讲 Day 16:索引性能优化策略
大数据·分布式·elasticsearch·搜索引擎·面试
北极光SD-WAN组网3 小时前
某光伏电力监控系统网络安全监测项目:智能组网技术优化方案实践
大数据·网络·分布式
a587694 小时前
Spring Cloud Gateway:下一代API网关的深度解析与实战指南
java·分布式·网关
潘达斯奈基~4 小时前
kafka:【2】工作原理
大数据·分布式·kafka
励志成为糕手4 小时前
Kafka架构:构建高吞吐量分布式消息系统的艺术
分布式·架构·kafka·消息中间件·数据流处理
小句5 小时前
RabbitMQ对接MQTT消息发布指南
分布式·rabbitmq·ruby
u0104058365 小时前
霸王餐返利app的分布式架构设计:基于事件驱动的订单处理系统
分布式