Kafka的项目实践

来源:互联网 发布:ie浏览器mac版 编辑:程序博客网 时间:2024/05/17 02:45

最近在项目中使用了Kafka作为高并发处理的一个环节,即多台机器采集数据后同时发送到Kafka,消费端接收数据后保存到elasticsearch里面。
在这个过程中,原本以为用Kafka就那样,建个topic分几个分区,数据生产端直接发送数据到该topic,然后消费端poll数据到本机处理就ok了。实际上等项目跑起来后发现,数据消费速度很慢,完全达不到要求。我们的要求是每秒起码要有1万条的处理效率。
看到这里,很多人可能会说多开启几个消费端进程处理就行。因为Kafka消费端是非线程安全的,所以一个进程只能有一个消费端。实际上我后面加了几个消费端之后,处理的效率还是比较差的。
所以只能从代码逻辑入手了。首先使用排除法,去除了插入es的影响,因为es使用bulk插入的速度也是验证过没问题的。后面我们通过重构Kafka消费端处理就解决了该问题,原因及解决办法如下:

1、幂等性验证降低速度
由于我们知道消息队列都是有可能导致的消息重复发送的,不管是网络抖动还是人为配置的原因,所以我在代码中原本是使用了redis存储消息的key,然后每次拉消息回来后,全部过滤一遍剔除重复的消息。后面我们重新思考了一下,对于我们的业务场景,多台机器采集了主机、CPU、内存等监控数据并发送到Kafka,这种监控数据不是特别在意重复问题,不像电商里面的订单一样涉及到了金钱,另外一方面我们做了一些其他的处理降低重复的概率。所以幂等性我就直接去掉了,处理如下:
生产者配置:props.put(“request.required.acks”, -1);//保证消息必须发送到Kafka
消费者配置:props.put(“auto.offset.reset”, “latest”);//只消费最新的数据
利用es的特性,如果插入的数据中ID一样的话会进行覆盖。

2、Kafka拉取消息和处理逻辑分开处理
这个是解决速度慢的关键,由于Kafka消费端是非线程安全,所以消费端拉取数据是使用单线程,处理数据则是多线程处理,代码逻辑如下:
创建线程池:

private final ExecutorService threadPool = new ThreadPoolExecutor(10,            30, 30, TimeUnit.MINUTES,            new ArrayBlockingQueue<>(30),            Executors.defaultThreadFactory(),            (r, executor) -> {                try {                    executor.getQueue().put(r);                } catch (InterruptedException e) {                    e.printStackTrace();                }            });

单线程消费数据:

List<MessageModel> msgList = new ArrayList<>();                ConsumerRecords<String, String> records = alarmsConsumer.readRecords();                for (TopicPartition partition : records.partitions()){                    for (ConsumerRecord<String, String> record : records.records(partition)){                        MessageModel message = new MessageModel(record.key(),record.value());                        msgList.add(message);                    }                }

多线程处理数据

if (msgList!=null && !msgList.isEmpty()){                    threadPool.execute(new Runnable() {                        @Override                        public void run() {                            try {                                DealMessageInterface processor = new DealMessage();                                //实际的处理逻辑                                processor.dealMessage(msgList);                            } catch (Exception e) {                                e.printStackTrace();                            }                        }                    });                }

稍微总结一下:消息队列的幂等性校验要根据实际的情况来,有些甚至都不用考虑这个的;凡是需要快速处理的,根据情况尽量使用多线程模式。当然,很多时候不用去怀疑Kafka的效率,基本上都是自己的代码逻辑问题导致的速度慢。