我是怎么用 Redis 把 ERP→CRM 同步提速到“几秒钟”的

我这边有一套比较典型的架构:

  • 后端用 Java 微服务
  • 数据源是 ERP(类似用友 U8)
  • 客户那边用的是 CRM(类似纷享销客)

领导的需求也很朴素: "ERP 里的客户、订单啥的,定时同步到 CRM,保证那边数据是最新的。"

刚听的时候我也觉得这不就是个"搬运工"活吗?

结果一上手才发现:这里面特别容易搞成"又慢又占资源"的那种烂活。

所以我最后加了一层 Redis,中间做了一步"对比判断",直接把同步过程提速到"几秒级"。下面我就用第一人称好好讲一下整个过程。


一、最开始的方案:暴力全量,跑一次半天

最开始我写的就是大家最容易想到的那种:

  1. 定时任务触发
  2. 从 ERP 全量查表,比如查客户表 N 条记录
  3. 对每一条数据,去 CRM 那边查一下是不是存在、数据是否一致
  4. 不一致就调用接口更新,一致就算了

很快问题就来了:

  • ERP 那边数据量上来了,全表查询本身就有点吃力
  • CRM 的接口一条要几十毫秒到几百毫秒
  • N 一大,整体同步时间就是N × 接口耗时
  • 更要命的是:大部分数据其实根本没变,只是我每次都"重新认识它们一遍"

每天看着日志里一条条接口调用,我心里只有一个感觉:太浪费了


二、我想明白的那个瞬间:我只需要知道"谁变了"

有一天我在看日志的时候,突然意识到一个很简单的事实:

业务上每天真正变动的只有很少一部分数据,但技术上我却在全量折腾所有数据。

那我真正想要的,其实只有一个东西:
"在这么多 ERP 数据里,哪些是这次新变出来的?"

如果我能很快知道"谁变了",同步逻辑就可以变成:

  • 只对"变化的数据"调用 CRM 接口
  • 其他完全不动

所以核心问题其实不是"怎么同步",而是:

我要有一种本地的方式,记住"上一次这些数据长什么样",然后跟这一次做个对比。

想到这里,其实答案就出来了:用 Redis 记一份"指纹"。


三、加一层 Redis:给每条记录做一个"指纹"

我最后采用的是一套很简单的设计:
每条 ERP 记录生成一个摘要(hash),把这个摘要存到 Redis 里。

下一次同步的时候,再算一遍摘要,对比一下:

  • 如果 Redis 里没有这条记录 → 说明是新增
  • 如果有,但摘要不一样 → 说明被修改过
  • 如果摘要一样 → 当作没变化,直接跳过

1. 摘要怎么生成?

我这边是 Java,大概长这样:

javascript 复制代码
String buildDigest(ErpCustomer c) {
    String raw = c.getId()
        + "|" + c.getName()
        + "|" + c.getCategory()
        + "|" + c.getPhone()
        + "|" + c.getAddress()
        + "|" + c.getUpdateTime(); // 或者最后修改时间
    return DigestUtils.md5Hex(raw);
}

也就是把对业务有影响的字段串起来,算一个 MD5。

只要这些字段里有一个变了,hash 一定不一样。

2. Redis 里怎么存?

我用的是 hash 结构:

makefile 复制代码
key: 客户ID
Value: 这一条客户数据的 MD5 摘要

这样:

  • 根据客户 ID 可以 O(1) 拿到上次的摘要
  • Redis 本身就是内存级访问,毫秒级的

四、完整同步流程:ERP → Redis → CRM

我现在的同步流程大概是这样:

Step 1:从 ERP 全量查询

ini 复制代码
List<ErpCustomer> erpList = erpDao.queryAll();

全量查这一步是没法省的,但只是一条 SQL,速度还能接受。

Step 2:遍历数据,计算摘要

scss 复制代码
for (ErpCustomer c : erpList) {
    String newDigest = buildDigest(c);
    String oldDigest = redis.hget("erp:customer:hash", c.getId());
    
    if (oldDigest == null) {
        // 新增
        createToCrm(c);
        redis.hset("erp:customer:hash", c.getId(), newDigest);
    } else if (!newDigest.equals(oldDigest)) {
        // 有变更
        updateToCrm(c);
        redis.hset("erp:customer:hash", c.getId(), newDigest);
    } else {
        // 没变,直接跳过
    }
}

Step 3:处理 ERP 已删除的数据(可选)

如果你希望 ERP 删掉的,CRM 那边也删掉(或标记失效),还可以做一步:

  • 把这次 ERP 查询出来的所有 ID 放一个 Set 里
  • 再从 Redis 里把所有 Field(ID)拿出来
  • 不在 ERP 集合里的那些 ID,就当作"已经在 ERP 被删除了"

伪代码:

scss 复制代码
Set<String> erpIds = erpList.stream()
    .map(ErpCustomer::getId)
    .collect(Collectors.toSet());

Set<String> redisIds = redis.hkeys("erp:customer:hash");

for (String id : redisIds) {
    if (!erpIds.contains(id)) {
        // ERP 没了,但 Redis 里还有,说明被删除
        deleteInCrm(id);
        redis.hdel("erp:customer:hash", id);
    }
}

