冗余数据同步方案

商品用户客户员工店铺仓库供应商这些数据修改后,需要同步到历史快照订单,发货单,订货单,采购单,调拨单,出库入库单等等....,之前的逻辑是直接在update接口里面加上synchronousUpdate方法,同步更新对应的表,有时候一个业务的update可能对应二十多张目标表,完全耦合在了业务里,接口执行的效率和代码的可维护性都不强

生产是在Handler里做的(THandler)

把这套逻辑通过canal监控的模式拆分出来,监控我们对应的原表,完全与业务进行解耦合通过@CanalTable注解监控对应的表,实现EntryHandler<T>,重写对应的update方法,这样原表一修改以后,canal就会来调我们的update方法,canal会在update方法里给我们 (T before,T after)的入参,这样我们就能拿到原表修改前后修改后的数据

通过一个通用的工具类EnableCompareUtils,通过getClass().getName()方法拿到className,去查询我们的redundant_source表,redundant_source表name记录了同步业务名称,sync_field记录了我们需要监控的字段,比如table_name:customer name:客户信息同步 sync_field:["name","phone"] 表示监控客户信息表customer的name,phone字段

从canal的容器CanalContext中拿到原表中实际更新过的字段updateField,用syncFields.contains(uf)比较updateField中哪些字段需要做冗余同步如果有的话取到SyncId向redundant_update表中插入一条数据同步状态为WAIT(1,"待同步")

自此生产过程结束

消费是在redundant-sync模块中做的

ScheduledTasks每100ms去redundant_update表中拉一条数据,把同步状态改为SYNCHRONIZING(2,"同步中"),之后通过

ApplicationEventPublisher去发布事件,监听者RedundantUpdateListener通过查询redundant_target表找出全部需要冗余同步的表,去做实际的更新逻辑,redundant_target表中的set_params/search_params是json格式,通过sqlAssemble去解析并交给dynamicService在Mapper层拼接sql动态更新具体的表,更新成功后把同步状态改为SUCCESS(3,"同步成功"),如果更新的过程报错了,用catch语句块捕获住异常,把同步状态改为FAIR(4,"同步失败"),最后把日志记录到redundant_sync_log表里,这张表记录了set_str,search_str,sync_date,source_id,target_id_update_id等信息,方便我们后期排查维护

自此消费过程结束

yeb-canal-support模块:生产者

引入canal监听对应表的binlog日志

创建TableNameHandler类实现canal-client 提供的 EntryHandler<T> 类来实现对于数据表的监控,从而达到数据的增删改同步

通过@CanalTable(value = "table_name")指定监控的表

@Component
@Slf4j
@CanalTable(value = "goods_info")
public class GoodsInfoHandler implements EntryHandler<GoodsInfo> {
    @Autowired
    private EnableCompareUtils enableCompareUtils;

    @Override
    public void insert(GoodsInfo goodsInfo) {
        log.info("insert message  {}", goodsInfo);
    }

    @Override
    public void update(GoodsInfo before, GoodsInfo after) {
        log.info("update before {} ", before);
        log.info("update after {}", after);
        enableCompareUtils.dataSync(before,after);
    }



    @Override
    public void delete(GoodsInfo goodsInfo) {
        log.info("delete  {}", goodsInfo);
    }


}

数据库主表更新后,canal会监听到并走update的逻辑

通过自定义的EnableCompareUtils工具类调用dataSyn方法

public void dataSync(Object before, Object after) {
  try {
    //拿到data数据
    List<Map<String, String>> data = CanalContext.getModel().getData();
    //有数据才记录
    if (CollectionUtil.isNotEmpty(data) && data.size() > 1) {
      //拿到原表修改的字段
      Set<String> updateField = data.get(1).keySet();
      //更新后的字段数据
      Map<String, String> afterDataMap = data.get(0);
      //拿到更新表的实体对象
      Class<?> afterClass = after.getClass();
      //通过class从数据库查询出冗余源对象
      RedundantSourceDTO redundantSourceDTO = redundantSourceReadFacade.getByClassName(afterClass.getName());
      //判断该class是否有冗余
      if (redundantSourceDTO != null && ObjectUtil.isNotEmpty(redundantSourceDTO.getSyncField())) {
        //有冗余
        //拿到需要更新的字段
        List<String> syncFields = JSON.parseArray(redundantSourceDTO.getSyncField(), String.class);
        for (String uf : updateField) {
          //判断原表修改的字段需要冗余的字段拿出来
          if (syncFields.contains(uf)) {
            //组装数据
            RedundantSyncAO redundantSyncAO = new RedundantSyncAO();
            redundantSyncAO.setSyncId(Long.valueOf(afterDataMap.get(ID)));
      redundantSyncAO.setTenantId(Long.valueOf(afterDataMap.get(TENANT_ID)));
            redundantSyncAO.setClassName(afterClass.getName());
            //增加一条记录到redundant_update表里,等待定时任务监控
            //同步状态为WAIT(1,"待同步")
            Boolean save = redundantUpdateWriteFacade.save(redundantSyncAO);
            log.info("EnableCompareUtils dataSync save状态={}", save);
            break;
          }
        }
      }
    }
  } catch (Exception e) {
    log.error("EnableCompareUtils dataSync error", e);
  }
}

从canal的容器CanalContext里拿到data数据

//拿到data数据
    List<Map<String, String>> data = CanalContext.getModel().getData();

