利用go-kit组件进行服务注册与发现和健康检查的操作

 更新时间:2021年04月27日 08:40:27   作者:鹿灏楷silves  
这篇文章主要介绍了利用go-kit组件进行服务注册与发现和健康检查的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

在go的微服务架构中

使用go-kit组件进行开发微服务

type Reg struct {
	Host string
	Port int
	Client consul.Client
}
func MakeReg (host string , port int) (*Reg , error) {
	reg := api.DefaultConfig()
	reg.Address = host + ":" + strconv.Itoa(port)
	apiclient , err = api.NewClient(reg)
	if err != nil {
		return nil , err
	}
	client := consul.NewClient(apiclient)
	return &Reg{Host : host , Port : port ,Client : client} , nil
}
func (r Reg) Resiter (servicename , tag , host , seviceid ,checkinter ,healthcheckhttp ,deregisterafter string , port int) bool {
	congig := api.AgentServiceRegistration{
		Port : port ,
		Address : host ,
		Name := servicename,
		ID := serviceid,
		Ckeck : &api.AgentServiceCheck{
			Interval : checkinter,
			HTTP : "http://" + host + ":" + healthcheckhttp ,
			DeregisterCriticalServiceAfter : deregisterafter,
		}
	}
	if err := r.Client.Register(&config) ; err != nil {
		fmt.Println(err)
		return false
	}
	return true
}
func (r Reg) Deregister (serviceid string) bool {
	dreg := api.AgentServiceRegistration{ID : serviceid}
	if err != r.Client.Deregister(&config) ; err != nil {
		fmt.Println(err)
		return false
	}
	return true
}
func (r Reg) discover (servicename , tag string ,passingonly bool) ( []*api.ServiceEntry ,error ) {
	if entries ,_ ,err := r.Client.Service(servicename , tag , passingonly , nil) ;err != nil {
		return nil ,err
	}else{
		return entries , nil
	}
}

补充:go-kit 与 grpc 结合实现注册发现与负载均衡

介绍

grpc提供了简单的负载均衡,需要自己实现服务发现resolve。我们既然要使用go-kit来治理微服务,那么我们就使用go-kit的注册发现、负载均衡机制。

go-kit官方【stringsvc3】例子中使用的负载均衡方案是通过服务端转发进行,翻找下源码go-kit的服务注册发现、负载均衡在【sd】包中。下面我们介绍怎么通过go-kit进行客户端负载均衡。

go-kit提供的注册中心

1、 etcd

2、 consul

3、 eureka

4、 zookeeper

go-kit提供的负载均衡

1、 random[随机]

2、 roundRobin[轮询]

只需实现Balancer接口,我们可以很容易的增加其它负载均衡机制

type Balancer interface {  
   Endpoint() (endpoint.Endpoint, error)  
}

etcd注册发现

etcd和zookeeper类似是一个高可用、强一致性的存储仓库,拥有服务发现功能。 我们就通过go-kit提供的etcd包来实现服务注册发现

服务端代码

服务注册

1、连接注册中心

2、注册当前服务

var (  
   //etcd服务地址  
   etcdServer = "127.0.0.1:2379"   
   //服务的信息目录  
   prefix     = "/services/book/"    
   //当前启动服务实例的地址  
   instance   = "127.0.0.1:50052"  
   //服务实例注册的路径  
   key        = prefix + instance    
   //服务实例注册的val  
   value      = instance  
   ctx        = context.Background()  
   //服务监听地址  
   serviceAddress = ":50052"  
)  
//etcd的连接参数  
options := etcdv3.ClientOptions{  
   DialTimeout: time.Second * 3,  
   DialKeepAlive: time.Second * 3,  
}  
//创建etcd连接  
client, err := etcdv3.NewClient(ctx, []string{etcdServer}, options)  
if err != nil {  
   panic(err)  
}  
// 创建注册器  
registrar := etcdv3.NewRegistrar(client, etcdv3.Service{  
   Key:   key,  
   Value: value,  
}, log.NewNopLogger())  
// 注册器启动注册  
registrar.Register()

完整代码