整体结构就变成:

arduino 复制代码
ERP(SQL Server)
    ↓ 全量查询
同步服务(Java)
    ↓ 生成摘要,对比 Redis
Redis(只存摘要)
    ↓ 只把新增/修改/删除的差异
CRM(纷享销客等)

五、实际效果:从"全量折磨"到"几秒钟搞定"

举一个比较接地气的数字(不是理论,是我这边真实体感):

  • ERP 客户表:5 万条
  • 每次变动:可能就几十条
  • CRM 接口耗时:假设 100ms 一次

原来的暴力方案:

  • 5 万条每条都要跟 CRM 打一次交道
  • 理论时间:5 万 × 100ms = 5,000,000ms ≈ 5,000 秒(一个多小时)
  • 实际上还有各种网络延迟、限流、重试,更难受

加了 Redis 之后:

  • 全量查 ERP:几十万行,几十到几百毫秒级(取决于表情况)
  • 遍历 5 万条生成 hash + 对比 Redis:都在内存里跑,非常快
  • 真正需要调 CRM 接口的:只有几十条

就算是 100 条更新 × 100ms,也就十来秒,加上外围逻辑,整体就是几秒到十几秒级别

最直观的变化是日志:

以前一大片接口调用刷屏,现在只剩下一小截"真正有必要发出去的请求",看着心情都好很多。


六、这套方案里我踩过的几个点

为了真实一点,这里说几个我自己遇到的问题:

1. hash 字段一定要选对

一开始我只用了部分字段,比如 name、phone 之类,后来发现有些"业务上很关键"的字段没被算进去(比如所属业务员、客户分类),导致这些字段改变了但 hash 没变,CRM 那边不会更新。

教训:

在设计摘要的时候,最好把所有会同步到 CRM 的字段都算进去,或者直接用"最后修改时间 + ID"的组合,前提是 ERP 能保证这个时间是可靠的。


2. CRM 调用失败时不要急着刷 Redis

有一点很关键:
只有当 CRM 调用成功了,才把新的 hash 写回 Redis。

否则会出现这种情况:

  • 我把摘要写进 Redis 了
  • 结果那次 CRM 调用其实失败了
  • 下次再同步时,程序会以为"一样,就跳过了",实际上 CRM 那边压根没更新成功

所以我的顺序是:

scss 复制代码
if (createOrUpdateCrmSuccess) {
    redis.hset(...); // 成功之后再写
} else {
    // 进重试队列/写错误日志
}

3. Redis 崩了一次怎么办?

Redis 本质上只是一个"加速对比"的缓存,不是唯一的真相来源。

如果哪天 Redis 崩了、丢数据了,我的策略也很简单粗暴:

重建一下 Key,让这一次同步当作"第一次同步",全量刷一遍 CRM。

这种情况对 CRM 影响就是那一次会比较慢,但数据不会错,只是开销稍微大点。


七、我现在是怎么用这套方案的?

目前我会把这套方案当成一个通用模板,不止是客户,可以用在很多地方:

  • ERP → CRM 的客户、商品、价格、库存
  • MES → 报表系统的数据
  • 甚至是两个内部系统之间的基础数据同步

基本套路都是:

  1. 确定主键
  2. 定义"会影响业务"的字段
  3. 生成摘要(fingerprint)
  4. 用 Redis 记录"上一次的 fingerprint"
  5. 每次同步时只处理 fingerprint 变化的数据

这套东西,说白了不是什么高大上的技术,就是一个很朴素但非常管用的"小心思"


最后

我一直觉得,像这种 ERP→CRM 的同步,看着是重复劳动,但里面其实有很多可以打磨的空间:

  • 你可以什么都不想,暴力查 + 暴力调接口,跑得慢就加机器
  • 也可以像这样,动一点点脑子,把"差异检测"前置到 Redis 这一层

我更喜欢第二种。

如果你现在手上也有类似"系统 A 同步数据到系统 B"的需求,而且同步特别慢、接口特别多,不妨试试这一套:

"全量扫描 + Redis 摘要对比 + 增量调用目标系统接口"。

实现不复杂,但效果往往会超出预期。

相关推荐
零日失眠者2 小时前
【Python好用到哭的库】pandas-数据分析神器
后端·python·ai编程
读书郎霍2 小时前
linux 从docker官网源码安装docker
后端
零日失眠者2 小时前
【Python好用到哭的库】numpy-数值计算基础
后端·python·ai编程
创新技术阁2 小时前
CryptoAiAdmin 项目后端启动过程详解
后端·python·fastapi
何中应2 小时前
【面试题-2】Java集合
java·开发语言·后端·面试题
a程序小傲2 小时前
scala中的Array
开发语言·后端·scala
coderCatIce2 小时前
JDK 动态代理
后端
kk哥88992 小时前
scala 介绍
开发语言·后端·scala
武子康2 小时前
大数据-181 Elasticsearch 段合并与磁盘目录拆解:Merge Policy、Force Merge、Shard 文件结构一文搞清
大数据·后端·elasticsearch