微服務目的
微服務目的是爲了更好的進行分佈式系統開發,拆分單體應用爲多個服務每個服務都是一個獨立運行的項目(松耦合)。
Spring Cloud Stream 的幾個概念
Spring Cloud Stream is a framework for building message-driven microservice applications.
總結一句話就是:屏蔽底層消息中間件的差異,降低切換版本,統一消息的編程模型
官方定義 Spring Cloud Stream 是一個構建消息驅動微服務的框架。
應用程序通過 inputs 或者 outputs 來與 SpringCloud Stream 中binder 交互,通過我們的配置來 binding ,而 SpringCloud Stream 的 binder 負責與中間件進行交互。所以,我們只需要搞清楚如何與 SpringCloud Stream 交互就可以使用消息驅動。
爲什麼要用SpringCloud Stream
比方說我們用到了RabbitMQ和Kafka,由於這兩個消息中間件的架構上的不同, 像RabbitMQ有exchange, kafka有 Topic和Partitions分區
這些中間件的差異性導致我們實際項目開發給我們造成了一定的困擾, 我們如果用了兩個消息隊列的其中一種, 後面的業務需求,我想往另外一種消息隊列進行遷移,這時候無疑就是一個災難性的, 一大堆東西都要重新推倒重新做,因爲它跟我們的系統耦合了,這時候SpringCloud Stream給我們提供了一種解耦合的方式。
結合 RabbitMQ 使用
示例源碼下載:https://download.csdn.net/download/weixin_44790046/12527597
示例所用工程介紹
04_cloud-eureka-server-port7001 => Eureka服務註冊中心1
05_cloud-eureka-server-port7002 => Eureka服務註冊中心2
以上兩個工程是一個簡易的Eureka集羣環境,兩個註冊中心相互守望
cloud-api-commons => 存放一些公共代碼,工具類
cloud-stream-rabbitmq-provider-port8801 => 消息發送方
cloud-stream-rabbitmq-consumer-port8802 => 消息接收方1
cloud-stream-rabbitmq-consumer-port8803 => 消息接收方2
8801消息發送方yml配置詳解
#當前服務端口號
server:
port: 8801
spring:
application:
name: cloud-stream-provider #服務名稱
cloud:
stream:
binders: #在此處配置要綁定的rabbitmq的服務信息
defaultRabbit: #表示定義的名稱,用於與binding整合
type: rabbit #消息組件類型
environment: #設置rabbitmq的相關的環境配置
spring:
rabbitmq:
host: 192.168.200.***
port: 5672
username: ***
password: ****
virtual-host: /huaxin #指定使用的虛擬主機
bindings: #服務的整合處理
output: #默認通道的名稱
destination: cloud-stream-rabbit #exchange名稱,交換模式默認是topic
content-type: application/json #設置消息類型,本次爲json,文本則設置“text/plain”
default-binder: defaultRabbit #設置要綁定的消息服務的具體設置
outputOrder: #自定義的通道的名稱
destination: customExchange #exchange名稱,交換模式默認是topic
content-type: application/json #設置消息類型,本次爲json,文本則設置“text/plain”
default-binder: defaultRabbit #設置要綁定的消息服務的具體設置
#eureka客戶端配置
eureka:
client:
register-with-eureka: true #表示是否將自己註冊進EurekaServer默認爲true。
fetchRegistry: true #是否從EurekaServer抓取已有的註冊信息,默認爲true。單節點無所謂,集羣必須設置爲true才能配合ribbon使用負載均衡
service-url:
#defaultZone: http://localhost:7001/eureka #單機版(一般不用)
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集羣版(註冊到兩個服務中心)
instance:
instance-id: cloud-stream-provider8801 #修改在Eureka顯示的主機名稱
prefer-ip-address: true #服務訪問路徑顯示ip地址
lease-renewal-interval-in-seconds: 2 #Eureka客戶端向服務端發送心跳的時間間隔,單位爲秒(默認是30秒)
lease-expiration-duration-in-seconds: 5 #Eureka服務端在收到最後一次心跳後等待時間上限,單位爲秒(默認是90秒),超時將剔除服務
8802、8003消息發送方yml配置詳解
#當前服務端口號
server:
port: 8802
spring:
application:
name: cloud-stream-consumer #服務名稱
cloud:
stream:
binders: #在此處配置要綁定的rabbitmq的服務信息
defaultRabbit: #表示定義的名稱,用於與binding整合
type: rabbit #消息組件類型
environment: #設置rabbitmq的相關的環境配置
spring:
rabbitmq:
host: 192.168.200.***
port: 5672
username: ****
password: ***
virtual-host: /huaxin #指定使用的虛擬主機
bindings: #服務的整合處理
input: #默認輸入通道的名稱
destination: cloud-stream-rabbit #exchange名稱,交換模式默認是topic
group: myQueue #指定組名稱(持久化),8802、8803都在這個組,同一個組的多個微服務實例,每次只會有一個拿到,這樣避免了重複消費
content-type: application/json #設置消息類型,本次爲json,文本則設置“text/plain”
default-binder: defaultRabbit #設置要綁定的消息服務的具體設置
inputOrder: #自定義輸入通道的名稱
destination: customExchange #exchange名稱,交換模式默認是topic
group: myCustomQueue #指定組名稱(持久化),8802、8803都在這個組,同一個組的多個微服務實例,每次只會有一個拿到,這樣避免了重複消費
content-type: application/json #設置消息類型,本次爲json,文本則設置“text/plain”
default-binder: defaultRabbit #設置要綁定的消息服務的具體設置
#eureka客戶端配置
eureka:
client:
register-with-eureka: true #表示是否將自己註冊進EurekaServer默認爲true。
fetchRegistry: true #是否從EurekaServer抓取已有的註冊信息,默認爲true。單節點無所謂,集羣必須設置爲true才能配合ribbon使用負載均衡
service-url:
#defaultZone: http://localhost:7001/eureka #單機版(一般不用)
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集羣版(註冊到兩個服務中心)
instance:
instance-id: cloud-stream-consumer8802 #修改在Eureka顯示的主機名稱
prefer-ip-address: true #服務訪問路徑顯示ip地址
lease-renewal-interval-in-seconds: 2 #Eureka客戶端向服務端發送心跳的時間間隔,單位爲秒(默認是30秒)
lease-expiration-duration-in-seconds: 5 #Eureka服務端在收到最後一次心跳後等待時間上限,單位爲秒(默認是90秒),超時將剔除服務
消息發送通道
SpringCloud Stream 基本用法,需要定義一個接口,下面是Stream內置的一個消息發送接口。
註解
@Outout
對應的方法,需要返回MessageChannel
,並且傳入一個參數值。
這就聲明瞭一個輸出通道的名稱爲output 。
消息發送方發送消息具體實現
package huaxin.service.impl;
import huaxin.service.MessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.support.MessageBuilder;
import javax.annotation.Resource;
import java.util.UUID;
/**
* <br>Source默認的輸出通道
* @author 孫啓新
* <br>FileName: MessageProviderImpl
* <br>Date: 2020/06/16 12:24:26
*/
@EnableBinding({Source.class}) //定義消息的推送管道
public class MessageProviderImpl implements MessageProvider {
/**
* 注入默認的消息發送管道
*/
@Resource(name = "output") //指定要引入的bean名稱
private MessageChannel output;
/**
* 發送消息,默認輸出通道
*
* @return
*/
@Override
public String send() {
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("*****默認輸出通道serial: "+serial);
return serial;
}
}
以上代碼就完成了最基本的消息生產者部分。
消息接收
與上面類似,需要定義一個接口,下面是Stream內置的一個消息接收接口。
註解
@Input
對應的方法,需要返回SubscribableChannel
,並且傳入一個參數值。
這就聲明瞭一個輸入通道的名稱爲input 。
消息接收方接收消息具體實現
package huaxin.controller;
import huaxin.config.CustomOrderProcessor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
/**
* <br>Sink默認的輸入通道
* @author 孫啓新
* <br>FileName: ReceiveMessageListenerController
* <br>Date: 2020/06/16 13:43:24
*/
@Component
@EnableBinding({Sink.class}) //定義消息的接收管道
public class ReceiveMessageListenerController {
@Value("${server.port}")
private String serverPort;
/**
* <br>@StreamListener: 監聽隊列,用於消費者的隊列的消息接收,默認輸入通道
*
* @param message
*/
@StreamListener(Sink.INPUT)
public void input(Message<String> message) {
System.out.println("默認輸入通道消費者1號,接受到的消息:"+message.getPayload()+"\t port:"+serverPort);
}
}
8803消費者2與8802消費者1代碼相同。
以上代碼就完成了最基本的消息消費者部分。
測試:
生成者發送消息
消費者1、2接收情況
自定義消息發送接收
SpringCloud Stream 內置了兩個接口,分別定義了 binding 爲 “input” 的輸入通道,和 “output” 的輸出通道,而在我們實際使用中,往往是需要定義各種輸入輸出通道。使用方法也很簡單。
定義一個接口(生產方)
package huaxin.config;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
/**
* 不使用SpringCloudStream默認的input輸入通道,output輸出通道
* <p>自定義各種輸入輸出通道</p>
* @author 孫啓新
* <br>FileName: CustomOrderProcessor
* <br>Date: 2020/06/16 14:55:40
*/
public interface CustomOrderProcessor {
String OUTPUT_ORDER = "outputOrder";
/**
* 自定義輸出的通道
* @return
*/
@Output(OUTPUT_ORDER)
MessageChannel outputOrder();
}
一個接口中,可以定義無數個輸入輸出通道,可以根據實際業務情況劃分。上述的接口,爲了測試簡單隻定義了一個訂單輸出通道。
使用時,需要在 @EnableBinding
註解中,添加自定義的接口。
注意:
@Resource
注入自定義的通道時需指定名稱
在MessageProviderImpl
實現類中再添加一個方法
/**
* 發送消息,自定義輸出通道
*
* @return
*/
@Override
public String customSend() {
String serial = UUID.randomUUID().toString();
customOutput.send(MessageBuilder.withPayload(serial).build());
System.out.println("*****自定義輸出通道serial: "+serial);
return serial;
}
最後yml配置文件中配置一下我們的自定義通道即可(上面yml已配)
以上代碼就完成了最基本的自定義的消息生產者部分。
定義一個接口(消費方)
package huaxin.config;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;
/**
* 不使用SpringCloudStream默認的input輸入通道,output輸出通道
* <p>自定義各種輸入輸出通道</p>
* @author 孫啓新
* <br>FileName: CustomOrderProcessor
* <br>Date: 2020/06/16 14:55:40
*/
public interface CustomOrderProcessor {
String INPUT_ORDER = "inputOrder";
/**
* 自定義輸入的通道
* @return
*/
@Input(INPUT_ORDER)
SubscribableChannel inputOrder();
}
一個接口中,可以定義無數個輸入輸出通道,可以根據實際業務情況劃分。上述的接口,爲了測試簡單隻定義了一個訂單輸入通道。
同樣,使用時,需要在 @EnableBinding
註解中,添加自定義的接口。
使用 @StreamListener
監聽隊列的時候,需要指定 CustomOrderProcessor.INPUT_ORDER
在ReceiveMessageListenerController
類中添加監聽方法
/**
* <br>@StreamListener: 監聽隊列,用於消費者的隊列的消息接收,自定義的輸入通道
*
* @param message
*/
@StreamListener(CustomOrderProcessor.INPUT_ORDER)
public void CustomInput(Message<String> message) {
System.out.println("自定義輸入通道消費者2號,接受到的消息:"+message.getPayload()+"\t port:"+serverPort);
}
最後yml配置文件中配置一下我們的自定義通道即可(上面yml已配)
以上代碼就完成了最基本的自定義的消費者部分。
8803消費者2與8802消費者1代碼相同。
測試:
生成者發送消息
兩個消費者接受消息
分組與持久化
上述代碼已經實現了分組與持久化,只需要在消費者端的 binding 添加配置項 spring.cloud.stream.bindings.[channelName].group = XXX 。對應的隊列就是持久化,並且名稱爲:[channelName].XXX。、
如果不配置group
的hauxin,SpringCloud Stream 會在 RabbitMQ 中創建一個臨時的隊列,程序關閉,對應的連接關閉的時候,該隊列也會消失。而在實際使用中,我們需要一個持久化的隊列,所以group分組屬性在消息重複消費和消息持久化消費 避免消息丟失是非常重要的屬性。