Springboot 2 集成 Nsq 消息中間件實現生產消費應用

什麼是NSQ?

NSQ是一個實時分佈式消息傳遞平臺。具體特性請前往Nsq官網查看了解,下面是我平常在Springboot項目對Nsq使用的一些技巧。

簡介

Nsq集成Java可基於JavaNSQClient實現,該jar包幫助我們封裝了操作nsq的一些api。
下面我們構建兩個SpringBoot測試項目,分別爲SpringBoot-Nsq-Consumer,SpringBoot-Nsq-Poducer,
實現基於Nsq消息中間件的簡單生產消費應用。

消息生產者

  1. 配置product 的 nsq信息,如果是集羣版的,可請求http://ip:4161/nodes,獲取可用節點,在添加進去。
nsq.topic=nimeio
nsq.address=120.79.12.138
nsq.port=4150
  1. 初始化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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章