Golang與NSQ消息隊列

1. 概述

NSQ 是一個基於Go語言的分佈式實時消息平臺,它基於MIT開源協議發佈,由bitly公司開源出來的一款簡單易用的消息中間件

相關描述如下:

NSQ是一個實時分佈式消息傳遞平臺,旨在大規模運行,每天處理數十億條消息。 它促進了沒有單點故障的分佈式和分散式拓撲,從而實現了容錯能力和高可用性,並提供了可靠的消息傳遞保證。 查看功能和保證。 從操作上講,NSQ易於配置和部署(所有參數均在命令行上指定,並且編譯的二進制文件不具有運行時相關性)。 爲了獲得最大的靈活性,它與數據格式無關(消息可以是JSON,MsgPack,協議緩衝區或其他任何東西)。 官方提供了Go和Python庫(以及許多其他客戶端庫),並且,如果您有興趣構建自己的庫,則有一個協議規範。

NSQ的特點

  • 支持水平橫向拓展(無縫添加更多節點到集羣中)
  • 部署配置容易,自帶集羣管理界面(nsqadmin)
  • 提倡分佈式拓撲,減少單點故障,提高容錯
  • 低延遲的消息傳遞
  • 可靠的消交付保障保障
    • 默認中消息都在內存中, nsq 內部機制保證在程序關閉時將隊列中的數據持久化到硬盤,重啓後就會恢復。
    • 消息最少被投遞一次

比較知名和常用的消息處理系統還有

RabbitMQ

KafKa

2. 基礎應用場景

我們知道一般的消息隊列(Message Queue) 常用的場景有系統解耦 異步處理 流量削峯 消息通信

3. 相關文檔

  1. 項目地址 : https://github.com/nsqio/nsq
  2. 項目文檔 英文: https://nsq.io/overview/design.html
  3. 下載地址: https://nsq.io/deployment/installing.html
  4. 客戶端下載地址 : https://nsq.io/clients/client_libraries.html

4.安裝操作

根據自己的操作平臺下載解壓即可

  • 根據自己的操作系統下載對應的壓縮包文件
  • 解壓壓縮文件
  • 進入解壓後 bin 目錄中

bin 目錄中我們能看到如下文件

-rwxr-xr-x 1 captain 197121 5515776 8月  28 13:46 nsq_stat.exe*
-rwxr-xr-x 1 captain 197121 5823488 8月  28 13:46 nsq_tail.exe*
-rwxr-xr-x 1 captain 197121 5997568 8月  28 13:46 nsq_to_file.exe*
-rwxr-xr-x 1 captain 197121 5923840 8月  28 13:46 nsq_to_http.exe*
-rwxr-xr-x 1 captain 197121 5903872 8月  28 13:46 nsq_to_nsq.exe*
-rwxr-xr-x 1 captain 197121 8787968 8月  28 13:46 nsqadmin.exe*
-rwxr-xr-x 1 captain 197121 9108992 8月  28 13:46 nsqd.exe*
-rwxr-xr-x 1 captain 197121 8384000 8月  28 13:46 nsqlookupd.exe*
-rwxr-xr-x 1 captain 197121 5639680 8月  28 13:46 to_nsq.exe*

5. NSQ服務端基礎組件介紹

5.1 nsqd

nsqd是一個守護進程負責接收,排隊,消息傳遞 到客戶端。 它可以獨立運行,但通常由nsqlookupd實例的羣集中配置(在這種情況下,它將能聲明topics和發現channel)。 它偵聽兩個TCP端口,一個偵聽客戶端,另一個偵聽HTTP API。 它可以選擇在第三個端口上偵聽HTTPS。

5.2 nsqlookupd

nsqlookupd 是管理拓撲信息的守護程序。 客戶端查詢nsqlookupd以發現特定 topicnsqd 生產者和 nsqd 節點廣播topicchannel信息。
有兩個接口:

nsqd用於廣播的TCP接口

客戶端(nsqadmin)執行發現和管理操作的HTTP接口

5.3 nsqadmin

nsqadmin 是一套 WEB管理界面,用來彙集集羣的實時統計,並執行不同的管理任務。

重點提示:

NSQ還有許多功能組件,我們只介紹這三個(nsqd nsqlookupd nsqadmin)最常用和主要的

NSQ的所有組件都可以通過參數 -- help 查看相關配置

nsqdnsqlookupd 都有對應的http API ,需要使用的時候查看文檔即可

