├── go.mod ├── .idea ├── misc.xml ├── vcs.xml ├── .gitignore ├── modules.xml └── nsq_go.iml ├── nsq_go_test.go ├── README.md ├── nsq_go.go ├── nsq_untils.go ├── nsq_consumer.go ├── go.sum └── nsq_producer.go /go.mod: -------------------------------------------------------------------------------- 1 | module nsq_go 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/nsqio/go-nsq v1.1.0 7 | go.uber.org/zap v1.19.1 8 | ) 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/nsq_go.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /nsq_go_test.go: -------------------------------------------------------------------------------- 1 | package nsq_go 2 | 3 | import ( 4 | "github.com/nsqio/go-nsq" 5 | "go.uber.org/zap" 6 | "testing" 7 | ) 8 | 9 | func TestNsqGoStart(t *testing.T) { 10 | //1.nsqGo 启动 11 | logger, _ := zap.NewProduction() 12 | nsqlookupHttpAddress:= []string{"127.0.0.1:4161"} //nsqlookup 的地址数组 13 | NsqGoStart(nsqlookupHttpAddress, logger, 30, nsq.LogLevelWarning) 14 | 15 | //2.nsqGo 初始化生产者 16 | InitNsqProducer(nsq.NewConfig(), 3) 17 | 18 | //3.nsqGo 初始化消费者 19 | NewNsqConsumer("channelTest", "topicTest", OnNsqMsg, true, nsq.NewConfig()) 20 | 21 | //4.nsqGo 生产者发消息 22 | NsqPush("topicTest", []byte("helloWorld")) 23 | 24 | } 25 | 26 | //消息Nsq消息处理 27 | func OnNsqMsg(msg *nsq.Message) { 28 | nsqGoLogInfo("ok", zap.Reflect("msg", msg)) 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nsq_go 2 | 3 | `nsq_go`适用于集群版nsq的场景 ,内部封装了`nsq`的生产者和消费者,生产者根据`nsqlookupd`发现连接`nsqd`。 4 | View the go-nsq [documentation](http://godoc.org/github.com/bitly/go-nsq#Config) for configuration options. 5 | 6 | ## Example 7 | 8 | ```go 9 | func TestNsqGoStart(t *testing.T) { 10 | 11 | //1.nsqGo 启动 12 | logger, _ := zap.NewProduction() 13 | nsqlookupHttpAddress:= []string{"127.0.0.1:4161"} //nsqlookup 的地址数组 14 | NsqGoStart(nsqlookupHttpAddress, logger, 30, nsq.LogLevelWarning) 15 | 16 | //2.nsqGo 初始化生产者 17 | InitNsqProducer(nsq.NewConfig(), 3) 18 | 19 | //3.nsqGo 初始化消费者 20 | NewNsqConsumer("channelTest", "topicTest", OnNsqMsg, true, nsq.NewConfig()) 21 | 22 | //4.nsqGo 生产者发消息 23 | NsqPush("topicTest", []byte("helloWorld")) 24 | 25 | } 26 | 27 | //消息Nsq消息处理 28 | func OnNsqMsg(msg *nsq.Message) { 29 | nsqGoLogInfo("ok",zap.Reflect("msg",msg)) 30 | } 31 | 32 | ``` 33 | 34 | # License 35 | 36 | MIT -------------------------------------------------------------------------------- /nsq_go.go: -------------------------------------------------------------------------------- 1 | package nsq_go 2 | 3 | import ( 4 | "github.com/nsqio/go-nsq" 5 | "go.uber.org/zap" 6 | ) 7 | 8 | var myNsqGo *nsqGo 9 | var myLogger *zap.Logger 10 | 11 | type nsqGo struct { 12 | nsqdTcpAddress []string //Nsqd的Tcp地址 13 | nsqdHttpAddress []string //Nsqd的http地址 14 | nsqlookupHttpAddress []string //Nsqlookup的HTTP地址 15 | lookupdPollInterval int64 //消费者通过lookUp轮询查找新Nsqd的时间间隔// 也是生产者需要等多久才可以往新的Nsqd发消息的时间 (秒) 16 | logLevel nsq.LogLevel //nsq日志级别 17 | 18 | } 19 | 20 | //1.nsqlookupHttpAddress地址 2.日志 3.消费者通过lookUp轮询查找新Nsqd的时间间隔,也是生产者需要等多久才可以往新的Nsqd发消息的时间 (秒) 21 | func NsqGoStart(nsqlookupHttpAddress []string, logger *zap.Logger, lookupdPollInterval int64, lvl nsq.LogLevel) { 22 | //日志赋值 23 | if myLogger == nil { 24 | myLogger = logger 25 | } 26 | if len(nsqlookupHttpAddress) <= 0 { 27 | nsqGoLogInfo("nsq_go nsqlookupHttpAddress 0") 28 | return 29 | } 30 | if myNsqGo == nil { 31 | myNsqGo = &nsqGo{ 32 | nsqlookupHttpAddress: nsqlookupHttpAddress, 33 | lookupdPollInterval: lookupdPollInterval, 34 | logLevel: lvl, 35 | } 36 | } 37 | nsqGoLogInfo("nsq_go start successful !!!!") 38 | } 39 | -------------------------------------------------------------------------------- /nsq_untils.go: -------------------------------------------------------------------------------- 1 | package nsq_go 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "go.uber.org/zap" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | //http post请求 14 | func nsqGoHttpPost(url string, data interface{}, contentType string) string { 15 | // 超时时间:5秒 16 | client := &http.Client{Timeout: 5 * time.Second} 17 | jsonStr, _ := json.Marshal(data) 18 | resp, err := client.Post(url, contentType, bytes.NewBuffer(jsonStr)) 19 | if err != nil { 20 | nsqGoLogError("post err", zap.Error(err)) 21 | return "" 22 | } 23 | defer resp.Body.Close() 24 | 25 | result, _ := ioutil.ReadAll(resp.Body) 26 | return string(result) 27 | } 28 | 29 | //http get请求 30 | func nsqGoHttpGet(url string) []byte { 31 | resp, err := http.Get(url) 32 | if err != nil { 33 | nsqGoLogError("nsqLookup get err", zap.Error(err)) 34 | return nil 35 | } 36 | defer resp.Body.Close() 37 | body, err := ioutil.ReadAll(resp.Body) 38 | if err != nil { 39 | nsqGoLogError("get err", zap.Error(err)) 40 | return nil 41 | } 42 | return body 43 | } 44 | 45 | //调试日志接口 46 | func nsqGoLogDebug(msg string, fields ...zap.Field) { 47 | if myLogger == nil { 48 | log.Println("nsqGo myLogger nil") 49 | return 50 | } 51 | myLogger.Debug(msg, fields...) 52 | } 53 | 54 | //关键信息日志接口 55 | func nsqGoLogInfo(msg string, fields ...zap.Field) { 56 | if myLogger == nil { 57 | log.Println("nsqGo myLogger nil") 58 | return 59 | } 60 | myLogger.Info(msg, fields...) 61 | } 62 | 63 | //警告信息日志接口 64 | func nsqGoLogWarn(msg string, fields ...zap.Field) { 65 | if myLogger == nil { 66 | log.Println("nsqGo myLogger nil") 67 | return 68 | } 69 | myLogger.Warn(msg, fields...) 70 | } 71 | 72 | //错误信息日志接口 73 | func nsqGoLogError(msg string, fields ...zap.Field) { 74 | if myLogger == nil { 75 | log.Println("nsqGo myLogger nil") 76 | return 77 | } 78 | myLogger.Error(msg, fields...) 79 | } 80 | 81 | func nsqGoSetTimeout(interval int, fn func(...interface{}) int, args ...interface{}) { 82 | if interval < 0 { 83 | nsqGoLogError("new timeout interval", zap.Int("interval", interval)) 84 | return 85 | } 86 | nsqGoLogInfo("new timeout interval", zap.Int("interval", interval)) 87 | 88 | go func() { 89 | var tick *time.Timer 90 | for interval > 0 { 91 | tick = time.NewTimer(time.Second * time.Duration(interval)) 92 | select { 93 | case <-tick.C: 94 | tick.Stop() 95 | interval = fn(args...) 96 | } 97 | } 98 | }() 99 | } 100 | -------------------------------------------------------------------------------- /nsq_consumer.go: -------------------------------------------------------------------------------- 1 | package nsq_go 2 | 3 | import ( 4 | "fmt" 5 | "github.com/nsqio/go-nsq" 6 | "go.uber.org/zap" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | //消费者处理函数啊 12 | type nsqConsumerFunc func(msg *nsq.Message) 13 | 14 | var myNsqConsumerMgr *nsqConsumerMgr 15 | //nsq的消息处理管理者 16 | type nsqConsumerMgr struct { 17 | nsqHandlerMap map[string]nsqConsumerFunc //key 是话题 value是一个处理函数 18 | nsqLock sync.RWMutex 19 | } 20 | 21 | //nsq消费者 22 | type nsqConsumerHandler struct { 23 | topic string //主题 24 | channel string //通道 25 | } 26 | 27 | //一个话题创建一个消费者 //创建时才建立channel通道 28 | // 创建话题消费者 //1.服务器name 2.主题 3.话题处理handler 地址 4.是否临时一旦断开 就不发消息了 5.日志级别 29 | func NewNsqConsumer(channel, topic string, fn nsqConsumerFunc, isEphemeral bool, conf *nsq.Config) { 30 | if myNsqGo == nil { 31 | nsqGoLogError("nsqGo not start") 32 | return 33 | } 34 | if conf == nil { 35 | nsqGoLogError("conf nil") 36 | return 37 | } 38 | //向每个nsq注册topic (不注册也不影响使用,topic会在使用的时候创建,只是会些警告) 39 | for _, v := range myNsqGo.nsqdHttpAddress { 40 | address := fmt.Sprintf("http://%s/topic/create?topic=%s", v, topic) 41 | nsqGoHttpPost(address, nil, "application/json") 42 | } 43 | if myNsqConsumerMgr == nil{ 44 | myNsqConsumerMgr = &nsqConsumerMgr{ 45 | nsqHandlerMap: make(map[string]nsqConsumerFunc), 46 | } 47 | } 48 | //注册handler 49 | myNsqConsumerMgr.nsqLock.Lock() 50 | myNsqConsumerMgr.nsqHandlerMap[topic] = fn 51 | myNsqConsumerMgr.nsqLock.Unlock() 52 | if isEphemeral { 53 | channel = fmt.Sprintf("%s#ephemeral", channel) 54 | } 55 | //添加消费者 56 | conf.LookupdPollInterval = time.Duration(myNsqGo.lookupdPollInterval) * time.Second //设置服务发现的轮询时间,例如新的nsq出现 57 | //channel 是服务器名字 58 | c, err := nsq.NewConsumer(topic, channel, conf) 59 | if err != nil { 60 | nsqGoLogError("[nsq] newConsumer err", zap.Error(err), zap.String("serverName", channel)) 61 | return 62 | } 63 | consumer := &nsqConsumerHandler{ 64 | channel: channel, 65 | topic: topic, 66 | } 67 | c.AddHandler(consumer) 68 | //设置日志 69 | c.SetLoggerLevel(myNsqGo.logLevel) 70 | //lookup地址 71 | addressList := myNsqGo.nsqlookupHttpAddress 72 | if err := c.ConnectToNSQLookupds(addressList); err != nil { // 通过nsqlookupd查询 73 | nsqGoLogError("[nsq] newConsumer err", zap.Error(err)) 74 | return 75 | } 76 | nsqGoLogInfo("nsq Consumer init successfull !!! ", zap.String("serverName", channel), zap.String("topic", topic), zap.Bool("isEphemeral", isEphemeral)) 77 | return 78 | } 79 | 80 | // HandleMessage 是需要实现的处理消息的方法 81 | func (n *nsqConsumerHandler) HandleMessage(msg *nsq.Message) (err error) { 82 | myNsqConsumerMgr.nsqLock.RLock() 83 | defer myNsqConsumerMgr.nsqLock.RUnlock() 84 | nsqGoLogInfo("【nsq】消费者处理", zap.String("Topic", n.topic), zap.Int64("time", time.Now().Unix())) 85 | if fn, ok := myNsqConsumerMgr.nsqHandlerMap[n.topic]; ok { 86 | fn(msg) 87 | } else { 88 | nsqGoLogError("nsq topic Handler nil", zap.String("topic", n.topic)) 89 | } 90 | return 91 | } 92 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 2 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 7 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 8 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 9 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 10 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 11 | github.com/nsqio/go-nsq v1.1.0 h1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE= 12 | github.com/nsqio/go-nsq v1.1.0/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY= 13 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 14 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 18 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 19 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 20 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 21 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 22 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= 23 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 24 | go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4= 25 | go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 26 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= 27 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 28 | go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= 29 | go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 30 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 31 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 32 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 33 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 34 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 35 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 36 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 37 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 38 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 39 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 40 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 41 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 44 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 46 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 47 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 48 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 49 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 50 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 51 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 52 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 53 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 54 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 55 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 56 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 57 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 58 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 59 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 60 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 61 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 62 | -------------------------------------------------------------------------------- /nsq_producer.go: -------------------------------------------------------------------------------- 1 | package nsq_go 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/nsqio/go-nsq" 7 | "go.uber.org/zap" 8 | "strings" 9 | "sync/atomic" 10 | "time" 11 | ) 12 | 13 | var nsqdManager *nsqdMgr 14 | 15 | // nsqdMgr nsqd管理 16 | type nsqdMgr struct { 17 | // 生产者nsq列表 18 | producerList []*nsqdProducer 19 | 20 | // 当前发消息的生产者下标 21 | currIndex int32 22 | 23 | // 记录当前所有nsq的 信息 (主要用于判断更新的nsq是否新增) 24 | //k:tcp地址 25 | nodesTcpMap map[string]struct{} 26 | //k:http地址 27 | nodesHttpMap map[string]struct{} 28 | } 29 | 30 | // nsqdProducer nsqd生成者 31 | type nsqdProducer struct { 32 | // 启动时间 33 | startTime int64 34 | producer *nsq.Producer 35 | } 36 | 37 | // nsqNodeList nsq节点列表,用于从 nsqlookup 获取消息 解析使用 38 | type nsqNodeList struct { 39 | Producers []*nsqdNode `json:"producers"` 40 | } 41 | 42 | // nsqdNode nsqd节点信息 43 | type nsqdNode struct { 44 | RemoteAddress string `json:"remote_address"` // nsqd的远端地址(ip+端口) 45 | HostName string `json:"host_name"` 46 | BroadcastAddress string `json:"broadcast_address"` 47 | TcpPort int `json:"tcp_port"` 48 | HttpPort int `json:"http_port"` 49 | Version string `json:"version"` 50 | Topics []string `json:"topics"` 51 | } 52 | 53 | // InitNsqProducer 初始化nsq生产者 54 | func InitNsqProducer(nsqConfig *nsq.Config, updateNsqInterval int) { 55 | if myNsqGo == nil { 56 | nsqGoLogError("[nsq] not start") 57 | return 58 | } 59 | if nsqConfig == nil { 60 | nsqGoLogError("[nsq] config nil") 61 | return 62 | } 63 | if nsqdManager == nil { 64 | nsqdManager = &nsqdMgr{ 65 | currIndex: 0, 66 | nodesTcpMap: make(map[string]struct{}), 67 | nodesHttpMap: make(map[string]struct{}), 68 | } 69 | } 70 | // 更新节点信息 71 | updateNsqdNodes(nsqConfig) 72 | // 定时更新 73 | nsqGoSetTimeout(updateNsqInterval, func(args ...interface{}) int { 74 | updateNsqdNodes(nsqConfig) 75 | return updateNsqInterval 76 | }) 77 | nsqGoLogInfo("[nsq] init producer successful") 78 | } 79 | 80 | // NsqPush 发送消息,采用轮训发送 81 | // isSync是否同步参数,isSync填写该值就是同步,不填默认异步 82 | func NsqPush(topic string, data []byte, isSync ...interface{}) bool { 83 | if nsqdManager == nil { 84 | nsqGoLogError("[nsq] manager nil") 85 | return false 86 | } 87 | lenList := len(nsqdManager.producerList) 88 | // 给一个nsqd发送即可,若发送失败就发送下一个 89 | for i := 0; i < lenList; i++ { 90 | nsqGoLogDebug("[nsq] send message", zap.String("topic", topic)) 91 | // TODO: 这次感觉使不使用原子操作都影响不大? 92 | nsqdManager.currIndex++ 93 | index := atomic.AddInt32(&nsqdManager.currIndex, 1) % int32(lenList) 94 | if index == 0 { 95 | atomic.StoreInt32(&nsqdManager.currIndex, 0) 96 | } 97 | nsqGoProducer := nsqdManager.producerList[index] 98 | if nsqGoProducer == nil { 99 | nsqGoLogError("[nsq] producer nil") 100 | continue 101 | } 102 | if nsqGoProducer.producer == nil { 103 | nsqGoLogError("[nsq] producer.producer nil") 104 | continue 105 | } 106 | // 防止消费者还未发现新添加的nsqd 107 | if nsqGoProducer.startTime+myNsqGo.nsqlookupPollInterval >= time.Now().Unix() { 108 | continue 109 | } 110 | if len(isSync) > 0 { 111 | err := nsqGoProducer.producer.Publish(topic, data) 112 | if err != nil { 113 | nsqGoLogError("[nsq] publishAsync err", zap.Error(err)) 114 | continue 115 | } 116 | } else { 117 | err := nsqGoProducer.producer.PublishAsync(topic, data, nil) 118 | if err != nil { 119 | nsqGoLogError("[nsq] publishAsync err", zap.Error(err)) 120 | continue 121 | } 122 | } 123 | return true 124 | } 125 | nsqGoLogError("[nsq] all producer publish err", zap.Int("len", lenList)) 126 | return false 127 | } 128 | 129 | // updateNsqdNodes 更新nsqd的节点信息 130 | // 会填充到 Config.Server.NsqConfig.nsqlookupHttpAddress,Config.Server.NsqConfig.NsqdTcpAddress 中 131 | func updateNsqdNodes(nsqConfig *nsq.Config) { 132 | if nsqConfig == nil { 133 | nsqGoLogError("[nsq] config nil") 134 | return 135 | } 136 | if myNsqGo == nil { 137 | nsqGoLogError("[nsq] not start") 138 | return 139 | } 140 | if nsqdManager == nil { 141 | nsqGoLogError("[nsq] manager is nil") 142 | return 143 | } 144 | // 增加的nsqd节点 145 | addNodesTcpMap := make(map[string]struct{}) 146 | addNodesHttpMap := make(map[string]struct{}) 147 | // 当前的nsqd连接 (会删除一些没有使用的) 148 | currNodesTcpMap := make(map[string]struct{}) 149 | for _, address := range myNsqGo.nsqlookupHttpAddress { 150 | url := fmt.Sprintf("http://%v/nodes", address) 151 | body := nsqGoHttpGet(url) 152 | if len(body) == 0 { 153 | continue 154 | } 155 | nl := &nsqNodeList{} 156 | err := json.Unmarshal(body, nl) 157 | if err != nil { 158 | nsqGoLogError("[nsq] json unmarshal err", zap.Error(err)) 159 | return 160 | } 161 | for _, v := range nl.Producers { 162 | if v == nil { 163 | continue 164 | } 165 | strList := strings.Split(v.RemoteAddress, ":") 166 | if len(strList) != 2 { 167 | nsqGoLogError("[nsq] remoteAddress err", zap.String("remoteAddress", v.RemoteAddress)) 168 | continue 169 | } 170 | ip := strList[0] 171 | if ip == "127.0.0.1" { 172 | ip = strings.Split(address, ":")[0] 173 | } 174 | tcpAddress := fmt.Sprintf("%v:%v", ip, v.TcpPort) 175 | // 填充tcpMap 176 | if _, ok := nsqdManager.nodesTcpMap[tcpAddress]; !ok { 177 | nsqdManager.nodesTcpMap[tcpAddress] = struct{}{} 178 | // 增加Tcp 179 | addNodesTcpMap[tcpAddress] = struct{}{} 180 | } 181 | // 记录当前的 182 | currNodesTcpMap[tcpAddress] = struct{}{} 183 | 184 | httpAddress := fmt.Sprintf("%v:%v", ip, v.HttpPort) 185 | // 填充HttpMap 186 | if _, ok := nsqdManager.nodesHttpMap[httpAddress]; !ok { 187 | nsqdManager.nodesHttpMap[httpAddress] = struct{}{} 188 | // 增加http 189 | addNodesHttpMap[httpAddress] = struct{}{} 190 | } 191 | } 192 | } 193 | isFirst := len(myNsqGo.nsqdTcpAddress) == 0 194 | // 填充 增加的 195 | for address := range addNodesTcpMap { 196 | myNsqGo.nsqdTcpAddress = append(myNsqGo.nsqdTcpAddress, address) 197 | // 增加nsqd的生产者 198 | producer, err := nsq.NewProducer(address, nsqConfig) 199 | if err != nil { 200 | nsqGoLogError("[nsq] create producer failed", zap.Error(err)) 201 | } 202 | startTime := time.Now().Unix() 203 | if isFirst { 204 | // 第一次可以立即使用 205 | startTime = 0 206 | } 207 | nsqdManager.producerList = append(nsqdManager.producerList, &nsqdProducer{ 208 | startTime: startTime, 209 | producer: producer, 210 | }) 211 | } 212 | for address := range addNodesHttpMap { 213 | myNsqGo.nsqdHttpAddress = append(myNsqGo.nsqdHttpAddress, address) 214 | } 215 | 216 | // 删除没有注册的 217 | for i := 0; i < len(nsqdManager.producerList); i++ { 218 | p := nsqdManager.producerList[i] 219 | if p == nil || p.producer == nil { 220 | nsqGoLogError("[nsq] producer nil", zap.Int("index", i)) 221 | continue 222 | } 223 | isDel := true 224 | for add := range currNodesTcpMap { 225 | if p.producer.String() == add { 226 | isDel = false 227 | break 228 | } 229 | } 230 | if !isDel { 231 | continue 232 | } 233 | // 判断下 但是基本不可能 234 | // slice中 如: arr:=[]int{1} arr[len(arr):]这样是合法的√ 235 | // arr[len(arr)]这样才是下标越界× 236 | if i+1 > len(nsqdManager.producerList) { 237 | nsqGoLogError("arr index out", zap.Int("i+1", i+1), zap.Int("len", len(nsqdManager.producerList))) 238 | continue 239 | } 240 | // 停止 241 | p.producer.Stop() 242 | // producerList 删除 243 | nsqdManager.producerList = append(nsqdManager.producerList[:i], nsqdManager.producerList[i+1:]...) 244 | i-- 245 | // NodesTcpMap 删除 246 | delete(nsqdManager.nodesTcpMap, p.producer.String()) 247 | // 重置currIndex 248 | nsqdManager.currIndex = 0 249 | } 250 | } 251 | 252 | // GetNsqdNum 获得nsqd数量 253 | func GetNsqdNum() int { 254 | if nsqdManager == nil { 255 | nsqGoLogError("[nsq] manager nil") 256 | return 0 257 | } 258 | return len(nsqdManager.producerList) 259 | 260 | } 261 | --------------------------------------------------------------------------------