kafka 多线程消费单节点数据

针对Kafka消费延迟问题,通过分析发现onMessage方法中的业务处理逻辑过于复杂,导致消费速度下降。解决方案是快速保存消息到数据库,并使用多线程池处理业务逻辑,避免阻塞消费。在处理过程中,利用Redis锁确保数据一致性,并通过定时任务协调多线程处理告警数据。
摘要由CSDN通过智能技术生成

背景

最近工作中遇到个问题,kafka的消费跟不上,导致大量数据堆积

恰好我们的业务对数据实时性要求比较高,消费能力跟不上使数据延迟了3天才处理完,因此被嘲讽

然而这里又遇到个问题,kafka的发送方式另一个部门的,沟通协商后,无法用新增分区数的方式来解决,只能另想办法

解决思路

后来看代码发现了问题,之前做这个功能的同事在消费的onMessage 方法中,写了太多的业务处理逻辑,处理时长甚至达到10s,这样的话肯定影响kafka的消费,所以最好的处理方式就是快速保存kafka消息,然后多线程处理业务逻辑。

代码

1.保存kafka数据至数据库

/**
 * 消息仅做数据库保存,需要在KafkaInfo 表中加字段 处理状态
   info_state 处理状态 '生成告警 0未处理 1处理中 2已处理'
   rancher 启动多节点时,会出现一个问题 多个节点都在处理同一批数据,防止此现象,可以用redis锁,先锁定查询出来的状态为0的数据,然后更新状态为1,此时再放开redis锁,处理完业务逻辑后,再将处理状态改为2
*/    
@Override
    public void onMessage(ConsumerRecord<String, String> record, Acknowledgment acknowledgment) {
        long start = System.currentTimeMillis();
        int partition = record.partition();
        long offset = record.offset();
        String topic = record.topic();
        System.out.println("kafka消费进入====onMessage=================\n" + "partition:" + partition + "==offset:" + offset +"==topic:" + topic +"==groupId:" + groupId +"\n========================");
        alarmInfoMapper.insertKafkaLog(partition, offset, topic, groupId);
        System.out.println("kafka消费进入=====================\n" + record.value() + "\n========================");

        //插入kafka日志消息表 消息体略,需配合kafka消息类
        KafkaInfo kInfo = new KafkaInfo();
        odo.insertKafkalnfo(kInfo);

        //手动提交
        acknowledgment.acknowledge();
        log.info("kafka消费====onMessage=================完成时间:" + (System.currentTimeMillis() - start));
    }

2.多线程处理业务逻辑

  //多线程池
  private static ExecutorService executor = new ThreadPoolExecutor(5, 7,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(1024),
            new ThreadPoolExecutor.DiscardOldestPolicy());
/**
* 定时任务,每2秒执行一次
*/
@Scheduled(cron="0/2 * * * * ?")
public void syncGetKafkaInfoToMessage() {
        try {
            log.info("开始执行处理信息");
            Integer number = syncGetKafkaInfoToMessage();
            log.info("结束执行处理信息:{}", number);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

/**
     * 多线程处理告警数据,偷了个懒  有需要的小伙伴可以自己加redis锁
     */
    public Integer syncGetKafkaInfoToMessage(){
        Integer num = 0;
        Integer whileNum = 0;
        //while循环处理kafka信息,直到数据处理结束,再等下次定时任务执行
        while (whileNum == 0){
            long start = System.currentTimeMillis();
            //redis锁开始...
            //获取所有的kafka信息
            List<KafkaInfo> kafkaInfoList = selectNoToAlarmKafkaInfo();
            //没有数据时跳出循环
            if(CollectionUtils.isEmpty(kafkaInfoList)){
                whileNum = 1;
            }
            //修改状态为修改中,更新状态为1
            List<Long> ids = kafkaInfoList.stream().map(u-> u.getId()).collect(Collectors.toList());
            updateKafkaInfoByIds(ids,1);
            //redis锁结束...
            logger.info("多线程处理数据========\n"+ Arrays.toString(kafkaInfoList.stream().map(u-> u.getId()).toArray()) +"\n========");
            CopyOnWriteArrayList<Long> resultList = new CopyOnWriteArrayList<>();
            CompletableFuture[] completableFutures = kafkaInfoList.stream().map(kafkaInfo -> CompletableFuture.runAsync(() -> {
                try {
                    try {
                        //这里处理业务逻辑,根据各自情况就不贴代码了
                        getKafkaInfoToMessage(kafkaInfo);
                    } catch (Exception e) {
                        logger.error("多线程处理数据报错========\n"+ item.getContext() +"\n========" + e.getMessage());
                        e.printStackTrace();
                        //将kafka数据处理有问题的存到处理错误的表中 方便以后查问题
                        KafkaInfoError error = new KafkaInfoError(item,e.getMessage());
                        insertKafkaInfoError(error);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                resultList.add(item.getId());
            }, executor)).toArray(CompletableFuture[]::new);
            CompletableFuture.allOf(completableFutures).join();
            //修改状态为已处理,更新状态为2
            num += updateKafkaInfoByIds(resultList.stream().collect(Collectors.toList()),2);
            log.info("多线程处理告警数据=================完成时间:" + (System.currentTimeMillis() - start));
        }
        return num;
    }
<select id="selectNoToAlarmKafkaInfo" resultType="com.hyit.zhnyfw.kafka.bean.KafkaInfo">
        select
        id,
        context,
        create_Time as createTime,
        alarm_Time as alarmTime,
        alarm_Size as alarmSize,
        c_alarm_no as cAlarmNo,
        reason,
        to_alarm as toAlarm,
        station_id as stationId
        from t_kafka_info
        where to_alarm = 0
        limit 300;
    </select>

<update id="updateKafkaInfoByIds">
        update t_kafka_info
        set to_alarm = #{val}
        <where>
            id in
            <foreach collection="list" separator="," item="item" open="(" close=")" >
                #{item}
            </foreach>
        </where>
    </update>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值