近期在做 SOFA 與 SpringCloud 的整合,希望通過一系列的 DEMO 工程去幫助大家更好的使用 SOFA 和 SpringCloud;同時也希望大家一起來參與共建和 star。
GitHub傳送門:spring-cloud-sofastack-samples
Kafka 簡介
官方網站:https://kafka.apache.org/
功能提供
Apache Kafka™ 是 一個分散式資料流平臺,從官方文件的解釋來看,其職能大體如下:
- Publish and subscribe to streams of records, similar to a message queue or enterprise messaging system。釋出和訂閱資料流,與訊息佇列或企業級訊息系統很像。
- Store streams of records in a fault-tolerant durable way。具有很強容災性的儲存資料流
- Process streams of records as they occur。及時的處理資料流。
作為一個後端司機,大多數情況下都是把 Kafka 作為一個分散式訊息佇列來使用的,分散式訊息佇列可以提供應用解耦、流量消峰、訊息分發等功能,已經是大型網際網路服務架構不可缺少的基礎設定了。
基本概念
topic 和 partition
Kafka 對資料提供的核心抽象,topic 是釋出的資料流的類別或名稱。topic 在 Kafka 中,支援多訂閱者; 也就是說,topic 可以有零個、一個或多個消費者訂閱寫到相應 topic 的資料。對應每一個 topic,Kafka 叢集會維護像一個如下這樣的分割槽的日誌:
每個 Partition 都是一個有序的、不可變的並且不斷被附加的記錄序列,也就是一個結構化提交日誌(commit log)。為了保證唯一標性識 Partition 中的每個資料記錄,Partition 中的記錄每個都會被分配一個叫做偏移(offset)順序的ID號。通過一個可配置的保留期,Kafka 叢集會保留所有被髮布的資料,不管它們是不是已經被消費者處理。例如,如果保留期設定為兩天,則在釋出記錄後的兩天內,資料都可以被消費,之後它將被丟棄以釋放空間。 Kafka 的效能是不為因為資料量大小而受影響的,因此長時間儲存資料並不成問題。 事實上,在每個消費者上保留的唯一後設資料是消費者在日誌中的偏移位置,這個偏移由消費者控制:通常消費者會在讀取記錄時線性地提高其偏移值(offset++),但實際上,由於偏移位置由消費者控制,它可以以任何順序來處理資料記錄。 例如,消費者可以重置為較舊的偏移量以重新處理來自過去的資料,或者跳過之前的記錄,並從“現在”開始消費。 這種特徵的組合意味著 Kafka 消費者非常輕量級,隨意的開啟和關閉並不會對其他的消費者有大的影響。日誌中的 Partition 有幾個目的:
- 保證日誌的擴充套件性,topic 的大小不受單個伺服器大小的限制。每個單獨的 Partition 大小必須小於託管它的伺服器磁碟大小,但 topic 可能有很多 Partition,因此它可以處理任意數量的海量資料。
- 作為並行處理的單位 (知乎-Partition:Kafka可以將主題劃分為多個分割槽(Partition),會根據分割槽規則選擇把訊息儲存到哪個分割槽中,只要如果分割槽規則設定的合理,那麼所有的訊息將會被均勻的分佈到不同的分割槽中,這樣就實現了負載均衡和水平擴充套件。另外,多個訂閱者可以從一個或者多個分割槽中同時消費資料,以支撐海量資料處理能力)
kafka中的topic為什麼要進行分割槽
原貼:kafka中的topic為什麼要進行分割槽 ,由於不能轉載,此處不摘抄原文~
生產者
生產者將資料釋出到他們選擇的 topic , 生產者負責選擇要吧資料分配給 topic 中哪個 Partition。這可以通過迴圈方式(round-robin)簡單地平衡負載,或者可以根據某些語義進行分割槽(例如基於資料中的某些關鍵字)來完成。
消費者
消費者們使用消費群組(consumer group )名稱來標註自己,幾個消費者共享一個 group,每一個釋出到 topic 的資料會被傳遞到每個消費群組(consumer group )中的一個消費者例項。 消費者例項可以在不同的程式中或不同的機器上。
如果所有的消費者例項具有相同的 consumer group,則記錄將在所有的消費者例項上有效地負載平衡
如果所有的消費者例項都有不同的 consumer group,那麼每個記錄將被廣播給所有的消費者程式,每個資料都發到了所有的消費者。
上圖解釋源自《Kafka 官方文件》 介紹:
如上圖,一個兩個伺服器節點的Kafka叢集, 託管著4個分割槽(P0-P3),分為兩個消費者群. 消費者群A有2個消費者例項,消費者群B有4個. 然而,更常見的是,我們發現主題具有少量的消費者群,每個消費者群代表一個“邏輯訂戶”。每個組由許多消費者例項組成,保證可擴充套件性和容錯能力。這可以說是“釋出-訂閱”語義,但使用者是一組消費者而不是單個程式。 在Kafka中實現消費的方式,是通過將日誌中的分割槽均分到消費者例項上,以便每個例項在任何時間都是“相應大小的一塊”分割槽的唯一消費者。維護消費者組成員資格的過程,由卡夫卡協議動態處理。 如果新的例項加入組,他們將從組中的其他成員接管一些分割槽; 如果一個例項消失,其分割槽將被分發到剩餘的例項。 Kafka僅提供單個分割槽內的記錄的順序,而不是主題中的不同分割槽之間的總順序。 每個分割槽排序結合按鍵分割槽,足以滿足大多數應用程式的需求。 但是,如果您需要使用總順序,則可以通過僅具有一個分割槽的主題來實現,儘管這僅意味著每個消費者組只有一個消費者程式。
Kafka 作為訊息系統
- 佇列模式中,消費者池可以從伺服器讀取,每條記錄只會被某一個消費者消費
- 允許在多個消費者例項上分配資料處理,但是一旦資料被消費之後,資料就沒有了
- 釋出訂閱模式中,記錄將廣播給所有消費者
- 允許將資料廣播到多個程式,但無法縮放和擴容,因為每個訊息都傳送給每個訂閱使用者
本篇只介紹 Kafka 作為訊息佇列的一些基本概念,更多介紹請參考官方文件。
Kafka 安裝
這裡來看下如何安裝 kafka,下載地址:https://kafka.apache.org/downloads。本篇使用的版本是 kafka_2.12-1.1.1。
-
獲取包檔案
> wget http://mirrors.shu.edu.cn/apache/kafka/1.1.1/kafka_2.12-1.1.1.tgz 複製程式碼
-
解壓壓縮包
> tar -zxvf kafka_2.12-1.1.1.tgz 複製程式碼
-
修改配置檔案
> cd kafka_2.12-1.1.1/config > vim server.properties 複製程式碼
我這裡主要修改項包括以下幾個:
# The id of the broker. This must be set to a unique integer for each broker. broker.id=0 listeners=PLAINTEXT://192.168.0.1:9092 advertised.listeners=PLAINTEXT://192.168.0.1:9092 # zookeeper 地址,可以多個 zookeeper.connect=192.168.0.6:2181 複製程式碼
Kafka 服務啟動需要依賴 Zookeeper ,所以在配置檔案中需要指定 Zookeeper 叢集地址。Kafka 自己的安裝包中解壓之後是包括 Zookeeper 的,可以通過以下的方式來啟動一個單節點 Zookeeper 例項:
> sh zookeeper-server-start.sh -daemon config/zookeeper.properties 複製程式碼
這裡我是指定了之前部署的一臺ZK機器,所以可以直接將ZK地址指到已部署好的地址。Zookeeper 安裝可以參考: Linux 下安裝 Zookeeper
通過上述操作,下面就可以直接來啟動Kafka 服務了:
> sh kafka-server-start.sh config/server.properties 複製程式碼
SpringBoot 整合 Kafka
構建一個簡單的 Kafka Producer 工具依賴
- 依賴引入
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>1.3.5.RELEASE</version><!--$NO-MVN-MAN-VER$-->
</dependency>
複製程式碼
- producer
為了可以把 Kafka 封裝已提供給其他模組使用,大家可以將 Kafka 的生產端工具類使用 SpringBoot 的自動配置機制進行包裝,如下:
@Configuration
public class KafkaProducerAutoConfiguration {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
@Bean
public KafkaSender kafkaSender(){
return new KafkaSender(kafkaTemplate);
}
}
複製程式碼
- KafkaSender
public class KafkaSender {
private KafkaTemplate<String, String> kafkaTemplate;
public KafkaSender(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
/**
* send message
*/
public void sendMessage(String topic, String message) {
kafkaTemplate.send(topic, message);
}
}
複製程式碼
- 自動配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.sofastack.cloud.core.kafka.configuration.KafkaProducerAutoConfiguration
複製程式碼
工程模組如下: image-20190306151759441.png
案例測試
在測試工程中引入依賴,這個依賴就是上面工程打包來的:
<dependency>
<groupId>io.sofastack.cloud</groupId>
<artifactId>sofastack-cloud-core-kafka</artifactId>
</dependency>
複製程式碼
- 在 resources 目錄下新建 application.properties 配置檔案
#============== kafka ===================
# 指定kafka 代理地址,可以多個,這裡的192.168.0.1是上面Kafka 啟動配置檔案中對應的
# 注:網上一些帖子中說 Kafka 這裡的配置只能是主機名,不支援 ip,沒有驗證過,
# 如果您在驗證時出現問題,可以嘗試本機繫結下 host
spring.kafka.bootstrap-servers= 192.168.0.1:9092
#=============== provider =======================
spring.kafka.producer.retries=0
# 每次批量傳送訊息的數量
spring.kafka.producer.batch-size=16384
spring.kafka.producer.buffer-memory=33554432
# 指定訊息key和訊息體的編解碼方式
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
#=============== consumer =======================
# 指定預設消費者group id
spring.kafka.consumer.group-id=test-consumer-group
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.enable-auto-commit=true
spring.kafka.consumer.auto-commit-interval=100ms
# 指定訊息key和訊息體的編解碼方式
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.application.name=kafka-test
logging.path=./logs
複製程式碼
- 啟動類中模擬傳送訊息
@SpringBootApplication
@PropertySource("classpath:application-kafka.properties")
public class ProviderApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(ProviderApplication.class, args);
// 這裡通過容器獲取,正常使用情況下,可以直接使用 Autowired 注入
KafkaSender bean = run.getBean(KafkaSender.class);
for (int i = 0; i < 3; i++) {
//呼叫訊息傳送類中的訊息傳送方法
bean.sendMessage(KafkaContants.TRADE_TOPIC, "send a test message");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
複製程式碼
- 編寫消費者,在 SpringBoot 工程中,消費者實現非常簡單
@Component
public class KafkaReceiver {
// 配置監聽的主體,groupId 和配置檔案中的保持一致
@KafkaListener(topics = { KafkaContants.TRADE_TOPIC }, groupId = "test-consumer-group")
public void listen(ConsumerRecord<?, ?> record) {
Optional<?> kafkaMessage = Optional.ofNullable(record.value());
if (kafkaMessage.isPresent()) {
Object message = kafkaMessage.get();
System.out.println(message);
}
}
}
複製程式碼
啟動工程後,可以在控制檯看下消費者列印的資訊:
這裡保持應用正常執行,再通過服務端來手動傳送訊息,看下是當前消費者能夠正確監聽到對應的 topic 並消費。> sh kafka-console-producer.sh --broker-list 192.168.0.1:9092 --topic trading
複製程式碼
執行上述命令之後,命令列將會等待輸入,這裡輸入先後輸入 glmapper 和 sofa :
然後再看下應用程式控制臺輸入結果如下: image-20190306153452565.png