刚做完一个项目,从现在开始习惯在有时间的时候做一些总结,也为大家分享一些我踩坑的经验。先说说我在项目中使用kafka遇到的坑。首先,介绍一下背景,kafka的基础实现是项目组另一位大哥帮忙编写的,我只需要配置和消费。
坑1:在配置消费者的时候,一定要配置好url、集群、主题、消费组+校验码。如果消费不到,需要反复校验2~3次。
坑2:在消费的第一时间给接收kafka数据的字符串打印日志,为方便提示。和生产者那边的同事核对发送过来的实体是否和我们消费的实体是一致的,这点还是蛮重要的。
Kafka是啥?
kafka是一个实时数据处理系统,也是一个消息中间件。可以横向扩展、高可靠、还变态快。
实时数据处理系统,就是数据一旦产生,就要能快速进行处理。
Kafka中的名词和解释
Producer:消息生产者。发送消息
Consumer:消息消费者,读取消息
Topic:主题,可以理解为一个队列。Kafka二代是这么做的。
消费组 Consumer Group(CG):若干个消费者的集合。这是kafka用来实现一个主题消息的广播和单播的手段。一个主题可以有多个消费组,主题消息会复制(不是真正的复制,是概念上的)到所有的消费组。但每个消费组只会把消息发送给其中的一个消费者。如果要实现广播,只需要每个消费者占据一个独立的消费组就可以了。
一台kafka服务器 Broker:一台kafka服务器就是一个broker。一个集群有多个broker,一个broker可以容纳多个topic。
Partition:为了实现扩展性,一个非常大的topic可以分布到多个kafka服务器上,一个topic可以分为多个partition,每个partition都是一个有序队列。每个partition是一个有序的队列。partition中的每条消息都会被分配一个有序的id(offset)。kafka只保证一个partition中的顺序,不保证一个topic的整体(多个partition)的顺序。
Offset:kafka的存储文件都是按照offset.kafka来命名,用offset做名字的好处是方便查找,例如你想找到位于2049的位置,只要找到2048.kafka的文件即可。第一个offset就是00000.kafka。
为什么需要消息中间件?
消息中间件的作用:
- 解耦消息的生产和消费。
- 缓冲。
想象一个场景:你的一个创建订单的操作,在订单创建完成之后,需要触发一系列其他操作,比如进行用户数据统计,给用户发送短信,给用户发送邮件等等。比如这样:
createOrder(...){
...
startOrderData(...); //
sendSMS();
sendEmail();
}
这么写似乎也没有问题,但是如果你想给系统引进一个用户行为分析服务,它也需要在订单创建完成之后进行。像这样的行为不断增多,系统不断壮大,代码也逐渐膨胀:
createOrder(...){
...
startOrderData(...);
sendSMS();
sendEmail();
// new operation
statUserBehavior(...);
doXXX(...);
doYYY(...);
// more and more operations
...
}
导致代码越来越膨胀的原因在于:消息的生产和消费耦合在一起了。
若把创建订单,和创建订单完成后的一系列操作解耦。如果使用消息中间件,创建订单在生产者中完成,那其他一切订单创建的行为就在消费者中完成,这样我们可以把创建订单简化成一个接口。
createOrder(...){
...
sendOrderCreatedMessage(...);
}
通过解耦,消费者在消费数据时就会更加灵活,不用每次消息一生产就马上去处理。等到自己有空了,再去处理也不迟。
kafka一代 - 消息队列
kafka能把生产者和消费者解耦,主要是因为它把消息存在了一个消息队列中,这个消息队列的数据结构是一个先进先出的队列。
![image-20200528151041059](/Users/sean/Library/Application Support/typora-user-images/image-20200528151041059.png)
kafka是怎么保证队列按顺序消费的?
kafka不会因为一个消费者消费了index=0的数据而去删除这条数据,因为消费者是多个,其他的消费者还需要去消费这条index=0的数据。
事实是:kafka会对数据持久化存储(至于存放多久,可以进行配置)。消费端会记录一个offset,表明消费者当前消费到哪条数据,当下一次消费时,只需取offset+1的数据就好了。
这算是一个简单的消息中间件,暂称消费者一代。它会存在一些问题:
- 主题(topic)鱼龙混杂。想象一下,若一个消费者订阅了主题“A”,却要在ABCDEF…等各种各样的主题中寻找主题为“A”的消息,这样的性能是很慢的。
- 吞吐量低。我们把全部消息都放在一个消息队列里,请求一多,这个队列肯定应付不过来。
由此,我们引入消费者二代。
Kafka二代 - Partition
要解决前面提到的两点问题,很简单--分布存储。
二代Kafka引入了Partition的概念,也就是采用多条队列,每条队列里的消息都是相同的topic:
![image-20200528152709305](/Users/sean/Library/Application Support/typora-user-images/image-20200528152709305.png)
Partition在设计上解决了上面提到的两个问题:
- 纯Topic队列。一个队列只有一种主题。消费者不用担心会碰到不是自己想要的主题的消息了。
- 提高吞吐量。不同主题的消息交给不同队列去存储,再也不用以一敌十了。