Go微服務框架go-kratos實戰04:kratos中服務註冊和服務發現的使用
- 2022 年 6 月 4 日
- 筆記
- [01]Golang, [04]架構-分佈式-微服務, Go, go-kratos, 微服務, 架構
一、簡介
關於服務註冊和服務發現介紹,我前面的文章有介紹過 – 服務註冊和發現的文章。
作為服務中心的軟件有很多,比如 etcd,consul,nacos,zookeeper 等都可以作為服務中心。
go-kratos 把這些服務中心的功能作為插件,集成進了 kratos 中。
下面就用 etcd 作為服務中心來說說 kratos 里服務註冊和服務發現功能的使用。
二、服務註冊和服務發現
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作為服務中心的使用步驟圖解:
客戶端獲取服務
客戶端的服務發現,主要也是 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":["//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":["//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 簡析服務註冊程序
一圖解千言:
-
etcdregitry.New(client)
這裡是對 etcd client 的包裝處理,那麼選擇的服務中心就是 etcd。也可以使用consul,zookeeper 等,kratos 對它們都有封裝。
// 對 etcd client 的包裝在處理
r := etcdregitry.New(client)
// //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()
對應用程序初始化化,應用程序參數初始化 – 默認參數或接受傳入的參數。
// //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
。
// //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
。
// //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() 方法把服務實例註冊到服務中心。
// //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 包含了一個服務實例所需的字段。
// //github.com/go-kratos/kratos/app.go#L154
func (a *App) buildInstance() (*registry.ServiceInstance, error)
// //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"`
}
三、參考
- //go-kratos.dev/docs/component/registry 服務註冊和服務發現
- //github.com/go-kratos/examples/blob/main/registry/etcd etcd registry
- //github.com/go-kratos/kratos