/**
 * FlatMessage形式维护
 * before and after数据 index:0-after;1-before
 */
private List<Map<String, String>> data;

redundant_sync模块:消费者

@Component
public class ScheduledTasks {

    @Resource
    private SyncService syncService;

    @Resource
    private RedundantUpdateService redundantUpdateService;

    @Scheduled(fixedRate = 100)
    public void syncData() {
      //根据优先级排序,把redundant_update表里状态为WAIT的一条一条拿出来
      //状态改为SYNCHRONIZING(2,"同步中")
        RedundantSync syncDate = redundantUpdateService.getSyncData();
        if (syncDate != null) {
          //同步逻辑
            syncService.getById(syncDate);
        }
    }
}

ScheduledTasks起一个定时任务,100ms执行一次

    @Override
    public void getById(RedundantSync redundantSync) {
      //动态根据数据库名,表名,ID,查询最新的数据
        Map<String, Object> byIdDynamic = dynamicService.getByIdDynamic(redundantSync);
        if (CollectionUtil.isNotEmpty(byIdDynamic)) {
            redundantSync.setSourceMap(byIdDynamic);
        }
        //基于spring,发布事件
        this.publishSearchEvent(redundantSync);
    }

发布事件

@Component
@Slf4j
//@Async
public class RedundantUpdateListener implements ApplicationListener<RedundantUpdateEvent> {

    @Resource
    private SyncService syncService;

    @Override
    public void onApplicationEvent(@NonNull RedundantUpdateEvent event) {
        log.info("同步信息!  {}", JSONObject.toJSONString(event));
        log.info("发送消息内容:{}", JSONObject.toJSONString(event.getRedundantSync()));
        RedundantSync redundantSync = event.getRedundantSync();
        syncService.select(redundantSync);
    }
}

RedundantUpdateListener监听

@Override
public void select(RedundantSync redundantSync) {
    RedundantUpdateDO redundantUpdateDO = new RedundantUpdateDO();
    redundantUpdateDO.setId(redundantSync.getId());
    //通过RedundantSourceId拿到所有的目标表信息
    List<Target> targetDOList = redundantTargetService.listByRedundantSourceId(redundantSync.getRedundantSourceId());
    if (CollectionUtil.isEmpty(targetDOList)){
      //没有目标表,更新状态设置为成功
        redundantUpdateDO.setStatus(RedundantUpdateStatusEnum.SUCCESS.getCode());
    }
    //原表最新数据
    Map<String, Object> objMap = redundantSync.getSourceMap();
    //对每张需要冗余更新的目标表进行操作
    for (Target target : targetDOList) {
        RedundantSyncLogDO logDO = getRedundantSyncLogDO(redundantSync, target);
        try {
          //更新SQL组装
            //查询数据组装
            String searchParams = target.getSearchParams();
            String searchStr = sqlAssemble(objMap, searchParams, AND);
            logDO.setSearchStr(searchStr);
            //更新数据组装
            String setParams = target.getSetParams();
            String setStr = sqlAssemble(objMap, setParams, COMMA);
            target.setSetParams(setStr);
            logDO.setSetStr(setStr);
            Long lastId = null;
          //记录时间
            StopWatch searchWatch = new StopWatch(target.getTableName());
            StopWatch syncWatch= new StopWatch(target.getTableName());
          //统计更新数量
            long total = 0L;
            while (true) {
              //通过searchStr查询目标表需要修改的IDS
                List<Long> updateIds = dynamicService.selectDynamic(target, lastId, searchStr, redundantSync.getPageSize(),searchWatch);
                if (CollectionUtil.isEmpty(updateIds)) {
                    break;
                }
                lastId = updateIds.get(updateIds.size() - 1);
                total = total + updateIds.size();
              //真正的同步更新逻辑
                this.sync(target, updateIds,syncWatch);
                if (updateIds.size() < redundantSync.getPageSize()) {
                    break;
                }
            }
            if (redundantUpdateDO.getStatus() == null || RedundantUpdateStatusEnum.SUCCESS.getCode().equals(redundantUpdateDO.getStatus())) {
                redundantUpdateDO.setStatus(RedundantUpdateStatusEnum.SUCCESS.getCode());
            }
            logDO.setSearchDate(searchWatch.getTotalTimeSeconds());
            logDO.setSyncDate(syncWatch.getTotalTimeSeconds());
            logDO.setTotalDate(logDO.getSyncDate() + logDO.getSearchDate());
            logDO.setTotal(total);
        } catch (Exception e) {
            e.printStackTrace();
          //异常,FAIR(4,"同步失败");
            redundantUpdateDO.setStatus(RedundantUpdateStatusEnum.FAIR.getCode());
            logDO.setError(e.getMessage());
            logDO.setErrorDate(new Date());
        }
      //记录日志,redundant_sync_log
        redundantSyncLogService.save(logDO);
    }
      //更新redundant_update表
    redundantUpdateService.updateById(redundantUpdateDO);
}
    @Override
    public void sync(Target target, List<Long> idList,StopWatch stopWatch) {
        if (CollectionUtil.isEmpty(idList)) {
            return;
        }
      //更新目标表
        dynamicService.updateDynamic(target, idList,stopWatch);
    }
        UPDATE
            ${databaseName}.${tableName}
        SET
            ${setParams}
        WHERE id in
        <foreach collection="idList" separator="," open="(" index="index" item="id" close=")">
            #{id}
        </foreach>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值