開發中會有場景需要對接第三方系統.第三方系統往往會提供一箇中間庫,然後我們系統讀取中間庫的數據,然後經過一系列的邏輯,把數據存到自己系統中. 在這裏分享一種通過生產者,消費者模式進行數據同步,接近實時
主要思路: 一個生產者線程, 實時去中間庫查詢沒有同步的數據.多個消費者,消費生產者生產的數據
1.一個生產者線程, 實時去中間庫查詢沒有同步的數據
2.使用消費者對生產者生產的數據進行同步
3.記錄下處理完成/處理失敗的同步記錄
/**
* 同步中間庫生產者.從中間庫中查詢數據
*/
public class Producer implements Runnable {
private OwnerMiddleQueryService queryService;//查詢中間庫Service,可考慮抽成接口
private LinkedBlockingQueue<Runnable> consumers;//消費者隊列
private ThreadPoolExecutor executor;//線程池
public Producer(OwnerMiddleQueryService queryService, LinkedBlockingQueue<Runnable>
consumers, ThreadPoolExecutor executor) {
this.queryService = queryService;
this.consumers = consumers;
this.executor = executor;
}
@Override
public void run() {
while (true){
List<OwnerMiddle> datas = queryService.selectCount(50);
if(datas != null && !datas.isEmpty()){
try {
Consumer consumer = (Consumer) consumers.take();//從隊列中取出一個消費者
queryService.updateStatus(datas);//更改狀態字段或記錄操作記錄
consumer.setDatas(datas);//查詢出的數據給到消費者中
executor.execute(consumer);//執行消費者操作
} catch (InterruptedException e) {
e.printStackTrace();
queryService.updateStatus(datas);//記錄下同步失敗的記錄
}
}else {
try {
TimeUnit.SECONDS.sleep(3);//防止一直查詢空的結果
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 同步中間庫消費者.消費生產者的數據
*/
public class Consumer implements Runnable{
private List<OwnerMiddle> datas;//待同步數據
private OwnerMiddleDealService ownerMiddleDealService;//用於同步數據的Service
private LinkedBlockingQueue<Runnable> consumers;//消費者隊列
public Consumer(OwnerMiddleDealService ownerMiddleDealService, LinkedBlockingQueue<Runnable>
consumers) {
this.ownerMiddleDealService = ownerMiddleDealService;
this.consumers = consumers;
}
@Override
public void run() {
try{
ownerMiddleDealService.dealData(datas);//進行同步數據:把數據插入到自己系統的庫中,並記錄下同步記錄
}catch (Exception e){
e.printStackTrace();
}finally {
try {
consumers.put(this);//消費者用完後放回隊列
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public List<OwnerMiddle> getDatas() {
return datas;
}
public void setDatas(List<OwnerMiddle> datas) {
this.datas = datas;
}
}
由於我們需要在系統啓動時即開啓該線程.故我們監聽ContextRefreshedEvent事件進行觸發.注意, 不能監聽ContextStartedEvent事件,該事件是在所有的bean初始化之前的.這樣我們一些Service,Mapper對象都會無法注入.
@Component
public class SyncMiddleComponent implements ApplicationListener<ContextRefreshedEvent> {
private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20)); //線程池
private LinkedBlockingQueue<Runnable> runnables = new LinkedBlockingQueue<>(10);//任務隊列
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
OwnerMiddleQueryService middleQueryService = new OwnerMiddleQueryService();
OwnerMiddleDealService middleDealService = new OwnerMiddleDealService();
for (int i = 0; i < 10; i++) {
//10個消費者
try {
runnables.put(new Consumer(middleDealService,runnables));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Producer producer = new Producer(middleQueryService, runnables, threadPoolExecutor);
new Thread(producer).start();//開啓生產者線程
}
}
OwnerMiddleDealService 和 OwnerMiddleQueryService的方法的實現都是些業務邏輯.主要邏輯已標明在註釋中.具體的邏輯則要根據系統的業務進行編碼.
另外值得注意的是.由於在線程中是不能直接注入Spring管理的bean的.因爲線程沒有被Spring管理.此處可以自定義一個工具類,實現 ApplicationContextAware進行手動獲取bean.可在類的構造器中進行獲取.
具體可以參考:https://blog.csdn.net/fubo1990/article/details/79648766