其他属性:
offset: partition中的每条消息都被标记了一个序号,这个序号表示消息在partition中的偏移量,称为offset,每一条消息在partition都有唯一的offset,消息者通过指定offset来指定要消费的消息。【消费者在消费完一条消息后会递增offset,准备去消费下一条消息,但也可以将offset设成一个较小的值,重新消费一些消费过的消息。】
producer: 生产者,生产者发送消息到指定的topic下,消息再根据分配规则append到某个partition的末尾。
consumer: 消费者,消费者从topic中消费数据。
consumer group: 消费者组,每个consumer属于一个特定的consumer group,可为每个consumer指定consumer group,若不指定则属于默认的group。
同一topic的一条消息只能被同一个consumer group内的一个consumer消费,但多个consumer group可同时消费这一消息。这也是kafka用来实现一个topic消息的广播和单播的手段,如果需要实现广播,一个consumer group内只放一个消费者即可,要实现单播,将所有的消费者放到同一个consumer group即可。
用consumer group还可以将consumer进行自由的分组而不需要多次发送消息到不同的topic。
leader: 每个partition有多个副本,其中有且仅有一个作为leader,leader会负责所有的客户端读写操作。
follower: follower不对外提供服务,只与leader保持数据同步,如果leader失效,则选举一个follower来充当新的leader。当follower与leader挂掉、卡住或者同步太慢,leader会把这个follower从ISR列表中删除,重新创建一个follower。
rebalance: 同一个consumer group下的多个消费者互相协调消费工作,我们这样想,一个topic分为多个分区,一个consumer group里面的所有消费者合作,一起去消费所订阅的某个topic下的所有分区(每个消费者消费部分分区),kafka会将该topic下的所有分区均匀的分配给consumer group下的每个消费者。
rebalance表示"重平衡",consumer group内某个消费者挂掉后,其他消费者自动重新分配订阅主题分区的过程,是 Kafka 消费者端实现高可用的重要手段。
上述参考文章:
https://mp.weixin.qq.com/s?__biz=MzU1NDA0MDQ3MA==&mid=2247483958&idx=1&sn=dffaad318b50f875eea615bc3bdcc80c&chksm=fbe8efcfcc9f66d9ff096fbae1c2a3671f60ca4dc3e7412ebb511252e7193a46dcd4eb11aadc&scene=21
systemctl start docker
su #进入root用户
systemctl start docker #启动docker
docker pull wurstmeister/zookeeper #安装zookeeper
docker pull wurstmeister/kafka #安装kafka
docker run -d --name zookeeper -p 2181:2181 -t wurstmeister/zookeeper #启动zk
# 起动kafka 服务器地址换成自己虚拟机的
docker run -d --name kafka --publish 9092:9092 --link zookeeper:zookeeper -e KAFKA_BROKER_ID=1 -e HOST_IP=192.168.33.114 -e KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.33.114:9092 -e KAFKA_ADVERTISED_HOST_NAME=192.168.33.114 -e KAFKA_ADVERTISED_PORT=9082 --restart=always -t wurstmeister/kafka
docker ps
查看启动的服务:
启动kafka后,进入kafka容器,进入bin目录:
到bin下后,开始创建topic:
创建两个topic:topic1、topic2,其分区卫和副本数为1。【记得将服务器地址修改成自己虚拟机的】
./kafka-topics.sh --create --zookeeper 192.168.33.114:2181 --replication-factor 1 --partitions 2 --topic topic1
./kafka-topics.sh --create --zookeeper 192.168.33.114:2181 --replication-factor 1 --partitions 2 --topic topic2
创建topic可由代码进行创建,不输入命令也行。在发送消息时,kafka会帮我们自动完成topic的创建工作,但这种情况下创建的topic默认只有一个分区,分区也没有副本。如果需要设置多个分区和副本需要进行配置。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.10</version>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.8.5</version>
</dependency>
</dependencies>
这里一定要注意kafka与springboot版本,一定要一一对应。参考网址:https://spring.io/projects/spring-kafka
不然会启动不起来,报下面错误:
2. kafka配置:【application.properties】
spring.application.name=kafka-demo
server.port=8081
###########【Kafka集群】###########
spring.kafka.bootstrap-servers=192.168.33.114:9092
###########【初始化生产者配置】###########
# 重试次数
spring.kafka.producer.retries=0
# 应答级别:多少个分区副本备份完成时向生产者发送ack确认(可选0、1、all/-1)
spring.kafka.producer.acks=1
# 批量大小
spring.kafka.producer.batch-size=16384
# 提交延时
spring.kafka.producer.properties.linger.ms=0
# 当生产端积累的消息达到batch-size或接收到消息linger.ms后,生产者就会将消息提交给kafka
# linger.ms为0表示每接收到一条消息就提交给kafka,这时候batch-size其实就没用了
# 生产端缓冲区大小
spring.kafka.producer.buffer-memory = 33554432
# Kafka提供的序列化和反序列化类
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
# 自定义分区器
# spring.kafka.producer.properties.partitioner.class=com.felix.kafka.producer.CustomizePartitioner
###########【初始化消费者配置】###########
# 默认的消费组ID
spring.kafka.consumer.properties.group.id=defaultConsumerGroup
# 是否自动提交offset
spring.kafka.consumer.enable-auto-commit=true
# 提交offset延时(接收到消息后多久提交offset)
spring.kafka.consumer.auto.commit.interval.ms=1000
# 当kafka中没有初始offset或offset超出范围时将自动重置offset
# earliest:重置为分区中最小的offset;
# latest:重置为分区中最新的offset(消费分区中新产生的数据);
# none:只要有一个分区不存在已提交的offset,就抛出异常;
spring.kafka.consumer.auto-offset-reset=latest
# 消费会话超时时间(超过这个时间consumer没有发送心跳,就会触发rebalance操作)
spring.kafka.consumer.properties.session.timeout.ms=120000
# 消费请求超时时间
spring.kafka.consumer.properties.request.timeout.ms=180000
# Kafka提供的序列化和反序列化类
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
# 消费端监听的topic不存在时,项目启动会报错(关掉)
spring.kafka.listener.missing-topics-fatal=false
# 设置批量消费
# spring.kafka.listener.type=batch
# 批量消费每次最多消费多少条消息
# spring.kafka.consumer.max-poll-records=50
/**
* KafkaTopicConfiguration
* kafka配置类
*
* @author : he zhe
* @date : 2022-08-01 17:18
**/
@Configuration
public class KafkaTopicConfiguration {
/**
* 创建 KafkaAmin,可以自动检测集群中是否存在topic,不存在则创建
* @return
*/
@Bean
public KafkaAdmin kafkaAdmin() {
Map<String, Object> props = new HashMap<>();
props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.33.114:9092");
return new KafkaAdmin(props);
}
@Bean
public NewTopic newTopic() {
// 创建 topic,指定 名称、分区数、副本数
return new NewTopic("hello-kafka-test-topic", 2, (short) 1);
}
}
/**
* KafkaProducer
* 生产者
*
* @author : he zhe
* @date : 2022-08-01 16:49
**/
@RestController
public class KafkaProducer {
@Autowired
private KafkaTemplate<String, Object> kafkaTemplate;
/**
* 发生简单信息
* @param normalMessage
*/
@RequestMapping(value = "/kafka/normal/{message}", method = RequestMethod.GET)
public void sendMessage1(@PathVariable("message") String normalMessage) {
kafkaTemplate.send("topic1", normalMessage);
}
/**
* 异步发送:带回调的发生消息
* @param callbackMessage
*/
@GetMapping("/kafka/callbackOne/{message}")
public void sendMessage2(@PathVariable("message") String callbackMessage) {
kafkaTemplate.send("topic1", callbackMessage).addCallback(success -> {
// 消息发送到的topic
String topic = success.getRecordMetadata().topic();
// 消息发送到的分区
int partition = success.getRecordMetadata().partition();
// 消息在分区内的offset
long offset = success.getRecordMetadata().offset();
System.out.println("发送消息成功:" + topic + "-" + partition + "-" + offset);
}, failure -> {
System.out.println("发送消息失败:" + failure.getMessage());
});
}
/**
* 事务处理
* @param message
*/
@GetMapping("/kafka/transaction/{message}")
public void sendMessage3(@PathVariable("message") String message) {
// 声明事务:operations函数报错,消息就不会发出去。
kafkaTemplate.executeInTransaction(operations -> {
//数据发往kafka
operations.send("topic1",message);
//模拟后续业务处理发生了异常
throw new RuntimeException("fail");
});
}
}
/**
* KafkaConsumer
* 消费者
*
* @author : he zhe
* @date : 2022-08-01 16:51
**/
@Service
public class KafkaConsumer {
// 消费监听
@KafkaListener(topics = {"topic1"})
public void onMessage1(ConsumerRecord, ?> record){
// 消费的哪个topic、partition的消息,打印出消息内容
System.out.println("简单消费:"+record.topic()+"-"+record.partition()+"-"+record.value());
}
}
/**
* KafkaAppliction
* 启动类
*
* @author : he zhe
* @date : 2022-08-01 16:43
**/
@SpringBootApplication
public class KafkaApplication {
public static void main(String[] args) {
SpringApplication.run(KafkaApplication.class, args);
System.out.println("KafkaApplication启动成功");
}
}
# 重试次数
spring.kafka.producer.retries=3
# 应答级别:多少个分区副本备份完成时向生产者发送ack确认(可选0、1、all/-1)
spring.kafka.producer.acks=-1
#增加配置
spring.kafka.producer.transaction-id-prefix=tx_