消息队列 NSQ 源码学习笔记 (一)
nsqlookupd 用于Topic, Channel, Node 三类信息的一致性分发
概要
nsqlookup 知识点总结
功能定位
- 为node 节点和客户端节点提供一致的topic, channel, node 查询服务
- Topic 主题, 和大部分消息队列的含义一致, 消息处理时,将相同主题的数据会归为一类消息
- channel,可以理解为 topic 的一份数据拷贝,一个或者多个消费者对接一个channel。
- node nsqd 启动的一个实例
- 一个channel会放置在某一个node 节点上,一个topic 下可以有多个channel.
- HTTP 接口 用于客户端服务发现以及admin 的交户使用
- TCP 接口 用于 node 节点做消息广告使用
- 为node 节点和客户端节点提供一致的topic, channel, node 查询服务
实现方式
- 数据包括了Topic, Channel, Node 等信息,全部存储于RegistrationDB中,RegistrationDB 采用读写锁和 map 实现,数据均存储于内存中
- 若存在多个nsqlookup 节点,各节点之间无耦合关系
nsqlookupd 源码阅读
程序入口文件: /apps/nsqlookupd/main.go
为了时NSQ 在windows 良好运行,NSQ 使用了 github.com/judwhite/go-svc/svc
包,用于构建一个可实现windows 服务。 可以用windows 的服务管理插件直接管理。
svc 包使用时,只需要实现 github.com/judwhite/go-svc/svc.Service
的接口即可。接口如下:
type Service interface {
// Init is called before the program/service is started and after it's
// determined if the program is running as a Windows Service.
Init(Environment) error
// Start is called after Init. This method must be non-blocking.
Start() error
// Stop is called in response to os.Interrupt, os.Kill, or when a
// Windows Service is stopped.
Stop() error
}
因此,nsqlookup 只需要实现上述三个方法即可:
Init 方法
此方法仅针对windows 的服务做了处理。若为windows 服务,则修改当前目录为可执行文件的目录。
Stop 方法
此方法做了nsqlookupd.Exit() 的处理。
此处用到了sync.Once. 即调用的退出程序仅执行一次。
Exit
的具体内容为:
func (l *NSQLookupd) Exit() {
if l.tcpListener != nil {
l.tcpListener.Close()
}
if l.httpListener != nil {
l.httpListener.Close()
}
l.waitGroup.Wait()
}
- 关闭 TCP Listener
- 关闭 Http Listener
- 等待所有goroutine的退出 (此处用到了sync.WaitGroup,用于等待goroutine 的退出)
Start 方法
参数的初始化
NSQ 命令行参数的构造,采用了golang 自带的flag 包。参数保存于Options对象中,采用了先初始化,后赋值的方式,减少了不必要的条件判断。
可以采用--config 的方式,直接添加配置文件。配置文件采用toml格式.
配置的解析,采用github.com/mreiferson/go-options
实现,优先级由高到低为:
- 命令行参数
- deprecated 的命令行参数名称
- 配置文件的值 (将命令行参数,连字符替换为下划线作为配置文件的key)
- 若参数实现了Getter,则使用Get() 方法
- 参数默认值
构造nsqlookupd
- 初始化一个RegistrationDB
- 建立 HttpListener 和 tcpListener (客户端请求)
- 启动服务,等待连接请求或者中断信号
RegistrationMap 的实现
// RegistrationDB 使用读写锁做读写控制。
type RegistrationDB struct {
sync.RWMutex
registrationMap map[Registration]ProducerMap
}
type Registration struct {
Category string // Category 有三种类型,Topic, Channel, Client.
Key string
SubKey string
}
type ProducerMap map[string]*Producer
type Producer struct {
peerInfo *PeerInfo //客户端的相关信息
tombstoned bool
tombstonedAt time.Time
}
type PeerInfo struct {
lastUpdate int64 // 上次更新的时间
id string // 使用ip标识的id
RemoteAddress string `json:"remote_address"`
Hostname string `json:"hostname"`
BroadcastAddress string `json:"broadcast_address"`
TCPPort int `json:"tcp_port"`
HTTPPort int `json:"http_port"`
Version string `json:"version"`
}
接口阅读
TcpListener
tcp 消息是 nsqd 与nsqlookupd 沟通的协议。 node 保存的是nsqd 的信息
Tcp Listener 是用来监听客户端发来的TCP 消息。
建立连接后,发送4个byte标识连接的版本号。目前是v1. "__V1" (下划线用空格替代)
消息之间按照换行符\n
分割。
目前客户端支持4类消息:
- PING
- 返回OK
- 若存在对端的信息,则更新client.peerInfo.lastUpdate <上次更新时间>
- IDENTIFY
- 用于消息的认证,将nsqd信息发送给nsqlookupd.
- 消息格式
IDENTIFY\nBODYLEN(32bit)BODY
|8bit |1 bit | 32bit | N bit |
|IDENTIFY| 换行 | body 长度 | body |
- BODY 为json格式
- 包含了如下字段:
- 广播地址
- TCP 端口
- HTTP 端口
- 版本号
- 服务器地址 (通过连接直接获取)
- REGISTER
- 将nsqd 中注册的topic 和channel 信息发送到nsqlookupd 上,做信息共享
- UNREGISTER
- 将nsqd 中注销的topic 和channel 信息发送到nsqlookupd 上,做信息共享
HTTPListener
** http 客户端的定位是用于服务的发现和admin的交互 **
- 在学习 http 请求时,可以先学习下
nsq/internal/http_api
包,此包是对golang 中http请求handler 的一次封装:
type Decorator func(APIHandler) APIHandler
type APIHandler func(http.ResponseWriter, *http.Request, httprouter.Params) (interface{}, error)
// f 是业务处理逻辑, ds 可以自定义多个包装器,用于对f 的输入和输出数据做处理。
func Decorate(f APIHandler, ds ...Decorator) httprouter.Handle {
decorated := f
for _, decorate := range ds {
decorated = decorate(decorated)
}
return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
decorated(w, req, ps)
}
}
// Decorator 的一个例子,做日志记录的处理
func Log(logf lg.AppLogFunc) Decorator {
return func(f APIHandler) APIHandler {
return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) {
start := time.Now()
response, err := f(w, req, ps)
elapsed := time.Since(start)
status := 200
if e, ok := err.(Err); ok {
status = e.Code
}
logf(lg.INFO, "%d %s %s (%s) %s",
status, req.Method, req.URL.RequestURI(), req.RemoteAddr, elapsed)
return response, err
}
}
}
这种处理方式类似于大部分web框架HTTP 中间件的处理方式,是利用递归嵌套的方式,保留了处理的上下文, 实现服务切片编程。
http 服务,使用
github.com/julienschmidt/httprouter
包实现http 的路由功能。目前HTTP 客户端支持以下的请求:
Method | Router | Param | Response |
---|---|---|---|
GET | /ping | - | "OK" |
GET | /info | - | 返回版本信息 |
GET | /debug | - | 返回 db 中所有信息 |
GET | /lookup | topic | 返回topic 关联的所有的channels 和 nsqd 服务的信息 |
GET | /topics | - | 返回所有topic 的值 |
GET | /channels | topic | 返回topic 下所有的channels 信息 |
GET | /nodes | - | 返回所有在线的nsqd 的node 信息, node 节点中包含了 topic 的信息,以及是否需要被删除 |
POST | /topic/create | topic | 创建topic <不超过64个字符长度> |
POST | /topic/delete | topic | 删除相应topic 的channel 和topic 信息 |
POST | /channel/create | topic, channel | 创建 channel , 若topic 不存在,创建topic |
POST | /channel/delete | topic, channel | 删除 channel, 支持 * |
POST | /topic/tombstone | topic, node | 将topic 下某个node 设置删除标识 tombstone, 给node 节点 一段空余时间用于删除相关topic 信息,并发送删除topic的命令 |
GET | /debug/pprof | - | pprof 提供的信息 |
GET | /debug/pprof/cmdline | - | pprof 提供的信息 |
GET | /debug/pprof/symbol | - | pprof 提供的信息 |
POST | /debug/pprof/symbol | - | pprof 提供的信息 |
GET | /debug/pprof/profile | - | pprof 提供的信息 |
GET | /debug/pprof/heap | - | pprof 提供的信息 |
GET | /debug/pprof/goroutine | - | pprof 提供的信息 |
GET | /debug/pprof/block | - | pprof 提供的信息 |
GET | /debug/pprof/threadcreate | - | pprof 提供的信息 |
学习总结
- sync.Once, sync.RWMutex 读写锁的使用
- http 包装函数的简单实现 nsq/internal/http_api.Decorate
github.com/judwhite/go-svc/svc
的使用github.com/julienschmidt/httprouter
的使用github.com/mreiferson/go-options
的使用
消息队列 NSQ 源码学习笔记 (一)的更多相关文章
- RocketMQ 源码学习笔记————Producer 是怎么将消息发送至 Broker 的?
目录 RocketMQ 源码学习笔记----Producer 是怎么将消息发送至 Broker 的? 前言 项目结构 rocketmq-client 模块 DefaultMQProducerTest ...
- RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的?
目录 RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 前言 项目结构 rocketmq-client 模块 DefaultMQProducerTest Roc ...
- JUC源码学习笔记2——AQS共享和Semaphore,CountDownLatch
本文主要讲述AQS的共享模式,共享和独占具有类似的套路,所以如果你不清楚AQS的独占的话,可以看我的<JUC源码学习笔记1> 主要参考内容有<Java并发编程的艺术>,< ...
- Underscore.js 源码学习笔记(下)
上接 Underscore.js 源码学习笔记(上) === 756 行开始 函数部分. var executeBound = function(sourceFunc, boundFunc, cont ...
- Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构
Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构 之前我们简要的看过了DataNode的main函数以及整个类的大至,现在结合前面我们研究的线程和RPC,则可以进一步 ...
- Hadoop源码学习笔记(4) ——Socket到RPC调用
Hadoop源码学习笔记(4) ——Socket到RPC调用 Hadoop是一个分布式程序,分布在多台机器上运行,事必会涉及到网络编程.那这里如何让网络编程变得简单.透明的呢? 网络编程中,首先我们要 ...
- Qt Creator 源码学习笔记04,多插件实现原理分析
阅读本文大概需要 8 分钟 插件听上去很高大上,实际上就是一个个动态库,动态库在不同平台下后缀名不一样,比如在 Windows下以.dll结尾,Linux 下以.so结尾 开发插件其实就是开发一个动态 ...
- JUC源码学习笔记4——原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法
JUC源码学习笔记4--原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法 volatile的原理和内存屏障参考<Java并发编程的艺术> 原子类源码基于JDK8 ...
- JUC源码学习笔记5——线程池,FutureTask,Executor框架源码解析
JUC源码学习笔记5--线程池,FutureTask,Executor框架源码解析 源码基于JDK8 参考了美团技术博客 https://tech.meituan.com/2020/04/02/jav ...
随机推荐
- 广告行业中那些趣事系列6:BERT线上化ALBERT优化原理及项目实践(附github)
摘要:BERT因为效果好和适用范围广两大优点,所以在NLP领域具有里程碑意义.实际项目中主要使用BERT来做文本分类任务,其实就是给文本打标签.因为原生态BERT预训练模型动辄几百兆甚至上千兆的大小, ...
- Lambda 语法
1.java8 Lambda表达式语法简介 (此处需要使用jdk1.8或其以上版本) Lambd表达式分为左右两侧 * 左侧:Lambda 表达式的参数列表 * 右侧:Lambda 表达式中所需要执行 ...
- form表单提交没有跨域问题,但ajax提交存在跨域问题
浏览器的策略本质是:一个域名下面的JS,没有经过允许是不能读取另外一个域名的内容,但是浏览器不阻止你向另外一个域名发送请求. 所以form表单提交没有跨域问题,提交form表单到另外一个域名,原来页面 ...
- JavaScript实现树结构(二)
JavaScript实现树结构(二) 一.二叉搜索树的封装 二叉树搜索树的基本属性: 如图所示:二叉搜索树有四个最基本的属性:指向节点的根(root),节点中的键(key).左指针(right).右指 ...
- iOS8 定位失败问题
iOS7升级到iOS8后,百度地图 iOS SDK 中的定位功能不可用,给广大开发者带来了不便,在此向大家分享一个方法来解决次问题.(官方的适配工作还在进行中,不久将会和广大开发者见面) 1.在inf ...
- Simulink仿真入门到精通(一) Simulink界面介绍
Simulink提供了一个动态系统建模.仿真和综合分析的集成环境,是MATLAB最重要的组件之一. 以模块为功能单位,通过信号线进行连接 通过GUI调配每个模块的参数 仿真结果以数值和图像等形象化方式 ...
- js 拖拽实现面向对象
1.js 支持面向对象编程,但只是基于面向对象,不使用类或者接口.2.演变 工厂模式------->构造函数模式---------->原型模式 工厂模式的缺点: ①函数内部new ,不太符 ...
- vue — 创建vue项目
创建vue项目 在程序开发中,有三种方式创建vue项目,本地引入vuejs.使用cdn引入vuejs.使用vue-cli创建vue项目.其中vue-cli可以结合webpack打包工具使用,大大方便了 ...
- git命令,github网站以及sourceTree用法详解
1.git下载安装 这里只是windows安装方法: 进入这个网址:http://msysgit.github.com/,点击下载,就会下载下来一个exe文件,双击打开,安装即可 完成安装之后,就可以 ...
- JavaScript每日学习日记(1)
8.11.2019 1. lastIndexOf() 方法从尾到头进行检索. 2. 有三种提取部分字符串的方法: 2.1 slice(start, end) 如果某个参数为负,则从字符串的结尾开始计 ...