使用Spring Request-Reply實現基於Kafka的同步請求響應
大家提到Kafka時第一印象就是它是一個快速的非同步訊息處理系統,不同於通常tomcat之類應用伺服器和前端之間的請求/響應方式請求,客戶端發出一個請求,必然會等到一個響應,這種方式對Kafka來說好像不適合,因為Kafka是一種事件驅動方式,透過事件才能啟用一個響應,但是,問題來了,很多人習慣請求響應模型以後很難接受這種事件響應模型,包括髮布訂閱模型。
當然,Kafka不是不能實現通常的請求響應模型,只要使用兩個Kafka主題,一個是負責請求的主題,另外一個是負責響應的主題,還必須在訊息的生產者記錄中構建相關ID,將與訊息的消費者記錄中的ID進行對應關聯起來,實際上就是將請求Id和響應Id進行關聯。
隨著Spring-Kafka最新版本推出(Spring replying kafka 模板),這種請求-響應模型實現就更加簡單了,不需要開發人員自己進行請求Id和響應Id的關聯,由Spring kafka模板實現。
下面這個案例例演示了Spring-Kafka是如何實現同步的請求響應模型的,原始碼見github
下圖是本案例的演示架構圖,這個案例是以同步行為返回兩個數字總和的結果。
下面我們開始看看開發這個演示步驟:
設定Springboot啟動類
首先需要在pom.xml引入Spring kafka模板:
程式碼如下:
在這個配置類中,我們需要配置核心的ReplyingKafkaTemplate類,這個類繼承了 KafkaTemplate 提供請求/響應的的行為;還有一個生產者工廠(參見 ProducerFactory 下面的程式碼)和 KafkaMessageListenerContainer。這是最基本的設定,因為請求響應模型需要對應到訊息生產者和消費者的行為。
這個消費者用於業務計算,把客戶端透過請求傳入的兩個數字進行相加,然後返回這個請求,透過@SendTo傳送到Kafka的響應主題。
下面開始執行這個案例:
1.下載原始碼見github
2.先啟動kafka
3.直接執行上面啟動類
4.透過postman等工具訪問:
http://localhost:8080/sum
post資料:
返回結果是:
在控制檯輸出記錄頭部資訊:
可見,Spring自動生成聚合ID(correlationId),無需我們自己手工比對了。
當然,Kafka不是不能實現通常的請求響應模型,只要使用兩個Kafka主題,一個是負責請求的主題,另外一個是負責響應的主題,還必須在訊息的生產者記錄中構建相關ID,將與訊息的消費者記錄中的ID進行對應關聯起來,實際上就是將請求Id和響應Id進行關聯。
客戶端---->請求的主題 ----消費者處理請求並把結果傳送到---->響應主題--->客戶端 <p class="indent"> |
隨著Spring-Kafka最新版本推出(Spring replying kafka 模板),這種請求-響應模型實現就更加簡單了,不需要開發人員自己進行請求Id和響應Id的關聯,由Spring kafka模板實現。
下面這個案例例演示了Spring-Kafka是如何實現同步的請求響應模型的,原始碼見github
下圖是本案例的演示架構圖,這個案例是以同步行為返回兩個數字總和的結果。
客戶端-->請求-->RESTcontroll-->Spring-kafka模板-->Kafka請求主題-->Kafka監聽器 客戶端<--響應<--RESTcontroll<--Spring-kafka模板<--Kafka響應主題<--Kafka監聽器 <p class="indent"> |
下面我們開始看看開發這個演示步驟:
設定Springboot啟動類
首先需要在pom.xml引入Spring kafka模板:
<dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> <p class="indent"> |
程式碼如下:
@SpringBootApplication public class RequestReplyKafkaApplication { public static void main(String[] args) { SpringApplication.run(RequestReplyKafkaApplication.class, args); } } <p class="indent"> |
設定Spring ReplyingKafkaTemplate
我們需要在Springboot配置類的KafkaConfig對Spring kafka模板進行配置:
@Configuration public class KafkaConfig { <p class="indent"> |
在這個配置類中,我們需要配置核心的ReplyingKafkaTemplate類,這個類繼承了 KafkaTemplate 提供請求/響應的的行為;還有一個生產者工廠(參見 ProducerFactory 下面的程式碼)和 KafkaMessageListenerContainer。這是最基本的設定,因為請求響應模型需要對應到訊息生產者和消費者的行為。
// 這是核心的ReplyingKafkaTemplate @Bean public ReplyingKafkaTemplate<String, Model, Model> replyKafkaTemplate(ProducerFactory<String, Model> pf, KafkaMessageListenerContainer<String, Model> container) { return new ReplyingKafkaTemplate<>(pf, container); } // 配件:監聽器容器Listener Container to be set up in ReplyingKafkaTemplate @Bean public KafkaMessageListenerContainer<String, Model> replyContainer(ConsumerFactory<String, Model> cf) { ContainerProperties containerProperties = new ContainerProperties(requestReplyTopic); return new KafkaMessageListenerContainer<>(cf, containerProperties); } // 配件:生產者工廠Default Producer Factory to be used in ReplyingKafkaTemplate @Bean public ProducerFactory<String,Model> producerFactory() { return new DefaultKafkaProducerFactory<>(producerConfigs()); } // 配件:kafka生產者的Kafka配置Standard KafkaProducer settings - specifying brokerand serializer @Bean public Map<String, Object> producerConfigs() { Map<String, Object> props = new HashMap<>(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); return props; } <p class="indent"> |
設定spring-Kafka的監聽器
這與通常建立的Kafka消費者相同。唯一的變化是額外是在工廠中設定ReplyTemplate,這是必須的,因為消費者需要將計算結果放入到Kafka的響應主題。
//消費者工廠 Default Consumer Factory @Bean public ConsumerFactory<String, Model> consumerFactory() { return new DefaultKafkaConsumerFactory<>(consumerConfigs(),new StringDeserializer(),new JsonDeserializer<>(Model.class)); } // 併發監聽器容器Concurrent Listner container factory @Bean public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, Model>> kafkaListenerContainerFactory() { ConcurrentKafkaListenerContainerFactory<String, Model> factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(consumerFactory()); // NOTE - set up of reply template 設定響應模板 factory.setReplyTemplate(kafkaTemplate()); return factory; } // Standard KafkaTemplate @Bean public KafkaTemplate<String, Model> kafkaTemplate() { return new KafkaTemplate<>(producerFactory()); } <p class="indent"> |
編寫我們的kafka消費者
這是過去建立的Kafka消費者一樣。唯一的變化是附加了@SendTo註釋,此註釋用於在響應主題上返回業務結果。
@KafkaListener(topics = "${kafka.topic.request-topic}") @SendTo public Model listen(Model request) throws InterruptedException { int sum = request.getFirstNumber() + request.getSecondNumber(); request.setAdditionalProperty("sum", sum); return request; } <p class="indent"> |
這個消費者用於業務計算,把客戶端透過請求傳入的兩個數字進行相加,然後返回這個請求,透過@SendTo傳送到Kafka的響應主題。
總結服務
現在,讓我們將所有這些都結合在一起放在RESTcontroller,步驟分為幾步,先建立生產者記錄,並在記錄頭部中設定接受響應的Kafka主題,這樣
把請求和響應在Kafka那裡對應起來,然後透過模板釋出訊息到Kafka,再透過future.get()堵塞等待Kafka的響應主題傳送響應結果過來。這時再
列印結果記錄中的頭部資訊,會看到Spring自動生成相關ID。
@ResponseBody @PostMapping(value="/sum",produces=MediaType.APPLICATION_JSON_VALUE,consumes=MediaType.APPLICATION_JSON_VALUE) public Model sum(@RequestBody Model request)throws InterruptedException,ExecutionException { //建立生產者記錄 ProducerRecord<String,Model> record = new ProducerRecord<String,Model>(requestTopic,request); //在記錄頭部中設定響應主題 record.headers().add(new RecordHeader(KafkaHeaders.REPLY_TOPIC, requestReplyTopic.getBytes())); //釋出到kafka主題中 RequestReplyFuture<String, Model, Model> sendAndReceive = kafkaTemplate.sendAndReceive(record); //確認生產者是否成功生產 SendResult<String, Model> sendResult = sendAndReceive.getSendFuture().get(); //列印結果記錄中所有頭部資訊 會看到Spring自動生成的相關ID,這個ID是由消費端@SendTo 註釋返回的值。 sendResult.getProducerRecord().headers().forEach(header -> System.out.println(header.key() + ":" + header.value().toString())); //獲取消費者記錄 ConsumerRecord<String, Model> consumerRecord = sendAndReceive.get(); //返回消費者結果 return consumerRecord.value(); } <p class="indent"> |
併發消費者
即使你要建立請求主題在三個分割槽中,三個併發的消費者的響應仍然合併到一個Kafka響應主題,這樣,Spring偵聽器的容器能夠完成匹配相關ID的繁重工作。
整個請求/響應的模型是一致的。
現在我們可以再修改啟動類如下:
@ComponentScan(basePackages = { "com.gauravg.config", "com.gauravg.consumer", "com.gauravg.controller", "com.gauravg.model" }) @SpringBootApplication public class RequestReplyKafkaApplication { public static void main(String[] args) { SpringApplication.run(RequestReplyKafkaApplication.class, args); } } <p class="indent"> |
下面開始執行這個案例:
1.下載原始碼見github
2.先啟動kafka
3.直接執行上面啟動類
4.透過postman等工具訪問:
http://localhost:8080/sum
post資料:
{ "firstNumber": "111", "secondNumber": "2222" } <p class="indent"> |
返回結果是:
{ "firstNumber": 111, "secondNumber": 2222, "sum": 2333 } <p class="indent"> |
在控制檯輸出記錄頭部資訊:
kafka_replyTopic:[B@1f59b198 kafka_correlationId:[B@356a7326 __TypeId__:[B@1a9111f <p class="indent"> |
可見,Spring自動生成聚合ID(correlationId),無需我們自己手工比對了。
Synchronous Kafka: Using Spring Request-Reply - DZ
[該貼被admin於2018-07-23 18:11修改過]
[該貼被admin於2018-07-23 18:13修改過]
相關文章
- ajax--實現非同步請求,接受響應及執行回撥非同步
- Spring MVC能響應HTTP請求的原因?SpringMVCHTTP
- 如何使用angularjs實現ajax非同步請求AngularJS非同步
- Spring Boot使用AOP在控制檯列印請求、響應資訊Spring Boot
- HTTP的請求與響應HTTP
- Spring系列 SpringMVC的請求與資料響應SpringMVC
- Spring基於註解的環繞通知實現請求方法日誌記錄Spring
- .net core基於HttpClient實現的網路請求庫HTTPclient
- 在 DotNetty 中實現同步請求Netty
- ThinkPHP 請求與響應PHP
- HTTP 請求與響應HTTP
- Http請求與響應HTTP
- WebxFrameworkFilter 請求響應流程WebFrameworkFilter
- Django請求響應物件Django物件
- iOS 同步請求 非同步請求 GET請求 POST請求iOS非同步
- 使用RestTemplate,顯示請求資訊,響應資訊REST
- Reactive Spring實戰 -- 響應式Kafka互動ReactSpringKafka
- okhttp 原始碼解析 - 網路協議的實現 - 請求流程: 請求的傳送與響應的接收HTTP原始碼協議
- WebApi系列~基於單請求封裝多請求的設計~請求的安全性設計與實現WebAPI封裝
- Go如何響應http請求?GoHTTP
- 理解Http請求與響應HTTP
- DRF之請求與響應
- jQuery實現的非同步請求程式碼例項jQuery非同步
- Django REST framework的請求與響應DjangoRESTFramework
- jquery的ajax請求servlet與響應jQueryServlet
- 關於常用的http請求頭以及響應頭詳解HTTP
- http請求頭與響應頭的應用HTTP
- HTTP的請求與響應以及使用Chrome的檢視方式HTTPChrome
- 深度解析Spring AI:請求與響應機制的核心邏輯SpringAI
- kafka叢集Broker端基於Reactor模式請求處理流程深入剖析-kafka商業環境實戰KafkaReact模式
- HTTP請求與響應簡析HTTP
- SpringMVC 入門、請求、響應SpringMVC
- request和response——請求響應物件物件
- HTTP請求頭與響應頭HTTP
- django從請求到響應的過程Django
- 非同步請求PHP伺服器,你如何做到無阻塞響應非同步PHP伺服器
- HTTP協議---HTTP請求中的常用請求欄位和HTTP的響應狀態碼及響應頭HTTP協議
- 如何使用cURL獲得請求/響應具體耗時?