什麼是NSQ?
NSQ是一個實時分佈式消息傳遞平臺。具體特性請前往Nsq官網查看了解,下面是我平常在Springboot項目對Nsq使用的一些技巧。
簡介
Nsq集成Java可基於JavaNSQClient實現,該jar包幫助我們封裝了操作nsq的一些api。
下面我們構建兩個SpringBoot測試項目,分別爲SpringBoot-Nsq-Consumer,SpringBoot-Nsq-Poducer,
實現基於Nsq消息中間件的簡單生產消費應用。
消息生產者
- 配置product 的 nsq信息,如果是集羣版的,可請求http://ip:4161/nodes,獲取可用節點,在添加進去。
nsq.topic=nimeio
nsq.address=120.79.12.138
nsq.port=4150
- 初始化jar包提供的NSQProduct,
@Configuration
public class NsqProductConfiguration {
@Value("${nsq.topic}")
String topic;
@Value("${nsq.address}")
String nsqAddress;
@Value("${nsq.port}")
Integer nsqPort;
@Bean
public NSQProducer nsqProducer() {
NSQProducer nsqProducer = new NSQProducer();
nsqProducer.addAddress(nsqAddress, nsqPort);
nsqProducer.start();
return nsqProducer;
}
}
由於Java Nsq Client 生產者不能選擇基於特定的Channel接收消費,日常我在項目中都是自己爲Topic制定特定傳輸格式,
在通過邏輯判斷指定特定的Consumer Channel消費處理。
如下圖:
設計的消息體如下,有一個特定id,action(相當於Channel)以及消息內容body,此處只是簡單示例,有特殊要求可自行改善:
public class NsqMessage implements Serializable {
@SerializedName("id")
@JsonProperty("id")
private Long id;
/**
* 相當於nsq消費者的channel名稱
*/
@SerializedName("action")
@JsonProperty("action")
private String action;
@SerializedName("body")
@JsonProperty("body")
private String body;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
接下來,測試發送消息:
public String sendTestMessageByChannel(String body) {
try {
NsqMessage message = new NsqMessage();
message.setId(UUID.randomUUID().getLeastSignificantBits());
message.setAction("testChannel");
message.setBody(body);
String msg = JSONParseUtils.object2JsonString(message);
nsqProducer.produce(topic, msg.getBytes());
log.info("消息發送特定Channel成功!時間:" + new Date());
return "Success";
} catch (NSQException e) {
log.error("nsq 連接異常!msg={}", e.getMessage());
return "Error:" + e.getMessage();
} catch (TimeoutException e) {
log.error("nsq 發送消息超時!msg={}", e.getMessage());
return "Error:" + e.getMessage();
} catch (Exception e) {
log.error("出現未知異常!", e);
return "Error:" + e.getMessage();
}
}
消息生產者到這裏就結束了,下面構建一個消息消費者:
消息消費者
同樣的,消費的配置如下,注意要與生產者的Topic相同,並且由於消費者需要通過nsqlookupd發現生產服務,
所以使用nsqlookupd的端口4161(默認的,有改的需注意更換):
server.port=8081
nsq.topic=nimeio
nsq.address=120.79.12.138
nsq.port=4161
消費消息邏輯如下,主要在於判斷是否爲特定Channel,如不是就確定即可。
public void mqConsumer() {
NSQLookup lookup = new DefaultNSQLookup();
lookup.addLookupAddress(nsqAddress, nsqPort);
NSQConsumer consumer = new NSQConsumer(lookup, topic, NsqChannelConst.DEFAULT_CHANNEL, (message) -> {
if (message != null) {
String msg = new String(message.getMessage());
NsqMessage nsqMessage = null;
try {
nsqMessage = JSONParseUtils.json2Object(msg, NsqMessage.class);
} catch (Exception e) {
log.error("消息無法轉換,存在問題");
message.finished();
return;
}
if (nsqMessage == null) {
message.finished();
log.error("消息爲空,瞎發的消息,確認即可");
message.finished();
return;
}
if (!NsqChannelConst.DEFAULT_CHANNEL.equals(nsqMessage.getAction())) {
// 如果nsq消息體中的action不等於當前的chanel名稱,說明不是當前消費者需要處理的數據,確認消費即可
message.finished();
return;
}
try {
log.info("消費特定消息: " + nsqMessage.getBody());
//確認消息
message.finished();
return;
} catch (Exception e) {
//異常,重試
message.requeue();
}
return;
}
message.finished();
return;
});
consumer.start();
log.info("nsq 消費者啓動成功!");
}
具體代碼以上傳至GitHub:
https://github.com/liaozihong/SpringBoot-Learning/tree/master/SpringBoot-Nsq-Consumer
https://github.com/liaozihong/SpringBoot-Learning/tree/master/SpringBoot-Nsq-Producer
參考鏈接:
NSQ官方文檔
NSQ相關curl API
JAVANSQClient
https://zhuanlan.zhihu.com/p/37081073