开发中会有场景需要对接第三方系统.第三方系统往往会提供一个中间库,然后我们系统读取中间库的数据,然后经过一系列的逻辑,把数据存到自己系统中. 在这里分享一种通过生产者,消费者模式进行数据同步,接近实时
主要思路: 一个生产者线程, 实时去中间库查询没有同步的数据.多个消费者,消费生产者生产的数据
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,STATUS.HANDLING);//更改状态字段或记录操作记录
consumer.setDatas(datas);//查询出的数据给到消费者中
executor.execute(consumer);//执行消费者操作
} catch (InterruptedException e) {
e.printStackTrace();
queryService.updateStatus(datas,STATUS.FAIL);//记录下同步失败的记录
}
}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