nsqlookupd:高性能消息中间件 NSQ 解析
摘要:本篇将会结合源码介绍 nsqlookupd 的实现细节。
本篇将会结合源码介绍 nsqlookupd 的实现细节。nsqlookupd 主要流程与nsqd 执行逻辑相似,区别在于具体运行的任务不同。
nsqlookupd是nsq管理集群拓扑信息以及用于注册和发现nsqd服务。所以,也可以把nsqlookupd理解为注册发现服务。当nsq集群中有多个nsqlookupd服务时,因为每个nsqd都会向所有的nsqlookupd上报本地信息,因此nsqlookupd具有最终一致性。
入口函数
在 nsq/apps/nsqlookupd/main.go 可以找到执行入口文件。
// 位于apps/nsqlookupd/main.go:45
func main() {
prg := &program{}
if err := svc.Run(prg, syscall.SIGINT, syscall.SIGTERM); err != nil {
logFatal("%s", err)
}
} func (p *program) Init(env svc.Environment) error {
if env.IsWindowsService() {
dir := filepath.Dir(os.Args[0])
return os.Chdir(dir)
}
return nil
} func (p *program) Start() error {
opts := nsqlookupd.NewOptions() flagSet := nsqlookupdFlagSet(opts)
...
}
同样,通过第三方 svc 包进行优雅的后台进程管理,svc.Run() -> svc.Init() -> svc.Start(),启动 nsqlookupd 实例。
// 位于 apps/nsqlookupd/main.go:80
options.Resolve(opts, flagSet, cfg)
nsqlookupd, err := nsqlookupd.New(opts)
if err != nil {
logFatal("failed to instantiate nsqlookupd", err)
}
p.nsqlookupd = nsqlookupd go func() {
err := p.nsqlookupd.Main()
if err != nil {
p.Stop()
os.Exit(1)
}
}()
初始化配置参数(优先级:flagSet-命令行参数 > cfg-配置文件 > opts-默认值),开启协程,进入 nsqlookupd.Main() 主函数。
监听请求
我们来看下 nsqlookupd 是如何监听请求的,代码实现如下:
// 位于 nsqlookupd/nsqlookupd.go:53
func (l *NSQLookupd) Main() error {
ctx := &Context{l} exitCh := make(chan error)
var once sync.Once
exitFunc := func(err error) {
once.Do(func() {
if err != nil {
l.logf(LOG_FATAL, "%s", err)
}
exitCh <- err
})
} tcpServer := &tcpServer{ctx: ctx}
l.waitGroup.Wrap(func() {
exitFunc(protocol.TCPServer(l.tcpListener, tcpServer, l.logf))
})
httpServer := newHTTPServer(ctx)
l.waitGroup.Wrap(func() {
exitFunc(http_api.Serve(l.httpListener, httpServer, "HTTP", l.logf))
}) err := <-exitCh
return err
}
开启 goroutine 执行 tcpServer, httpServer,分别监听 nsqd, nsqadmin 的客户端请求。
处理请求
// 位于 internal/protocol/tcp_server.go:17
func TCPServer(listener net.Listener, handler TCPHandler, logf lg.AppLogFunc) error {
logf(lg.INFO, "TCP: listening on %s", listener.Addr()) for {
clientConn, err := listener.Accept()
if err != nil {
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
logf(lg.WARN, "temporary Accept() failure - %s", err)
runtime.Gosched()
continue
}
// theres no direct way to detect this error because it is not exposed
if !strings.Contains(err.Error(), "use of closed network connection") {
return fmt.Errorf("listener.Accept() error - %s", err)
}
break
}
go handler.Handle(clientConn)
} logf(lg.INFO, "TCP: closing %s", listener.Addr()) return nil
}
TCPServer 循环监听客户端请求,建立长连接进行通信,并开启 handler 处理每一个客户端 conn。
装饰 http 路由
httpServer 通过 http_api.Decorate 装饰器实现对各 http 路由进行 handler 装饰,如加 log 日志、V1 协议版本号的统一格式输出等;
func newHTTPServer(ctx *Context) *httpServer {
log := http_api.Log(ctx.nsqlookupd.logf)
router := httprouter.New()
router.HandleMethodNotAllowed = true
router.PanicHandler = http_api.LogPanicHandler(ctx.nsqlookupd.logf)
router.NotFound = http_api.LogNotFoundHandler(ctx.nsqlookupd.logf)
router.MethodNotAllowed = http_api.LogMethodNotAllowedHandler(ctx.nsqlookupd.logf)
s := &httpServer{
ctx: ctx,
router: router,
}
router.Handle("GET", "/ping", http_api.Decorate(s.pingHandler, log, http_api.PlainText))
router.Handle("GET", "/info", http_api.Decorate(s.doInfo, log, http_api.V1))
// v1 negotiate
router.Handle("GET", "/debug", http_api.Decorate(s.doDebug, log, http_api.V1))
router.Handle("GET", "/lookup", http_api.Decorate(s.doLookup, log, http_api.V1))
router.Handle("GET", "/topics", http_api.Decorate(s.doTopics, log, http_api.V1))
router.Handle("GET", "/channels", http_api.Decorate(s.doChannels, log, http_api.V1))
}
处理客户端命令
tcp 解析 V1 协议,内部协议封装的 prot.IOLoop(conn) 进行循环处理客户端命令,直到客户端命令全部解析处理完毕才关闭连接。
var prot protocol.Protocol
switch protocolMagic {
case " V1":
prot = &LookupProtocolV1{ctx: p.ctx}
default:
protocol.SendResponse(clientConn, []byte("E_BAD_PROTOCOL"))
clientConn.Close()
p.ctx.nsqlookupd.logf(LOG_ERROR, "client(%s) bad protocol magic '%s'",
clientConn.RemoteAddr(), protocolMagic)
return
} err = prot.IOLoop(clientConn)
执行命令
通过内部协议进行 p.Exec(执行命令)、p.SendResponse(返回结果),保证每个 nsqd 节点都能正确的进行服务注册(register)与注销(unregister),并进行心跳检测(ping)节点的可用性,确保客户端取到的 nsqd 节点列表都是最新可用的。
for {
line, err = reader.ReadString('\n')
if err != nil {
break
}
line = strings.TrimSpace(line)
params := strings.Split(line, " ")
var response []byte
response, err = p.Exec(client, reader, params)
if err != nil {
ctx := ""
if parentErr := err.(protocol.ChildErr).Parent(); parentErr != nil {
ctx = " - " + parentErr.Error()
}
_, sendErr := protocol.SendResponse(client, []byte(err.Error()))
if sendErr != nil {
p.ctx.nsqlookupd.logf(LOG_ERROR, "[%s] - %s%s", client, sendErr, ctx)
break
}
continue
}
if response != nil {
_, err = protocol.SendResponse(client, response)
if err != nil {
break
}
}
}
conn.Close()
nsqlookupd 服务同时开启 tcp 和 http 两个监听服务,nsqd 会作为客户端,连上 nsqlookupd 的 tcp 服务,并上报自己的 topic 和 channel 信息,以及通过心跳机制判断 nsqd 状态;还有个 http 服务提供给 nsqadmin 获取集群信息。
小结
本文主要介绍 nsqlookupd 的实现,nsqlookupd 同样是一个守护进程,负责管理拓扑信息。客户端通过查询 nsqlookupd 来发现指定话题( topic )的生产者,并且 nsqd 节点广播话题(topic)和通道( channel )信息。有两个接口: TCP 接口, nsqd 用它来广播。 HTTP 接口,客户端用它来发现和管理。
下一篇文章,将会继续介绍 nsq 中其他模块实现的细节。
本文分享自华为云社区《高性能消息中间件 NSQ 解析-nsqlookupd 实现细节介绍》,原文作者:aoho 。
nsqlookupd:高性能消息中间件 NSQ 解析的更多相关文章
- 高性能消息中间件——NATS
前 言 这段时间我的主要工作内容是将公司系统中使用的RabbitMQ替换成NATS,而此之前我对Nats一无所知.经过一段时间紧张的学习和开发之后我顺利的完成了任务,并对消息中间件有了更深的了解.在此 ...
- 高性能JSON解析器及生成器RapidJSON
RapidJSON是腾讯公司开源的一个C++的高性能的JSON解析器及生成器,同时支持SAX/DOM风格的API. 直击现场 RapidJSON是腾讯公司开源的一个C++的高性能的JSON解析器及生成 ...
- Kafka设计解析
Kafka剖析(一):Kafka背景及架构介绍 Kafka设计解析(二):Kafka High Availability (上) Kafka设计解析(三):Kafka High Availabilit ...
- Go之NSQ简介,原理和使用
NSQ简介 NSQ是Go语言编写的一个开源的实时分布式内存消息队列,其性能十分优异. NSQ 是实时的分布式消息处理平台,其设计的目的是用来大规模地处理每天数以十亿计级别的消息.它具有分布式和去中心化 ...
- 高性能ORM 框架之 MySqlSugar
mysql 3.X API地址: http://www.cnblogs.com/sunkaixuan/p/5987308.html MySqlSugar 1.5 API 一.介简 SqlSugar ...
- Asp.net 高性能 Sqlite ORM 框架之 sqliteSugar
一.介简 easyliter框架的升级版本,并且正式命名为SqliteSugar框架,另外Sugar系列还有 MySql和MsSql版本,Oracle版本待开发中(因为客户端太大一直在忧郁当中) 用S ...
- Golang入门教程(十七)Linux/Windows下快速搭建和配置NSQ
前言 NSQ是一个基于Go语言的分布式实时消息平台,它基于MIT开源协议发布,代码托管在GitHub,其当前最新版本是0.3.1版.NSQ可用于大规模系统中的实时消息服务,并且每天能够处理数亿级别的消 ...
- golang 中操作nsq队列数据库
首先先在本地将服务跑起来,我用的是docker-compose ,一句话6666 先新建一个docker-compose.yml version: '2' services: nsqlookupd: ...
- 基于supersocket、C#对JT808协议进行解析构建gps监控平台服务端
GPS监控平台.车联网.物联网系统中GPRS网络数据的并发通讯和处理解析,主要功能有socket的UDP和TCP链路建立和维持,网络数据协议包接收与解析,分发上传到其他业务规则服务器,在物联网以及位置 ...
随机推荐
- How to use JavaScript to implement precise setTimeout and setInterval
How to use JavaScript to implement precise setTimeout and setInterval 如何使用 JavaScript 实现精确的 setTimeo ...
- taro & Block
taro & Block https://nervjs.github.io/taro/docs/children.html#注意事项-1 import Taro, { Component, E ...
- git alias & zsh
git alias & zsh VPN & git work tree # git pull === gp ➜ .git git:(feature/select-seat-system ...
- 人物传记:Mila Fletcher:如何勤于思考抓住关键?
Mila Fletcher于2007年毕业于耶鲁大学,她是一名真正意义上的法学博士,在校期间获得了马歇尔奖学金,毕业后曾在美国多家知名律师事务所任职,目前就职于星盟全球投资公司,专注于帮助公司和客户提 ...
- 云原生系列6 基于springcloud架构风格的本地debug实现
debug是程序员在日常开发中最常使用的操作, 那么,你是如何快速在微服务架构风格下快速debug后端服务呢? 开发现状 开发的理想状态 本地调测的使用步骤 登录智能网关 如果集成开发环境是在本地局域 ...
- 验证销售部门的数据查看权限-脚本demo
1 # coding:utf-8 2 ''' 3 @file: run_old.py 4 @author: jingsheng hong 5 @ide: PyCharm 6 @createTime: ...
- 06.numpy聚合运算
>>> import numpy as np >>> L = np.random.random(100) >>> L array([0.82846 ...
- Vue的学习总结之---Vue项目 前后端分离模式解决开发环境的跨域问题
原文:https://blog.csdn.net/localhost_1314/article/details/83623526 在前后端分离的web开发中,我们与后台联调时,会遇到跨域的问题. 比如 ...
- C++指针的算术运算 、关系运算
下面随笔是关于指针的算术运算 .关系运算. 指针类型的算术运算 指针与整数的加减运算 指针++,--运算 指针类型的算术运算 指针p加上或减去n 其意义是指针当前指向位置的前方或后方第n个数据的起始位 ...
- Prometheus时序数据库-数据的插入
Prometheus时序数据库-数据的插入 前言 在之前的文章里,笔者详细的阐述了Prometheus时序数据库在内存和磁盘中的存储结构.有了前面的铺垫,笔者就可以在本篇文章阐述下数据的插入过程. 监 ...