package main  
import (  
   "grpc-test/pb"  
   "context"  
   grpc_transport "github.com/go-kit/kit/transport/grpc"  
   "github.com/go-kit/kit/endpoint" 
   "google.golang.org/grpc" 
   "net" 
   "github.com/go-kit/kit/sd/etcdv3" 
   "github.com/go-kit/kit/log" 
   "time"
)  
type BookServer struct {  
   bookListHandler  grpc_transport.Handler  
   bookInfoHandler  grpc_transport.Handler  
}  
//通过grpc调用GetBookInfo时,GetBookInfo只做数据透传, 调用BookServer中对应Handler.ServeGRPC转交给go-kit处理  
func (s *BookServer) GetBookInfo(ctx context.Context, in *book.BookInfoParams) (*book.BookInfo, error) {  
   _, rsp, err := s.bookInfoHandler.ServeGRPC(ctx, in)  
   if err != nil {  
      return nil, err  
   }  
   return rsp.(*book.BookInfo),err  
}  
//通过grpc调用GetBookList时,GetBookList只做数据透传, 调用BookServer中对应Handler.ServeGRPC转交给go-kit处理  
func (s *BookServer) GetBookList(ctx context.Context, in *book.BookListParams) (*book.BookList, error) {  
   _, rsp, err := s.bookListHandler.ServeGRPC(ctx, in)  
   if err != nil {  
      return nil, err  
   }  
   return rsp.(*book.BookList),err  
}  
//创建bookList的EndPoint  
func makeGetBookListEndpoint() endpoint.Endpoint {  
   return func(ctx context.Context, request interface{}) (interface{}, error) {  
      //请求列表时返回 书籍列表  
      bl := new(book.BookList)  
      bl.BookList = append(bl.BookList, &book.BookInfo{BookId:1,BookName:"21天精通php"})  
      bl.BookList = append(bl.BookList, &book.BookInfo{BookId:2,BookName:"21天精通java"})  
      return bl,nil  
   }  
}  
//创建bookInfo的EndPoint  
func makeGetBookInfoEndpoint() endpoint.Endpoint {  
   return func(ctx context.Context, request interface{}) (interface{}, error) {  
      //请求详情时返回 书籍信息  
      req := request.(*book.BookInfoParams)  
      b := new(book.BookInfo)  
      b.BookId = req.BookId  
      b.BookName = "21天精通php"  
      return b,nil  
   }  
}  
func decodeRequest(_ context.Context, req interface{}) (interface{}, error) {  
   return req, nil  
}  
func encodeResponse(_ context.Context, rsp interface{}) (interface{}, error) {  
   return rsp, nil  
}  
func main() {  
   var (  
      //etcd服务地址  
      etcdServer = "127.0.0.1:2379"  
      //服务的信息目录  
      prefix     = "/services/book/"  
      //当前启动服务实例的地址  
      instance   = "127.0.0.1:50052"  
      //服务实例注册的路径  
      key        = prefix + instance  
      //服务实例注册的val  
      value      = instance  
      ctx        = context.Background()  
      //服务监听地址  
      serviceAddress = ":50052"  
   )  
   //etcd的连接参数  
   options := etcdv3.ClientOptions{  
      DialTimeout: time.Second * 3,  
      DialKeepAlive: time.Second * 3,  
   }  
   //创建etcd连接  
   client, err := etcdv3.NewClient(ctx, []string{etcdServer}, options)  
   if err != nil {  
      panic(err)  
   }  
   // 创建注册器  
   registrar := etcdv3.NewRegistrar(client, etcdv3.Service{  
      Key:   key,  
      Value: value,  
   }, log.NewNopLogger())  
   // 注册器启动注册  
   registrar.Register()  
   bookServer := new(BookServer)  
   bookListHandler := grpc_transport.NewServer(  
      makeGetBookListEndpoint(),  
      decodeRequest,  
      encodeResponse,  
   )  
   bookServer.bookListHandler = bookListHandler  
   bookInfoHandler := grpc_transport.NewServer(  
      makeGetBookInfoEndpoint(),  
      decodeRequest,  
      encodeResponse,  
   )  
   bookServer.bookInfoHandler = bookInfoHandler  
   ls, _ := net.Listen("tcp", serviceAddress)  
   gs := grpc.NewServer(grpc.UnaryInterceptor(grpc_transport.Interceptor))  
   book.RegisterBookServiceServer(gs, bookServer)  
   gs.Serve(ls)  
}

客户端代码

客户端流程

1、 连接注册中心

2、 获取提供的服务

3、 监听服务目录变化,目录变化更新本地缓存

