kafka消息异步处理

​    实现平台间的数据联网,利用kafka传递消息,考虑到平台内可能有多个项目会进行数据推送,为了各项目间推送的消息进行数据处理不会受到彼此间的阻塞影响,同时保证消息的消费速度,因此需要各项目间独立异步批量的处理数据。

本地队列处理

    将从kafka监听到的消息放入本地队列中,保证每个项目拥有自己的队列,细分还可以保证每个项目每个数据类型的消息拥有自己的队列,然后让对应的线程去取并异步处理该队列中的数据。

   kafka消息监听:

    //从kafka中获取消息
    @KafkaListener(topics = {"#{'${kafka.consumer.topic}'.split(',')}"},containerFactory = "dataKafkaListenerContainerFactory")
    public void handleData(List<String> messages){

        for(String message:messages){
            KafkaMessage kafkaMessage = JSONObject.parseObject(message, KafkaMessage.class);
            if(Objects.isNull(kafkaMessage)){
                logger.info("kafka handler处理消息不存在"+message);
                continue;
            }
    }

    根据本地缓存的项目信息创建不同的队列,并将消息放入对应的线程中进行处理:

//从本地缓存中获取该项目队列
BlockingQueue<KafkaMessage> dataQueue = DATA_MESSAGE_MAP.get(kafkaMessage.getProjectId());
//缓存中没有则创建新的队列启动新的线程
if(dataQueue==null){
    dataQueue = new LinkedBlockingQueue<>();
    DATA_MESSAGE_MAP.put(kafkaMessage.getProjectId(),dataQueue);
    MessageHander messageHander = (MessageHander)AppContext.getBean("messageHander");
    messageHander.createDataBlockingQueue(dataQueue);
    new Thread(messageHander,"data-hander-"+kafkaMessage.getProjectId()).start();
}
                    
dataQueue.put(kafkaMessage);

  线程处理类,如果单线程处理某类数据时,该数据量过大,还可以在线程内部启动多线程处理:

public class EventMessageHander implements Runnable {

    //创建一个消息处理队列
    public  BlockingQueue<KafkaMessage> EVENT_BLOCKING_QUEUE;

    public void createEventBlockingQueue(BlockingQueue<KafkaMessage> queue) {

        EVENT_BLOCKING_QUEUE = queue;
    }

    /** 是否结束线程 */
    private Boolean isClose = false;

    //线程专用的线程池
    private ThreadPoolExecutor executor = new ThreadPoolExecutor(8, 16, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10000),
            new ThreadFactory() {
                final AtomicInteger threadSeq = new AtomicInteger(1);
                @Override
                public Thread newThread(Runnable r) {
                    String threadName = "hand-event-thread-" + threadSeq.getAndIncrement();
                    Thread t = new Thread(Thread.currentThread().getThreadGroup(), r, threadName);
                    t.setDaemon(true);
                    t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                        @Override
                        public void uncaughtException(Thread t, Throwable e) {
                            logger.error("thread {} error", t.getName(), e);
                        }
                    });
                    return t;
                }
            }, new ThreadPoolExecutor.CallerRunsPolicy());

    //线程处理数据
    @Override
    public void run() {
        while (!isClose) {
            try {
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            KafkaMessage kafkaMessage;
                            try {
                                kafkaMessage = EVENT_BLOCKING_QUEUE.take();
                                //数据具体处理
                            } catch (InterruptedException e) {
                                logger.error("内部线程出错",e);
                            }
                        }
                    };
                    executor.submit(runnable);
            } catch (Exception e) {
                logger.error("event handler occur exception!",e);
            }
        }
        logger.info( "process handler event thread end");
    }

    该实现机制在获取到kafka消息后,将消息存到本地阻塞队列ArrayBlockingQueue中,一类消息拥有自己的队列,让对应的线程去取并处理该阻塞队列中的消息;一方面可以尽快的消费kafka的消息,防止消费者无法跟上数据生成的速度;另一方面容易扩展,具体的消息消费类实现通用方法,实现方法的具体逻辑即可在新线程中异步执行消费,不需要在具体的消费类中关注是否开启新线程执行。不过本地队列存储有个缺陷,如果队列中堆积着数据,如果服务挂掉的话,队列中的数据就会丢失

kafka队列处理

    将kafka从监听到的消息继续分类放入kafka中,利用kafka的topic进行消息分类,同时kafka本身支持对消息的批量处理,来保证数据的快速处理。

    可根据消息类型创建不同的topic,再进行消息推送


//根据不同的消息类型将消息推往不同的topic              
if(MessageType.TYPE_FACEEVENT.getValue().equals(kafkaMessage.getMessageType())){     
                              
     KafkaSendUtil.send("event_handle",JSONObject.toJSONString(kafkaMessage));
}

    kafka消息并发处理机制,kafka创建topick的时候,可通过分配多个分区(等同于多个线程)对同一个topic的消息进行并发处理,topic创建方式:

    @Bean //创建一个kafka管理类,没有此bean无法自定义的使用adminClient创建topic
    public KafkaAdmin kafkaAdmin() {
        Map<String, Object> props = new HashMap<>();
        //配置Kafka实例的连接地址
        //kafka的地址,不是zookeeper
        props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
        KafkaAdmin admin = new KafkaAdmin(props);
        return admin;
    }

    @Bean  //kafka客户端,在spring中创建这个bean之后可以注入并且创建topic,用于集群环境,创建多个副本
    public AdminClient adminClient() {
        return AdminClient.create(kafkaAdmin().getConfig());
    }


    @Bean//通过bean创建(bean的名字为initialTopic)
    public NewTopic initialTopic() {
        return new NewTopic("event_handle",40, (short) 1 );
    }

同时在消费者配置里,工厂配置的时候需要配置并发数据,且并发数量最好不要大于分区数

    @Bean
    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> eventKafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerEventFactory());
        //并发数量,不大于40,最好同于分区数
        factory.setConcurrency(40);
        //批量获取
        factory.getContainerProperties().setPollTimeout(3000);
        factory.setBatchListener(true);
        return factory;
    }

监听消息,并发处理:

@KafkaListener(topics = "event_handle",containerFactory = "eventKafkaListenerContainerFactory")
public void handleEvent(List<String> messages){
    //消息处理
}

利用kafka进行消息批量处理,同时还能有效保证数据不会因为服务挂断而丢失,但是就目前项目,无法动态根据项目进行消费,根据不同的项目可创建不同的topic进行消息推送,消息监听方法不使用注解动态创建的话,无法使用分区概念进行批量处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值