nsq 優秀的消息隊列

簡介php

NSQ是Go語言編寫的,開源的分佈式消息隊列中間件,其設計的目的是用來大規模地處理天天數以十億計級別的消息。NSQ 具備分佈式和去中心化拓撲結構,該結構具備無單點故障、故障容錯、高可用性以及可以保證消息的可靠傳遞的特徵,是一個成熟的、已在大規模生成環境下應用的產品。html

NSQ在國內公司用的不多,在使用當中愈發的以爲驚喜,好比他的簡單易用、部署快捷,再好比以前比較困擾的 延時定時消息,發現nsq 也支持,官方文檔比較全,諮詢問題時回覆也很是的耐心和即時,因此我以爲有必要發佈一篇文章來介紹下nsq,惠及大衆。git

nsq 有三個必要的組建nsqd、nsqlookupd、nsqadmin 其中nsqd 和 nsqlookup是必須部署的 下面咱們一一介紹。github

nsqd :web

負責接收消息,存儲隊列和將消息發送給客戶端,nsqd 能夠多機器部署,當你使用客戶端向一個topic發送消息時,能夠配置多個nsqd地址,消息會隨機的分配到各個nsqd上,nsqd優先把消息存儲到內存channel中,當內存channel滿了以後,則把消息寫到磁盤文件中。他監聽了兩個tcp端口,一個用來服務客戶端,一個用來提供http的接口 ,nsqd 啓動時置頂下nsqlookupd地址便可:算法

nsqd –lookupd-tcp-address=127.0.0.1:4160

也能夠指定端口 與數據目錄sql

nsqd –lookupd-tcp-address=127.0.0.1:4160 --broadcast-address=127.0.0.1 -tcp-address=127.0.0.1:4154 -http-address=」0.0.0.0:4155″ –data-path=/data/nsqdata

其餘配置項可詳見官網服務器

nsqlookupd
主要負責服務發現 負責nsqd的心跳、狀態監測,給客戶端、nsqadmin提供nsqd地址與狀態php7

nsqadmin:
nsqadmin是一個web管理界面 啓動方式以下:異步

nsqadmin –lookupd-http-address=127.0.0.1:4161

圖片描述

channel詳情頁示例圖以下 ,empty能夠清空當前channel的信息,delete刪除當前channel, pause是暫停消息消費。

圖中也有幾個比較重要的參數 depth當前的積壓量,in-flight表明已經投遞還未消費掉的消息,deferred是未消費的定時(延時)消息數,ready count比較重要,go的客戶端是經過設置max-in-flight 除以客戶端鏈接數獲得的,表明一次推給客戶端多少條消息,或者客戶端準備一次性接受多少條消息,謹慎設置其值,由於可能形成服務器壓力,若是消費能力比較弱,rdy建議設置的低一點好比3

圖片描述

Topic 和 Channel

其實nsqd至關於kafka當中的分區,channel和consumers客戶端的多個鏈接 至關於kafka的消費組,但nsq比kafka使用方式便捷概念上更容易理解
拋開與kafka的對比,nsq的topic 能夠設置多個channel,由於有可能有多個業務方須要定值topic的消息,這樣互不影響,
固然一個消息會發送topic下的全部channel,而後會分配到不一樣客戶端的鏈接上,以下圖。
圖片描述

這篇文章主要介紹nsq的使用,源碼就不展開講,若是有興趣的同窗多的話 過幾天我會再開一篇專門敘述nsq的源碼與分析。

這裏提下延時消息:

nsq支持延時消息的投遞,好比我想這條消息5分鐘以後才被投遞出去被客戶端消費,較於普通的消息投遞,多了個毫秒數,默認支持最大的毫秒數爲3600000毫秒也就是60分鐘,不過這個值能夠在nsqd 啓動的時候 用 -max-req-timeout參數修改最大值。

延時消息可用於如下場景,好比一個訂單超過30分鐘未付款,修改其狀態 或者給客戶發短信提醒,好比以前看到的滴滴打車訂單完成後 必定時間內未評價的能夠未其設置默認值,再好比用戶的積分過時,等等場景避免了全表掃描,異步處理,kafka不支持延時消息的投遞,目前知道支持的有rabbitmq rocketmq,可是rabbitmq 有坑,有可能會超時投遞,而rocketmq只有阿里雲付費版支持的比較好。

nsq延時消息的實現是用最小堆算法完成,做者繼承實現heap的一系類接口,專門寫了一個pqueque最小堆的優先隊列,在internal/pequeque 目錄能夠看到相關實現,pub的時候若是chanMsg.deferred != 0則會調用channel.PutMessageDeferred方法,最終會調用繼承了go heap接口的pqueque.push方法

