Go之NSQ簡介,原理和使用

men發表於2020-10-27

NSQ簡介

NSQ是Go語言編寫的一個開源的實時分散式記憶體訊息佇列,其效能十分優異。

NSQ 是實時的分散式訊息處理平臺,其設計的目的是用來大規模地處理每天數以十億計級別的訊息。它具有分散式和去中心化拓撲結構,該結構具有無單點故障、故障容錯、高可用性以及能夠保證訊息的可靠傳遞的特徵

適合小型專案使用,用來學習訊息佇列實現原理、學習 golang channel知識以及如何用 go 來寫分散式,為什麼說適合小型小型專案使用因為,nsq 如果沒有能力進行二次開發的情況存在的問題還是很多的。

NSQ優勢
/*
		1. NSQ提倡分散式和分散的拓撲,沒有單點故障,支援容錯和高可用性, 並提供可靠的訊息交付保證.
		2. NSQ支援橫向擴充套件,沒有任何集中式代理
		3. NSQ易於配置和部署,並且內建了管理介面
*/
NSQ特性
/*
		1. 支援無 SPOF 的分散式拓撲
		2. 水平擴充套件(沒有中介軟體,無縫地新增更多的節點到叢集)
		3. 低延遲訊息傳遞 (效能)
    4. 結合負載均衡和多播訊息路由風格
    5. 擅長面向流媒體(高通量)和工作(低吞吐量)工作負載
    6. 主要是記憶體中(除了高水位線訊息透明地儲存在磁碟上)
    7. 執行時發現消費者找到生產者服務(nsqlookupd)
    8. 傳輸層安全性 (TLS)
    9. 資料格式不可知
    10. 一些依賴項(容易部署)和健全的,有界,預設配置
    11. 任何語言都有簡單 TCP 協議支援客戶端庫
    12. HTTP 介面統計、管理行為和生產者(不需要客戶端庫釋出)
    13. 為實時檢測整合了 statsd
    14. 健壯的叢集管理介面 (nsqadmin)
*/

注意點

/*
		1. 訊息預設不持久化, 可以配置成持久化模式, nsq採用的方式是記憶體+硬碟的模式,當記憶體到一定程度就會持久化到硬碟.
				如果將 --mem-queue-size設定為0, 所有的訊息將會儲存到磁碟.
				伺服器重啟時也會將在記憶體中的訊息持久化
		2. 每條訊息至少傳遞一次
		3. 訊息不保證有序.
*/

NSQ應用場景

參考上圖利用訊息佇列把業務流程中的非關鍵流程非同步化, 從而顯著降低業務請求的響應時間

應用解耦

通過使用訊息佇列將不同業務邏輯解耦,降低系統間耦合,提高系統的健壯性, 後續有其他的業務要使用訂單資料可直接訂閱訊息佇列, 提高系統的靈活性.

流量削峰

類似秒殺(大秒)等場景下,某一時間可能會產生大量的請求,使用訊息佇列能夠為後端處理請求提供一定的緩衝區,保證後端服務的穩定性。

NSQ架構

NSQ模組介紹

nsqd: 是一個程式監聽了http,tcp兩種協議, 用來建立topic,channel, 分發訊息給消費者,向nsqlooup 註冊自己的後設資料資訊(topic、channel、consumer),自己的服務資訊,最核心模組。

nsqd 是一個守護程式,負責接收,排隊,投遞訊息給客戶端。也就是說這個服務是幹活的。它可以獨立執行,不過通常它是由 nsqlookupd 例項所在叢集配置的。

/*
		特性:
		1. 對訂閱了同一個topic,同一個channel的消費者使用負載均衡策略(不是輪詢)
		
		2. 只要channel存在,即使沒有該channel的消費者,也會將生產者的message快取到佇列中(注意訊息的過期處理)
		
		3. 保證佇列中的message至少會被消費一次,即使nsqd退出,也會將佇列中的訊息暫存磁碟上(結束程式等意外情況除外)

		4. 限定記憶體佔用,能夠配置nsqd中每個channel佇列在記憶體中快取的message數量,一旦超出,message將被快取到磁碟中

		5. topic,channel一旦建立,將會一直存在,要及時在管理臺或者用程式碼清除無效的topic和channel,避免資源的浪費
*/

nsqlookup: 儲存了nsqd的後設資料和服務資訊(endpoind),向消費者提供服務發現功能, 向nsqadmin提供資料查詢功能.

nsqlookupd 是守護程式負責管理拓撲資訊。客戶端通過查詢 nsqlookupd 來發現指定話題(topic)的生產者,並且 nsqd 節點廣播話題(topic)和通道(channel)資訊。也就是說nsqlookupd是管理者。

/*
		特性:
		
		1. 唯一性,在一個Nsq服務中只有一個nsqlookupd服務。當然也可以在叢集中部署多個nsqlookupd,但它們之間是沒有關聯的.
		2. 去中心化,即使nsqlookupd崩潰,也會不影響正在執行的nsqd服務
		3. 充當nsqd和naqadmin資訊互動的中介軟體
		4. 提供一個http查詢服務,給客戶端定時更新nsqd的地址目錄.
*/

nsqadmin: 簡單的管理介面,展示了topic, channel以及channel上的消費者,也可以建立topic,channel

/*
		特性:
		
		1. 提供一個對topic和channel統一管理的操作介面以及各種實時監控資料的展示,介面設計的很簡潔,操作也很簡單
		2. 展示所有message的數量
		3. 能夠在後臺建立topic和channel
		4. nsqadmin的所有功能都必須依賴於nsqlookupd,nsqadmin只是向nsqlookupd傳遞使用者操作並展示來自nsqlookupd的資料
		
*/
NSQ工作模式

