kafka中partition类似数据库中的分表数据,可以起到水平扩展数据的目的,比如有a,b,c,d,e,f 6个数据,某个topic有两个partition,一般情况下partition-0存储a,c,e3个数据,partition-1存储b,d,f另外3个数据。
消费者组数量的不同以及partition数量的不同对应着不同的消费情况,下面分别进行梳理之:
(1)topic只有1个partition,该组内有多个消费者时,此时同一个partition内的消息只能被该组中的一个consumer消费。当消费者数量多于partition数量时,多余的消费者是处于空闲状态的,如图1所示。topic,pis-business只有一个partition,并且只有1个group,G1,该group内有多个consumer,只能被其中一个消费者消费,其他的处于空闲状态。
该topic有多个partition,该组内有多个消费者,
第一种场景:
比如pis-business 有3个partition,该组内有2个消费者,那么可能就是C0对应消费p0,p1内的数据,c1对应消费p2的数据;
第二种场景:
如果有3个消费者,就是一个消费者对应消费一个partition内的数据了这种模式在集群模式下使用是非常普遍的,比如我们可以起3个服务,对应的topic设置3个partiition,这样就可以实现并行消费,大大提高处理消息的效率。
如果想实现广播的模式就需要设置多个消费者组,这样当一个消费者组消费完这个消息后,丝毫不影响其他组内的消费者进行消费,这就是广播的概念。
该topic内的数据被多个消费者组同时消费,当某个消费者组有多个消费者时也只能被一个消费者消费,如图所示:
该topic内的数据可被多个消费者组多次消费,在一个消费者组内,每个消费者又可对应该topic内的一个或者多个partition并行消费,如图所示:
这里使用Java服务进行实践,模拟3个parition,然后同一个组内有3个消费者的情况:
org.springframework.kafka spring-kafka 1.3.10.RELEASE com.fasterxml.jackson.core jackson-databind 2.5.4 com.alibaba fastjson 1.2.83
package com.gblfy.kafka;import org.apache.kafka.clients.producer.RecordMetadata;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Service;
import org.springframework.util.concurrent.ListenableFuture;/*** kafka发送工具类** @author gblfy* @date: 2022-11-18**/
@Service
public class KafkaProducerService {@Autowiredprivate KafkaTemplate kafkaTemplate;// @Autowired// private xxxMapper xxxMMapper;TODO 数据库轨迹Mapper/*** @param topic topic分区* @param message kafka消息* @return 发送Kafka成功并返回请求响应信息*/public void sendMessageForgetResult(String topic, String message) {try {System.out.println("---------topic-------" + topic);ListenableFuture> listenableFuture = kafkaTemplate.send(topic, message);SendResult sendResult = listenableFuture.get();RecordMetadata recordMetadata;if (sendResult != null) {recordMetadata = sendResult.getRecordMetadata();System.out.println("发送成功!");System.out.println("topic:" + recordMetadata.topic());System.out.println("partition:" + recordMetadata.partition());System.out.println("offset:" + recordMetadata.offset());}// TODO 设置消息发送状态// TODO 更新数据落库} catch (Exception e) {e.printStackTrace();System.out.println("--------kafka发送失败---------!");// TODO 设置消息发送状态// TODO 更新数据落库}System.out.println("发送成功!");}}
package com.gblfy.producer;import com.alibaba.fastjson.JSON;
import com.gblfy.kafka.KafkaProducerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.Map;/*** 监听类的实现** @author gblfy* @date 2022-11-18*/
@RestController
@RequestMapping("kafka")
public class KafkaProducer {@Autowiredprivate KafkaProducerService kafkaProducerService;//kafka发送的主题public static final String KAFKA_TOPIC = "pis-business";/*** 消息发送* http://localhost:8080/springmvc-producer/kafka/producer?value=123*/@GetMapping("producer")public String producer(@RequestParam String value) {Map map = new HashMap<>();map.put("key", value);String message = JSON.toJSONString(map);// 发送kafkakafkaProducerService.sendMessageForgetResult(KAFKA_TOPIC, message);return "已发送";}}
package com.gblfy.kafka.encode;import com.gblfy.kafka.BeanUtils;
import org.apache.kafka.common.serialization.Serializer;
import org.springframework.stereotype.Component;import java.util.Map;@Component
public class EncodeingKafka implements Serializer
这里使用Java服务进行实践,模拟3个parition,然后同一个组内有3个消费者的情况:
org.springframework.kafka spring-kafka 1.3.10.RELEASE com.fasterxml.jackson.core jackson-databind 2.5.4 com.alibaba fastjson 1.2.83
pis-business
package com.gblfy.kafka.listener;import com.alibaba.fastjson.JSONObject;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.listener.AcknowledgingMessageListener;
import org.springframework.kafka.support.Acknowledgment;/*** 监听类的实现** @author gblfy* @date 2022-11-18*/
public class KafkaConsumerListener implements AcknowledgingMessageListener {@Overridepublic void onMessage(ConsumerRecord data, Acknowledgment acknowledgment) {String topic = data.topic();System.out.println("消费的topic主题:-----" + topic);System.out.println("消费-----" + data.partition() + "分区的消息");System.out.println("offset-----" + data.offset());System.out.println("message-----" + data.value());/*获取kafka数据*/JSONObject pardeData = (JSONObject) JSONObject.parse(data.value());String value1 = pardeData.getString("key");System.out.println("value1-----" + value1);// TODO 业务逻辑处理//消费完成后提交offsetif (acknowledgment != null) {acknowledgment.acknowledge();}}
}
package com.gblfy.kafka.encode;import com.gblfy.kafka.BeanUtils;
import org.apache.kafka.common.serialization.Deserializer;
import org.springframework.stereotype.Component;import java.util.Map;@Component
public class DecodeingKafka implements Deserializer
生产者和消费者一样
package com.gblfy.kafka;import java.io.*;public class BeanUtils {private BeanUtils() {}/*** 对象序列化为byte数组** @param obj* @return*/public static byte[] bean2Byte(Object obj) {byte[] bb = null;try (ByteArrayOutputStream byteArray = new ByteArrayOutputStream();ObjectOutputStream outputStream = new ObjectOutputStream(byteArray)){outputStream.writeObject(obj);outputStream.flush();bb = byteArray.toByteArray();} catch (IOException e) {e.printStackTrace();}return bb;}/*** 字节数组转为Object对象** @param bytes* @return*/public static Object byte2Obj(byte[] bytes) {Object readObject = null;try (ByteArrayInputStream in = new ByteArrayInputStream(bytes);ObjectInputStream inputStream = new ObjectInputStream(in)){readObject = inputStream.readObject();} catch (Exception e) {e.printStackTrace();}return readObject;}
}
创建kafka主题名称为pis-business
分区3个 副本数量3个
kafka-topics.sh -zookeeper 192.168.105.125:2181 --create --partitions 3 --replication-factor 3 --topic pis-business
这里使用同一个消费者项目,启动一个项目后修改端口依次启动,端口分别为8081、082、8083
发送3笔消息
消费者1 端口8081 消费的分区2的消息
消费者2 端口8082消费的分区1的消息
消费者3 端口8083 消费的分区0的消息
-发送5笔消息
清空控制台,重新发送5笔消息测试
消费者1 端口8081 消费的分区2的1笔消息
消费者2 端口8082消费的分区1的2笔消息
消费者3 端口8083 消费的分区0的2笔消息
从第二轮测试结果数据分析来看:
生产者:投递5笔消息,有2笔投递到了分区模型0中,有2笔投递到了分区模型1中,有1笔投递到了分区模型2中。
消费者:
消费1端口8081分区模型2中1笔消息,
消费2端口8082分区模型1中2笔消息,
消费3端口8083分区模型0中2笔消息,
从消费者消费来看,是按照分区模型来消费的,起到了均衡消费的效果,目前没有出现,一个消费者消费不同分取模型中的消息对吧。
创建kafka主题名称为pis-business
分区3个 副本数量3个
kafka-topics.sh -zookeeper 192.168.105.125:2181 --create --partitions 3 --replication-factor 3 --topic pis-business
kafka-console-producer.sh --broker-list 192.168.105.125:9092 --topic pis-business
消息内容
1
2
3
4
5
bin/kafka-console-consumer.sh --bootstrap-server 192.168.105.125:9092 --from-beginning --topic pis-business
消费者1消费消息数量2笔,消息内容为2和5
消费者2消费消息数量2笔,消息内容为1和4
消费者3消费消息数量1笔,消息内容为3
所以在微服务的集群中,我们可以通过给topic设置多个partition,然后让每一个实例对应消费1个partition的数据,从而实现并行的处理数据,可以显著地提高处理消息的速度。
首先点击 Add Partitions 增加partition的数量,然后点击Generate Partition Assignments ,此时系统自动会为每个分区下的副本分配broker, 最后点击 Reassign Partitions,可以平衡集群的负载 。