4、 创建负载均衡器

5、 获取请求的 endPoint

完整代码

package main  
import (  
   "context"  
   "github.com/go-kit/kit/sd/etcdv3" 
   "time" 
   "github.com/go-kit/kit/sd" 
   "github.com/go-kit/kit/log" 
   "github.com/go-kit/kit/endpoint" 
   "io" 
   "github.com/go-kit/kit/sd/lb" 
   "grpc-test/pb" 
   "fmt" 
   "google.golang.org/grpc"
)  
func main() {  
   var (  
      //注册中心地址  
      etcdServer = "127.0.0.1:2379"  
      //监听的服务前缀  
      prefix     = "/services/book/"  
      ctx        = context.Background()  
   )  
   options := etcdv3.ClientOptions{  
      DialTimeout: time.Second * 3,  
      DialKeepAlive: time.Second * 3,  
   }  
   //连接注册中心  
   client, err := etcdv3.NewClient(ctx, []string{etcdServer}, options)  
   if err != nil {  
      panic(err)  
   }  
   logger := log.NewNopLogger()  
   //创建实例管理器, 此管理器会Watch监听etc中prefix的目录变化更新缓存的服务实例数据  
   instancer, err := etcdv3.NewInstancer(client, prefix, logger)  
   if err != nil {  
      panic(err)  
   }  
   //创建端点管理器, 此管理器根据Factory和监听的到实例创建endPoint并订阅instancer的变化动态更新Factory创建的endPoint  
   endpointer := sd.NewEndpointer(instancer, reqFactory, logger)  
   //创建负载均衡器  
   balancer := lb.NewRoundRobin(endpointer)  
   /**  
   我们可以通过负载均衡器直接获取请求的endPoint,发起请求  
   reqEndPoint,_ := balancer.Endpoint() 
   */  
   /**  
   也可以通过retry定义尝试次数进行请求  
   */  
   reqEndPoint := lb.Retry(3, 3*time.Second, balancer)  
   //现在我们可以通过 endPoint 发起请求了  
   req := struct{}{}  
   if _, err = reqEndPoint(ctx, req); err != nil {  
      panic(err)  
   }  
}  
//通过传入的 实例地址  创建对应的请求endPoint  
func reqFactory(instanceAddr string) (endpoint.Endpoint, io.Closer, error) {  
   return func(ctx context.Context, request interface{}) (interface{}, error) {  
      fmt.Println("请求服务: ", instanceAddr)
      conn, err := grpc.Dial(instanceAddr, grpc.WithInsecure())  
      if err != nil {  
         fmt.Println(err)  
         panic("connect error")  
      }  
      defer conn.Close()  
      bookClient := book.NewBookServiceClient(conn)  
      bi,_:=bookClient.GetBookInfo(context.Background(),&book.BookInfoParams{BookId:1})  
      fmt.Println("获取书籍详情")  
      fmt.Println("bookId: 1", " => ", "bookName:", bi.BookName)  
      bl,_ := bookClient.GetBookList(context.Background(), &book.BookListParams{Page:1, Limit:10})  
      fmt.Println("获取书籍列表")  
      for _,b := range bl.BookList {  
         fmt.Println("bookId:", b.BookId, " => ", "bookName:", b.BookName)  
      }  
      return nil,nil  
   },nil,nil  
} 

测试

请求测试

请求服务: 127.0.0.1:50052
获取书籍详情
bookId: 1  =>  bookName: 21天精通php
获取书籍列表
bookId: 1  =>  bookName: 21天精通php
bookId: 2  =>  bookName: 21天精通java

负载均衡测试

1、 修改server的注册监听端口,启动多个server

instance   = "127.0.0.1:50052"  
serviceAddress = ":50052"

2、client发起多次请求

req := struct{}{}  
for i := 1; i <= 8; i++ {  
   if _, err = reqEndPoint(ctx, req); err != nil {  
      panic(err)  
   }  
}

通过返回结果中记录的请求地址,我们可以看到已经按照轮询的方式请求不同的微服务实例。

请求服务:  127.0.0.1:50051
        获取书籍详情
        bookId: 1  =>  bookName: 21天精通php
        获取书籍列表
        bookId: 1  =>  bookName: 21天精通php
        bookId: 2  =>  bookName: 21天精通java
