golang微服务之注册与发现(zookeeper,etcd,consul)

golang微服务之注册与发现

在微服务中,服务注册与发现是必不可少的一环,其中etcd,zookeeper,consul在golang中较为常用。

程序对于这种中间件的依赖,都建议加一层接口,基于接口去实现

下面会分层三大板块去说明

接口定义

因为是上层调用是基于接口调用的,所以需要将discover和register接口化。 在discover发现endpoint有变化时,需要调用callback去更新本地的缓存,所以也需要endpoint的接口。如下

const (
    EtcdBackend      = "etcd"
    ZookeeperBackend = "zookeeper"
    ConsulBackend    = "consul"
)

// 服务发现接口

type Discover interface {
   
    // Start watch with block, 需要一个callback去更新本地endpoint
    Start(callback EndpointCacher)
    Stop()
}

// 服务注册接口

type Register interface {
   
    Start() error
    Stop() error
}

// endpoint接口
type EndpointCacher interface {
   
    AddOrUpdate(endpoint string, attribute []byte)
    Delete(endpoint string)
    AddError(err error)
    Error(err error)
}

接口定义完了,需要有连接注册中心的信息配置。
// 服务发现端配置

type DiscoverConfig struct {
   
	BackendType      string   // one of etcd|consul|zookeeper
	BackendEndPoints []string // register backend endpoint
	DiscoverPrefix   string
	ServiceName      string
	HostName         string
}

// 注册端配置

type RegisterConfig struct {
   
	BackendType         string   // one of etcd|consul|zookeeper
	BackendEndPoints    []string // register backend endpoint
	DiscoverPrefix      string
	ServiceName         string
	HeartBeatPeriod     int64
	ServiceEndPoint     string // register service endpoint to backend
	Attr                string // custom attribute. like: {"hostname": "xxx", "weight": 1}
	HealthCheckEndPoint string
}

有了配置之后,需要有注册和服务发现实例的创建方法

// NewDiscover 创建一个服务发现实例
func NewDiscover(cfg *DiscoverConfig) (Discover, error) {
   
    switch cfg.BackendType {
   
    case EtcdBackend:
        return newEtcdDiscover(cfg)
    case ConsulBackend:
        return newConsulDiscover(cfg)
    case ZookeeperBackend:
        return newZookeeperDiscover(cfg)
    }
    return nil, fmt.Errorf("unknown backend: %s, use etcd|consul|zookeeper", cfg.BackendType)
}



// NewRegister 创建一个注册实例
func NewRegister(cfg *RegisterConfig) (Register, error) {
   
    switch cfg.BackendType {
   
    case EtcdBackend:
        return newEtcdRegister(cfg)
    case ConsulBackend:
        return newConsulRegister(cfg)
    case ZookeeperBackend:
        return newZookeeperRegister(cfg)
    }
    return nil, fmt.Errorf("unknown backend: %s, use etcd|consul|zookeeper", cfg.BackendType)
}

这里实现了简易版的endpoint,可供参考,主要是将endpoint存在map中,然后有变化时做变更

// LiteEndpoint EndpointCacher lite impl
type LiteEndpoint struct {
   
    Endpoints map[string][]byte `json:"value"`
    lock      sync.Mutex
    Err       error
}

func NewLiteEndpoint() *LiteEndpoint {
   
    return &LiteEndpoint{
   
        Endpoints: map[string][]byte{
   },
        lock:      sync.Mutex{
   },
    }
}

func (e *LiteEndpoint) AddOrUpdate(endpoint string, attribute []byte) {
   
    e.lock.Lock()
    defer e.lock.Unlock()
    e.Endpoints[endpoint] = attribute
}

func (e *LiteEndpoint) Delete(endpoint string) {
   
    e.lock.Lock()
    defer e.lock.Unlock()
    delete(e.Endpoints, endpoint)
}

func (e *LiteEndpoint) Error(err error) {
   
    e.Err = err
}

func (e *LiteEndpoint) List() []string {
   
    var endpointSlice []string
    for k, _ := range e.Endpoints {
   
        endpointSlice = append(endpointSlice, k)
    }
    return endpointSlice
}

func (e *LiteEndpoint) Attr(endpoint string) []byte {
   
    return e.Endpoints[endpoint]
}

接口的具体实现

etcd

注册服务
服务注册到etcd后,利用etcd租期的特性,每次续租几秒,在续期过期前完成续租。当实例异常时无法续租,则会在etcd端该实例会被过期删除,达到下线异常节点的效果。

func newEtcdRegister(cfg *RegisterConfig) (*etcdRegister, error) {
   
    var err error
    etcdClient, err := clientv3.New(clientv3.Config{
   Endpoints: cfg.BackendEndPoints})
    if err != nil {
   
        return nil, err
    }

    r := &etcdRegister{
   
        etcdEndpoints:  cfg.BackendEndPoints,
        discoverPrefix: cfg.DiscoverPrefix,
        serviceName:    cfg.ServiceName,
        endpoint:       cfg.ServiceEndPoint,
        attr:           cfg.Attr,
        ttl:            cfg.HeartBeatPeriod,
        stopCh:         make(chan struct{
   }),
        etcdClient:     etcdClient,
    }
    return r, nil
}

// Start 开启一个协程
func (r *etcdRegister) Start() error {
   

    go r.keepAlive()
    return nil
}

func (r *etcdRegister) Stop() error {
   
    close(r.stopCh)

    ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
    defer cancel()
    _, err := r.etcdClient.Delete(ctx, r.key())
    //if r.grpcResolver != nil {
   
    //    return r.grpcResolver.Update(ctx, r.key(), grpcnaming.Update{Op: grpcnaming.Delete, Addr: r.endpoint})
    //}
    return err
}

// 定时续租
func (r *etcdRegister) keepAlive() {
   
    duration := time.Duration(r.ttl) * time.Second
    timer := time.NewTimer(duration)
    for {
   
        select {
   
        case <-r.stopCh:
            return
        case <-timer.C:
            if r.leaseID > 0 {
   
                if err := r.leaseRenewal(); err != nil {
   
                    logrus.Warnf("%s leaseid[%x] keepAlive err: %s, try to reset...", r.endpoint, r.leaseID, err.Error())
                    r.leaseID = 0
                }
            } else {
   
                if err := r.register(); err != nil {
   
                    logrus.Warnf("register endpoint %s error: %s", r.endpoint, err.Error())
                
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值