开源一个超好用的数据核对/对账框架

项目地址:github.com/starslink/d…

🚀 高性能、多策略的数据对账框架,支持内存、流式、并行、Redis、数据库等多种处理方式

✨ 特性

  • 🎯 多种对账策略 - 内存、流式、并行、Redis、数据库、增量处理
  • 🔧 灵活配置 - 支持注解和适配器两种数据定义方式
  • 📊 性能优化 - 根据数据量自动选择最优处理策略
  • 🌐 分布式支持 - Redis分布式缓存,支持集群部署
  • 💾 内存友好 - 流式处理避免大数据量内存溢出
  • 高并发 - 多线程并行处理,充分利用CPU资源

🚀 快速开始

1. 添加依赖


<dependency>
  <groupId>com.starslink</groupId>
  <artifactId>data-check</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>

2. 基础使用

// 创建对账配置
CheckConfig config = CheckConfig.builder()
        .id("order-check")
        .name("订单对账")
        .srcLoader(date -> loadSourceData(date))      // 源数据加载器
        .targetLoader(date -> loadTargetData(date))   // 目标数据加载器
        .allowableErrorRange(BigDecimal.valueOf(0.01)) // 允许误差
        .checkAfter(context -> {
          // 处理对账结果
          System.out.println("发现差异: " + context.getCheckResult().getDiffDetails().size());
        })
        .build();

// 执行对账
CheckExecutor executor = CheckExecutor.buildExecutor(config);
CheckContext result = executor.process("2023-12-01");

3. 数据实体定义

方式一:使用注解

public class Order {

  @CheckIdentity
  private String orderId;

  @CheckField(order = 1)
  private BigDecimal amount;

  @CheckField(order = 2)
  private String status;
}

方式二:实现适配器

public class OrderAdapter implements CheckAdapter {

  @Override
  public String getKey() {
    return orderId;
  }

  @Override
  public Map<String, Object> getCheckData() {
    return MapUtil.of("amount", amount, "status", status);
  }
}

📊 处理器选择

处理器类型适用数据量主要特点使用场景
Memory< 10万全内存,速度最快小数据量,实时对账
Stream10万-100万分批处理,内存友好大数据量,防止OOM
Parallel任意多线程并行CPU密集型,追求速度
Redis任意分布式缓存集群环境,数据共享
Database> 100万SQL优化超大数据量
Incremental任意增量处理定期对账,减少计算

🔧 高级配置

流式处理(推荐大数据量)

CheckConfig config = CheckConfig.builder()
    .id("stream-check")
    .batchSize(5000)  // 批次大小
    .checkProcessor(new StreamCheckProcessor())
    .build();

并行处理(推荐多核CPU)

CheckConfig config = CheckConfig.builder()
    .id("parallel-check")
    .isAsync(true)
    .checkProcessor(new ParallelCheckProcessor())
    .build();

Redis分布式处理

CheckConfig config = CheckConfig.builder()
    .id("redis-check")
    .checkCacheEnum(CheckCacheEnum.REDIS)
    .redisConfig(redisConfig)
    .build();

数据库处理(超大数据量)

CheckConfig config = CheckConfig.builder()
    .id("database-check")
    .checkProcessor(new DatabaseCheckProcessor(dataSource))
    .build();

增量处理

IncrementalDataProvider provider = new MyIncrementalDataProvider();
CheckConfig config = CheckConfig.builder()
    .id("incremental-check")
    .checkProcessor(new IncrementalCheckProcessor(provider))
    .build();

🎯 智能选择策略

public CheckProcessor selectOptimalProcessor(int dataSize) {
  if (dataSize < 10_000) {
    return new MemoryCheckProcessor();           // 小数据量
  } else if (dataSize < 100_000) {
    return new ParallelCheckProcessor();         // 中等数据量
  } else if (dataSize < 1_000_000) {
    return new StreamCheckProcessor();           // 大数据量
  } else {
    return new DatabaseCheckProcessor(dataSource); // 超大数据量
  }
}

📈 性能对比

数据量: 100万条记录测试结果

Memory处理器:    ❌ OutOfMemoryError
Stream处理器:    ✅ 45秒, 内存占用: 200MB
Parallel处理器:  ✅ 28秒, 内存占用: 800MB  
Database处理器:  ✅ 35秒, 内存占用: 50MB

🛠️ 完整示例


@Test
public void testOrderReconciliation() {
  // 模拟数据
  List<Order> sourceOrders = loadOrdersFromSystem1("2023-12-01");
  List<Order> targetOrders = loadOrdersFromSystem2("2023-12-01");

  // 配置对账
  CheckConfig config = CheckConfig.builder()
      .id("daily-order-check")
      .name("每日订单对账")
      .srcLoader(date -> CheckEntry.wrap(sourceOrders))
      .targetLoader(date -> CheckEntry.wrap(targetOrders))
      .allowableErrorRange(BigDecimal.valueOf(0.01))
      .checkProcessor(selectOptimalProcessor(sourceOrders.size()))
      .checkPre(context -> validateData(context))
      .checkAfter(context -> {
        CheckResult result = context.getCheckResult();
        generateReport(result);
        sendNotification(result);
      })
      .build();

  // 执行对账
  CheckExecutor executor = CheckExecutor.buildExecutor(config);
  CheckContext result = executor.process("2023-12-01");

  // 输出结果
  System.out.println("对账完成:");
  System.out.println("- 源数据: " + result.getSource().size());
  System.out.println("- 目标数据: " + result.getTarget().size());
  System.out.println("- 差异记录: " + result.getCheckResult().getDiffDetails().size());
}