1. 服务注册与发现基本概念

在单体应用向微服务架构演进的过程中,原本的巨石型应用会按照业务需求被拆分成多个微服务,每个服务提供特定的功能,也可能依赖于其他的微服务。此时,每个微服务实例都可以动态部署,服务实例之间的调用通过轻量级的远程调用方式(HTTP、消息队列等)实现,它们之间通过预先定义好的接口进行访问。

在微服务架构中,多个微服务间的通信需要依赖服务注册与发现组件获取指定服务实例的地址信息,才能正确地发起 RPC 调用,保证分布式系统的高可用、高并发。

服务注册与发现主要包含两部分:服务注册的功能与服务发现的功能。

  • 服务注册是指服务实例启动时将自身信息注册到服务注册与发现中心,并在运行时通过心跳等方式向其汇报自身服务状态;
  • 服务发现是指服务实例向服务注册与发现中心获取其他服务实例信息,用于远程调用;

2. 服务注册与发现中心功能

服务注册与发现中心主要有以下职责:

  • 管理当前注册到服务注册与发现中心的微服务实例元数据信息,包括服务实例的服务名、IP 地址、端口号、服务描述和服务状态等;

  • 与注册到服务发现与注册中心的微服务实例维持心跳,定期检查注册表中的服务实例是否在线,并剔除无效服务实例信息;

  • 提供服务发现能力,为服务调用方提供服务提供方的服务实例元数据;

通过服务发现与注册中心,我们可以很方便地管理系统中动态变化的服务实例信息。但是与此同时,它也可能成为系统的瓶颈和故障点。因为服务之间的调用信息来自服务注册与发现中心,当它不可用时,服务之间的调用也就无法正常进行。因此服务发现与注册中心一般会集群化部署,提供高可用性和高稳定性。

3. 服务端代码

package mainimport ("context""fmt""time""github.com/coreos/etcd/clientv3"
)//服务注册对象
type ServiceRegister struct {client     *clientv3.Clientkv         clientv3.KVlease      clientv3.Leasecanclefunc func()key        stringleaseResp     *clientv3.LeaseGrantResponsekeepAliveChan <-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 = leaseResps.canclefunc = cancelFuncs.keepAliveChan = leaseRespChanreturn 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{"192.168.0.129:2379"}, 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 {}
}

4. 客户端代码

package mainimport ("context""fmt""sync""time""github.com/coreos/etcd/clientv3""github.com/coreos/etcd/mvcc/mvccpb"
)// 客户端对象
type Client struct {client     *clientv3.Clientkv         clientv3.KVlease      clientv3.Leasewatch      clientv3.WatcherserverList map[string]stringlock       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 valueaddrs := 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{"192.168.0.129:2379"})c.GetService("/wohu")// 使得程序阻塞运行,模拟服务的持续运行select {}
}

etcd 笔记(09)— 基于 etcd 实现微服务的注册与发现相关推荐

  1. 基于consul实现微服务的服务发现和负载均衡

    一. 背景 随着2018年年初国务院办公厅联合多个部委共同发布了<国务院办公厅关于促进"互联网+医疗健康"发展的意见(国办发[2018]26号)>,国内医疗IT领域又迎 ...

  2. 日10亿级处理,基于云的微服务架构

    德比软件:基于云的微服务架构 作者:朱攀,德比软件架构师,同济大学研究生,2007 年 2 月加入德比软件(DerbySoft),拥有 10 年以上的软件架构和开发经验.目前主要负责公司数据对接平台的 ...

  3. 基于 Docker 的微服务架构实践

    http://dockone.io/article/4887 前言 基于 Docker 的容器技术是在2015年的时候开始接触的,两年多的时间,作为一名 Docker 的 DevOps,也见证了 Do ...

  4. docker容器 eureka 集成_微服务:基于 Docker 的微服务架构之分布式企业级实践参考...

    编者按:本文分享自CSDN技术博客,作者为 FlyWine,所有权归原著者.若有不妥,联系本头条号以做必要处理. 目录 Microservice 和 Docker 服务发现模式 客户端发现模式 Net ...

  5. iUAP云运维平台v3.0全面支持基于K8s的微服务架构

    什么是微服务架构? 微服务(MicroServices)架构是当前互联网业界的一个技术热点,业内各公司也都纷纷开展微服务化体系建设.微服务架构的本质,是用一些功能比较明确.业务比较精练的服务去解决更大 ...

  6. 基于 Docker 的微服务架构

    基于 Docker 的微服务架构-分布式企业级实践 前言 Microservice 和 Docker 服务发现模式 客户端发现模式 Netflix-Eureka 服务端发现模式 Consul Etcd ...

  7. 一个近乎完美基于Dubbo的微服务改造实践

    网易考拉(以下简称考拉)是网易旗下以跨境业务为主的综合型电商,自 2015 年 1 月 9 日上线公测后,业务保持了高速增长,这背后离不开其技术团队的支撑. 微服务化是电商 IT 架构演化的必然趋势, ...

  8. 基于SpringCloud的微服务架构分析,神仙框架!

    点击上方"芋道源码",选择"设为星标" 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | ...

  9. (祈福九寨)网易蜂巢基于容器和微服务加快迭代速度实践

    题图:Afterquake by Angelo Giordano@pixabay 编辑:冷锋 文章转自网易云(微信公众号Netease_cloud) 刘超 网易云首席解决方案架构师,代码级略懂Open ...

最新文章

  1. TCP连接管理【三次握手-四次挥手】
  2. mysql 插入指定值_mysql实现随机把字段值插入指定表
  3. python 决策树和随机森林_【python机器学习笔记】使用决策树和随机森林预测糖尿病...
  4. 生产者跟消费者问题(C++实现)
  5. Web 设计:实现干净代码的12条定律
  6. 模板:广义二项式反演/广义容斥(组合数学)
  7. 二叉树查找后继节点(即中序遍历情况下的这个节点的下一个) Python实现
  8. 为什么CPU的使用率总是100%
  9. html 折叠焦点图切换,自适应全屏焦点图切换CSS3特效
  10. vue 文件名乱码_如何解决vue.js中文乱码问题
  11. matlab中uigetfile函数使用方法 (选择文件提示框)
  12. MySQL备份与恢复
  13. 2012年主流U盘启动盘制作工具合集下载
  14. jQuery fsBanner 手风琴
  15. 戴尔计算机软件的安装,怎么安装dell电脑系统
  16. opencascade 0xXXXXXXXX处最可能的异常: 0xC0000005: 写入位置 0x00000014 时发生访问冲突
  17. POJO、JavaBean和EJB的区别
  18. MAYA XGen创建毛发时报错找不到过程“XgCreateDescription“的解决方法
  19. ConvertUtil
  20. C51单片机,基于LCD液晶屏的简易时钟

热门文章

  1. 2022-2028年中国散热产业深度调研及投资前景预测报告(全卷)
  2. RabbitMQ 入门系列(2)— 生产者、消费者、信道、代理、队列、交换器、路由键、绑定、交换器
  3. Jquery实现form表单回填数据
  4. linux配置java环境变量(详细)
  5. bert-as-service使用
  6. GPT-3 Finetune
  7. torch.nn.functional.cross_entropy.ignore_index
  8. ISOOSI网络模型的通俗解析
  9. 王道考研 计算机网络笔记 第一章:概述计算机网络体系结构
  10. TVM示例展示 README.md,Makefile,CMakeLists.txt