canal客户端使用多线程消费消息

4 篇文章 0 订阅
该博客探讨了Canal客户端在消费时由于单线程阻塞导致的线程利用率低的问题。作者提供了一个示例,通过使用线程池和定时任务来异步处理和确认Canal消息,以提高效率。示例中,消息被放入队列并由多个线程并行处理,然后根据处理结果进行批量ACK或ROLLBACK。同时,博客提到了批处理ID的乱序问题和避免重复确认的注意事项。
摘要由CSDN通过智能技术生成

由于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乱序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值