6.操作NSQ

6.1 安裝客戶端

根據不同的開發語言選擇不同的客戶端

我們是使用Golang操作所以採用NSQ的官方提供客戶端 go-nsq

go get -u github.com/nsqio/go-nsq

6.1 單機啓動nsqd

默認啓動的nsqd 監聽 HTTP對應的4151端口和TCP對應的4150端口

$ ./nsqd
[nsqd] 2019/11/10 13:41:29.575014 INFO: nsqd v1.2.0 (built w/go1.12.9)
[nsqd] 2019/11/10 13:41:29.593002 INFO: ID: 825
[nsqd] 2019/11/10 13:41:29.597000 INFO: TOPIC(topic_demo): created
[nsqd] 2019/11/10 13:41:29.599998 INFO: TOPIC(topic_demo): new channel(aa)
[nsqd] 2019/11/10 13:41:29.599998 INFO: NSQ: persisting topic/channel metadata to nsqd.dat
[nsqd] 2019/11/10 13:41:29.644973 INFO: HTTP: listening on [::]:4151
[nsqd] 2019/11/10 13:41:29.644973 INFO: TCP: listening on [::]:4150

我們同樣可以指定端口

$ ./nsqd -http-address="0.0.0.0:8080" -tcp-address="0.0.0.0:8081"
[nsqd] 2019/11/10 14:05:40.726849 INFO: nsqd v1.2.0 (built w/go1.12.9)
[nsqd] 2019/11/10 14:05:40.745838 INFO: ID: 825
[nsqd] 2019/11/10 14:05:40.747836 INFO: NSQ: persisting topic/channel metadata to nsqd.dat
[nsqd] 2019/11/10 14:05:40.788814 INFO: TCP: listening on [::]:8081
[nsqd] 2019/11/10 14:05:40.788814 INFO: HTTP: listening on [::]:8080

這樣我們就啓動了一個nsqd 的實例

在NSQ中有兩個非常重要的概念 topicChannel

我們看一下文檔中的描述:

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

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

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

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

在這裏插入圖片描述

總而言之,消息是從topic->channel多播的(每個channel都接收該topic的所有消息的副本),但從channel-> 消息消費者 均勻分發(每個消費者都接收該頻道的一部分消息)。

6.1.1 單NSQ的使用

編寫一個消息生產者
nsq_single_product.go

package main

import (
	"fmt"
	"github.com/nsqio/go-nsq"
	"time"
)
func main() {
	nsqAddr := "127.0.0.1:8081"
	conf :=nsq.NewConfig()
	p ,err := nsq.NewProducer(nsqAddr,conf)
	if err != nil {
		fmt.Println(err)
		return
	}
	for  {
		message := "message :"+ time.Now().Format("2006-01-02 15:04:05")
		fmt.Println(message)
		// 發送消息
		p.Publish("topic-demo1",[]byte(message))
		time.Sleep(time.Second)
	}

}

編寫一個消息消費者

nsq_single_consumer.go

package main

import (
	"fmt"
	"github.com/nsqio/go-nsq"
)

type NewHandler struct{}

func (m *NewHandler) HandleMessage(msg *nsq.Message) (err error) {
	addr := msg.NSQDAddress
	message := string(msg.Body)
	fmt.Println(addr, message)
	return
}
func MyConsumers(topic, channel, addr string) {
	conf := nsq.NewConfig()
	new_consumer, err := nsq.NewConsumer(topic, channel, conf)
	if err != nil {

	}
	// 接收消息
	new_handler := &NewHandler{}
	new_consumer.AddHandler(new_handler)
	err = new_consumer.ConnectToNSQD(addr)
	if err != nil {

	}
}
func main() {
	addr := "127.0.0.1:8081"
	go MyConsumers("topic-demo1", "channel-aa", addr)
    // 模擬多個從多個channel去消息
	go MyConsumers("topic-demo1", "channel-bb", addr)
	select {}
}

6.1.2 通過nsqadmin查看

啓動nsqadmin

nsqadmin 的web界面默認監聽了 4171端口

$ ./nsqadmin --nsqd-http-address="127.0.0.1:8080"
[nsqadmin] 2019/11/10 16:06:15.842033 INFO: nsqadmin v1.2.0 (built w/go1.12.9)
[nsqadmin] 2019/11/10 16:06:15.858026 INFO: HTTP: listening on [::]:4171

我們在地址欄中輸如

http://127.0.0.1:4171/

就能看看管理界面

