Springboot集成Kafka

一、書寫背景:

最近陸續(xù)收到不少朋友反饋,他們在計(jì)劃要在springboot項(xiàng)目引入kafka中間件。在網(wǎng)上找過很多資料,但都比較零散,按照其說明進(jìn)行操作后讓項(xiàng)目正常跑起來仍是比較坎坷,聽說我對kafka比較了解,希望給予一起分享。剛好最近因?yàn)橐咔椋瑢?shí)際相對于平常稍寬松,也想借此寫點(diǎn)東西,一來作為自己的總結(jié),二來可以給予有需要的朋友一些引導(dǎo),針對此文期望對各位遇到問題的朋友有一定的幫助。

二、前期準(zhǔn)備:

1. 安裝JDK,具體版本可以根據(jù)項(xiàng)目實(shí)際情況選擇,目前使用最多的為jdk8

2.安裝Zookeeper,具體版本可以根據(jù)項(xiàng)目實(shí)際情況選擇,本項(xiàng)目使用的是3.5.8

3.安裝Kafka?,具體版本可以根據(jù)項(xiàng)目實(shí)際情況選擇,本項(xiàng)目使用的是3.5.1

4.安裝Kafka Manage? (非必要:安裝主要是了對kafka項(xiàng)目操作提供圖形化界面操作),具體版本可以根據(jù)項(xiàng)目實(shí)際情況選擇,本項(xiàng)目使用的是1.3.3.7

三、具體實(shí)現(xiàn):

1.pom.xml加依賴:

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.3.12.RELEASE</version>

<relativePath/> <!-- lookup parent from repository -->

</parent>

<dependencies>

<!--springboot web依賴 -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<!--kafka依賴 -->

<dependency>

<groupId>org.springframework.kafka</groupId>

<artifactId>spring-kafka</artifactId>

</dependency>

</dependencies>

2.application.yml做配置

spring:

? kafka:

? ? bootstrap-servers: 127.0.0.1:9092

? ? producer: #生產(chǎn)者

? ? ? # 發(fā)生錯(cuò)誤后,消息重發(fā)的次數(shù)。重試1次,此值需要結(jié)合業(yè)務(wù)場景,重試與否各有千秋(重試,好處:盡可能的確保生產(chǎn)者寫入block成功;壞處:有可能時(shí)帶有順序?qū)懭氲臄?shù)據(jù)打亂順序

? ? ? #比如:依次寫入數(shù)據(jù) 1/2/3,但寫入1時(shí)因網(wǎng)絡(luò)異常,進(jìn)行了重寫,結(jié)果落到block的數(shù)據(jù)成了2/3/1)

? ? ? retries: 1

? ? ? #當(dāng)有多個(gè)消息需要被發(fā)送到同一個(gè)分區(qū)時(shí),生產(chǎn)者會把它們放在同一個(gè)批次里。該參數(shù)指定了一個(gè)批次可以使用的內(nèi)存大小,按照字節(jié)數(shù)計(jì)算。模式時(shí)16k

? ? ? batch-size: 16384 #16k

? ? ? # 設(shè)置生產(chǎn)者內(nèi)存緩沖區(qū)的大小

? ? ? buffer-memory: 33554432

? ? ? acks: 1

? ? ? # 鍵的序列化方式

? ? ? key-serializer: org.apache.kafka.common.serialization.StringSerializer

? ? ? # 值的序列化方式

? ? ? value-serializer: org.apache.kafka.common.serialization.StringSerializer

? ? consumer:

? ? ? #group-id: default-group

? ? ? # 自動(dòng)提交的時(shí)間間隔 在spring boot 2.X 版本中這里采用的是值的類型為Duration 需要符合特定的格式,如1S,1M,2H,5D.此屬性只有在enable-auto-commit:true時(shí)生效

? ? ? auto-commit-interval: 1S

? ? ? enable-auto-commit: false

? ? ? # 該屬性指定了消費(fèi)者在讀取一個(gè)沒有偏移量的分區(qū)或者偏移量無效的情況下該作何處理:

? ? ? # latest(默認(rèn)值)在偏移量無效的情況下,消費(fèi)者將從最新的記錄開始讀取數(shù)據(jù)(在消費(fèi)者啟動(dòng)之后生成的記錄)

? ? ? # earliest :在偏移量無效的情況下,消費(fèi)者將從起始位置讀取分區(qū)的記錄

? ? ? auto-offset-reset: earliest

? ? ? # 鍵的反序列化方式

? ? ? key-deserializer: org.apache.kafka.common.serialization.StringSerializer

? ? ? # 值的反序列化方式

? ? ? value-deserializer: org.apache.kafka.common.serialization.StringSerializer

? ? listener:

