etcd学习和实战:3、go使用etcd实现服务发现和管理

2023-11-16

etcd学习和实战:3、go使用etcd实现服务发现和管理


1. 前言

根据我们上一节学习的gRPC服务和etcd结合的内容,我们已经了解到v3版本下gRPC服务和etcd结合的方法,主要就是用etcd客户端提供的gRPC解析器来创建要管理的服务端点的前缀用于监听是否存在新注册的服务端点,之后通过租约方式确认服务端点是否正常运行,长时间未续约则可能删除服务节点(真像租房啊,租客找房东租房并根据租约定期交房租,到期后不续约就删掉你这个租客.,etcd就是中介,这个中介不收钱提供免费平台接口给你用而且性能还挺好可太良心了)。

根据原理的话服务发现功能的实现就分为两大部分:1、使用etcd提供的客户端进行的服务监听和管理;2、服务端点启动时调用etcd接口注册服务并签订租约,此后定期续约即可。

2. 代码及编译运行问题总结

自:https://bingjian-zhu.github.io/2020/05/14/etcd实现服务发现/

2.1 服务注册

register.go:

  • 设置endpoints(端点)并创建etcd客户端
  • 设置租约注册服务并绑定
  • 持续监听租约
package main

import (
    "context"
    "log"
    "time"

    "go.etcd.io/etcd/clientv3"
)

//ServiceRegister 创建租约注册服务
type ServiceRegister struct {
    cli     *clientv3.Client //etcd client
    leaseID clientv3.LeaseID //租约ID
    //租约keepalieve相应chan
    keepAliveChan <-chan *clientv3.LeaseKeepAliveResponse
    key           string //key
    val           string //value
}

//NewServiceRegister 新建注册服务
func NewServiceRegister(endpoints []string, key, val string, lease int64) (*ServiceRegister, error) {
    cli, err := clientv3.New(clientv3.Config{
        Endpoints:   endpoints,
        DialTimeout: 5 * time.Second,
    })
    if err != nil {
        log.Fatal(err)
    }

    ser := &ServiceRegister{
        cli: cli,
        key: key,
        val: val,
    }

    //申请租约设置时间keepalive
    if err := ser.putKeyWithLease(lease); err != nil {
        return nil, err
    }

    return ser, nil
}

//设置租约
func (s *ServiceRegister) putKeyWithLease(lease int64) error {
    //设置租约时间
    resp, err := s.cli.Grant(context.Background(), lease)
    if err != nil {
        return err
    }
    //注册服务并绑定租约
    _, err = s.cli.Put(context.Background(), s.key, s.val, clientv3.WithLease(resp.ID))
    if err != nil {
        return err
    }
    //设置续租 定期发送需求请求
    leaseRespChan, err := s.cli.KeepAlive(context.Background(), resp.ID)

    if err != nil {
        return err
    }
    s.leaseID = resp.ID
    log.Println(s.leaseID)
    s.keepAliveChan = leaseRespChan
    log.Printf("Put key:%s  val:%s  success!", s.key, s.val)
    return nil
}

//ListenLeaseRespChan 监听 续租情况
func (s *ServiceRegister) ListenLeaseRespChan() {
    for leaseKeepResp := range s.keepAliveChan {
        log.Println("续约成功", leaseKeepResp)
    }
    log.Println("关闭续租")
}

// Close 注销服务
func (s *ServiceRegister) Close() error {
    //撤销租约
    if _, err := s.cli.Revoke(context.Background(), s.leaseID); err != nil {
        return err
    }
    log.Println("撤销租约")
    return s.cli.Close()
}

func main() {
    var endpoints = []string{"localhost:2379"}
    ser, err := NewServiceRegister(endpoints, "/web/node1", "localhost:8000", 5)
    if err != nil {
        log.Fatalln(err)
    }
    //监听续租相应chan
    go ser.ListenLeaseRespChan()
    select {
    // case <-time.After(20 * time.Second):
    // ser.Close()
    }
}

2.2 服务发现

  • 设置endpoints创建etcd客户端
  • 初始化配置并监听服务前缀(实际上也可以直接监听key,但不够灵活,监听前缀值更好一些)
  • 根据监听到的对key的操作类型进行进一步处理

discovery.go:

package main

import (
    "context"
    "log"
    "sync"
    "time"

    "github.com/coreos/etcd/mvcc/mvccpb"
    "go.etcd.io/etcd/clientv3"
)

//ServiceDiscovery 服务发现
type ServiceDiscovery struct {
    cli        *clientv3.Client  //etcd client
    serverList map[string]string //服务列表
    lock       sync.Mutex
}

//NewServiceDiscovery  新建发现服务
func NewServiceDiscovery(endpoints []string) *ServiceDiscovery {
    cli, err := clientv3.New(clientv3.Config{
        Endpoints:   endpoints,
        DialTimeout: 5 * time.Second,
    })
    if err != nil {
        log.Fatal(err)
    }

    return &ServiceDiscovery{
        cli:        cli,
        serverList: make(map[string]string),
    }
}