在這裏插入圖片描述
在這裏插入圖片描述

6.1.3 NSQ的單點結構

在這裏插入圖片描述

6.3 NSQ集羣

6.3.1 啓動NSQ各組件

構建一個NSQ的基礎拓撲結構

我們可以簡單的說nsqlookupd 是用來管理nsqd實例節點的

第一步
啓動nsqlookupd

啓動的nsqlookupd 採用了默認配置 通過參數 --help 查看配置項

$ ./nsqlookupd
[nsqlookupd] 2019/11/10 16:40:55.968588 INFO: nsqlookupd v1.2.0 (built w/go1.12.9)
[nsqlookupd] 2019/11/10 16:40:55.983580 INFO: HTTP: listening on [::]:4161
[nsqlookupd] 2019/11/10 16:40:55.984579 INFO: TCP: listening on [::]:4160

第二步

添加nsqd 實例

與前面的啓動不同,需要帶上參數 -lookupd-tcp-address

添加第一個實例

./nsqd -http-address="0.0.0.0:8080" -tcp-address="0.0.0.0:8081" -lookupd-tcp-address="127.0.0.1:4160"

添加第二個實例

 ./nsqd -http-address="0.0.0.0:8090" -tcp-address="0.0.0.0:8091" -lookupd-tcp-address="127.0.0.1:4160"

第三步

啓動nsqadmin

與前面的也不同了需要帶上參數 -lookupd-http-address

$ ./nsqadmin -lookupd-http-address="127.0.0.1:4161"

在瀏覽器中訪問nsqadmin

在這裏插入圖片描述

在這裏插入圖片描述

6.3.2 NSQ的拓撲結構

在這裏插入圖片描述

  1. 在集羣模式中,消息生產方發送消息給任意一個nsqd 實例都不影響
  2. 消息的消費者需要通過nsqlookupd 查詢nsqd的地址後才能獲取消息
  3. 增加nsqd 節點完全不影響其他的節點
6.3.3 Go語言操作NSQ代碼示例

消息生產者

nsq_cluster_product.go

package main

import (
	"bufio"
	"fmt"
	"github.com/nsqio/go-nsq"
	"log"
	"os"
	"strings"
)

var pro *nsq.Producer

func NewPro(addr string) (err error) {
	conf := nsq.NewConfig()
	pro, err = nsq.NewProducer(addr, conf)
	if err != nil {
		log.Println(err)
		return err
	}
	return nil
}
func main() {
	nsqAddr := "127.0.0.1:8091"
	err := NewPro(nsqAddr)
	if err != nil {
		fmt.Println(err)
		return
	}else{
		fmt.Println("connect 127.0.0.1:8091 success")
	}
	// 讀取標準輸入
	reader := bufio.NewReader(os.Stdin)
	for {
		// 讀取所有內容直到遇見回車(\n)
		data, err := reader.ReadString('\n')
		if err != nil {
			fmt.Println("read data from stdin is field : ", err)
			continue
		}
		// 當輸入q的時候退出
		data = strings.TrimSpace(data)
		if strings.ToUpper(data) == "Q" {
			break
		}
		err = pro.Publish("topic-demo1", []byte(data))
		if err != nil {
			fmt.Println("nsq publish is field ", err)
			continue
		}
	}
	fmt.Println("exit !")
}

消息消費者

nsq_cluster_consumer.go

package main

import (
	"fmt"
	"github.com/nsqio/go-nsq"
)

type Handler struct{}

func (m *Handler) HandleMessage(msg *nsq.Message) (err error) {
	addr := msg.NSQDAddress
	message := string(msg.Body)
	fmt.Println(addr, message)
	return
}
func NewConsumers(t string, c string, addr string) error {
	conf := nsq.NewConfig()
	nc, err := nsq.NewConsumer(t, c, conf)
	if err != nil {
		fmt.Println("create consumer failed err ", err)
		return err
	}
	consumer := &Handler{}
	nc.AddHandler(consumer)
	// 連接nsqlookupd
	if err:= nc.ConnectToNSQLookupd(addr);err!=nil{
		fmt.Println("connect nsqlookupd failed ", err)
		return err
	}
	return nil
}
func main() {
	// 這是nsqlookupd的地址
	addr := "127.0.0.1:4161"
	err := NewConsumers("topic-demo1", "channel-aa", addr)
	if err != nil {
		fmt.Println("new nsq consumer failed", err)
		return
	}
	select {}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章