SpringBoot定時(shí)任務(wù)實(shí)現(xiàn)數(shù)據(jù)庫(kù)數(shù)據(jù)同步全過(guò)程
一、技術(shù)方案選型
1. 核心組件
- Spring Scheduler:輕量級(jí)定時(shí)任務(wù)框架
- Spring Data JPA:數(shù)據(jù)庫(kù)操作(可替換為MyBatis)
- Quartz:復(fù)雜調(diào)度需求(集群/持久化)
- Spring Batch:大批量數(shù)據(jù)處理
2. 架構(gòu)示意圖
[定時(shí)觸發(fā)器] -> [數(shù)據(jù)抽取] -> [數(shù)據(jù)轉(zhuǎn)換] -> [數(shù)據(jù)加載] -> [結(jié)果通知]
二、基礎(chǔ)實(shí)現(xiàn)步驟
1. 添加依賴
<!-- Spring Boot Starter Web (包含Scheduler) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 數(shù)據(jù)庫(kù)驅(qū)動(dòng)(示例使用MySQL) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>2. 啟用定時(shí)任務(wù)
@SpringBootApplication
@EnableScheduling
public class DataSyncApplication {
public static void main(String[] args) {
SpringApplication.run(DataSyncApplication.class, args);
}
}3. 實(shí)現(xiàn)定時(shí)任務(wù)類
@Component
public class DataSyncScheduler {
private static final Logger logger = LoggerFactory.getLogger(DataSyncScheduler.class);
@Autowired
private SourceRepository sourceRepo;
@Autowired
private TargetRepository targetRepo;
// 每天凌晨1點(diǎn)執(zhí)行
@Scheduled(cron = "0 0 1 * * ?")
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public void syncDataDaily() {
try {
logger.info("開始數(shù)據(jù)同步任務(wù)...");
// 1. 獲取增量數(shù)據(jù)
LocalDateTime lastSyncTime = getLastSyncTime();
List<SourceEntity> newData = sourceRepo.findByUpdateTimeAfter(lastSyncTime);
// 2. 數(shù)據(jù)轉(zhuǎn)換
List<TargetEntity> transformedData = transformData(newData);
// 3. 批量保存
targetRepo.saveAll(transformedData);
// 4. 更新同步時(shí)間
updateLastSyncTime(LocalDateTime.now());
logger.info("數(shù)據(jù)同步完成,處理記錄數(shù):{}", newData.size());
} catch (Exception e) {
logger.error("數(shù)據(jù)同步任務(wù)異常:", e);
// 添加重試或報(bào)警邏輯
}
}
// 數(shù)據(jù)轉(zhuǎn)換方法
private List<TargetEntity> transformData(List<SourceEntity> sourceList) {
return sourceList.stream()
.map(entity -> new TargetEntity(
entity.getId(),
entity.getName(),
entity.getData(),
LocalDateTime.now()))
.collect(Collectors.toList());
}
// 獲取上次同步時(shí)間(示例)
private LocalDateTime getLastSyncTime() {
// 可從數(shù)據(jù)庫(kù)或緩存獲取
return LocalDateTime.now().minusDays(1);
}
// 更新同步時(shí)間
private void updateLastSyncTime(LocalDateTime time) {
// 持久化存儲(chǔ)邏輯
}
}三、其他優(yōu)化方案
1. 分布式鎖機(jī)制
// 使用Redis實(shí)現(xiàn)分布式鎖
@Scheduled(cron = "${sync.cron}")
public void distributedSyncTask() {
String lockKey = "data_sync_lock";
String requestId = UUID.randomUUID().toString();
try {
// 嘗試獲取鎖
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId, 30, TimeUnit.MINUTES);
if (locked) {
// 執(zhí)行同步邏輯
performSync();
}
} finally {
// 釋放鎖
if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
}2. 分頁(yè)批量處理
private void batchSyncWithPagination() {
int pageSize = 1000;
int page = 0;
Page<SourceEntity> dataPage;
do {
dataPage = sourceRepo.findAll(PageRequest.of(page, pageSize));
List<TargetEntity> targetList = transformData(dataPage.getContent());
targetRepo.saveAll(targetList);
page++;
} while (dataPage.hasNext());
}3. 事務(wù)優(yōu)化配置
# application.yml
spring:
jpa:
properties:
hibernate:
jdbc:
batch_size: 500
order_inserts: true
order_updates: true4. 性能監(jiān)控配置
@Aspect
@Component
public class SyncMonitorAspect {
@Around("@annotation(org.springframework.scheduling.annotation.Scheduled)")
public Object monitorTask(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long duration = System.currentTimeMillis() - start;
Metrics.timer("sync.task.duration")
.tag("task", joinPoint.getSignature().getName())
.record(duration, TimeUnit.MILLISECONDS);
}
}
}四、異常處理策略
1. 重試機(jī)制
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 5000))
public void performSync() {
// 同步邏輯
}
@Recover
public void recoverSync(Exception e) {
// 報(bào)警通知
alertService.sendAlert("數(shù)據(jù)同步失敗:" + e.getMessage());
}2. 死信隊(duì)列處理
// 使用Spring Retry + RabbitMQ實(shí)現(xiàn)
@Bean
public RetryOperationsInterceptor retryInterceptor() {
return RetryInterceptorBuilder.stateless()
.maxAttempts(3)
.backOffOptions(1000, 2.0, 5000)
.recoverer(new RepublishMessageRecoverer(rabbitTemplate, "dead-letter-exchange"))
.build();
}五、生產(chǎn)環(huán)境建議
- 配置中心管理:將cron表達(dá)式放在配置中心實(shí)現(xiàn)動(dòng)態(tài)調(diào)整
- 多數(shù)據(jù)源配置:使用AbstractRoutingDataSource實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換
- 版本控制:維護(hù)數(shù)據(jù)版本號(hào)實(shí)現(xiàn)冪等同步
- 數(shù)據(jù)校驗(yàn):添加MD5校驗(yàn)機(jī)制保證數(shù)據(jù)一致性
- 監(jiān)控告警:集成Prometheus + Grafana實(shí)現(xiàn)可視化監(jiān)控
六、完整配置示例
# application.properties # 定時(shí)任務(wù)配置 sync.cron=0 0 2 * * * sync.batch-size=1000 sync.max-retry=3 # 數(shù)據(jù)源配置 spring.datasource.source.url=jdbc:mysql://source-db:3306/db spring.datasource.source.username=user spring.datasource.source.password=pass spring.datasource.target.url=jdbc:mysql://target-db:3306/db spring.datasource.target.username=user spring.datasource.target.password=pass
七、常見問(wèn)題排查
任務(wù)未執(zhí)行:
- 檢查@EnableScheduling是否啟用
- 確認(rèn)cron表達(dá)式格式正確
- 查看線程池配置
數(shù)據(jù)不一致:
- 檢查事務(wù)隔離級(jí)別
- 驗(yàn)證數(shù)據(jù)轉(zhuǎn)換邏輯
- 添加數(shù)據(jù)校驗(yàn)機(jī)制
性能瓶頸:
- 優(yōu)化SQL查詢(添加索引)
- 調(diào)整批量提交大小
- 增加JVM內(nèi)存分配
內(nèi)存溢出:
- 使用分頁(yè)查詢代替全量加載
- 優(yōu)化對(duì)象重用機(jī)制
- 增加JVM堆內(nèi)存
建議使用Arthas進(jìn)行運(yùn)行時(shí)診斷:https://arthas.aliyun.com
總結(jié)
通過(guò)以上方案,可以實(shí)現(xiàn)從簡(jiǎn)單到企業(yè)級(jí)的數(shù)據(jù)庫(kù)同步需求。實(shí)際應(yīng)用中應(yīng)根據(jù)數(shù)據(jù)量級(jí)、同步頻率和業(yè)務(wù)需求選擇合適的實(shí)現(xiàn)策略,并建立完善的監(jiān)控告警體系。
這些僅為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java開發(fā)工作中對(duì)InheritableThreadLocal使用思考
這篇文章主要為大家介紹了java開發(fā)工作中對(duì)InheritableThreadLocal使用思考詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
Java數(shù)據(jù)結(jié)構(gòu)貪心算法的實(shí)現(xiàn)
本文主要介紹了Java數(shù)據(jù)結(jié)構(gòu)貪心算法的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2007-03-03
java處理異常的機(jī)制關(guān)鍵字throw和throws使用解析
這篇文章主要介紹了java處理異常的機(jī)制關(guān)鍵字throw和throws使用解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09
解決IDEA Maven下載依賴時(shí)報(bào)錯(cuò)ERROR - #org.jetbrains.ide
這篇文章主要介紹了解決IDEA Maven下載依賴時(shí)報(bào)錯(cuò)ERROR - #org.jetbrains.idea.maven - Cannot reconnect.問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
Spring JDK動(dòng)態(tài)代理實(shí)現(xiàn)過(guò)程詳解
這篇文章主要介紹了Spring JDK動(dòng)態(tài)代理實(shí)現(xiàn)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02

