EtcdRegistry初始化init函数调用pluginMgr的RegisterPlugin函数
func init() {
registry.RegisterPlugin(etcdRegistry)
go etcdRegistry.run()
}
经过一次包装后实际是调用plugin模块的registerPlugin函数
func RegisterPlugin(registry Registry) (err error) {
return pluginMgr.registerPlugin(registry)
}
func (p *PluginMgr) registerPlugin(plugin Registry) (err error) {
p.lock.Lock()
defer p.lock.Unlock()
_, ok := p.plugins[plugin.Name()]
if ok {
err = fmt.Errorf("duplicate registry plugin")
return
}
p.plugins[plugin.Name()] = plugin
return
}
Plugin结构体
type PluginMgr struct {
plugins map[string]Registry
lock sync.Mutex
}
plugin模块的registerPlugin函数会在结构体的map中注册好服务pluginMgr.plugins["etcd"] = etcdRegistry
其实就是吧EtcdRegistry模块实现注册到pluginMgr管理模块中
程序从InitRegistry进入,
registryInst, err := registry.InitRegistry(context.TODO(), "etcd",
registry.WithAddrs([]string{"127.0.0.1:2379"}),
registry.WithTimeout(time.Second),
registry.WithRegistryPath("/ibinarytree/koala/"),
registry.WithHeartBeat(5), )
name就是服务的名称,opts是配置EtcdRegistry的options参数,
type EtcdRegistry struct {
options *registry.Options
client *clientv3.Client
serviceCh chan *registry.Service
//atomic.Value 可以存储任何值,并且是原子操作的 ,线程安全的
value atomic.Value
lock sync.Mutex
registerServiceMap map[string]*RegisterService
}
registry.InitRegistry中的opts实际就是把里面的参数配置到etcdRegistry对象中去
EtcdRegistry模块中的atomic.Value参数,是用来做院子操作,用来把所有etcd服务注册模块都缓存到allServiceInfo中
func init() {
allServiceInfo := &AllServiceInfo{
serviceMap: make(map[string]*registry.Service, MaxServiceNum),
}
etcdRegistry.value.Store(allServiceInfo)
registry.RegisterPlugin(etcdRegistry)
go etcdRegistry.run()
}
所有初始化完成后,会运行etcdRegistry.run函数,run函数是启动一个协程运行的,循环从etcdRegistry的服务注册管道(serviceCh)中读取服务,然后进行注册,
//Register函数把服务放到serviceCh中, run函数把服务从管道中取出,存到registerServiceMap中去
//如果serviceCh中没有服务了, 则进行注册和续约流程er.registerOrKeepAlive()
func (er *EtcdRegistry) run() {
ticker := time.NewTicker(MaxSyncServiceInterval)
for {
select {
case service := <-er.serviceCh:
//服务在map里面是否存在
registerService, ok := er.registerServiceMap[service.Name]
if ok {
//服务已经在map中
for _, node := range service.Nodes{
registerService.service.Nodes = append(registerService.service.Nodes, node)
}
registerService.registered = false
break
}
registerService = &RegisterService{
service: service,
}
er.registerServiceMap[service.Name] = registerService
case <-ticker.C:
//同步AllServiceInfo
er.syncServiceFromEtcd()
default: //如果channel里面没有服务,则进行续约操作
er.registerOrKeepAlive()
//如果serviceCh里面没有数据, registerServiceMap里面也没有数据,则会进入死循环
//这里sleep 500毫秒
time.Sleep(500 * time.Millisecond)
}
}
}
那什么时候把service放到serviceCh中呢?
实际是在调用EtcdRegistry模块的Register函数的只会把 服务放到服务注册管道中,然后run协程会在后台从管道中读取服务,然后进行注册
service := ®istry.Service{
Name: "comment_service",
}
service.Nodes = append(service.Nodes,
®istry.Node{
IP: "127.0.0.1",
Port: 8801,
},
®istry.Node{
IP: "127.0.0.2",
Port: 8801,
},
)
registryInst.Register(context.TODO(), service)
func (er *EtcdRegistry) Register(ctx context.Context, service *registry.Service) (err error) {
select {
case er.serviceCh <- service:
default:
err = fmt.Errorf("register chan is full")
return
}
return
}
run函数中会有3个分支
select {
case service := <-er.serviceCh:
//服务在map里面是否存在
registerService, ok := er.registerServiceMap[service.Name]
if ok {
//服务已经在map中
for _, node := range service.Nodes{
registerService.service.Nodes = append(registerService.service.Nodes, node)
}
registerService.registered = false
break
}
registerService = &RegisterService{
service: service,
}
er.registerServiceMap[service.Name] = registerService
case <-ticker.C:
//同步AllServiceInfo
er.syncServiceFromEtcd()
default: //如果channel里面没有服务,则进行续约操作
er.registerOrKeepAlive()
//如果serviceCh里面没有数据, registerServiceMap里面也没有数据,则会进入死循环
//这里sleep 500毫秒
time.Sleep(500 * time.Millisecond)
}
case 1: 是从服务注册管道中读取通过Register函数放到管道中的服务,首先判断etcdRegistry的服务存放map中是否有这个服务,
如果已经有这个服务,就把节点信息追加到原来服务后面,然后把这个服务的状态设置为需要注册,如果服务没有在etcd注册模块的map中,就把服务Service封装成RegisterService保存到etcd注册模块的registerServiceMap中去
case 2: 是从etcd服务器中10秒钟一次同步节点信息到allServiceInfo中,当有客户端需要获取服务信息的时候,直接从缓存中获取 ,不需要 在请求etcd服务器
default分支: 是进行服务注册或续约的 ,
func (er *EtcdRegistry) registerOrKeepAlive() {
for _, registerService := range er.registerServiceMap {
//已经注册续约
if registerService.registered {
er.keepAlive(registerService)
continue
}
//未注册 则进入注册流程
er.registerService(registerService)
}
}
如果判断服务是要注册还是续约呢 ,根据etcd注册模块中的registered参数 ,如果是已经注册过的 ,就进行续约流程,如果是没有注册过的就进行注册流程
服务注册流程:申请一个租约, 把租约的ID配置到服务中去,遍历服务的节点node信息,这里为什么要把node节点拆开来插入,因为服务的配置node是一个node切片,需要把每一个节点都注册到etcd中去,所以需要遍历节点
service := ®istry.Service{
Name: "comment_service",
}service.Nodes = append(service.Nodes,
®istry.Node{
IP: "127.0.0.1",
Port: 8801,
},
®istry.Node{
IP: "127.0.0.2",
Port: 8801,
},
)
存放到etcd中的时候 是分为以下两条信息。说明comment_service有两个节点,
key = /ibinarytree/koala/comment_service/127.0.0.1:8801 , value = {"name":"comment_service","nodes":[{"id":"","ip":"127.0.0.1","port":8801,"weight":0}]}
key = /ibinarytree/koala/comment_service/127.0.0.2:8801 , value = {"name":"comment_service","nodes":[{"id":"","ip":"127.0.0.2","port":8801,"weight":0}]}
临时申请一个registry.Service变量用来存放一个node节点的服务,然后把服务转成json格式put到etcd中去,然后去续约,把续约管道复制给 registerService.KeepAliveCh中
func (er *EtcdRegistry) registerService(registerService *RegisterService) (err error) {
resp, err := er.client.Grant(context.TODO(), er.options.HeartBeat)
if err != nil {
return
}
registerService.id = resp.ID
for _, node := range registerService.service.Nodes {
//创建一个新的临时服务,这个服务里面只有一个节点
tmp := ®istry.Service{
Name: registerService.service.Name,
Nodes: []*registry.Node{
node,
},
}
data, err := json.Marshal(tmp)
if err != nil {
continue
}
key := er.serviceNodePath(tmp)
fmt.Printf("key = %s , value = %s\n", key, data)
_, err = er.client.Put(context.TODO(), key, string(data), clientv3.WithLease(resp.ID))
//注册失败
if err != nil {
continue
}
//注册成功,续约
ch, err := er.client.KeepAlive(context.TODO(), resp.ID)
if err != nil {
continue
}
registerService.keepAliveCh = ch
registerService.registered = true
}
return
}
注册成功的服务,在进行run函数循环的时候,会进入到续约的流程KeepAlive(),从服务的keepaliveCh中后去续约信息,检查是否续约成功,如果管道为nil,则把服务状态置为false 重新进行注册流程。下面的fmt循环是为了测试 注册的,正式环境中需要屏蔽的
func (er *EtcdRegistry) keepAlive(registerService *RegisterService) {
select {
case resp := <-registerService.keepAliveCh:
if resp == nil {
registerService.registered = false
return
}
//成功
for _, node := range registerService.service.Nodes {
fmt.Printf("service:%s, node:%s:%d, ttl:%v\n",
registerService.service.Name, node.IP,
node.Port, resp)
}
}
return
}