由于canal消费时是单线程阻塞的,大大降低了程序对于线程的利用率
canal客户端实现官方demo
以下是通过线程池去消费canal订阅的消息代码
//存放操作完后的batchId和结果
private static ConcurrentHashMap<Long,Boolean> batchIdMap = new ConcurrentHashMap<>();
//将需要消费的batchId按顺序存放队列中
private volatile static LinkedBlockingQueue<Long> batchIds = new LinkedBlockingQueue<>();
protected void process() {
while (running) {
connector.connect();
connector.subscribe(canalInstance.getFilter());
//创建线程去响应canal
//定时通知canal消费是否成功
Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(() -> {
try {
while (true) {
if (CollectionUtils.isEmpty(batchIdMap) || batchIds.isEmpty()) {
return;
}
Long peek = batchIds.peek();
Boolean aBoolean = batchIdMap.get(peek);
if (null == aBoolean) {
return;
}
if (aBoolean) {
logger.info("canal ack batch-----{}", peek);
connector.ack(batchIds.poll());
} else {
logger.info("canal rollback batch-----{}", peek);
connector.rollback(batchIds.poll());
}
batchIdMap.remove(peek);
}
} catch (Exception e) {
logger.error("scheduleWithFixedDelay", e);
}
}, 0, 10, TimeUnit.SECONDS);
try {
MDC.put("destination", canalInstance.getDestination());
//定时通知canal消费是否成功
while (running) {
// 获取指定数量的数据
Message message = connector.getWithoutAck(canalInstance.getBatchsize());
long batchId = message.getId();
int size = message.getEntries().size();
if ((batchId == -1 || size == 0)) {
continue;
}
logger.info("canal client execute batchId:{}, size:{}", batchId, size);
//往队列中添加消费的batchId
batchIds.add(message.getId());
//消费message
readMessage(message);
}
} catch (Exception e) {
if(e instanceof CanalClientException){
CanalClientException canalClientException = (CanalClientException) e;
if(canalClientException.getCause() instanceof IOException || canalClientException.getCause() instanceof ConnectException){
logger.error("connector error!retry connector", e);
connector.connect();
}
}
logger.error("process error!", e);
} finally {
connector.disconnect();
logger.info("connector destination");
MDC.remove("destination");
}
}
}
/**
* 读取message信息
* @param message
* @throws Exception
*/
private void readMessage(Message message) throws Exception {
//启动线程池
threadPoolExecutorUtil.createThreadPoolExecutor().execute(() -> {
try {
//具体的处理方法ing....返回是否成功
boolean b = canalEntry(message.getEntries());
logger.info("完成消费batchId========={}---Finished====={}---Size====={}",message.getId(),b,message.getEntries().size());
//存放已经消费完成的batchId和是否成功
batchIdMap.put(message.getId(),b);
} catch (Exception e) {
logger.error("consumeMessage", e);
try {
Thread.sleep(1000);
} catch (Exception e2) {}
}
});
}
参考资料:canal支持多线程消费吗?
参考资料:canal客户端针对同一个batchId重复ack确认
参考资料:canal客户端确认batchId乱序