Topic和Channel

每個nsqd例項旨在一次處理多個資料流。這些資料流稱為“topics”,一個topic具有1個或多個“channels”。每個channel都會收到topic所有訊息的副本,實際上下游的服務是通過對應的channel來消費topic訊息。

topicchannel不是預先配置的。topic在首次使用時建立,方法是將其釋出到指定topic,或者訂閱指定topic上的channelchannel是通過訂閱指定的channel在第一次使用時建立的。

topicchannel都相互獨立地緩衝資料,防止緩慢的消費者導致其他chennel的積壓(同樣適用於topic級別)。

channel可以並且通常會連線多個客戶端。假設所有連線的客戶端都處於準備接收訊息的狀態,則每條訊息將被傳遞到隨機客戶端。例如:

生產者向某個topic中傳送訊息,如果topic有一個或者多個channle,那麼該訊息會被複制多分傳送到每一個channel中。類似 rabbitmq中的fanout型別,channle類似佇列。 官方說 nsq 是分散式的訊息佇列服務,但是在我看來只有channel到消費者這部分提現出來分散式的感覺,nsqd 這個模組其實就是單點的,nsqd 將 topic、channel、以及訊息都儲存在了本地磁碟,官方還建議一個生產者使用一個 nsqd,這樣不僅浪費資源還沒有資料備份的保障。一旦 nsqd 所在的主機磁損壞,資料都將丟失。

總而言之,訊息是從topic--> channel (每個channel接受該topic的所有訊息的副本)多播的,但是從channel --> consumers均勻分佈 (每個消費者接受該channel的一部分訊息)

NSQ接受和傳送訊息流程

Centos安裝NSQ

下載
wget https://s3.amazonaws.com/bitly-downloads/nsq/nsq-1.2.0.linux-amd64.go1.12.9.tar.gz


tar xf nsq-1.2.0.linux-amd64.go1.12.9.tar.gz -C /usr/local/
本地解析Hosts
[root@nsq-47 ~]# tail -1 /etc/hosts
192.168.43.47 nsq-47
啟動
# 開三個終端,分別按順序啟動
./nsqlookupd 

./nsqd --lookupd-tcp-address=192.168.43.47:4160
  
./nsqadmin --lookupd-http-address=192.168.43.47:4161
  
# 訪問
http://192.168.43.47:4171

Go操作NSQ

安裝go客戶端
/*
		go get -u github.com/nsqio/go-nsq
*/
生產者
// nsq_producer/main.go
package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"

	"github.com/nsqio/go-nsq"
)

// NSQ Producer Demo

var producer *nsq.Producer

// 初始化生產者
func initProducer(str string) (err error) {
	config := nsq.NewConfig()
	producer, err = nsq.NewProducer(str, config)
	if err != nil {
		fmt.Printf("create producer failed, err:%v\n", err)
		return err
	}
	return nil
}

func main() {
	nsqAddress := "192.168.43.47:4150"
	err := initProducer(nsqAddress)
	if err != nil {
		fmt.Printf("init producer failed, err:%v\n", err)
		return
	}

	reader := bufio.NewReader(os.Stdin) // 從標準輸入讀取
	for {
		data, err := reader.ReadString('\n')
		if err != nil {
			fmt.Printf("read string from stdin failed, err:%v\n", err)
			continue
		}
		data = strings.TrimSpace(data)
		if strings.ToUpper(data) == "Q" { // 輸入Q退出
			break
		}
		// 向 'topic_demo' publish 資料
		err = producer.Publish("topic_demo", []byte(data))
		if err != nil {
			fmt.Printf("publish msg to nsq failed, err:%v\n", err)
			continue
		}
	}
}
消費者

/nodes這個頁面我們能夠很方便的檢視當前接入lookupdnsqd節點。

這個/counter頁面顯示了處理的訊息數量,因為我們沒有接入消費者,所以處理的訊息數量為0。

/lookup介面支援建立topicchannel

// nsq_consumer/main.go
package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/nsqio/go-nsq"
)

// NSQ Consumer Demo

// MyHandler 是一個消費者型別
type MyHandler struct {
	Title string
}

// HandleMessage 是需要實現的處理訊息的方法
func (m *MyHandler) HandleMessage(msg *nsq.Message) (err error) {
	fmt.Printf("%s recv from %v, msg:%v\n", m.Title, msg.NSQDAddress, string(msg.Body))
	return
}

// 初始化消費者
func initConsumer(topic string, channel string, address string) (err error) {
	config := nsq.NewConfig()
	config.LookupdPollInterval = 15 * time.Second
	c, err := nsq.NewConsumer(topic, channel, config)
	if err != nil {
		fmt.Printf("create consumer failed, err:%v\n", err)
		return
	}
	consumer := &MyHandler{
		Title: "沙河1號",
	}
	c.AddHandler(consumer)

	// if err := c.ConnectToNSQD(address); err != nil { // 直接連NSQD
	if err := c.ConnectToNSQLookupd(address); err != nil { // 通過lookupd查詢
		return err
	}
	return nil

}

func main() {
	err := initConsumer("topic_demo", "first", "192.168.43.47:4161")
	if err != nil {
		fmt.Printf("init consumer failed, err:%v\n", err)
		return
	}
	c := make(chan os.Signal)        // 定義一個訊號的通道
	signal.Notify(c, syscall.SIGINT) // 轉發鍵盤中斷訊號到c
	<-c                              // 阻塞
}

相關文章