//WatchService 初始化服务列表和监视
func (s *ServiceDiscovery) WatchService(prefix string) error {
    //根据前缀获取现有的key
    resp, err := s.cli.Get(context.Background(), prefix, clientv3.WithPrefix())
    if err != nil {
        return err
    }

    for _, ev := range resp.Kvs {
        s.SetServiceList(string(ev.Key), string(ev.Value))
    }

    //监视前缀,修改变更的server
    go s.watcher(prefix)
    return nil
}

//watcher 监听前缀
func (s *ServiceDiscovery) watcher(prefix string) {
    rch := s.cli.Watch(context.Background(), prefix, clientv3.WithPrefix())
    log.Printf("watching prefix:%s now...", prefix)
    for wresp := range rch {
        for _, ev := range wresp.Events {
            switch ev.Type {
            case mvccpb.PUT: //修改或者新增
                s.SetServiceList(string(ev.Kv.Key), string(ev.Kv.Value))
            case mvccpb.DELETE: //删除
                s.DelServiceList(string(ev.Kv.Key))
            }
        }
    }
}

//SetServiceList 新增服务地址
func (s *ServiceDiscovery) SetServiceList(key, val string) {
    s.lock.Lock()
    defer s.lock.Unlock()
    s.serverList[key] = string(val)
    log.Println("put key :", key, "val:", val)
}

//DelServiceList 删除服务地址
func (s *ServiceDiscovery) DelServiceList(key string) {
    s.lock.Lock()
    defer s.lock.Unlock()
    delete(s.serverList, key)
    log.Println("del key:", key)
}

//GetServices 获取服务地址
func (s *ServiceDiscovery) GetServices() []string {
    s.lock.Lock()
    defer s.lock.Unlock()
    addrs := make([]string, 0)

    for _, v := range s.serverList {
        addrs = append(addrs, v)
    }
    return addrs
}

//Close 关闭服务
func (s *ServiceDiscovery) Close() error {
    return s.cli.Close()
}

func main() {
    var endpoints = []string{"localhost:2379"}
    ser := NewServiceDiscovery(endpoints)
    defer ser.Close()
    ser.WatchService("/web/")
    ser.WatchService("/gRPC/")
    for {
        select {
        case <-time.Tick(10 * time.Second):
            log.Println(ser.GetServices())
        }
    }
}

2.3 问题

我们在编译运行过程中可能会出现如下问题:

go mod ini
go mod tidy
...
go: gitlab/zhengyao/etcd_grpc imports
    go.etcd.io/etcd/clientv3 tested by
    go.etcd.io/etcd/clientv3.test imports
    github.com/coreos/etcd/auth imports
    github.com/coreos/etcd/mvcc/backend imports
    github.com/coreos/bbolt: github.com/coreos/bbolt@v1.3.6: parsing go.mod:
    module declares its path as: go.etcd.io/bbolt
        but was required as: github.com/coreos/bbolt

etcd中使用的bbolt和grpc版本冲突(包的相互依赖以及各个包不同版本的兼容性问题和库的相互依赖以及版本兼容问题类似)

解决方法:

参考自:https://blog.csdn.net/skh2015java/article/details/111060465

删除原来已生成得go.mod和go.sum

go mod init
 
go mod edit -replace github.com/coreos/bbolt@v1.3.4=go.etcd.io/bbolt@v1.3.4
 
go mod edit -replace google.golang.org/grpc@v1.38.0=google.golang.org/grpc@v1.26.0
 
go mod tidy

注意:上面改前的grpc的版本取决于你的etcd的版本,也就是报的错误中提到的grpc版本。

2.4 运行结果

#运行服务发现
$go run discovery.go
watching prefix:/web/ now...
watching prefix:/gRPC/ now...
...
put key : /web/node1 val:localhost:8000
[localhost:8000]
...

#另一个终端运行服务注册
$go run register.go
Put key:/web/node1 val:localhost:8000 success!
续约成功 cluster_id:14841639068965178418 member_id:10276657743932975437 revision:29 raft_term:7 
续约成功 cluster_id:14841639068965178418 member_id:10276657743932975437 revision:29 raft_term:7 
...
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

etcd学习和实战:3、go使用etcd实现服务发现和管理 的相关文章

