Kafka集羣搭建踩坑記錄
最近在學習kafka,但是在用虛擬機跑的時候,動不動就會卡死,內存不足之類的,於是想用docker來搭建kafka的運行環境。當然也踩了不少的坑,寫一篇博文記錄一下。
這裏我們先不搭建集羣的kafka,而是搭建單機的。
Zookeeper環境搭建
弄Kafka不搞zookeeper那是不可能的,因此我們先開始弄zookeeper的環境。
首先我們在docker裏新建一個內網環境:
docker network create docker_net # 接下來zookeeper和kafka都在這個內網裏實現連通
docker-compose-zookeeper.yml文件如下:
version: '1.0'
networks:
# 使用外部網絡,即在docker中的自定義網絡
docker_net:
external: true
services:
zoo:
# 指定的鏡像名
image: zookeeper
restart: unless-stopped
# 這裏我們修改了docker中的hostname,內網裏可以直接用zoo來代替其IP地址
hostname: zoo
container_name: zoo
ports:
- 2182:2181
#配置zookeeper的id與其服務器
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server=zoo:2888:3888
# 掛載linux中的文件
volumes:
- ./zookeeper/zoo1/data:/data
- ./zookeeper/zoo1/datalog:/datalog
# 選擇內網網絡
networks:
- docker_net
執行命令開啓zookeeper
docker-compose -f docker-compose-zookeeper.yml up -d
進入其中查看zookeeper的啓動狀態
docker exec -it zoo /bin/bash
bin/zkServer.sh status #最後得出zookeeper執行成功
Kafka集羣搭建
這裏需要好好說一下
還是先看docker-compose-kafka.yml的文件該怎麼寫
這裏還是需要記錄一下兩個概念,這裏引用一下這篇文章的解釋:listeners和advertised.listeners
listeners
: 學名叫監聽器,其實就是告訴外部連接者要通過什麼協議訪問指定主機名和端口開放的Kafka
服務。advertised.listeners
:和listeners
相比多了個advertised
。Advertised
的含義表示宣稱的、公佈的,就是說這組監聽器是Broker
用於對外發布的。
比如說:
listeners: INSIDE://172.17.0.10:9092,OUTSIDE://172.17.0.10:9094
advertised_listeners: INSIDE://172.17.0.10:9092,OUTSIDE://<公網 ip>:端口
kafka_listener_security_protocol_map: "INSIDE:SASL_PLAINTEXT,OUTSIDE:SASL_PLAINTEXT"
kafka_inter_broker_listener_name: "INSIDE"
advertised_listeners
監聽器會註冊在 zookeeper
中;
當我們對 172.17.0.10:9092
請求建立連接,kafka
服務器會通過 zookeeper
中註冊的監聽器,找到 INSIDE
監聽器,然後通過 listeners
中找到對應的 通訊 ip
和 端口;
同理,當我們對 <公網 ip>:端口
請求建立連接,kafka
服務器會通過 zookeeper
中註冊的監聽器,找到 OUTSIDE
監聽器,然後通過 listeners
中找到對應的 通訊 ip
和 端口 172.17.0.10:9094
;
總結:advertised.listeners在docker中的作用在於將內部的listeners
註冊到zookeeper中,從而可以被外界訪問到
version: '1.0'
networks:
docker_net:
external: true
services:
kafka:
# 這個鏡像時候docker-hub上比較火的kafka鏡像。更新的比較勤快
image: wurstmeister/kafka
restart: unless-stopped
container_name: kafka
ports:
- "9093:9092"
# 相當於docker --link zoo, 可以通過這個實現kafka的鏡像訪問zoo
external_links:
- zoo
environment:
KAFKA_BROKER_ID: 1
# 將會通知給其他生產者和消費者的主機名。會註冊在zookeeper中 -- 這裏需要改爲宿主機的IP地址
# 這裏沒有寫listners的IP,因爲它爲空的時候其實就是暴露的本地IP,localhost,而我們通過advertised.listeners其實就是建立了kafka的本地IP和外部宿主機之間的IP互通
KAFKA_ADVERTISED_HOST_NAME: 10.181.56.101 ## 修改:宿主機IP
KAFKA_ADVERTISED_PORT: 9093 # 修改:宿主機映射port
# 註冊到zookeeper中,暴露給外界,從而可以從外界調用到
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://10.181.56.101:9093 ## 綁定發佈訂閱的端口。修改:宿主機IP
KAFKA_ZOOKEEPER_CONNECT: "zoo:2181"
volumes:
- "./kafka/kafka1/docker.sock:/var/run/docker.sock"
- "./kafka/kafka1/data/:/kafka"
networks:
- docker_net
接着通過以下命令執行;
docker-compose -f docker-compose-kafka.yml up -d
我們通過java代碼進行調試
生產者
public class ProducerStart {
private static final String brokerList = "dennis-1:9092"; //這裏的dennis-1是我的虛擬機主機名hostname
private static final String topic = "heima"; // 最開始看的黑馬的視頻學的
public static void main(String[] args) throws InterruptedException {
Properties properties = new Properties();
//設置Key序列化器
//properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//設置重試次數
properties.put(ProducerConfig.RETRIES_CONFIG, 10);
//設置值序列化器
properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
//設置集羣地址
properties.put("bootstrap.servers", brokerList);
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
// //封裝了發送的消息
ProducerRecord<String, String> record = new ProducerRecord<>(topic, "kafka-demo",
"hello,kafka run");
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(3);
producer.send(record, (metadata, e) -> {
if (e == null) {
System.out.println("topic:" + metadata.topic());
System.out.println("partitions:" + metadata.partition());
System.out.println("offset:" + metadata.offset());
}
});
}
producer.close();
}
}
測試成功,可以發佈。
消費者
接下來我們來測試消費者的訂閱
public class ConsumerStart {
private static final String brokerList = "dennis-1:9092";
private static final String topic = "heima";
private static final String groupId = "group.demo";
public static void main(String[] args) {
Properties properties = new Properties();
//設置Key反序列化器
properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
//設置值反序列化器
properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
//設置集羣地址
properties.put("bootstrap.servers", brokerList);
//設置GroupId
properties.put("group.id", groupId);
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
consumer.subscribe(Collections.singleton(topic));
while (true){
// 接收消息
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));
records.forEach(System.out::println);
}
}
}
接受到了發佈的消息,訂閱成功。
一個困擾了我很久的坑
樓主最開始在調試的時候,一直在報一個錯誤:
[Kafka]Error while fetching metadata with correlation id 4192 : {testXX=LEADER_NOT_AVAILABLE}
消息的發佈和訂閱找不到leader結點。後來才發現其實是宿主機IP設置的不對,我最開始將其設置爲了kafka鏡像內部的
即這裏的172.18.0.2.其實宿主機應該指的是你虛擬機的IP地址,而且應該是靜態IP(我用的是virtualbox,有關靜態IP的設置可以參考我的另一篇博文:虛擬機靜態ip配置)
當advertised.listeners進行IP轉發的時候,將獲取到的本地IP又通過zookeeper轉發了回去,這樣肯定訪問不到
因此應該將advertised.listeners改爲虛擬機的IP,端口用我們映射到虛擬機的端口號,這樣就解決了這個bug。
至於集羣的搭建可以參考這篇文章,其實我的很多東西也是看的這兩篇:
- zookeeper搭建:https://zhuanlan.zhihu.com/p/110677319
- kafka集羣:https://zhuanlan.zhihu.com/p/110905106