? ? ? # 當(dāng)每一條記錄被消費(fèi)者監(jiān)聽器(ListenerConsumer)處理之后提交

? ? ? # RECORD

? ? ? # 當(dāng)每一批poll()的數(shù)據(jù)被消費(fèi)者監(jiān)聽器(ListenerConsumer)處理之后提交

? ? ? # BATCH

? ? ? # 當(dāng)每一批poll()的數(shù)據(jù)被消費(fèi)者監(jiān)聽器(ListenerConsumer)處理之后,距離上次提交時(shí)間大于TIME時(shí)提交

? ? ? # TIME

? ? ? # 當(dāng)每一批poll()的數(shù)據(jù)被消費(fèi)者監(jiān)聽器(ListenerConsumer)處理之后,被處理record數(shù)量大于等于COUNT時(shí)提交

? ? ? # COUNT

? ? ? # TIME | COUNT 有一個(gè)條件滿足時(shí)提交

? ? ? # COUNT_TIME

? ? ? # 當(dāng)每一批poll()的數(shù)據(jù)被消費(fèi)者監(jiān)聽器(ListenerConsumer)處理之后, 手動(dòng)調(diào)用Acknowledgment.acknowledge()后提交

? ? ? # MANUAL

? ? ? # 手動(dòng)調(diào)用Acknowledgment.acknowledge()后立即提交,一般使用這種

? ? ? # MANUAL_IMMEDIATE

? ? ? ack-mode: manual_immediate

? ? ? # 在偵聽器容器中運(yùn)行的線程數(shù)

? ? ? concurrency: 5

3.創(chuàng)建topic:

3.1 通過命令創(chuàng)建,如下:

bin/kafka-topics.sh --create --zookeeper 127.0.0.1:2181 --replication-factor 1 --partitions 1 --topic test

?-- 127.0.0.1:2181? zookeeper?服務(wù)器地址

--??replication-factor?partitions 副本數(shù)量

--partitions?partition數(shù)量

3.2 通過Kafka Manager創(chuàng)建(推薦使用),操作如下:


3.2.1 新建Cluster

點(diǎn)擊【Cluster】>【Add Cluster】打開如下添加集群的配置界面:

輸入集群的名字(如Kafka-Cluster-1)和 Zookeeper 服務(wù)器地址(如localhost:2181),選擇最接近的Kafka版本(如0.10.1.0)


注意:如果沒有在 Kafka 中配置過 JMX_PORT,千萬不要選擇第一個(gè)復(fù)選框。Enable JMX Polling如果選擇了該復(fù)選框,Kafka-manager 可能會無法啟動(dòng)。




3.2.2 新建主題


新建topic操作


查看新創(chuàng)建的topic

4.編代碼:

4.1 定義消息生產(chǎn)者 API

package com.charlie.cloudconsumer.service.impl.kafka;

import com.charlie.cloudconsumer.common.utils.JSON;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.kafka.core.KafkaTemplate;

import org.springframework.kafka.support.SendResult;

import org.springframework.util.concurrent.ListenableFuture;

import org.springframework.util.concurrent.ListenableFutureCallback;

import java.util.UUID;

/**

* @Author: charlie

* @CreateTime: 2022/4/9

* @Description : kafka消息生產(chǎn)端,為實(shí)踐業(yè)務(wù)提供向kafka block發(fā)現(xiàn)消息的API

*/

@Component

@Slf4j

public class QueueProducer {

@Autowired

? ? private KafkaTemplatekafkaTemplate;

public void sendQueue(String topic,Object msgContent) {

String obj2String =JSON.toJSONString(msgContent);

log.info("kafka service 準(zhǔn)備發(fā)送消息為:{}",obj2String);

//發(fā)送消息

? ? ? ? ListenableFuture>future =kafkaTemplate.send(topic,UUID.randomUUID().toString(),obj2String);

future.addCallback(new ListenableFutureCallback>() {

//消息發(fā)送成功

? ? ? ? ? ? @Override

? ? ? ? ? ? public void onSuccess(SendResult stringObjectSendResult) {

log.info("[kafka service-生產(chǎn)成功]topic:{},結(jié)果{}",topic, stringObjectSendResult.toString());

}

//消息發(fā)送失敗

? ? ? ? ? ? @Override

? ? ? ? ? ? public void onFailure(Throwable throwable) {

//發(fā)送失敗的處理,本處只是記錄了錯(cuò)誤日志,可結(jié)合實(shí)際業(yè)務(wù)做處理

? ? ? ? ? ? ? ? log.info("[kafka service-生產(chǎn)失敗]topic:{},失敗原因{}",topic, throwable.getMessage());

}

});

}

}

4.2 定義消息處理業(yè)務(wù)類

