4. 参考 go 代码——服务注册与发现

简介: 4. 参考 go 代码——服务注册与发现

来源自etcd 笔记(09)— 基于 etcd 实现微服务的注册与发现_serviceregistry": { "type": "etcd", "endpoints":-CSDN博客

服务注册

go

复制代码

package main
import (
  "context"
  "fmt"
  "time"
  "github.com/coreos/etcd/clientv3"
)
// 服务注册对象
type ServiceRegister struct {
  client     *clientv3.Client
  kv         clientv3.KV
  lease      clientv3.Lease
  canclefunc func()
  key        string
  leaseResp     *clientv3.LeaseGrantResponse
  keepAliveChan <-chan *clientv3.LeaseKeepAliveResponse
}
// 初始化注册服务
func InitService(host []string, timeSeconds int64) (*ServiceRegister, error) {
  config := clientv3.Config{
    Endpoints:   host,
    DialTimeout: 5 * time.Second,
  }
  client, err := clientv3.New(config)
  if err != nil {
    fmt.Printf("create connection etcd failed %s\n", err)
    return nil, err
  }
  // 得到KV和Lease的API子集
  kv := clientv3.NewKV(client)
  lease := clientv3.NewLease(client)
  service := &ServiceRegister{
    client: client,
    kv:     kv,
    lease:  lease,
  }
  return service, nil
}
// 设置租约
func (s *ServiceRegister) setLease(timeSeconds int64) error {
  leaseResp, err := s.lease.Grant(context.TODO(), timeSeconds)
  if err != nil {
    fmt.Printf("create lease failed %s\n", err)
    return err
  }
  // 设置续租
  ctx, cancelFunc := context.WithCancel(context.TODO())
  leaseRespChan, err := s.lease.KeepAlive(ctx, leaseResp.ID)
  if err != nil {
    fmt.Printf("KeepAlive failed %s\n", err)
    return err
  }
  s.leaseResp = leaseResp
  s.canclefunc = cancelFunc
  s.keepAliveChan = leaseRespChan
  return nil
}
// 监听续租情况
func (s *ServiceRegister) ListenLeaseRespChan() {
  for {
    select {
    case leaseKeepResp := <-s.keepAliveChan:
      if leaseKeepResp == nil {
        fmt.Println("续租功能已经关闭")
        return
      } else {
        fmt.Println("续租成功")
      }
    }
  }
}
// 通过租约注册服务
func (s *ServiceRegister) PutService(key, val string) error {
  fmt.Printf("PutService key <%s> val <%s>\n", key, val)
  _, err := s.kv.Put(context.TODO(), key, val, clientv3.WithLease(s.leaseResp.ID))
  return err
}
// 撤销租约
func (s *ServiceRegister) RevokeLease() error {
  s.canclefunc()
  time.Sleep(2 * time.Second)
  _, err := s.lease.Revoke(context.TODO(), s.leaseResp.ID)
  return err
}
func main() {
  service, _ := InitService([]string{"127.0.0.1:12379", "127.0.0.1:22379", "127.0.0.1:32379"}, 5)
  service.setLease(10)
  defer service.RevokeLease()
  go service.ListenLeaseRespChan()
  err := service.PutService("/wohu", "http://localhost:8080")
  if err != nil {
    fmt.Printf("PutService failed %s\n", err)
  }
  // 使得程序阻塞运行,便于观察输出结果
  select {}
}

服务发现

go

复制代码