请求服务:  127.0.0.1:50052
        获取书籍详情
        bookId: 1  =>  bookName: 21天精通php
        获取书籍列表
        bookId: 1  =>  bookName: 21天精通php
        bookId: 2  =>  bookName: 21天精通java
请求服务:  127.0.0.1:50051
        获取书籍详情
        bookId: 1  =>  bookName: 21天精通php
        获取书籍列表
        bookId: 1  =>  bookName: 21天精通php
        bookId: 2  =>  bookName: 21天精通java
请求服务:  127.0.0.1:50052
        获取书籍详情
        bookId: 1  =>  bookName: 21天精通php
        获取书籍列表
        bookId: 1  =>  bookName: 21天精通php
        bookId: 2  =>  bookName: 21天精通java
请求服务:  127.0.0.1:50051
        获取书籍详情
        bookId: 1  =>  bookName: 21天精通php
        获取书籍列表
        bookId: 1  =>  bookName: 21天精通php
        bookId: 2  =>  bookName: 21天精通java
请求服务:  127.0.0.1:50052
        获取书籍详情
        bookId: 1  =>  bookName: 21天精通php
        获取书籍列表
        bookId: 1  =>  bookName: 21天精通php
        bookId: 2  =>  bookName: 21天精通java
请求服务:  127.0.0.1:50051
        获取书籍详情
        bookId: 1  =>  bookName: 21天精通php
        获取书籍列表
        bookId: 1  =>  bookName: 21天精通php
        bookId: 2  =>  bookName: 21天精通java
请求服务:  127.0.0.1:50052
        获取书籍详情
        bookId: 1  =>  bookName: 21天精通php
        获取书籍列表
        bookId: 1  =>  bookName: 21天精通php
        bookId: 2  =>  bookName: 21天精通java
Process finished with exit code 0

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。如有错误或未考虑完全的地方,望不吝赐教。

相关文章

  • go的defer和闭包示例说明(非内部实现)

    go的defer和闭包示例说明(非内部实现)

    这篇文章主要为大家介绍了go的defer和闭包示例说明(非内部实现),有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • golang select 机制和超时问题

    golang select 机制和超时问题

    golang 中的协程使用非常方便,但是协程什么时候结束是一个控制问题,可以用 select 配合使用,这篇文章主要介绍了golang select 机制和超时问题,需要的朋友可以参考下
    2022-06-06
  • Go语言中的原子操作使用详解

    Go语言中的原子操作使用详解

    这篇文章主要介绍了Go语言中的原子操作使用详解的相关资料,需要的朋友可以参考下
    2023-08-08
  • Golang操作Kafka的实现示例

    Golang操作Kafka的实现示例

    本文主要介绍了Golang操作Kafka的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • Golang slice切片操作之切片的追加、删除、插入等

    Golang slice切片操作之切片的追加、删除、插入等

    这篇文章主要介绍了Golang slice切片操作之切片的追加、删除、插入等,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11
  • Go语言操作redis用法实例

    Go语言操作redis用法实例

    这篇文章主要介绍了Go语言操作redis用法,实例分析了Go语言操作redis的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • Go语言实现逐行读取和写入文件详解

    Go语言实现逐行读取和写入文件详解

    这篇文章主要介绍了如何使用go语言实现从输入文件中读取每行数据,然后将每行字段组合成SQL插入脚本,然后逐行写入另外一个空白文件中,有需要的可以参考下
    2024-01-01
  • 几个小技巧帮你实现Golang永久阻塞

    几个小技巧帮你实现Golang永久阻塞

    Go 的运行时的当前设计,假定程序员自己负责检测何时终止一个 goroutine 以及何时终止该程序。有时候我们需要的是使程序阻塞在这一行,本文就来详细的介绍一下,感兴趣的可以了解一下
    2021-12-12
  • Go语言正则表达式的使用详解

    Go语言正则表达式的使用详解

    正则表达式是一种进行模式匹配和文本操纵的功能强大的工具。这篇文章主要介绍了Go正则表达式使用,本文给大家介绍的非常详细,对大家的工作或学习具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-03-03
  • Ubuntu安装Go语言运行环境

    Ubuntu安装Go语言运行环境

    由于最近偏爱Ubuntu,在加上作为一门开源语言,在Linux上从源代码开始搭建环境更让人觉得有趣味性。让我们直接先从Go语言的环境搭建开始
    2015-04-04

最新评论