package com.charlie.cloudconsumer.service.impl.kafka;

import org.apache.commons.lang3.ObjectUtils;

import org.springframework.stereotype.Component;

/**

* @Author: charlie

* @CreateTime: 2022/4/9

* @Description : 消費(fèi)端實(shí)際的業(yè)務(wù)處理對象

*/

@Component //添加此注解的原因是因?yàn)橄M(fèi)端在項(xiàng)目啟動(dòng)時(shí)就會初始化,消費(fèi)端需要用到此類,故也讓此類在項(xiàng)目啟動(dòng)時(shí)進(jìn)行注冊

public class QueueDataProcess {

public boolean doExec(Object obj) {

// todu 具體的業(yè)務(wù)邏輯

? ? ? ? if (ObjectUtils.isNotEmpty(obj)) {

return true;

}else {

return false;

}

}

}

4.3 定義消費(fèi)者

package com.charlie.cloudconsumer.service.impl.kafka;

import com.charlie.cloudconsumer.common.utils.JSON;

import com.charlie.cloudconsumer.model.Order;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang.exception.ExceptionUtils;

import org.apache.kafka.clients.consumer.ConsumerRecord;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.kafka.annotation.KafkaListener;

import org.springframework.kafka.support.Acknowledgment;

import org.springframework.kafka.support.KafkaHeaders;

import org.springframework.messaging.handler.annotation.Header;

import org.springframework.stereotype.Component;

import java.util.Optional;

/**

* @Author: charlie

* @CreateTime: 2022/4/9

* @Description : kafka消息消費(fèi)端,負(fù)責(zé)消費(fèi)特定topic消息

*/

@Component

@Slf4j

@SuppressWarnings("all")

public class QueueConsumer {

@Autowired

? ? private QueueDataProcess queueDataProcess;

/**

*

*/

? ? @KafkaListener(topics ="test", groupId ="consumer")

public void doConsumer(ConsumerRecord record,Acknowledgment ack,@Header(KafkaHeaders.RECEIVED_TOPIC)String topic) {

Optional message =Optional.ofNullable(record.value());

if (message.isPresent()) {

try {

Object msg =message.get();

log.info("[kafka-消費(fèi)] doConsumer 消費(fèi)了: Topic:" + topic +",Message:" +msg);

boolean res =queueDataProcess.doExec(JSON.parseObject(msg.toString(),Order.class));

if (res) {

ack.acknowledge();

}

}catch (Exception ex) {

log.error("[kafka-消費(fèi)異常] doConsumer Error {} ",ExceptionUtils.getFullStackTrace(ex));

}

}

}

}

4.4 定義控制器

package com.charlie.cloudconsumer.controller;

import com.alibaba.fastjson.JSON;

import com.charlie.cloudconsumer.common.utils.AjaxResult;

import com.charlie.cloudconsumer.common.utils.BuildResponseUtils;

import com.charlie.cloudconsumer.model.Order;

import com.charlie.cloudconsumer.service.impl.kafka.QueueProducer;

import org.apache.commons.lang3.ObjectUtils;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RestController;

/**

* @Author: charlie

* @CreateTime: 2022/4/9

* @Description : kafka消息發(fā)送控制器,負(fù)責(zé)接受用戶的發(fā)送的隊(duì)列消息

*/

@RestController

@RequestMapping(value ="/kafka",produces =MediaType.APPLICATION_JSON_VALUE)

public class KafkaController {

? ? @Autowired

? ? private QueueProducer queueProducer;

? ? @RequestMapping(value = "/send",method = RequestMethod.POST)

? ? public? AjaxResult<?> sendMsg(@RequestBody Order order) {

? ? ? ? AjaxResult<?> ajaxResult= null;

? ? ? ? if (ObjectUtils.isNotEmpty(order)) {

? ? ? ? ? queueProducer.sendQueue("test",order);

? ? ? ? ? ? ajaxResult = BuildResponseUtils.success(0,"發(fā)送消息:"+ JSON.toJSONString(order) + "成功!");

? ? ? ? } else {

? ? ? ? ? ? ajaxResult = BuildResponseUtils.success(1,"發(fā)送消息:"+ JSON.toJSONString(order) + "失敗!");

? ? ? ? }

? ? ? ? return ajaxResult;

? ? }

}

四、效果展示:

1 postman模擬用戶發(fā)起操作


發(fā)送參數(shù)

2. springboot項(xiàng)目處理情況



3. postman端數(shù)據(jù)返回情況



以上的代碼是經(jīng)過本人運(yùn)行過的,希望對看到的您有所幫助!在實(shí)際生產(chǎn)過程中的更多問題請查閱:Kafka問題總結(jié)及性能優(yōu)化最佳實(shí)踐

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,993評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內(nèi)容