kafka是以zookeeper为基础的,必须先启动zookeeper才能再启动kafka
https://zookeeper.apache.org/releases.html
文件夹:/home/soft/zookeeper
解压:tar -zxvf apache-zookeeper-3.6.2-bin.tar.gz
进去文件夹conf
cp zoo_sample.cfg zoo.cfg
编辑zoo.cfg
配置这行:(提前去把这个data文件夹建立好
)
dataDir=/home/soft/zookeeper/apache-zookeeper-3.6.2-bin/data
在bin目录下执行:
./zkServer.sh start
ZooKeeper服务器是用Java编写创建,它运行在JVM,先保证jdk环境
复制zoo_sample.cfg为zoo.cfg,zoo.cfg才是默认配置文件zoo_sample.cfg是样本文件
cp zoo_sample.cfg zoo.cfg
编辑zoo.cfg
配置这行:(提前去把这个data文件夹建立好)
dataDir=/home/soft/zookeeper/apache-zookeeper-3.6.2-bin/data
重点:
在上面这个目录(dataDir指定的)建立一个名为myid的文件(避免格式问题最好用linux命令创建:touch myid)
编辑myid文件:vim myid 填入数字 1 (这里与后面的集群配置的server.1的1是对应的,第几个节点就配几)
Zookeeper启动时读取此文件,拿到里面的数据与zoo.cfg里面的配置信息比较从而判断到底是哪个server
继续编辑zoo.cfg
#集群配置
server.1=192.168.200.135:2888:3888
server.2=192.168.200.136:2888:3888
server.3=192.168.200.137:2888:3888
一个zoo.cfg完整配置:
dataDir=/home/soft/kafka/kafka_2.11-2.2.0/zookeeper/zkData
clientPort=2181
maxClientCnxns=0
#通信心跳数,Zookeeper服务器与客户端心跳时间,单位毫秒
tickTime=2000
#集群中的Follower跟随者服务器与Leader领导者服务器之间初始连接时能容忍的最多心跳数(tickTime的数量)
initLimit=10
#集群中Leader与Follower之间的最大响应时间单位
syncLimit=5
#集群配置
server.1=192.168.200.135:2888:3888
server.2=192.168.200.136:2888:3888
server.3=192.168.200.137:2888:3888
zookeeper的配置文件所有节点都一样
集群中的每个zookeeper的配置文件都一样,
Zookeeper虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的
启动进入:zookeeper的bin目录,执行:./zkServer.sh start
查看状态:./zkServer.sh status
**特别说明:**0.5.x 及以上版本自带zookeeper,直接配置它自带的也是可以的,配置跟上面一样,启动是这样(在bin目录外层执行)nohup bin/zookeeper-server-start.sh config/zookeeper.properties &
kafka的启动:
vim server.properties
需配置这几项:
broker.id=2 集群之间不重复,且只能配数字
delete.topic.enable=true
log.dirs=/home/soft/kafka/kafka_2.11-2.2.0/data
zookeeper.connect=192.168.200.135:2181,192.168.200.136:2181,192.168.200.137:2181
listeners = PLAINTEXT://192.168.200.137:9092
最终版:
#broker 的全局唯一编号,不能重复
broker.id=0
#是否允许自动删除topic
delete.topic.enable=true
#不指定的话,按照默认9092
port = 9092
#监听地址与当前服务器一致
listeners = PLAINTEXT://192.168.200.135:9092
#处理网络请求的线程数量
num.network.threads=3
#用来处理磁盘 IO 的现成数量
num.io.threads=8
#发送套接字的缓冲区大小
socket.send.buffer.bytes=102400
#接收套接字的缓冲区大小
socket.receive.buffer.bytes=102400
#请求套接字的缓冲区大小
socket.request.max.bytes=104857600
#kafka 运行日志存放的路径
log.dirs=/home/soft/kafka/kafka_2.11-2.2.0/data
#topic 在当前 broker 上的分区个数
num.partitions=1
#用来恢复和清理 data 下数据的线程数量
num.recovery.threads.per.data.dir=1
#offset topic的replicas数量(设置更高以确保可用性)。在集群大小满足此复制因子要求之前(例如set为3,而broker不足3个),内部topic创建将失败
offsets.topic.replication.factor=3
#事务主题的复制因子(设置更高以确保可用性)。 内部主题创建将失败,直到群集大小满足此复制因素要求
transaction.state.log.replication.factor=3
#覆盖事务主题的min.insync.replicas配置,在min.insync.replicas中,replicas数量为1,该参数将默认replicas定义为2
transaction.state.log.min.isr=1
#保留的数据最小删除时间
log.retention.hours=168
#topic的分区是以一堆segment文件存储的,这个控制每个segment的大小,会被topic创建时的指定参数覆盖
log.segment.bytes=1073741824
#文件大小检查的周期时间,是否处罚 log.cleanup.policy中设置的策略
log.retention.check.interval.ms=300000
#连接zookeeper集群的地址
zookeeper.connect=192.168.200.135:2181,192.168.200.136:2181,192.168.200.137:2181
#zookeeper连接超时时间
zookeeper.connection.timeout.ms=6000
#让coordinator推迟空消费组接收到成员加入请求后本应立即开启的rebalance。在实际使用时,假设你预估你的所有consumer组成员加入需要在10s内完成,那么你就可以设置该参数=10000
group.initial.rebalance.delay.ms=10000
#每当服务器停止而不是硬杀死时,将自动同步日志,但受控leader迁移需要使用特殊设置
controlled.shutdown.enable = true
每个kafka节点的配置文件几乎都一样(除了listeners )
在解压目录 执行 bin/kafka-server-start.sh -daemon config/server.properties
-daemon是指后台启动的意思
查看是否启动 jps:就会看到
2998 Jps
1511 QuorumPeerMain
2840 Kafka
<dependency>
<groupId>org.springframework.kafkagroupId>
<artifactId>spring-kafkaartifactId>
dependency>
spring:
kafka:
#kafka连接地址
bootstrap-servers: 192.168.200.135:9092,192.168.200.136:9092,192.168.200.137:9092
#生产者的配置信息
producer:
#当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。
batch-size: 16
# 设置生产者内存缓冲区的大小。
buffer-memory: 33554432
# 发生错误后,消息重发的次数。
retries: 2
# 键的序列化方式
key-serializer: org.apache.kafka.common.serialization.StringSerializer
# 值的序列化方式
value-serializer: org.apache.kafka.common.serialization.StringSerializer
# acks=0 : 生产者在成功写入消息之前不会等待任何来自服务器的响应。
# acks=1 : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。
# acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
acks: 1
#消费者的配置
consumer:
# 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
# latest(默认值)在偏移量无效的情况下,消费者将从最新的记录开始读取数据(在消费者启动之后生成的记录)
# earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录
auto-offset-reset: earliest
# 是否自动提交偏移量,默认值是true,为了避免出现重复数据和数据丢失,可以把它设置为false,然后手动提交偏移量
enable-auto-commit: true
# 自动提交的时间间隔 在spring boot 2.X 版本中这里采用的是值的类型为Duration 需要符合特定的格式,如1S,1M,2H,5D
auto-commit-interval: 100
# 键的反序列化方式
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
# 值的反序列化方式
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
listener:
# 在侦听器容器中运行的线程数。
concurrency: 5
#listner负责ack,每调用一次,就立即commit
ack-mode: manual_immediate
# 消费端监听的topic不存在时,项目启动会报错(关掉)
missing-topics-fatal: false
@RestController
@RequestMapping("/kafka/produce")
@Api(tags = "生产者")
public class Producer {
@Autowired
private KafkaTemplate kafkaTemplate;
@PostMapping("/sendMessage")
public R sendMessage(String topic, String message){
kafkaTemplate.send(topic,message);
return R.ok("发送了");
}
}
@Component
@Slf4j
public class Consumer {
@KafkaListener(topics = "topick1")
public void consumerMessage(ConsumerRecord<?,?> record){
log.info("主题:"+record.topic()+",partition:"+record.partition()+",数据:"+record.value());
}
@KafkaListener(topics = "mykafk")
public void consumerMessage2(ConsumerRecord<?,?> record){
log.info("主题:"+record.topic()+",partition:"+record.partition()+",数据:"+record.value());
}
}
以kafka为例:
1.1、发送端确认机制的具体实现
spring.kafka.producer.acks=1;
#ack=0:就是说消息只要通过网络发送出去就不会再管,无论是否被服务器接收到。也就是我只管发,你接到还是没接到我并不关心。
#ack=1(默认):producer(消息发送方)收到leader副本的确认,才会认为发送成功。就像是TCP连接一样,我要收到服务器的ack才行。
#ack=-1(ALL):leader副本在返回确认或错误响应之前,会等待所有follower副本都收到消息,才会认为发送成功。这个ALL很好理解,就是所有副本都收到消息,才会认为发送成功。
1.2、消费端消息确认机制的具体实现
spring.kafka.consumer.enable-auto-commit=false
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.listener.ack-mode=manual_immediate
spring.kafka.consumer.auto-offset-reset的属性设置
earliest
#当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
latest
#当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
none
#topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常
enable-auto-commit属性设置
enable-auto-commit=false,是否自动提交偏移量,默认值是true,为了避免出现重复数据和数据丢失,可以把它设置为false,然后手动提交偏移量
,消费端手动确认都设置为false
spring.kafka.listener.ack-mode属性设置
spring.kafka.listener.ack-mode=manual_immediate的含义是MANUAL_IMMEDIATE和MANUAL的区别就是MANUAL不会立即提交,它是获取poll中的值后设置record,最后批量提交。manual_immediate每调用一次,就立即commit
1.3、broker消息异常处理
每一个消息都在数据库做好记录,定期将失败的消息再次发送,
①比如在catch中把消息写入mysql,再定时读取表中数据,重新发送
②也可以在catch里面再用死循环发送,线程沉睡模式
}
总结:1、做好消息的确认机制(发送端、消费端【手动确认】)都要确认
2、每一个消息都在数据库做好记录,定期将失败的消息再次发送
-消息消费成功,事务已提交,ack时,机器宕机。导致没有ack成功,Broker的消息重新由unack变为ready,并发送给其他消费者
-消息消费失败,由于重试机制,自动又将消息发送出去
-成功消费,ack时宕机,消息由unack变为ready,Broker又重新发送
①消费者的业务消费接口应该设计为幂等性(主要)>,比如扣库存有工作单的状态标志
②使用防重表(redis/mysql)发送消息每一个都有业务的唯一标识,处理过就不用处理
③RabbitMQ的每一个消息都有redelivered字段,可以获取是否是被重新投递过来的,而不是第一次投递过来
原因:
-消费者宕机积压
-消费者能力不足积压
-发送者发送流量太大积压
***解决办法:
①上线更多消费服务
②上线专门的队列消费服务,将消息批量取出来,记录到数据库,离线慢慢处理