延時消息的處理 和普通消息同樣都是 nsqd/protocol_v2.go下messagePump 中把消息發送給客戶端 而後在queueScanWorker中分別處理,pop是peekAndShift方法中,拿當前時間 和 deferred[0]對好比果大於 就彈出發送給客戶端 以下代碼:

func (n *NSQD) queueScanWorker(workCh chan *Channel, responseCh chan bool, closeCh chan int) {
    for {
        select {
        case c := <-workCh:
            now := time.Now().UnixNano()
            dirty := false
            if c.processInFlightQueue(now) {
                dirty = true
            }
            if c.processDeferredQueue(now) {
                dirty = true
            }
            responseCh <- dirty
        case <-closeCh:
            return
        }
    }
}

func (c *Channel) processDeferredQueue(t int64) bool {
    c.exitMutex.RLock()
    defer c.exitMutex.RUnlock()

    if c.Exiting() {
        return false
    }

    dirty := false
    for {
        c.deferredMutex.Lock()
        item, _ := c.deferredPQ.PeekAndShift(t)
        c.deferredMutex.Unlock()

        if item == nil {
            goto exit
        }
        dirty = true

        msg := item.Value.(*Message)
        _, err := c.popDeferredMessage(msg.ID)
        if err != nil {
            goto exit
        }
        c.put(msg)
    }

exit:
    return dirty
}

func (pq *PriorityQueue) PeekAndShift(max int64) (*Item, int64) {
    if pq.Len() == 0 {
        return nil, 0
    }

    item := (*pq)[0]
    if item.Priority > max {
        return nil, item.Priority - max
    }
    heap.Remove(pq, 0)

    return item, 0
}

php和go的客戶端的使用

官網客戶端連接:Client Libraries php客戶端以前官網有一個5年前比較老的客戶端,已經沒人維護 甚至沒法運行,因而我貢獻了一個php72擴展版本 php-nsq,速度塊了近三倍,正在逐步完善,支持各類配置與特性,目前已被官網收納,簡單介紹下使用 順便求下star

php-nsq pub :

$nsqd_addr = array(
    "127.0.0.1:4150",
    "127.0.0.1:4154"
);

$nsq = new Nsq();
$is_true = $nsq->connect_nsqd($nsqd_addr);
for($i = 0; $i < 20; $i++){
    $nsq->publish("test", "nihao");
}

php-nsq 延時pub :

參數 僅僅多一個毫秒參數,so easy!

$deferred = new Nsq();
$isTrue = $deferred->connectNsqd($nsqdAddr);
for($i = 0; $i < 20; $i++){
    $deferred->deferredPublish("test", "message daly", 3000); // 第三值默認範圍 millisecond default : [0 < millisecond < 3600000] ,能夠更改 上面已提到
}

php-nsq sub :

拋異常消息能夠自動重試,重試時間能夠有retry_delay_time設定,多少時間後再次接收被重試的消息

$nsq_lookupd = new NsqLookupd("127.0.0.1:4161"); //the nsqlookupd tcp addr
$nsq = new Nsq();
$config = array(
    "topic" => "test",
    "channel" => "struggle",
    "rdy" => 2,                //optional , default 1
    "connect_num" => 1,        //optional , default 1
    "retry_delay_time" => 5000,  //optional, default 0 , after 5000 msec, message will be retried
);

$nsq->subscribe($nsq_lookupd, $config, function($msg){

    echo $msg->payload;
    echo $msg->attempts;
    echo $msg->message_id;
    echo $msg->timestamp;

});

go client pub

package main

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

var producer *nsq.Producer

func main() {
    nsqd := "127.0.0.1:4150"
    producer, err := nsq.NewProducer(nsqd, nsq.NewConfig())
    producer.Publish("test", []byte("nihao"))
    if err != nil {
        panic(err)
    }
}

go client sub

package main

import (
 "fmt"
 "sync"
 "github.com/nsqio/go-nsq"
)
type NSQHandler struct {
}

func (this *NSQHandler) HandleMessage(msg *nsq.Message) error {
    fmt.Println("receive", msg.NSQDAddress, "message:", string(msg.Body))
    return nil
}

func testNSQ() {
    waiter := sync.WaitGroup{}
    waiter.Add(1)

    go func() {
        defer waiter.Done()
        config:=nsq.NewConfig()
        config.MaxInFlight=9

    //創建多個鏈接
    for i := 0; i<10; i++ {
        consumer, err := nsq.NewConsumer("test", "struggle", config)
        if nil != err {
            fmt.Println("err", err)
            return
        }

        consumer.AddHandler(&NSQHandler{})
        err = consumer.ConnectToNSQD("127.0.0.1:4150")
        if nil != err {
            fmt.Println("err", err)
            return
        }
    }
        select{}

    }()

    waiter.Wait()
}
func main() {
        testNSQ();

}

同時此篇文章 更新到了本身博客

相關文章
相關標籤/搜索