Kafka
Kafka基本介绍
LInkedin 18年就有5.6亿用户
一开始用的是RabbitMQ,但是流量太大了就研究出Kafka
Kafka是个作家
每秒可收几十万条
内部设置了partition,可以直接扩容
数据存在磁盘持久化,有副本
作用
消息传递 异步通信,削峰,业务解耦
网站活动跟踪
指标监控
日志聚合
Kafka安装
单机
1、下载解压kafka
获取下载地址(点开具体版本): kafka.apache.org/downloads
下载Binary 二进制版本而不是源码
复制链接:
cd /usr/local/soft
wget https://mirror.bit.edu.cn/apache/kafka/2.5.0/kafka_2.13-2.5.0.tgz
tar -xzvf kafka_2.13-2.5.0.tgz
cd kafka_2.13-2.5.0
2、启动zookeeper
kafka需要依赖ZK,安装包中已经自带了一个ZK,也可以改成指定已运行的ZK。 如果改成指定的ZK需要修改修改 kafka 安装目录下的 config/server.properties 文件中的 zookeeper.connect 。这里使用自带的ZK。
后台启动ZK:
nohup ./bin/zookeeper-server-start.sh config/zookeeper.properties >> zookeeper.nohup &
检查zookeeper是否启动成功:
ps -ef|grep zookeeper
3、启动kafka
修改相关配置
vim config/server.properties
有几个配置可以根据需求改
broker.id=0
默认端口
对Kafka来说,日志就是数据,数据就是日志
partition
消息留存时间
消息保留大小
Broker ID启动以后就不能改了
broker.id=1
取消注释,改成本机IP:
listeners=PLAINTEXT://192.168.44.160:9092
num.partitions后面增加2行。 发送到不存在topic自动创建。允许永久删除topic。
num.partitions=1
auto.create.topics.enable=true
delete.topic.enable=true
后台启动kafka(kafka安装目录下):
nohup ./bin/kafka-server-start.sh ./config/server.properties &
日志在logs目录下
4、创建Topic
创建一个名为gptest的topic,只有一个副本,一个分区:
sh bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic gptest
查看已经创建的 topic:
sh bin/kafka-topics.sh -list -zookeeper localhost:2181
5、启动Producer
打开一个窗口,在kafka解压目录下:
sh bin/kafka-console-producer.sh --broker-list localhost:9092 --topic gptest
6、启动Consumer
在一个新的远程窗口中:
sh bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic gptest --from-beginning
7、Producer窗口发送消息
输入hello world 回车
消费者收到了消息:
删除kafka全部数据步骤: 1、停止每台机器上的kafka; 2、删除kafka存储目录(server.properties文件log.dirs配置,默认为“/tmp/kafka-logs”)全部topic的数据目录; 3、删除zookeeper上与kafka相关的znode节点;除了/zookeeper 4、重启kafka。
集群
注意: 单机的kafka和集群的kafka不要混用一个zk,否则会出现数据混乱的问题。
集群肯定要用到zk
0、安装zookeeper
1、下载ZK
zookeeper.apache.org/releases.ht… 这里下载二进制版本,不需要编译 以安装路径 /usr/local/soft为例
cd /usr/local/soft
wget https://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.6.1/apache-zookeeper-3.6.1-bin.tar.gz
2、解压
tar -xzvf apache-zookeeper-3.6.1-bin.tar.gz
3、修改配置文件
cd apache-zookeeper-3.6.1-bin/conf
cp zoo_sample.cfg zoo.cfg
端口号默认2181。 配置文件zoo.cfg里面的dataDir要修改,如果不改,需要创建这个目录
mkdir -p /tmp/zookeeper
4、启动ZK
cd ../bin
./zkServer.sh start
zookeeper默认端口 2181
输出日志:
ZooKeeper JMX enabled by default
Using config: /usr/local/soft/apache-zookeeper-3.6.1-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
查看是否启动成功
ps -ef | grep zookeeper
其他命令: 查看zk的运行状态
./zkServer.sh status
客户端链接zk
./zkCli.sh
ls 查看节点
ls
get 获取节点数据和更新信息
get /节点名字
create 创建节点 e 临时节点 s 顺序节点
create [-s] [-e] path data acl
删除节点
delete path [version]
1、下载解压kafka
cd /usr/local/soft
wget https://mirror.bit.edu.cn/apache/kafka/2.5.0/kafka_2.13-2.5.0.tgz
tar -xzvf kafka_2.13-2.5.0.tgz
cd kafka_2.13-2.5.0
2、修改配置文件
复制3个配置文件
cd config
cp server.properties server1.properties
cp server.properties server2.properties
cp server.properties server3.properties
修改配置文件中的broker.id分别为1、2、3 listeners这一行取消注释,端口号分别为9093、9094、9095 log.dirs分别设置为kafka-logs1、kafka-logs2、kafka-logs3(先创建)
mkdir -p /tmp/kafka-logs1 /tmp/kafka-logs2 /tmp/kafka-logs3
server1.properties 的配置:
broker.id=1
listeners=PLAINTEXT://192.168.44.161:9093
log.dirs=/tmp/kafka-logs1
server2.properties 的配置:
broker.id=2
listeners=PLAINTEXT://192.168.44.161:9094
log.dirs=/tmp/kafka-logs2
server3.properties 的配置:
broker.id=3
listeners=PLAINTEXT://192.168.44.161:9095
log.dirs=/tmp/kafka-logs3
3、启动3个服务
第一步: 启动ZK。
可以使用alias
再启动kafka。
cd ../bin
./kafka-server-start.sh -daemon ../config/server1.properties
./kafka-server-start.sh -daemon ../config/server2.properties
./kafka-server-start.sh -daemon ../config/server3.properties
PS:如果遇到zk node exists的问题,先把brokers节点删掉(临时解决方案)。
4、集群下创建Topic
在bin目录下, 创建一个名为gptest的topic,只有一个副本,一个分区:
sh kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic gptest
查看已经创建的 topic:
sh kafka-topics.sh -list -zookeeper localhost:2181
5、集群下启动Consumer
在一个新的远程窗口中:
sh kafka-console-consumer.sh --bootstrap-server 192.168.44.161:9093,192.168.44.161:9094,192.168.44.161:9095 --topic gptest --from-beginning
6、集群下启动Producer
打开一个新的窗口,在kafka解压目录下:
sh kafka-console-producer.sh --broker-list 192.168.44.161:9093,192.168.44.161:9094,192.168.44.161:9095 --topic gptest
7、集群下Producer窗口发送消息
在生产者窗口输入hello world 回车
大致代码
依赖:
<dependencies>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
生产者:
public class ProducerTest {
public static void main(String[] args) {
Properties pros=new Properties();
pros.put("bootstrap.servers","192.168.44.161:9093,192.168.44.161:9094,192.168.44.161:9095");
pros.put("group.id","gp-test-group");
pros.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
pros.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
// 0 发出去就确认 | 1 leader 落盘就确认| all 所有Follower同步完才确认
pros.put("acks","-1");
// 异常自动重试次数
pros.put("retries",3);
// 多少条数据发送一次,默认16K
pros.put("batch.size",16384);
// 批量发送的等待时间
pros.put("linger.ms",5);
// 客户端缓冲区大小,默认32M,满了也会触发消息发送
pros.put("buffer.memory",33554432);
// 获取元数据时生产者的阻塞时间,超时后抛出异常
pros.put("max.block.ms",3000);
Producer<String,String> producer = new KafkaProducer<String,String>(pros);
for (int i =0 ;i<1000000;i++) {
producer.send(new ProducerRecord<String,String>("mytopic",Integer.toString(i),Integer.toString(i)));
// System.out.println("发送:"+i);
}
producer.close();
}
}
消费者:
public class ConsumerTest {
public static void main(String[] args) {
Properties props= new Properties();
props.put("bootstrap.servers","192.168.44.161:9093,192.168.44.161:9094,192.168.44.161:9095");
props.put("group.id","gp-test-group");
// 是否自动提交偏移量,只有commit之后才更新消费组的 offset
props.put("enable.auto.commit","true");
// 消费者自动提交的间隔
props.put("auto.commit.interval.ms","1000");
// 从最早的数据开始消费 earliest | latest | none
props.put("auto.offset.reset","earliest");
props.put("key.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String,String> consumer=new KafkaConsumer<String, String>(props);
consumer.subscribe(Arrays.asList("mytopic"));
try {
while (true){
ConsumerRecords<String,String> records=consumer.poll(1000);
for (ConsumerRecord<String,String> record:records){
System.out.printf("offset = %d ,key =%s, value= %s, partition= %s%n" ,record.offset(),record.key(),record.value(),record.partition());
}
}
}finally {
consumer.close();
}
}
}
Kafka架构
整体架构图
1.Broker
中介、或者代理
2.Producer、Consumer、Message
Message和Record一个意思
消息传输需要序列化
上面的测试代码里面也可以看到对应的序列化
消息是consumer根据自己的处理速度从Broker拿的
有两种方式
第一种是pull,相当于你到快递站拿快递 异步(Kafka两种都支持)
第二种是push,相当于送货上门 阻塞(rabbitmq主要是这个)
3.Topic
一类消息的分类
发布订阅模式
4.Partition
如果是3的模型的话,不好做横向拓展,就有了Partition
topic是逻辑的,Partition是物理的,进文件可以看到关于内存的信息
配置文件里有个segment,内存块,当一个partition内存满了之后,会自动扩,下面第一张图是配置文件(改16M是为了测试扩容),第二张图是扩容的两个segment(那一串数字是基于上一个segment的内存块的偏移量(offset)得出的)
下面的配置文件的意思是,创建一个Topic默认会有一个partition
5.Replica
数据的备份
每个partition只会有一个Leader(标红的),其他的Follower有机制去同步数据
(不一定一个机器一个Leader)
比如:
会有一个assignReplicasToBrokers的方法分配
这么多partition,选哪个收消息呢?
可以在代码中自定义实现算法
producer.send(new ProducerRecord<String, String>("mytopic",string1,string2))
这个ProducerRecord是可以重载的:
不指定就发送到默认的partition
比如这样:先获取size,再负载均衡
选举的流程
先选个Controller,通过rpc通知各个partition
6.Consumer Group
7.Consumer offset
别的消息队列都是消费完消息就删除,所以下次来直接读取就可以了,但是Kafka 的消费者是怎么知道下次来读取哪条消息的呢
offset记录了消息的序号
对应关系存在broker中
通过hash取模算出下标
8.Kafka与Zookeeper
zk作用:
最后,整体架构图:
Java结合Kafka
代码地址: wwcz.lanzout.com/iKSa80m66q0…
Java中使用
consumer:
public class ConsumerTest {
public static void main(String[] args) {
Properties props= new Properties();
props.put("bootstrap.servers","192.168.44.161:9093,192.168.44.161:9094,192.168.44.161:9095");
props.put("group.id","gp-test-group");
// 是否自动提交偏移量,只有commit之后才更新消费组的 offset
props.put("enable.auto.commit","true");
// 消费者自动提交的间隔
props.put("auto.commit.interval.ms","1000");
// 从最早的数据开始消费 earliest | latest | none
props.put("auto.offset.reset","earliest");
props.put("key.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String,String> consumer=new KafkaConsumer<String, String>(props);
consumer.subscribe(Arrays.asList("mytopic"));
try {
while (true){
ConsumerRecords<String,String> records=consumer.poll(1000);
for (ConsumerRecord<String,String> record:records){
System.out.printf("offset = %d ,key =%s, value= %s, partition= %s%n" ,record.offset(),record.key(),record.value(),record.partition());
}
}
}finally {
consumer.close();
}
}
}
26行可以订阅多个topic
producer:
public class ProducerTest {
public static void main(String[] args) {
Properties pros=new Properties();
pros.put("bootstrap.servers","192.168.44.161:9093,192.168.44.161:9094,192.168.44.161:9095");
pros.put("group.id","gp-test-group");
pros.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
pros.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
// 0 发出去就确认 | 1 leader 落盘就确认| all 所有Follower同步完才确认
pros.put("acks","-1");
// 异常自动重试次数
pros.put("retries",3);
// 多少条数据发送一次,默认16K
pros.put("batch.size",16384);
// 批量发送的等待时间
pros.put("linger.ms",5);
// 客户端缓冲区大小,默认32M,满了也会触发消息发送
pros.put("buffer.memory",33554432);
// 获取元数据时生产者的阻塞时间,超时后抛出异常
pros.put("max.block.ms",3000);
Producer<String,String> producer = new KafkaProducer<String,String>(pros);
for (int i =0 ;i<1000000;i++) {
producer.send(new ProducerRecord<String,String>("mytopic",Integer.toString(i),Integer.toString(i)));
// System.out.println("发送:"+i);
}
producer.close();
}
}
SpringBoot
依赖:
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
配置类更加优雅:
server.port=7271
#============== kafka ===================
# Ö¸¶¨kafka serverµÄµØÖ·£¬¼¯ÈºÅä¶à¸ö£¬Öм䣬¶ººÅ¸ô¿ª
spring.kafka.bootstrap-servers=192.168.44.161:9093,192.168.44.161:9094,192.168.44.161:9095
#=============== provider =======================
spring.kafka.producer.retries=0
spring.kafka.producer.batch-size=16384
spring.kafka.producer.buffer-memory=33554432
spring.kafka.producer.acks=1
spring.kafka.producer.properties.linger.ms=0
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
#=============== consumer =======================
spring.kafka.consumer.group-id=springboottopic-group
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.enable-auto-commit=true
spring.kafka.consumer.auto-commit-interval=1000
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
consumer:
@Component
public class ConsumerListener {
@KafkaListener(topics = "springboottopic")
public void onMessage(String msg){
System.out.println("----收到消息:"+msg+"----");
}
}
producer:
@Component
public class KafkaProducer {
@Autowired
private KafkaTemplate<String,Object> kafkaTemplate;
public String send(@RequestParam String msg){
kafkaTemplate.send("springboottopic", msg);
return "ok";
}
}
当然send也会有很多重载:
public ListenableFuture<SendResult<K, V>> send(String topic, @Nullable V data) {
ProducerRecord<K, V> producerRecord = new ProducerRecord(topic, data);
return this.doSend(producerRecord);
}
public ListenableFuture<SendResult<K, V>> send(String topic, K key, @Nullable V data) {
ProducerRecord<K, V> producerRecord = new ProducerRecord(topic, key, data);
return this.doSend(producerRecord);
}
public ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, K key, @Nullable V data) {
ProducerRecord<K, V> producerRecord = new ProducerRecord(topic, partition, key, data);
return this.doSend(producerRecord);
}
public ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, Long timestamp, K key, @Nullable V data) {
ProducerRecord<K, V> producerRecord = new ProducerRecord(topic, partition, timestamp, key, data);
return this.doSend(producerRecord);
}
public ListenableFuture<SendResult<K, V>> send(ProducerRecord<K, V> record) {
return this.doSend(record);
}
public ListenableFuture<SendResult<K, V>> send(Message<?> message) {
ProducerRecord<?, ?> producerRecord = this.messageConverter.fromMessage(message, this.defaultTopic);
if (!producerRecord.headers().iterator().hasNext()) {
byte[] correlationId = (byte[])message.getHeaders().get("kafka_correlationId", byte[].class);
if (correlationId != null) {
producerRecord.headers().add("kafka_correlationId", correlationId);
}
}
return this.doSend(producerRecord);
}
启动类没啥要加的:
@SpringBootApplication
public class KafkaApp {
public static void main(String[] args) {
SpringApplication.run(KafkaApp.class, args);
}
}
Kafka和RabbitMQ对比
rabbitmq:
消息灵活分发(Exchange绑定key)、功能(死信队列、延迟队列)
Kafka:
消息保存在服务器上、更高的吞吐量、消息的顺序
Q&A
Kafka收发消息不用ack速度比较快,顺序文件,0拷贝