随机推荐

  • day32 贪心

    122 买卖股票的最佳时机II 每天都有着卖出和不卖出两种状态 dp解决 55 跳跃游戏 贪心找到最大的覆盖范围 每次都看覆盖范围是否超过了总范围 超过即可 45 跳跃游戏II 贪心找到最大的覆盖范围 和 最小步数 package algo
  • 关于error: possibly undefined macro: AC_PROG_LIBTOOL问题解决

    通过baidu或者google 大多数解决方案都是通过安装libtool 不同的平台有不同的安装方式 比如ubuntu安装 sudo apt get install libtool 大多数情况下都能解决问题 但是通过安装libtool也不能
  • 关于list理论最大大小Integer.MAX_VALUE - 8

    今天看源码对list最大大小为什么是Integer MAX VALUE 8产生了兴趣 目前得出以下结论 首先数组头需要存储数组大小信息以及其它的一些信息 假设数组达到了最大则数组大小需要8个字节来存储 而当留下8个数组大小时则可保证至少有8
  • CMAKE之add_dependencies使用

    CMAKE之add dependencies使用 问题引入 问题解决 总结 问题引入 在项目中通常会遇见这样的情况 例如一个项目中有 main libhello a libworld a 当项目过小的时候 编译顺序是 a 然后是main 但
  • 也谈免拆机破解中兴B860av1.1(解决不能安装软件/解决遥控)

    20170221更新 部分用户 自己恢复出厂测试过 操作后仍然无法直接在当贝市场安装应用了 在第8条 最后两步 先改为中国通用市场 后面再改为未知局方 如果开机想优先启动当贝桌面 就把导出的0 ini里AutoStartIptv 1改为0
  • Ubuntu修改环境变量后命令不可用的解决办法

    方法一 导入临时变量 但是重启虚拟后会失效 export PATH PATH usr local sbin usr local bin sbin bin usr sbin usr bin 方法二 根本方法 普通用户 root用户都适用 解决
  • word打印

    package com stonewomb business outerContract contractApply utils import java io IOException import java text SimpleDateF
  • 【LeetCode】9月 每日一题

    LeetCode 9月 每日一题 9 1 题目 1475 商品折扣后的最终价格 思路 模拟即可 代码 function finalPrices prices number number let len number prices lengt
  • 设计模式(一)

    1 适配器模式 1 概述 适配器中有一个适配器包装类Adapter 其包装的对象为适配者Adaptee 适配器作用就是将客户端请求转化为调用适配者中的接口 当调用适配器中的方法时 适配器内部会调用适配者类的方法 这个调用对客户端是透明 实现
  • Python爬虫技术及其原理探

    导言 随着互联网的发展 大量的数据被存储在网络上 而我们需要从中获取有用的信息 Python作为一种功能强大且易于学习的编程语言 被广泛用于网络爬虫的开发 本文将详细介绍Python爬虫所需的技术及其原理 并提供相关的代码案例 1 HTTP
  • Tomcat日志配置远程rsyslog采集

    Tomcat日志数据的采集有很多种方式 使用tail是最简单的方法 但必须保证catalina out日志中的每行都是以日期格式开头的 除了tail方法外 还可以通过对rsyslog配置实现 本博客主要通过配置rsyslog进行Tomcat
  • 经验分享:解决 错误0x80071AC3:无法完成操作,请运行chkdsk并重试

    在Windows系统电脑下 使用移动硬盘或者U盘复制拷贝文件的时候 如果遇到一个这样的提示 一个意外错误使您无法移动该文件夹 如果您继续收到此错误 可以使用错误代码来搜索有关此问题的帮助 错误0x80071AC3 无法完成操作 因为卷有问题
  • Unity shader系列:内置Shader代码查看

    官网下载 https unity3d com cn get unity download archive 对自己使用的unity版本进行下图操作
  • centos7 将home的空间扩容到根目录

    Centos7把home目录下多余的空间转移到 根目录下 通过df h发现 根目录只有35G 而home目录可用的 居然有19G 我现在想分出8G给根目录 把你需要挂载的机器的逻辑卷记住 上面的图 左边是逻辑卷 右边是虚拟磁盘 dev ma
  • 图像处理-State of the Art

    https github com BlinkDL BlinkDL github io 目前常见图像任务的 State of the Art 方法 从 Super resolution 到 Captioning CV 二维图像任务 Image
  • 【Java】【NIO】【04】通过SocketChannel读写Socket

    package easing common java demo import lombok SneakyThrows import java net InetSocketAddress import java nio ByteBuffer
  • 全局变量 multiple definition of 问题解决方法

    解决方法 1 给每一个头文件加上条件编译 注 此方法不是解决上述问题的方法 只是解决multiple definition of的一个方法 当多个文件包含同一个头文件时 而头文件中没有加上条件编译 就会独立的解释 然后生成每个文件生成独立的
  • 江西武功山旅游攻略(周末两日游)

    一 往返路线 1 出发路线 周五晚上上海出发坐火车 到江西萍乡 11 5小时 卧铺550左右 打车到江西武功山景区 120 150元左右 人均30元 1小时10分左右到达 或者 到达萍乡北之后 出站后步行200米到长途汽车站 乘旅游巴士直达
  • RestHighLevelClient初始化http参数的含义

    high level rest client 初始化 一般初始化时需要设置验证信息 http相关参数 Bean public RestHighLevelClient createClient return new RestHighLevel
  • etcd学习和实战:3、go使用etcd实现服务发现和管理

    etcd学习和实战 3 go使用etcd实现服务发现和管理 文章目录 etcd学习和实战 3 go使用etcd实现服务发现和管理 1 前言 2 代码及编译运行问题总结 2 1 服务注册 2 2 服务发现 2 3 问题 2 4 运行结果 1