• Go微服务框架gokratos学习04:服务注册和服务发现(etcd作为注册中心)


    一、简介

    关于服务注册和服务发现介绍,我前面的文章有介绍过 - 服务注册和发现的文章(https://www.cnblogs.com/jiujuan/p/15087196.html)。

    作为服务中心的软件有很多,比如 etcd,consul,nacos,zookeeper 等都可以作为服务中心。

    go-kratos 把这些服务中心的功能作为插件,集成进了 kratos 中。

    下面就用 etcd 作为服务中心来说说 kratos 里服务注册和服务发现功能的使用。

    image-20220603213155270

    二、服务注册和服务发现

    2.1 接口定义

    从 go-kratos 服务注册和发现文档中,我们知道它的接口定义非常简单:

    注册和反注册服务:

    type Registrar interface {
        // 注册实例
        Register(ctx context.Context, service *ServiceInstance) error
        // 反注册实例
        Deregister(ctx context.Context, service *ServiceInstance) error
    }
    

    获取服务:

    type Discovery interface {
        // 根据 serviceName 直接拉取实例列表
        GetService(ctx context.Context, serviceName string) ([]*ServiceInstance, error)
        // 根据 serviceName 阻塞式订阅一个服务的实例列表信息
        Watch(ctx context.Context, serviceName string) (Watcher, error)
    }
    

    2.2 简单使用

    服务端注册服务

    使用 etcd 作为服务中心。

    1.新建 etcd连接client, etcdregitry.New(client)

    2.把 regitry传入 kratos.Registrar(r)

    3.传入服务名称 kratos.Name("helloworld")

    看官方的示例代码,server/main.go

    package main
    
    import (
    	"context"
    	"fmt"
    	"log"
    
    	etcdregitry "github.com/go-kratos/kratos/contrib/registry/etcd/v2"
    	"github.com/go-kratos/kratos/v2"
    	"github.com/go-kratos/kratos/v2/middleware/recovery"
    	"github.com/go-kratos/kratos/v2/transport/grpc"
    	"github.com/go-kratos/kratos/v2/transport/http"
    
    	pb "github.com/go-kratos/examples/helloworld/helloworld"
    	etcdclient "go.etcd.io/etcd/client/v3"
    )
    
    type server struct {
    	pb.UnimplementedGreeterServer
    }
    
    func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    	return &pb.HelloReply{Message: fmt.Sprintf("welcome %+v!", in.Name)}, nil
    }
    
    func main() {
    	// 创建 etcd client 连接
    	client, err := etcdclient.New(etcdclient.Config{
    		Endpoints: []string{"127.0.0.1:2379"},
    	})
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	// 初始化 http server
    	httpSrv := http.NewServer(
    		http.Address(":8080"),
    		http.Middleware(
    			recovery.Recovery(),
    		),
    	)
    
    	// 初始化 grpc server
    	grpcSrv := grpc.NewServer(
    		grpc.Address(":9000"),
    		grpc.Middleware(
    			recovery.Recovery(),
    		),
    	)
    
    	// 在服务器上注册服务
    	s := &server{}
    	pb.RegisterGreeterServer(grpcSrv, s)
    	pb.RegisterGreeterHTTPServer(httpSrv, s)
    
    	// 创建一个 registry 对象,就是对 ectd client 操作的一个包装
    	r := etcdregitry.New(client)
    
    	app := kratos.New(
    		kratos.Name("helloworld"), // 服务名称
    		kratos.Server(
    			httpSrv,
    			grpcSrv,
    		),
    		kratos.Registrar(r), // 填入etcd连接(etcd作为服务中心)
    	)
    	if err := app.Run(); err != nil {
    		log.Fatal(err)
    	}
    }
    

    etcd作为服务中心的使用步骤图解:
    image-20220604032611364

    客户端获取服务

    客户端的服务发现,主要也是 3 个步骤.

    1.新建 etcd连接, 传入到 etcdregitry.New(client)

    2.将 registry 传入 WithDiscovery(r)

    3.获取服务WithEndpoint("discovery:///helloworld")

    步骤与服务没有多大区别。

    官方的示例代码,client/main.go

    package main
    
    import (
    	"context"
    	"log"
    	"time"
    
    	"github.com/go-kratos/examples/helloworld/helloworld"
    	etcdregitry "github.com/go-kratos/kratos/contrib/registry/etcd/v2"
    	"github.com/go-kratos/kratos/v2/transport/grpc"
    	"github.com/go-kratos/kratos/v2/transport/http"
    	etcdclient "go.etcd.io/etcd/client/v3"
    	srcgrpc "google.golang.org/grpc"
    )
    
    func main() {
    	client, err := etcdclient.New(etcdclient.Config{
    		Endpoints: []string{"127.0.0.1:2379"},
    	})
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	r := etcdregitry.New(client) // 传入 etcd client,也就是选择 etcd 为服务中心
    
    	connGRPC, err := grpc.DialInsecure(
    		context.Background(),
    		grpc.WithEndpoint("discovery:///helloworld"), // 服务发现
    		grpc.WithDiscovery(r),                        // 传入etcd registry
    	)
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer connGRPC.Close()
    
    	connHTTP, err := http.NewClient(
    		context.Background(),
    		http.WithEndpoint("discovery:///helloworld"),
    		http.WithDiscovery(r),
    		http.WithBlock(),
    	)
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer connHTTP.Close()
    
    	for {
    		callHTTP(connHTTP)
    		callGRPC(connGRPC)
    		time.Sleep(time.Second)
    	}
    }
    
    func callHTTP(conn *http.Client) {
    	client := helloworld.NewGreeterHTTPClient(conn)
    	reply, err := client.SayHello(context.Background(), &helloworld.HelloRequest{Name: "go-kratos"})
    	if err != nil {
    		log.Fatal(err)
    	}
    	log.Printf("[http] SayHello %+v\n", reply)
    }
    
    func callGRPC(conn *srcgrpc.ClientConn) {
    	client := helloworld.NewGreeterClient(conn)
    	reply, err := client.SayHello(context.Background(), &helloworld.HelloRequest{Name: "go-kratos"})
    	if err != nil {
    		log.Fatal(err)
    	}
    	log.Printf("[grpc] SayHello %+v\n", reply)
    }
    

    运行程序

    1.运行etcd,没有安装etcd的请自行百度或gg安装

    2.运行服务端

    $ cd ./etcd/server
    $ go run ./main.go
    INFO msg=[HTTP] server listening on: [::]:8080
    INFO msg=[gRPC] server listening on: [::]:9000
    

    3.运行客户端

    $ cd ./client
    $ go run .\main.go
    INFO msg=[resolver] update instances: [{"id":"8fc08b88-e37b-11ec-bb6f-88d7f62323b4","name":"helloworld","version":"","metadata":null,"endpoints":["http://192.168.56.1:8080","grpc://192.168.56.1:9000"]}]
    2022/06/04 04:28:21 [http] SayHello message:"welcome go-kratos!"
    2022/06/04 04:28:21 [grpc] SayHello message:"welcome go-kratos!"
    INFO msg=[resolver] update instances: [{"id":"8fc08b88-e37b-11ec-bb6f-88d7f62323b4","name":"helloworld","version":"","metadata":null,"endpoints":["http://192.168.56.1:8080","grpc://192.168.56.1:9000"]}]
    2022/06/04 04:28:22 [http] SayHello message:"welcome go-kratos!"
    2022/06/04 04:28:22 [grpc] SayHello message:"welcome go-kratos!"
    2022/06/04 04:28:23 [http] SayHello message:"welcome go-kratos!"
    2022/06/04 04:28:23 [grpc] SayHello message:"welcome go-kratos!"
    2022/06/04 04:28:24 [http] SayHello message:"welcome go-kratos!"
    2022/06/04 04:28:24 [grpc] SayHello message:"welcome go-kratos!"
    
    ... ...
    

    程序运行成功

    看看 etcd 运行日志:

    2022-06-04 04:26:03.896230 W | wal: sync duration of 1.1565369s, expected less than 1s
    2022-06-04 04:26:03.991356 N | embed: serving insecure client requests on 127.0.0.1:2379, this is strongly discouraged!
    2022-06-04 04:27:18.187663 W | etcdserver: request "header:<ID:7587862969930594823 > put:<key:\"/microservices/helloworld/8fc08b88-e37b-11ec-bb6f-88d7f62323b4\" value_size:162 lease:7587862969930594821 >" with result "size:4" took too long (113.4545ms) to execute
    

    2.3 简析服务注册程序

    一图解千言:

    image-20220604043508393

    • etcdregitry.New(client)

      这里是对 etcd client 的包装处理,那么选择的服务中心就是 etcd。也可以使用consul,zookeeper 等,kratos 对它们都有封装。

    // 对 etcd client 的包装在处理
    r := etcdregitry.New(client)
    
    // https://github.com/go-kratos/kratos/contrib/registry/etcd/registry.go#L56
    // New creates etcd registry
    func New(client *clientv3.Client, opts ...Option) (r *Registry) {
    	op := &options{
    		ctx:       context.Background(),
    		namespace: "/microservices",
    		ttl:       time.Second * 15,
    		maxRetry:  5,
    	}
    	for _, o := range opts {
    		o(op)
    	}
    	return &Registry{
    		opts:   op,
    		client: client,
    		kv:     clientv3.NewKV(client),
    	}
    }
    
    • kratos.New()

      对应用程序初始化化,应用程序参数初始化 - 默认参数或接受传入的参数。

    // https://github.com/go-kratos/kratos/blob/v2.3.1/app.go#L39
    func New(opts ...Option) *App {
        o := options{
            ctx:              context.Background(),
            sigs:             []os.Signal{syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT},
            registrarTimeout: 10 * time.Second,
            stopTimeout:      10 * time.Second,
        }
        ... ...
        return &App{
            ctx:    ctx,
            cancel: cancel,
            opts:   o,
        }
    }
    
    • kratos.Name("helloworld")

      处理应用的服务参数。这个参数传入到上面 func New(opts ...Option) *App

    // https://github.com/go-kratos/kratos/options.go#L41
    // Name with service name.
    func Name(name string) Option {
         return func(o *options) { o.name = name }
    }
    
    • kratos.Registrar(r)

      选择哪个服务中心(etcd,consul,zookeeper,nacos 等等)作为 kratos 的服务中心。
      这个参数传入到上面 func New(opts ...Option) *App

    // https://github.com/go-kratos/kratos/blob/v2.3.1/options.go#L81
    func Registrar(r registry.Registrar) Option {
    	return func(o *options) { o.registrar = r }
    }
    
    • registrar.Register()

    真正把服务注册到服务中心的是 app.Run() 这个方法里的 a.opts.registrar.Register() 方法,Register() 方法把服务实例注册到服务中心。

    // https://github.com/go-kratos/kratos/app.go#L84
    if err := a.opts.registrar.Register(rctx, instance); err != nil {
       return err
    }
    

    参数 instance 就是方法 buildInstance() 返回的服务实例 ServiceInstance,ServiceInstance struct 包含了一个服务实例所需的字段。

    // https://github.com/go-kratos/kratos/app.go#L154
    func (a *App) buildInstance() (*registry.ServiceInstance, error)
    
    // https://github.com/go-kratos/kratos/registry/registry.go#L33
    type ServiceInstance struct {
        ID string `json:"id"`
        Name string `json:"name"`
        Version string `json:"version"`
        Metadata map[string]string `json:"metadata"`
        Endpoints []string `json:"endpoints"`
    }
    

    三、参考

  • 相关阅读:
    web应用程序的状态管理
    web学习笔记二
    java Web 学习笔记一
    简述Bootstrap栅格布局方式
    CSS3笔记
    CSS 笔记
    初识HTML流水笔记
    数据库初识
    java流的操作
    Java 套接字使用
  • 原文地址:https://www.cnblogs.com/jiujuan/p/16341183.html
Copyright © 2020-2023  润新知