package main
import (
  "context"
  "fmt"
  "sync"
  "time"
  "github.com/coreos/etcd/clientv3"
  "github.com/coreos/etcd/mvcc/mvccpb"
)
// 客户端对象
type Client struct {
  client     *clientv3.Client
  kv         clientv3.KV
  lease      clientv3.Lease
  watch      clientv3.Watcher
  serverList map[string]string
  lock       sync.Mutex
}
// 初始化客户端对象
func InitClient(addr []string) (*Client, error) {
  conf := clientv3.Config{
    Endpoints:   addr,
    DialTimeout: 5 * time.Second,
  }
  client, err := clientv3.New(conf)
  if err != nil {
    fmt.Printf("create connection etcd failed %s\n", err)
    return nil, err
  }
  // 得到 KV 、Lease、 Watcher 的API子集
  kv := clientv3.NewKV(client)
  lease := clientv3.NewLease(client)
  watch := clientv3.NewWatcher(client)
  // 给客户端对象赋值
  c := &Client{
    client:     client,
    kv:         kv,
    lease:      lease,
    watch:      watch,
    serverList: make(map[string]string),
  }
  return c, nil
}
// 根据注册的服务名,获取服务实例的信息
func (c *Client) getServiceByName(prefix string) ([]string, error) {
  // 读取的时候带有 WithPrefix 选项,所以会读取该前缀所有的字段值
  resp, err := c.kv.Get(context.Background(), prefix, clientv3.WithPrefix())
  if err != nil {
    fmt.Printf("getServiceByName failed %s\n", err)
    return nil, err
  }
  // 返回的 resp 是多个字段值。需要遍历提取对应的 key value
  addrs := c.extractAddrs(resp)
  return addrs, nil
}
// 根据 etcd 的响应,提取服务实例的数组
func (c *Client) extractAddrs(resp *clientv3.GetResponse) []string {
  addrs := make([]string, 0)
  if resp == nil || resp.Kvs == nil {
    return addrs
  }
  for i := range resp.Kvs {
    if v := resp.Kvs[i].Value; v != nil {
      // 将 key  value 值保存在  ServiceList 表中
      c.SetServiceList(string(resp.Kvs[i].Key), string(resp.Kvs[i].Value))
      addrs = append(addrs, string(v))
    }
  }
  return addrs
}
// 设置 serverList
func (c *Client) SetServiceList(key, val string) {
  c.lock.Lock()
  defer c.lock.Unlock()
  // serverList 为初始化设置的本地 map 对象,由于考虑到多个 client 运行,所以需要加锁控制
  c.serverList[key] = string(val)
  fmt.Println("set data key :", key, "val:", val)
}
// 删除本地缓存的服务实例信息
func (c *Client) DelServiceList(key string) {
  c.lock.Lock()
  defer c.lock.Unlock()
  delete(c.serverList, key)
  fmt.Println("del data key:", key)
  newRes, err := c.getServiceByName(key)
  if err != nil {
    fmt.Printf("getServiceByName failed %s\n", err)
  } else {
    fmt.Printf("get  key %s", key, " current val is: %v\n", newRes)
  }
}
// 获取服务实例信息
func (c *Client) GetService(prefix string) ([]string, error) {
  if addrs, err := c.getServiceByName(prefix); err != nil {
    panic(err)
  } else {
    fmt.Println("get service ", prefix, " for instance list: ", addrs)
    go c.watcher(prefix)
    return addrs, nil
  }
}
// 监控指定键值对的变更
func (c *Client) watcher(prefix string) {
  watchRespChan := c.watch.Watch(context.Background(), prefix, clientv3.WithPrefix())
  for watchResp := range watchRespChan {
    for _, event := range watchResp.Events {
      switch event.Type {
      case mvccpb.PUT: // 写入的事件
        c.SetServiceList(string(event.Kv.Key), string(event.Kv.Value))
      case mvccpb.DELETE: // 删除的事件
        c.DelServiceList(string(event.Kv.Key))
      }
    }
  }
}
func main() {
  /*
    先创建 etcd 连接,构建 Client 对象,随后获取指定的服务 /wohu 实例信息;
    最后监测 wohu 服务实例的变更事件,根据不同的事件产生不同的行为。
  */
  c, _ := InitClient([]string{"127.0.0.1:12379", "127.0.0.1:22379", "127.0.0.1:32379"})
  c.GetService("/wohu")
  // 使得程序阻塞运行,模拟服务的持续运行
  select {}
}


相关文章
|
3月前
|
Cloud Native Go 开发工具
不改一行代码轻松玩转 Go 应用微服务治理
为了更好的进行 Go 应用微服务治理,提高研发效率和系统稳定性,本文将介绍 MSE 微服务治理方案,无需修改业务代码,实现治理能力。
19850 10
|
3月前
|
缓存 弹性计算 API
用 Go 快速开发一个 RESTful API 服务
用 Go 快速开发一个 RESTful API 服务
|
10天前
|
Go UED
Go Web服务中如何优雅平滑重启?
在生产环境中,服务升级时如何确保不中断当前请求并应用新代码是一个挑战。本文介绍了如何使用 Go 语言的 `endless` 包实现服务的优雅重启,确保在不停止服务的情况下完成无缝升级。通过示例代码和测试步骤,详细展示了 `endless` 包的工作原理和实际应用。
27 3
|
11天前
|
JSON Go UED
Go Web服务中如何优雅关机?
在构建 Web 服务时,优雅关机是一个关键的技术点,它确保服务关闭时所有正在处理的请求都能顺利完成。本文通过一个简单的 Go 语言示例,展示了如何使用 Gin 框架实现优雅关机。通过捕获系统信号和使用 `http.Server` 的 `Shutdown` 方法,我们可以在服务关闭前等待所有请求处理完毕,从而提升用户体验,避免数据丢失或不一致。
15 1
|
18天前
|
安全 Go 开发者
代码之美:Go语言并发编程的优雅实现与案例分析
【10月更文挑战第28天】Go语言自2009年发布以来,凭借简洁的语法、高效的性能和原生的并发支持,赢得了众多开发者的青睐。本文通过两个案例,分别展示了如何使用goroutine和channel实现并发下载网页和构建并发Web服务器,深入探讨了Go语言并发编程的优雅实现。
32 2
|
2月前
|
Go API 开发者
深入探讨:使用Go语言构建高性能RESTful API服务
在本文中,我们将探索Go语言在构建高效、可靠的RESTful API服务中的独特优势。通过实际案例分析,我们将展示Go如何通过其并发模型、简洁的语法和内置的http包,成为现代后端服务开发的有力工具。
|
1月前
|
JSON 搜索推荐 Go
ZincSearch搜索引擎中文文档及在Go语言中代码实现
ZincSearch官网及开发文档均为英文,对非英语用户不够友好。GoFly全栈开发社区将官方文档翻译成中文,并增加实战经验和代码,便于新手使用。本文档涵盖ZincSearch在Go语言中的实现,包括封装工具库、操作接口、统一组件调用及业务代码示例。官方文档https://zincsearch-docs.zinc.dev;中文文档https://doc.goflys.cn/docview?id=41。
|
3月前
|
安全 Go Docker
Go服务Docker Pod不断重启排查和解决
该文章分享了Go服务在Docker Pod中不断重启的问题排查过程和解决方案,识别出并发写map导致fatal error的问题,并提供了使用sync.Map或concurrent-map库作为并发安全的替代方案。
43 4
|
3月前
|
缓存 NoSQL 数据库
go-zero微服务实战系列(五、缓存代码怎么写)
go-zero微服务实战系列(五、缓存代码怎么写)
|
3月前
|
监控 Go 微服务
使用 ServiceWeaver 构建 go 服务
使用 ServiceWeaver 构建 go 服务