本文目录导读:

我来为你详细介绍Java增量同步的开发案例,包括几种常见的实现方式。
基于时间戳的增量同步
实现方案
/**
* 数据变更记录实体
*/
@Data
public class ChangeRecord {
private Long id;
private String tableName;
private String recordId;
private String operationType; // INSERT, UPDATE, DELETE
private String changeData; // JSON格式的变更数据
private Date createTime;
private Integer syncStatus; // 0-未同步, 1-已同步
private Integer version;
}
/**
* 增量同步服务
*/
@Service
public class IncrementalSyncService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 获取待同步数据
*/
public List<ChangeRecord> getPendingSyncRecords(Date lastSyncTime, int batchSize) {
String sql = "SELECT * FROM change_records WHERE create_time > ? AND sync_status = 0 " +
"ORDER BY create_time ASC LIMIT ?";
return jdbcTemplate.query(sql, new Object[]{lastSyncTime, batchSize},
(rs, rowNum) -> {
ChangeRecord record = new ChangeRecord();
record.setId(rs.getLong("id"));
record.setTableName(rs.getString("table_name"));
record.setRecordId(rs.getString("record_id"));
record.setOperationType(rs.getString("operation_type"));
record.setChangeData(rs.getString("change_data"));
record.setCreateTime(rs.getDate("create_time"));
return record;
});
}
/**
* 执行增量同步
*/
public SyncResult performSync(SyncConfig config) {
SyncResult result = new SyncResult();
result.setStartTime(new Date());
try {
// 1. 获取上次同步时间
Date lastSyncTime = getLastSyncTime(config.getSyncKey());
// 2. 批量获取待同步数据
List<ChangeRecord> records = getPendingSyncRecords(
lastSyncTime, config.getBatchSize());
// 3. 执行同步
for (ChangeRecord record : records) {
boolean success = syncSingleRecord(record, config);
if (success) {
markAsSynced(record.getId());
result.addSuccessRecord(record.getRecordId());
} else {
result.addFailedRecord(record.getRecordId());
}
}
// 4. 更新同步时间戳
updateSyncTime(config.getSyncKey(), result.getStartTime());
result.setEndTime(new Date());
result.setSuccess(true);
} catch (Exception e) {
result.setSuccess(false);
result.setErrorMessage(e.getMessage());
log.error("增量同步失败", e);
}
return result;
}
/**
* 同步单条记录
*/
private boolean syncSingleRecord(ChangeRecord record, SyncConfig config) {
try {
switch (record.getOperationType()) {
case "INSERT":
return insertToTarget(record, config);
case "UPDATE":
return updateToTarget(record, config);
case "DELETE":
return deleteFromTarget(record, config);
default:
log.warn("未知操作类型: {}", record.getOperationType());
return false;
}
} catch (Exception e) {
log.error("同步记录失败: {}", record.getRecordId(), e);
return false;
}
}
private Date getLastSyncTime(String syncKey) {
String cacheKey = "sync:last_time:" + syncKey;
Object value = redisTemplate.opsForValue().get(cacheKey);
if (value != null) {
return (Date) value;
}
// 默认从30天前开始
return DateUtils.addDays(new Date(), -30);
}
private void updateSyncTime(String syncKey, Date syncTime) {
String cacheKey = "sync:last_time:" + syncKey;
redisTemplate.opsForValue().set(cacheKey, syncTime, 7, TimeUnit.DAYS);
}
}
基于版本号的增量同步
/**
* 数据版本管理
*/
@Component
public class VersionBasedSyncManager {
@Autowired
private DataSource dataSource;
private final ConcurrentHashMap<String, AtomicLong> versionMap = new ConcurrentHashMap<>();
/**
* 获取数据版本
*/
public Long getCurrentVersion(String tableName) {
return versionMap.computeIfAbsent(tableName, k -> {
try {
String sql = "SELECT MAX(version) FROM " + tableName;
return new AtomicLong(queryMaxVersion(sql));
} catch (Exception e) {
return new AtomicLong(0L);
}
}).get();
}
/**
* 基于版本增量同步
*/
public List<Map<String, Object>> syncByVersion(String tableName,
Long lastVersion,
int batchSize) {
String sql = "SELECT * FROM " + tableName +
" WHERE version > ? ORDER BY version ASC LIMIT ?";
return jdbcTemplate.queryForList(sql, lastVersion, batchSize);
}
/**
* 批量版本同步
*/
@Transactional
public BatchSyncResult batchVersionSync(String sourceTable,
String targetTable,
Long lastSyncVersion) {
BatchSyncResult result = new BatchSyncResult();
while (true) {
// 获取增量数据
List<Map<String, Object>> records = syncByVersion(
sourceTable, lastSyncVersion, 1000);
if (records.isEmpty()) {
break;
}
// 批量写入目标表
int batchSize = batchInsertToTarget(targetTable, records);
result.addProcessed(batchSize);
// 更新版本号
lastSyncVersion = records.get(records.size() - 1)
.get("version") instanceof Long ?
(Long) records.get(records.size() - 1).get("version") :
Long.parseLong(records.get(records.size() - 1).get("version").toString());
}
result.setLastSyncVersion(lastSyncVersion);
return result;
}
}
基于CDC的增量同步(使用Debezium)
/**
* Debezium CDC配置
*/
@Configuration
public class CdcSyncConfig {
@Bean
public DebeziumEngine<ChangeEvent<String, String>> debeziumEngine() {
Properties props = new Properties();
props.setProperty("name", "engine");
props.setProperty("connector.class",
"io.debezium.connector.mysql.MySqlConnector");
props.setProperty("offset.storage",
"org.apache.kafka.connect.storage.FileOffsetBackingStore");
props.setProperty("offset.storage.file.filename",
"/tmp/offsets.dat");
props.setProperty("offset.flush.interval.ms", "60000");
props.setProperty("database.hostname", "localhost");
props.setProperty("database.port", "3306");
props.setProperty("database.user", "user");
props.setProperty("database.password", "password");
props.setProperty("database.server.id", "85744");
props.setProperty("database.server.name", "my-app-connector");
props.setProperty("database.history",
"io.debezium.relational.history.FileDatabaseHistory");
props.setProperty("database.history.file.filename",
"/tmp/dbhistory.dat");
props.setProperty("table.whitelist", "mydb.users,mydb.orders");
props.setProperty("database.history.skip.unparseable.ddl", "true");
props.setProperty("decimal.handling.mode", "string");
return DebeziumEngine.create(ChangeEventFormat.of(Connect.class))
.using(props)
.notifying(this::handleChangeEvent)
.build();
}
private void handleChangeEvent(ChangeEvent<String, String> event) {
SourceRecord sourceRecord = (SourceRecord) event.value();
Struct sourceStruct = (Struct) sourceRecord.value();
// 解析变更事件
String operation = sourceStruct.getString("op");
Struct after = sourceStruct.getStruct("after");
Struct before = sourceStruct.getStruct("before");
// 根据操作类型处理
switch (operation) {
case "c": // 创建
handleCreate(after);
break;
case "u": // 更新
handleUpdate(before, after);
break;
case "d": // 删除
handleDelete(before);
break;
}
}
}
完整的增量同步框架
/**
* 增量同步配置
*/
@Data
public class SyncConfig {
private String syncKey; // 同步标识
private String sourceTable; // 源表
private String targetTable; // 目标表
private String primaryKey; // 主键字段
private String timestampField; // 时间戳字段
private String versionField; // 版本字段
private int batchSize = 1000; // 批量大小
private int retryCount = 3; // 重试次数
private long retryInterval = 1000L; // 重试间隔ms
}
/**
* 增量同步执行器
*/
@Component
public class IncrementalSyncExecutor {
@Autowired
private ApplicationContext applicationContext;
private final Map<String, SyncTask> runningTasks = new ConcurrentHashMap<>();
/**
* 启动同步任务
*/
public void startSync(SyncConfig config) {
SyncTask task = new SyncTask(config, applicationContext);
runningTasks.put(config.getSyncKey(), task);
Thread syncThread = new Thread(task, "sync-" + config.getSyncKey());
syncThread.start();
}
/**
* 停止同步任务
*/
public void stopSync(String syncKey) {
SyncTask task = runningTasks.get(syncKey);
if (task != null) {
task.stop();
runningTasks.remove(syncKey);
}
}
/**
* 同步任务
*/
private class SyncTask implements Runnable {
private final SyncConfig config;
private final IncrementalSyncService syncService;
private volatile boolean running = true;
public SyncTask(SyncConfig config, ApplicationContext context) {
this.config = config;
this.syncService = context.getBean(IncrementalSyncService.class);
}
@Override
public void run() {
while (running) {
try {
SyncResult result = syncService.performSync(config);
if (!result.isSuccess()) {
log.error("同步失败: {}", result.getErrorMessage());
// 失败重试
handleSyncFailure(result);
}
// 间隔时间
Thread.sleep(config.getRetryInterval());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
log.error("同步异常", e);
}
}
}
public void stop() {
this.running = false;
}
}
}
使用示例
@RestController
@RequestMapping("/sync")
public class SyncController {
@Autowired
private IncrementalSyncExecutor syncExecutor;
@Autowired
private IncrementalSyncService syncService;
/**
* 手动触发增量同步
*/
@PostMapping("/manual")
public ApiResponse manualSync(@RequestBody SyncRequest request) {
SyncConfig config = new SyncConfig();
config.setSyncKey(request.getSyncKey());
config.setSourceTable(request.getSourceTable());
config.setTargetTable(request.getTargetTable());
config.setTimestampField("update_time");
config.setBatchSize(1000);
SyncResult result = syncService.performSync(config);
return ApiResponse.success(result);
}
/**
* 启动自动同步
*/
@PostMapping("/start")
public ApiResponse startAutoSync(@RequestBody SyncConfig config) {
syncExecutor.startSync(config);
return ApiResponse.success("同步任务已启动");
}
/**
* 停止自动同步
*/
@PostMapping("/stop/{syncKey}")
public ApiResponse stopAutoSync(@PathVariable String syncKey) {
syncExecutor.stopSync(syncKey);
return ApiResponse.success("同步任务已停止");
}
}
选择合适的同步策略:
- 时间戳方式:简单易实现,适合数据量不大的场景
- 版本号方式:支持乐观锁,适合并发场景
- CDC方式:实时性高,适合大数据量场景
- 混合方式:结合多种策略,应对复杂需求
关键考虑点:
- 数据一致性:使用事务保证同步的完整性
- 性能优化:批量处理、异步处理
- 错误处理:重试机制、死信队列
- 监控告警:同步延迟、失败记录监控
根据你的业务场景选择合适的同步策略,并做好异常处理和监控。