etcd服务注册一

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 := &registry.Service{
		Name: "comment_service",
	}

	service.Nodes = append(service.Nodes,
		&registry.Node{
			IP:   "127.0.0.1",
			Port: 8801,
		},
		&registry.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 := &registry.Service{
        Name: "comment_service",
    }

    service.Nodes = append(service.Nodes,
        &registry.Node{
            IP:   "127.0.0.1",
            Port: 8801,
        },
        &registry.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 := &registry.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
}
发布了166 篇原创文章 · 获赞 26 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/qq_28710983/article/details/92851473