• NSQ


    NSQ介绍

    实时的分布式消息处理平台,nsq设计的目的是用来大规模地处理每天数以十亿计级别的消息。 

    官网:https://nsq.io/overview/quick_start.html

    client端:https://nsq.io/clients/client_libraries.html

    GitHub:https://github.com/nsqio/nsq

    组件构成

    nsq有三个组件以及辅助的几个工具构成。 

    nsqd 是一个守护进程,负责接收,排队,投递消息给客户端。 
    它可以独立运行,不过通常它是由 nsqlookupd 实例所在集群配置的(它在这能声明 topics 和 channels,以便大家能找到)。

    • 服务启动后有两个端口:一个给客户端,另一个是 HTTP API。还能够开启HTTPS。
    • 同一台服务器启动多个nsqd,要注意端口和数据路径必须不同,包括:–lookupd-tcp-address、 -tcp-address、–data-path。
    • 删除topic、channel需要http api调用。

    nsqlookupd 是守护进程,负责管理拓扑信息并提供最终一致性的发现服务。

    客户端通过查询 nsqlookupd 来发现指定话题(topic)的生产者,并且 nsqd 节点广播话题(topic)和通道(channel)信息。

    • 该服务运行后有两个端口:TCP 接口,nsqd 用它来广播;HTTP 接口,客户端用它来发现和管理。
    • 在生产环境中,为了高可用,最好部署三个nsqlookupd服务。

    nsqadmin 是一套 WEB UI,用来汇集集群的实时统计,并执行不同的管理任务。 
    运行后,能够通过4171端口查看并管理topic和channel。

    utilities 常见基础功能、数据流处理工具,如nsq_stat、nsq_tail、nsq_to_file、nsq_to_http、nsq_to_nsq、to_nsq。

    概念

    Topic ——一个topic就是程序发布消息的一个逻辑键,当程序第一次发布消息时就会创建topic。

    Channels ——channel组与消费者相关,是消费者之间的负载均衡,channel在某种意义上来说是一个“队列”。每当一个发布者发送一条消息到一个topic,消息会被复制到所有消费者连接的channel上,消费者通过这个特殊的channel读取消息,实际上,在消费者第一次订阅时就会创建channel。

    Channel会将消息进行排列,如果没有消费者读取消息,消息首先会在内存中排队,当量太大时就会被保存到磁盘中。

    Message s——消息构成了我们数据流的中坚力量,消费者可以选择结束消息,表明它们正在被正常处理,或者重新将他们排队待到后面再进行处理。每个消息包含传递尝试的次数,当消息传递超过一定的阀值次数时,我们应该放弃这些消息,或者作为额外消息进行处理。

    Topic和Channel 

    官方有个非常漂亮的动态图,展示了一个topic对应多个channel的效果 

    部署

    准备192.168.1.93、192.168.2.41两台部署

    1、下载 https://nsq.io/deployment/installing.html

    解压到/usr/local/nsq目录下,创建conf、data、logs目录

    2、定义配置文件

    nsqd.cfg

    ##是否启用详细记录
    verbose = true
    
    ## unique part for message IDs, (int) in range [0,1024) (default is hash of hostname) (default 616)
    # node_id = 5150
    
    ## <addr>:<port>客户端TCP地址,客户端通过这个地址连接nsqd并进行订阅,发布。注意订阅必须通过TCP连接实现。 
    tcp_address = "0.0.0.0:4150"
    
    ## <addr>:<port> 用来进行发布的http端口,经过测试这里的ip只能是nsqd所在机器网卡的任意一个ip,如果填写127.0.0.1只接受本机请求。
    http_address = "0.0.0.0:4151"
    
    ## <addr>:<port> to listen on for HTTPS clients (default "0.0.0.0:4152")
    # https_address = "0.0.0.0:4152"
    
    ## address that will be registered with lookupd (defaults to the OS hostname) (default "PROSNAKES.local")
    broadcast_address = "192.168.1.93"
    
    ## cluster of nsqlookupd TCP 地址,可以设置多个。
    nsqlookupd_tcp_addresses = [
        "192.168.1.93:4160",
        "192.168.2.41:4160"
    ]
    
    ## duration to wait before HTTP client connection timeout
    http_client_connect_timeout = "2s"
    
    ## duration to wait before HTTP client request timeout
    http_client_request_timeout = "5s"
    
    ## path to store disk-backed messages
    data_path="/usr/local/nsq/data/"
    
    ## number of messages to keep in memory (per topic/channel) (default 10000)
    mem_queue_size = 10000
    
    ## number of bytes per diskqueue file before rolling
    max_bytes_per_file = 104857600
    
    ## number of messages per diskqueue fsync
    sync_every = 2500
    
    ## duration of time per diskqueue fsync (time.Duration)
    sync_timeout = "2s"
    
    ## duration to wait before auto-requeing a message
    msg_timeout = "60s"
    
    ## maximum duration before a message will timeout
    max_msg_timeout = "15m"
    
    ## maximum size of a single message in bytes
    max_msg_size = 1024768
    
    ## maximum requeuing timeout for a message
    max_req_timeout = "1h"
    
    ## maximum size of a single command body
    max_body_size = 5123840
    
    ## maximum client configurable duration of time between client heartbeats
    max_heartbeat_interval = "60s"
    
    ## maximum RDY count for a client
    max_rdy_count = 2500
    
    ## maximum client configurable size (in bytes) for a client output buffer
    max_output_buffer_size = 65536
    
    ## maximum client configurable duration of time between flushing to a client (time.Duration)
    max_output_buffer_timeout = "1s"
    
    
    ## UDP <addr>:<port> of a statsd daemon for pushing stats
    # statsd_address = "127.0.0.1:8125"
    
    ## prefix used for keys sent to statsd (%s for host replacement)
    statsd_prefix = "nsq.%s"
    
    ## duration between pushing to statsd (time.Duration)
    statsd_interval = "60s"
    
    ## toggle sending memory and GC stats to statsd
    statsd_mem_stats = true
    
    ## path to certificate file
    tls_cert = ""
    
    ## path to private key file
    tls_key = ""
    
    ## set policy on client certificate (require - client must provide certificate,
    ##  require-verify - client must provide verifiable signed certificate)
    # tls_client_auth_policy = "require-verify"
    
    ## set custom root Certificate Authority
    # tls_root_ca_file = ""
    
    ## require client TLS upgrades
    tls_required = false
    
    ## minimum TLS version ("ssl3.0", "tls1.0," "tls1.1", "tls1.2")
    tls_min_version = ""
    
    ## enable deflate feature negotiation (client compression) (default true)
    deflate = true
    
    ## max deflate compression level a client can negotiate (> values == > nsqd CPU usage)
    max_deflate_level = 6
    
    ## enable snappy feature negotiation (client compression)
    snappy = true
    

    nsqlookupd.cfg

    ##enable verbose logging
    verbose = false
    
    ## <addr>:<port> nsqd通过这个tcp地址注册到nsqlookup
    tcp_address = "0.0.0.0:4160"
    
    ## <addr>:<port> 消费客户端通过这个地址获取可用的服务列表
    http_address = "0.0.0.0:4161"
    
    ## address that will be registered with lookupd (defaults to the OS hostname)
    # broadcast_address = ""
    
    ## duration of time a producer will remain in the active list since its last ping
    inactive_producer_timeout = "300s"
    
    ## duration of time a producer will remain tombstoned if registration remains
    tombstone_lifetime = "45s"
    

    nsqadmin.cfg

    ## 在浏览器中访问这个地址进入管理。
    http_address = "0.0.0.0:4171"
    
    ## HTTP endpoint (fully qualified) to which POST notifications of admin actions will be sent
    notification_http_endpoint = ""
    
    ## nsq发现服务地址
    nsqlookupd_http_addresses = [
        "192.168.1.93:4161"
    ]
    

    3、启动进程

    nohup nsqlookupd -config /usr/local/nsq/conf/nsqlookupd.cfg > /usr/local/nsq/logs/nsqlookupd.log 2>&1&
    nohup nsqd -config /usr/local/nsq/conf/nsqd.cfg > /usr/local/nsq/logs/nsqd.log 2>&1&
    nohup nsqadmin -config /usr/local/nsq/conf/nsqadmin.cfg > /usr/local/nsq/logs/nsqadmin.log 2>&1&
    

    4、访问页面

     GO的client

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

    使用样例可以参考这个包中的test内容。

    python的client

    #-*- coding:utf-8 -*-
    import nsq
    import json
    import logging
    logger = logging.getLogger("file")
    
    from threading import Thread
    import tornado.ioloop
    
    class MQSUB(Thread):
        def __init__(self, addr_list, channel):
            super(MQSUB,self).__init__()
            self.lookupd_http_addresses = addr_list
            self.channel = channel
            
        def message_handle(self, message):
            msg_s = message.body
            try:
                msg = json.loads(msg_s)
                print msg
                logger.debug('msg:%s'%msg)
                message.finish()
            except Exception as e:
                logger.error('Exception:%s' % e)
                message.requeue(backoff=False, delay=10)
                
        def sub(self, topic):
            nsq.Reader(message_handler=self.message_handle,
                lookupd_http_addresses=self.lookupd_http_addresses,
                topic=topic, channel=self.channel, max_in_flight=9)
        
        def run(self):
            tornado.ioloop.IOLoop.instance().start()
            #nsq.run()
        
    if __name__ == '__main__':
        address1 = '192.168.1.73:4161'
        address2 = '192.168.1.74:4161'
        addr_list = [address1,address2]
        topic = 'idcinfo'
        channel = 'async'
        m_s = MQSUB(addr_list,channel)
        m_s.sub(topic)
        m_s.start()
    

    服务开机自启动注册

    /lib/systemd/system/nsqlookupd.service内容如下

    [Unit]
    Description=nsqlookupd
    ConditionFileIsExecutable=/usr/local/nsq/bin/nsqlookupd
    
    [Service]
    StartLimitInterval=5
    StartLimitBurst=10
    ExecStart=/usr/bin/sh -c '/usr/local/nsq/bin/nsqlookupd -config /usr/local/nsq/conf/nsqlookupd.cfg > /usr/local/nsq/logs/nsqlookupd.log 2>&1'
    Restart=always
    RestartSec=120
    LimitNOFILE=65535
    
    [Install]
    WantedBy=multi-user.target

    /lib/systemd/system/nsqd.service 内容如下

    [Unit]
    Description=nsqd
    ConditionFileIsExecutable=/usr/local/nsq/bin/nsqd
    
    [Service]
    StartLimitInterval=5
    StartLimitBurst=10
    ExecStart=/usr/bin/sh -c '/usr/local/nsq/bin/nsqd -config /usr/local/nsq/conf/nsqd.cfg > /usr/local/nsq/logs/nsqd.log 2>&1'
    Restart=always
    RestartSec=120
    LimitNOFILE=65535
    
    [Install]
    WantedBy=multi-user.target

    /lib/systemd/system/nsqadmin.service内容如下

    [Unit]
    Description=nsqadmin
    ConditionFileIsExecutable=/usr/local/nsq/bin/nsqadmin
    
    [Service]
    StartLimitInterval=5
    StartLimitBurst=10
    ExecStart=/usr/bin/sh -c '/usr/local/nsq/bin/nsqadmin -config /usr/local/nsq/conf/nsqadmin.cfg > /usr/local/nsq/logs/nsqadmin.log 2>&1'
    Restart=always
    RestartSec=120
    LimitNOFILE=65535
    
    [Install]
    WantedBy=multi-user.target
    

    nsqd API调用样例

    package main
    
    import (
    	"fmt"
    	"time"
    	"bytes"
    	"net/http"
    	"net"
    	"io/ioutil"
    	"github.com/gin-gonic/gin/json"
    )
    
    const (
    	nsqdMaxIdleConns        int = 5
    	nsqdMaxIdleConnsPerHost int = 5
    	nsqdIdleConnTimeout     int = 30
    )
    
    func nsqdHttpCommon(method, url string, data []byte) error{
    	client := &http.Client{
    		Transport: &http.Transport{
    			DisableKeepAlives: true, 
    			Proxy:             http.ProxyFromEnvironment,
    			DialContext: (&net.Dialer{
    				Timeout:   30 * time.Second,
    				KeepAlive: 30 * time.Second,
    			}).DialContext,
    			MaxIdleConns:        nsqdMaxIdleConns,
    			MaxIdleConnsPerHost: nsqdMaxIdleConnsPerHost,
    			IdleConnTimeout:     time.Duration(nsqdIdleConnTimeout) * time.Second,
    		},
    		Timeout: 6 * time.Second,
    	}
    	req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
    	req.Header.Set("Content-Type", "application/json")
    	resp, err := client.Do(req)
    	if err != nil {
    		fmt.Println(err)
    		return err
    	}
    	defer resp.Body.Close()
    
    	body, err := ioutil.ReadAll(resp.Body)
    	if err != nil {
    		fmt.Println(err)
    		return err
    	}
    	fmt.Println(resp.StatusCode, string(body))
    
    	return nil
    }
    
    func nsqdPing(addr string){
    	url := fmt.Sprint("http://", addr , "/ping")
    	fmt.Println(url)
    	nsqdHttpCommon("GET", url, nil)
    }
    
    func nsqdInfo(addr string){
    	url := fmt.Sprint("http://", addr , "/info")
    	fmt.Println(url)
    	nsqdHttpCommon("GET", url, nil)
    }
    
    func stats(addr string){
    	url := fmt.Sprint("http://", addr , "/stats")
    	fmt.Println(url)
    	nsqdHttpCommon("GET", url, nil)
    }
    
    func pub(addr, topic string, data []byte){
    	url := fmt.Sprintf("http://%s/pub?topic=%s", addr, topic)
    	fmt.Println(url)
    	nsqdHttpCommon("POST", url, data)
    }
    
    func mpub(addr, topic string, data []byte){
    	url := fmt.Sprintf("http://%s/mpub?topic=%s", addr, topic)
    	fmt.Println(url)
    	nsqdHttpCommon("POST", url, data)
    }
    
    func nsqdTopicCreate(addr, topic string){
    	url := fmt.Sprint("http://", addr , "/topic/create?topic=", topic)
    	fmt.Println(url)
    	nsqdHttpCommon("POST", url, nil)
    }
    
    func nsqdTopicDelete(addr, topic string){
    	url := fmt.Sprint("http://", addr , "/topic/delete?topic=", topic)
    	fmt.Println(url)
    	nsqdHttpCommon("POST", url, nil)
    }
    
    func nsqdTopicEmpty(addr, topic string){
    	url := fmt.Sprint("http://", addr , "/topic/empty?topic=", topic)
    	fmt.Println(url)
    	nsqdHttpCommon("POST", url, nil)
    }
    
    func nsqdTopicPause(addr, topic string){
    	url := fmt.Sprint("http://", addr , "/topic/pause?topic=", topic)
    	fmt.Println(url)
    	nsqdHttpCommon("POST", url, nil)
    }
    
    func nsqdTopicUnPause(addr, topic string){
    	url := fmt.Sprint("http://", addr , "/topic/unpause?topic=", topic)
    	fmt.Println(url)
    	nsqdHttpCommon("POST", url, nil)
    }
    
    func nsqdChannelCreate(addr, topic, ch string){
    	url := fmt.Sprintf("http://%s/channel/create?topic=%s&channel=%s", addr, topic, ch)
    	fmt.Println(url)
    	nsqdHttpCommon("POST", url, nil)
    }
    
    func nsqdChannelDelete(addr, topic, ch string){
    	url := fmt.Sprintf("http://%s/channel/delete?topic=%s&channel=%s", addr, topic, ch)
    	fmt.Println(url)
    	nsqdHttpCommon("POST", url, nil)
    }
    
    func nsqdChannelEmpty(addr, topic, ch string){
    	url := fmt.Sprintf("http://%s/channel/empty?topic=%s&channel=%s", addr, topic, ch)
    	fmt.Println(url)
    	nsqdHttpCommon("POST", url, nil)
    }
    
    func nsqdChannelPause(addr, topic, ch string){
    	url := fmt.Sprintf("http://%s/channel/pause?topic=%s&channel=%s", addr, topic, ch)
    	fmt.Println(url)
    	nsqdHttpCommon("POST", url, nil)
    }
    
    func nsqdChannelUnPause(addr, topic, ch string){
    	url := fmt.Sprintf("http://%s/channel/unpause?topic=%s&channel=%s", addr, topic, ch)
    	fmt.Println(url)
    	nsqdHttpCommon("POST", url, nil)
    }
    
    func main(){
    	addr := "192.168.1.93:4151"
    	topic := "shhnwangjian"
    	var data []string
    	data = append(data, "1323", "fdsf", "fdsafds")
    	dataj, _ := json.Marshal(data)
    	fmt.Println(dataj)
    	nsqdPing(addr)
    	nsqdInfo(addr)
    	stats(addr)
    	pub(addr, topic, dataj)
    	mpub(addr, topic, dataj)
    	nsqdTopicCreate(addr, topic)
    	nsqdTopicDelete(addr, topic)
    	nsqdTopicEmpty(addr, topic)
    	nsqdTopicPause(addr, topic)
    	nsqdTopicUnPause(addr, topic)
    	nsqdChannelCreate(addr, topic, "ch1")
    	nsqdChannelDelete(addr, topic, "ch1")
    	nsqdChannelEmpty(addr, topic, "ch2")
    	nsqdChannelPause(addr, topic, "ch1")
    	nsqdChannelUnPause(addr, topic, "ch1")
    }
    

    nsqlookupd调用样例

    package main
    
    import (
    	"time"
    	"bytes"
    	"net"
    	"net/http"
    	"io/ioutil"
    	"fmt"
    )
    
    const (
    	MaxIdleConns        int = 5
    	MaxIdleConnsPerHost int = 5
    	IdleConnTimeout     int = 30
    )
    
    func httpCommon(method, url string, data []byte) error{
    	client := &http.Client{
    		Transport: &http.Transport{
    			DisableKeepAlives: true,
    			Proxy:             http.ProxyFromEnvironment,
    			DialContext: (&net.Dialer{
    				Timeout:   30 * time.Second,
    				KeepAlive: 30 * time.Second,
    			}).DialContext,
    			MaxIdleConns:        MaxIdleConns,
    			MaxIdleConnsPerHost: MaxIdleConnsPerHost,
    			IdleConnTimeout:     time.Duration(IdleConnTimeout) * time.Second,
    		},
    		Timeout: 6 * time.Second,
    	}
    	req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
    	req.Header.Set("Content-Type", "application/json")
    	resp, err := client.Do(req)
    	if err != nil {
    		fmt.Println(err)
    		return err
    	}
    	defer resp.Body.Close()
    
    	body, err := ioutil.ReadAll(resp.Body)
    	if err != nil {
    		fmt.Println(err)
    		return err
    	}
    	fmt.Println(resp.StatusCode, string(body))
    
    	return nil
    }
    
    func lookup(addr, topic string) {
    	url := fmt.Sprint("http://", addr , "/lookup?topic=", topic)
    	fmt.Println(url)
    	httpCommon("GET", url, nil)
    }
    
    func topics(addr string){
    	url := fmt.Sprint("http://", addr , "/topics")
    	fmt.Println(url)
    	httpCommon("GET", url, nil)
    }
    
    func channles(addr, topic string){
    	url := fmt.Sprint("http://", addr , "/channels?topic=", topic)
    	fmt.Println(url)
    	httpCommon("GET", url, nil)
    }
    
    func ping(addr string){
    	url := fmt.Sprint("http://", addr , "/ping")
    	fmt.Println(url)
    	httpCommon("GET", url, nil)
    }
    
    func info(addr string){
    	url := fmt.Sprint("http://", addr , "/info")
    	fmt.Println(url)
    	httpCommon("GET", url, nil)
    }
    
    func nodes(addr string){
    	url := fmt.Sprint("http://", addr , "/nodes")
    	fmt.Println(url)
    	httpCommon("GET", url, nil)
    }
    
    func topicCreate(addr, topic string){
    	url := fmt.Sprint("http://", addr , "/topic/create?topic=", topic)
    	fmt.Println(url)
    	httpCommon("POST", url, nil)
    }
    
    func topicDelete(addr, topic string){
    	url := fmt.Sprint("http://", addr , "/topic/delete?topic=", topic)
    	fmt.Println(url)
    	httpCommon("POST", url, nil)
    }
    
    func channelCreate(addr, topic, ch string){
    	url := fmt.Sprintf("http://%s/channel/create?topic=%s&channel=%s", addr, topic, ch)
    	fmt.Println(url)
    	httpCommon("POST", url, nil)
    }
    
    func channelDelete(addr, topic, ch string){
    	url := fmt.Sprintf("http://%s/channel/delete?topic=%s&channel=%s", addr, topic, ch)
    	fmt.Println(url)
    	httpCommon("POST", url, nil)
    }
    
    func topicTombstone(addr, topic, node string){
    	url := fmt.Sprintf("http://%s/topic/tombstone?topic=%s&node=%s", addr, topic, node)
    	fmt.Println(url)
    	httpCommon("POST", url, nil)
    }
    
    func main(){
    	addr := "192.168.1.93:4161"
    	topic := "test"
    	lookup(addr, topic)
    	topics(addr)
    	channles(addr, topic)
    	ping(addr)
    	info(addr)
    	nodes(addr)
    	topicCreate(addr, topic)
    	topicDelete(addr, topic)
    	channelCreate(addr, topic, "ch1")
    	channelDelete(addr, topic, "ch1")
    	topicTombstone(addr, topic, "192.168.1.93:4151")
    }
    

    go-nsq发布消息样例

    package main
    
    import (
    	"github.com/nsqio/go-nsq"
    	"fmt"
    	"time"
    )
    
    var (
    	addrs  = [...]string{"192.168.1.93:4150", "192.168.2.41:4150"}
    )
    
    func ProducerPing(addr string) bool {
    	config := nsq.NewConfig()
    	w, _ := nsq.NewProducer(addr, config)
    	err := w.Ping()
    
    	if err != nil {
    		fmt.Println("should connect on ping")
    		return false
    	}
    	return true
    }
    
    func ProducerPublish (addr string){
    	config := nsq.NewConfig()
    	w, _ := nsq.NewProducer(addr, config)
    	defer w.Stop()
    	for {
    		err := w.Publish("shhnwangjian", []byte(addr + "_publish_test_case_" + fmt.Sprint(time.Now().Unix())))
    		if err != nil {
    			fmt.Println(err)
    		}
    		time.Sleep(10 * time.Millisecond)
    	}
    
    }
    
    func main(){
    	for _, addr := range addrs{
    		if ProducerPing(addr){
    			ProducerPublish(addr)
    		}
    	}
    }
    

    go-nsq订阅消息样例

    package main
    
    import (
    	"github.com/nsqio/go-nsq"
    	"fmt"
    	"sync"
    	"time"
    )
    
    type NSQHandler struct {
    }
    
    func (this *NSQHandler) HandleMessage(message *nsq.Message) error {
    	fmt.Println("recv:", string(message.Body))
    	return nil
    }
    
    func consumer () {
    	waiter := sync.WaitGroup{}
    	waiter.Add(1)
    
    	go func() {
    		defer waiter.Done()
    		config := nsq.NewConfig()
    		config.LookupdPollInterval = 5 * time.Second
    		q, _ := nsq.NewConsumer("shhnwangjian", "ch1", config)
    		q.AddHandler(&NSQHandler{})
    		//addr := "192.168.1.93:4150"
    
    		// 建立一个nsqd连接
    		//err := q.ConnectToNSQD(addr)
    		//if err != nil {
    		//	fmt.Println(err)
    		//}
    
    		//建立多个nsqd连接
    		err := q.ConnectToNSQDs([]string{"192.168.1.93:4150", "192.168.2.41:4150"})
    		if err != nil {
    			fmt.Println(err)
    		}
    
    		select {}
    	}()
    	waiter.Wait()
    }
    
    func main(){
    	consumer()
    }
    

    参考文章:

    https://blog.csdn.net/keets1992/article/details/52326600

  • 相关阅读:
    从函数作用域和块级作用域看javascript的作用域链
    基于vue实现一个简单的MVVM框架(源码分析)
    发布-订阅模式
    希尔排序
    直接插入排序
    选择排序
    React Fiber源码分析 第三篇(异步状态)
    React Fiber源码分析 第二篇(同步模式)
    React Fiber源码分析 第一篇
    数据结构 之 树总结
  • 原文地址:https://www.cnblogs.com/shhnwangjian/p/9287121.html
Copyright © 2020-2023  润新知