zoukankan      html  css  js  c++  java
  • etcd做服务注册和服务发现

    etcd做服务注册和服务发现

    服务注册:同一service的所有节点注册到相同目录下,节点启动后将自己的信息注册到所属服务的目录中。

    健康检查:服务节点定时进行健康检查。注册到服务目录中的信息设置一个较短的TTL,运行正常的服务节点每隔一段时间会去更新信息的TTL ,从而达到健康检查效果。

    服务发现:通过服务节点能查询到服务提供外部访问的 IP 和端口号。比如网关代理服务时能够及时的发现服务中新增节点、丢弃不可用的服务节点。

    服务注册

    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()
    	}
    }
    

    服务发现

    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())
    		}
    	}
    }
    

    GRPC使用ETCD进行注册发现

    服务端

    package main
    
    import (
        "fmt"
        "google.golang.org/grpc"
        "goshare/etcd"
        "grpc-service/services"
        "log"
        "net"
    )
    
    func main() {
        addr := "127.0.0.1:8972"
        rpcServer := grpc.NewServer()
        services.RegisterOrderServiceServer(rpcServer, new(services.OrderServiceImpl))
        listen, err := net.Listen("tcp", addr)
        if err != nil {
            log.Fatalf("启动网络监听失败 %v
    ", err)
        }
    
        //etcd服务注册
        reg, err := etcd.NewService(etcd.ServiceInfo{
            Name: "mirco.service.order",
            IP:   addr, //grpc服务节点ip
        }, []string{"172.24.132.232:2379"}) // etcd的节点ip
        if err != nil {
            log.Fatal(err)
        }
        go reg.Start()
    
        //启动服务
        if err := rpcServer.Serve(listen); err != nil {
            fmt.Println("启动错误", err)
        } else {
            fmt.Println("服务开启")
        }
    }
    

    客户端

    r := etcd.NewResolver([]string{"172.24.132.232:2379"}, "mirco.service.order")
        resolver.Register(r)
    
        addr := fmt.Sprintf("%s:///%s", r.Scheme(), "")
        //addr := "127.0.0.1:8972"
        fmt.Println("addr", addr)
    
        conn, err := grpc.Dial(addr, grpc.WithInsecure())
        if err != nil {
            log.Fatalf("连接GRPC服务端失败 %v
    ", err)
        }
        defer conn.Close()
    
        orderServiceClient := sOrder.NewOrderServiceClient(conn)
    
        orderId := "order_" + strconv.Itoa(time.Now().Second())
        orderRequest := &sOrder.OrderRequest{OrderId: orderId, TimeStamp: time.Now().Unix()}
        orderInfo, err := orderServiceClient.GetOrderInfo(context.Background(), orderRequest)
        if orderInfo != nil {
            fmt.Println(orderInfo.GetOrderId(), orderInfo.GetOrderName(), orderInfo.GetOrderStatus())
            return orderInfo.GetOrderStatus()
        } else {
            return "订单服务读取失败"
        }
    

    相关链接

    https://github.com/Bingjian-Zhu/etcd-example
    https://www.cnblogs.com/FireworksEasyCool/p/12890649.html

    【励志篇】: 古之成大事掌大学问者,不惟有超世之才,亦必有坚韧不拔之志。
  • 相关阅读:
    JDK线程池原理之一:工作原理
    Hystrix Feign 特定状态码不熔断
    Hystrix熔断的方法级别(自定义commonKey)
    谨慎使用Exception
    FunctionalInterface~一个批量处理数据的类
    keycloak~账号密码认证和授权码认证
    keycloak~OIDC&OAuth2&自定义皮肤
    docker~添加hosts绑定的方法
    docker~产生的IP段与现有IP冲突问题
    高中数学知识要点及解题方法精粹[网摘]
  • 原文地址:https://www.cnblogs.com/tomtellyou/p/14843723.html
Copyright © 2011-2022 走看看