nsqlookup服务为nsqd的服务发现,分析和借鉴下服务发现的源码:
//首先看看每个节点的地址信息,包括域名、地址、端口等等
type PeerInfo struct {
lastUpdate int64 //ping值的时候update这个时间戳
id string //节点的唯一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"`
}
//节点之上再封装的一层结构,方便管理节点是否过期
type Producer struct {
peerInfo *PeerInfo
tombstoned bool
tombstonedAt time.Time
}
再来看看nsqlookup是怎么去存储管理这些Producer:
type RegistrationDB struct {
sync.RWMutex
registrationMap map[Registration]ProducerMap //topic或者channel的具体分类->ProducerMap
}
type Registration struct {
Category string //服务分类topic还是channel
Key string //topic的名字
SubKey string //channel的名字或者""
}
//其中key和subKey查找的时候支持通配符"*"的方式查找
type Registrations []Registration
type ProducerMap map[string]*Producer //节点Id->具体的peer的封装结构Producer
分别看看怎么新增Registration和Producer:
//新增registration和producer都很简单
func (r *RegistrationDB) AddRegistration(k Registration) {
r.Lock()
defer r.Unlock()
_, ok := r.registrationMap[k]
if !ok {
//增加一个Registeration直接赋值一个map
r.registrationMap[k] = make(map[string]*Producer)
}
}
// add a producer to a registration
func (r *RegistrationDB) AddProducer(k Registration, p *Producer) bool {
r.Lock()
defer r.Unlock()
_, ok := r.registrationMap[k]
if !ok {
r.registrationMap[k] = make(map[string]*Producer)
}
//把对应的producer存到registration下面的producerMap里面去
producers := r.registrationMap[k]
_, found := producers[p.peerInfo.id]
if found == false {
producers[p.peerInfo.id] = p
}
return !found
}
再看看Find相应的Registration和Producer的操作:
func (r *RegistrationDB) needFilter(key string, subkey string) bool {
//有通配符的时候需要过滤
return key == "*" || subkey == "*"
}
func (k Registration) IsMatch(category string, key string, subkey string) bool {
//分别比较category,key,subkey的值来判断
if category != k.Category {
return false
}
if key != "*" && k.Key != key {
return false
}
if subkey != "*" && k.SubKey != subkey {
return false
}
return true
}
func (r *RegistrationDB) FindRegistrations(category string, key string, subkey string) Registrations {
r.RLock()
defer r.RUnlock()
if !r.needFilter(key, subkey) {
//不需要过滤,就不需要k的IsMath操作,直接查找
k := Registration{category, key, subkey}
if _, ok := r.registrationMap[k]; ok {
return Registrations{k}
}
return Registrations{}
}
results := Registrations{}
for k := range r.registrationMap {
//需要匹配通配符的方式查找
if !k.IsMatch(category, key, subkey) {
continue
}
results = append(results, k)
}
return results
}
func (r *RegistrationDB) FindProducers(category string, key string, subkey string) Producers {
r.RLock()
defer r.RUnlock()
//找ProducerMap,然后转换为Slice方式返回
if !r.needFilter(key, subkey) {
k := Registration{category, key, subkey}
return ProducerMap2Slice(r.registrationMap[k])
}
results := make(map[string]*Producer)
for k, producers := range r.registrationMap {
if !k.IsMatch(category, key, subkey) {
continue
}
for _, producer := range producers {
_, found := results[producer.peerInfo.id]
if found == false {
results[producer.peerInfo.id] = producer
}
}
}
return ProducerMap2Slice(results)
}
最后再看看外层如何调用这个RegistrationDB:
func (p *LookupProtocolV1) REGISTER(client *ClientV1, reader *bufio.Reader, params []string) ([]byte, error) {
//注册前需要鉴权
if client.peerInfo == nil {
return nil, protocol.NewFatalClientErr(nil, "E_INVALID", "client must IDENTIFY")
}
//找到topic和channel的name
topic, channel, err := getTopicChan("REGISTER", params)
if err != nil {
return nil, err
}
//如果参数channel不为"",则说明这个producer是一个channel类型,否则是一个topic类型
if channel != "" {
key := Registration{"channel", topic, channel}
if p.ctx.nsqlookupd.DB.AddProducer(key, &Producer{peerInfo: client.peerInfo}) {
p.ctx.nsqlookupd.logf(LOG_INFO, "DB: client(%s) REGISTER category:%s key:%s subkey:%s",
client, "channel", topic, channel)
}
}
//注册完channel后还需要注册topic对应的producer
key := Registration{"topic", topic, ""}
if p.ctx.nsqlookupd.DB.AddProducer(key, &Producer{peerInfo: client.peerInfo}) {
p.ctx.nsqlookupd.logf(LOG_INFO, "DB: client(%s) REGISTER category:%s key:%s subkey:%s",
client, "topic", topic, "")
}
return []byte("OK"), nil
}
//服务取消注册
func (p *LookupProtocolV1) UNREGISTER(client *ClientV1, reader *bufio.Reader, params []string) ([]byte, error) {
if client.peerInfo == nil {
return nil, protocol.NewFatalClientErr(nil, "E_INVALID", "client must IDENTIFY")
}
topic, channel, err := getTopicChan("UNREGISTER", params)
if err != nil {
return nil, err
}
if channel != "" {
只删除channel对应的producer
key := Registration{"channel", topic, channel}
removed, left := p.ctx.nsqlookupd.DB.RemoveProducer(key, client.peerInfo.id)
if removed {
p.ctx.nsqlookupd.logf(LOG_INFO, "DB: client(%s) UNREGISTER category:%s key:%s subkey:%s",
client, "channel", topic, channel)
}
// for ephemeral channels, remove the channel as well if it has no producers
if left == 0 && strings.HasSuffix(channel, "#ephemeral") {
p.ctx.nsqlookupd.DB.RemoveRegistration(key)
}
} else {
//需要删除topic所有对应的channel
registrations := p.ctx.nsqlookupd.DB.FindRegistrations("channel", topic, "*")
for _, r := range registrations {
if removed, _ := p.ctx.nsqlookupd.DB.RemoveProducer(r, client.peerInfo.id); removed {
p.ctx.nsqlookupd.logf(LOG_WARN, "client(%s) unexpected UNREGISTER category:%s key:%s subkey:%s",
client, "channel", topic, r.SubKey)
}
}
//删除topic下的producer
key := Registration{"topic", topic, ""}
if removed, _ := p.ctx.nsqlookupd.DB.RemoveProducer(key, client.peerInfo.id); removed {
p.ctx.nsqlookupd.logf(LOG_INFO, "DB: client(%s) UNREGISTER category:%s key:%s subkey:%s",
client, "topic", topic, "")
}
}
return []byte("OK"), nil
}
总结:服务发现通过topic和channel下存放不同的nsqd的peer节点信息,方便找出某类topic和channel下面有哪些peer节点