Appearance
20如何在微服务框架中集成etcd?
上一讲我们介绍了基于 etcd 实现微服务注册与发现的案例。由于服务实例是动态部署的,每个服务实例的地址和服务信息都可能动态变化,势必需要一个中心化的组件对各个服务实例的信息进行管理。该组件管理了各个部署好的服务实例元数据,包括但不限于服务名、IP 地址、端口号、服务描述和服务状态等。
现有的主流微服务框架大都集成了服务注册与发现的功能,这一讲我们就来介绍并实践如何集成 etcd 到主流的 Go 微服务框架中。
go-micro 集成 etcd
在构建微服务时,使用服务发现可以减少配置的复杂性,go-micro 也是 Go 语言中常用的微服务框架。go-micro 的发现机制是可插拔的,支持多种组件,如 etcd 和 ZooKeeper 等,具体详见micro/go-plugins。
go-micro 介绍
首先介绍一下 go-micro 微服务框架。go-micro 是一个可插拔的 RPC 框架,用于分布式系统的开发,具有以下特性。
服务发现(Service Discovery):自动服务注册与名称解析。
负载均衡(Load Balancing):在服务发现之上构建了智能的负载均衡机制。
同步通信(Synchronous Comms):基于 RPC 的通信,支持双向流。
异步通信(Asynchronous Comms):内置发布/订阅的事件驱动架构。
消息编码(Message Encoding):基于 Content-Type 的动态编码,支持 ProtoBuf、JSON,开箱即用。
服务接口(Service Interface):所有特性都被打包在简单且高级的接口中,方便开发微服务。
go-micro 旨在利用接口使微服务架构抽象化,并且提供了一系列默认且完整的开箱即用的插件。
定义消息格式
go-micro 使用 ProtoBuf 定义消息格式。我们创建一个类型为 proto 的文件 hi.proto,其中定义了调用接口的参数以及返回的对象:
go
syntax = "proto3";
package hello;
service Greeter {
rpc Hello(HelloRequest) returns (HelloResponse) {}
}
message HelloRequest {
string from = 1;
string to = 2;
string msg = 3;
}
message HelloResponse {
string from = 1;
string to = 2;
string msg = 3;
}
如上的代码定义了 Greeter 的接口,Hello 方法的参数为 HelloRequest ,结果返回了 HelloResponse 对象。
接着生成 API 接口。我们需要使用 protoc 来生成 protobuf 代码文件,以此生成对应的 Go 语言代码。包括如下的三个插件:
protoc
protoc-gen-go
protoc-gen-micro
使用如下命令分别安装这几个插件:
java
go get github.com/golang/protobuf/{proto,protoc-gen-go}
go get github.com/micro/protoc-gen-micro
接着在当前目录下运行如下的命令,生成两个模板文件:
java
$ protoc --micro_out=. --go_out=. greeter.proto
运行之后,当前目录的结构如下所示:
java
$ tree
.
├── hello.pb.go
├── hello.pb.micro.go
└── hello.proto
可以看到,我们通过工具生成了两个文件,一个是 Go 结构文件,另一个属于 go-micro RPC 的接口文件。基于生成的两个文件,我们可以创建"打招呼"的请求。下面是部分生成的代码:
go
// Greeter service 客户端的 API
type GreeterService interface {
Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error)
}
type greeterService struct {
c client.Client
name string
}
func NewGreeterService(name string, c client.Client) GreeterService {
if c == nil {
c = client.NewClient()
}
if len(name) == 0 {
name = "hello"
}
return &greeterService{
c: c,
name: name,
}
}
func (c *greeterService) Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error) {
req := c.c.NewRequest(c.name, "Greeter.Hello", in)
out := new(HelloResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Greeter service 服务端
type GreeterHandler interface {
Hello(context.Context, *HelloRequest, *HelloResponse) error
}
func RegisterGreeterHandler(s server.Server, hdlr GreeterHandler, opts ...server.HandlerOption) error {
type greeter interface {
Hello(ctx context.Context, in *HelloRequest, out *HelloResponse) error
}
type Greeter struct {
greeter
}
h := &greeterHandler{hdlr}
return s.Handle(s.NewHandler(&Greeter{h}, opts...))
}
type greeterHandler struct {
GreeterHandler
}
func (h *greeterHandler) Hello(ctx context.Context, in *HelloRequest, out *HelloResponse) error {
return h.GreeterHandler.Hello(ctx, in, out)
}
gRPC 的调用方法装在生成的 go-micro RPC 的接口文件中。为了演示,我们只定义了一个Hello
接口,可以看到上面的代码实现还是比较简单的。
server 服务端
下面我们开始实现服务端,服务端需要注册 handlers 处理器,用以对外提供服务并接收请求。服务端的具体实现代码如下所示:
go
package main
import (
"context"
hello "github.com/keets2012/etcd-book-code/ch10/micro/srv/proto"
"log"
"github.com/micro/go-micro"
"github.com/micro/go-micro/registry"
"github.com/micro/go-plugins/registry/etcdv3"
)
type Greet struct{}
func (s *Greet) Hello(ctx context.Context, req *hello.HelloRequest, rsp *hello.HelloResponse) error {
log.Printf("received req %#v \n", req)
rsp.From = "server"
rsp.To = "client"
rsp.Msg = "ok"
return nil
}
func main() {
reg := etcdv3.NewRegistry(func(op *registry.Options) {
op.Addrs = []string{"127.0.0.1:2379",
}
})
service := micro.NewService(
micro.Name("hello.srv.say"),
micro.Registry(reg),
)
service.Init()
// 注册 GreeterHandler,传入服务和处理器
hello.RegisterGreeterHandler(service.Server(), new(Greet))
// 运行服务
if err := service.Run(); err != nil {
panic(err)
}
}
micro.NewService 用于初始化服务,然后返回一个 Service 接口的实例。
上述实现中,使用 etcd 替换了默认的 Consul 作为服务注册与发现组件。处理器会与服务一起被注册,就像 HTTP 处理器一样,通过调用 server.Run 服务启动,同时绑定代码配置中的地址作为接收请求的地址。服务启动时向注册中心注册自身服务的相关信息,并在接收到关闭信号时注销。
client 调用
下面我们来看客户端如何调用。客户端应用发起到服务端的远程调用请求,实现客户端与服务端"打招呼"的功能,代码如下所示:
go
package main
import (
"context"
hello "github.com/keets2012/etcd-book-code/ch10/micro/srv/proto"
"log"
"github.com/micro/go-micro"
"github.com/micro/go-micro/registry"
"github.com/micro/go-plugins/registry/etcdv3"
)
func main() {
reg := etcdv3.NewRegistry(func(op *registry.Options) {
op.Addrs = []string{
"127.0.0.1:2379",
}
})
//创建 service
service := micro.NewService(
micro.Registry(reg),
)
service.Init()
// 创建 greet 客户端,需要传入服务名与服务客户端方法构建的对象
greetClient := hello.NewGreeterService("hello.srv.say", service.Client())
param := &hello.HelloRequest{
From: "client",
To: "server",
Msg: "hello aoho",
}
rsp, err := greetClient.Hello(context.Background(), param)
if err != nil {
panic(err)
}
log.Println(rsp)
}
proto 生成的 RPC 接口已经将调用方法的流程封装好。hello.NewGreeterService
需要使用服务名与客户端对象来请求指定的接口,即hello.srv.say
,然后调用 Hello 方法。
go
func (c *sayService) Hello(ctx context.Context, in *SayParam, opts ...client.CallOption) (*SayResponse, error) {
req := c.c.NewRequest(c.name, "Say.Hello", in)
out := new(SayResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
主要的流程都在 c.c.Call 方法里。我们简单梳理一下整个流程,首先得到服务节点的地址,根据该地址查询连接池里是否有连接,如果有则取出来,如果没有则创建。然后进行数据传输,传输完成后把 client 连接放回到连接池内。
运行结果
上述操作实现了客户端与服务端的"打招呼"功能,下面我们分别运行服务端和客户端的应用程序,注意执行的先后顺序,得到的结果如下所示:
java
// 服务端的控制台输出
2021-03-16 23:00:23.365137 I | Transport [http] Listening on [::]:65331
2021-03-16 23:00:23.365230 I | Broker [http] Connected to [::]:65332
2021-03-16 23:00:23.365474 I | Registry [etcd] Registering node: hello.srv.say-6407b896-66d4-4cb1-81fd-d743ff6a97ec
2021-03-16 23:01:16.946948 I | received req &hello.SayRequest{From:"client", To:"server", Msg:"hello aoho", XXX_NoUnkeyedLiteral:struct {}{}, XXX_unrecognized:[]uint8(nil), XXX_sizecache:0}
//客户端的控制台输出
2021-03-16 23:01:16.947531 I | from:"server" to:"client" msg:"ok"
依次启动服务端、客户端,客户端发起一个打招呼的请求给服务端,可以看到服务端的控制台输出了收到的请求,并返回了 ok 响应给到客户端,符合我们的实现预期。
至此,我们成功在 go-micro 框架中集成了 etcd 作为服务注册与发现组件。
Go-kit 集成 etcd
介绍完 go-micro 集成 etcd,我们来看另一个流行的 Go 微服务框架 Go-kit 如何集成 etcd。
Go-kit 介绍
Go-kit 提供了用于实现系统监控和弹性模式组件的库,例如日志记录、跟踪、限流和熔断等,这些库协助工程师提高微服务架构的性能和稳定性。Go-kit 框架分层如下图所示。
Go-kit 框架分层图
除了用于构建微服务的工具包,Go-kit 还为工程师提供了良好的架构设计原则示范。Go-kit 提倡工程师使用 Alistair Cockburn 提出的 SOLID 设计原则、领域驱动设计(DDD)。所以 Go-kit 不仅仅是微服务工具包,它也非常适合构建优雅的整体结构。
Go-kit 提供了三层模型来解耦业务,这也是我们使用它的主要目的,模型由上到下分别是transport -> endpoint -> service
。
传输层用于网络通信,服务通常使用 HTTP、gRPC 等网络传输方式,或使用 NATS 等发布订阅系统相互通信。除此之外,Go-kit 还支持使用 AMQP 和 Thrift 等多种网络通信模式。
接口层是服务器和客户端的基本构建模块。在 Go-kit 中,每个对外提供的服务接口方法都会定义为一个端点(Endpoint),以便在服务器和客户端之间进行网络通信。每个端点利用传输层通过使用 HTTP 或 gRPC 等具体通信模式对外提供服务。
服务层是具体的业务逻辑实现。服务层的业务逻辑包含核心业务逻辑,即你要实现的主要功能。它不会也不应该进行 HTTP 或 gRPC 等具体网络传输,或者请求和响应消息类型的编码和解码。
Go-kit 在性能和扩展性等方面表现优异。下面我们就来介绍如何在 Go-kit 中集成 etcd 作为服务注册与发现组件,以及构建用户登录的场景、用户登录系统之后获取认证的令牌,接着实现 Go-kit 的 gRPC 调用。
定义消息格式
Go-kit 的消息通信也是基于 protobuf 格式。这里我们定义了两个 proto,其中一个定义了登录的 RPC 请求和响应的结构体,另一个则定义了 RPC 请求的方法。分别如下:
java
// user.proto
syntax = "proto3";
package pb;
message Login {
string Account = 1;
string Password = 2;
}
message LoginAck {
string Token = 1;
}
user.proto 定义了 Login 请求和 LoginAck 应答的结构体
// service.proto
syntax = "proto3";
package pb;
import "user.proto";
service User {
rpc RpcUserLogin (Login) returns (LoginAck) {
}
}
service.proto 引用了 user.proto 中定义的结构体,定义了一个方法 RpcUserLogin,请求参数为 Login 对象,响应结果为 LoginAck。
生成对应的 gRPC pb 文件,执行如下的命令:
java
$ protoc --go_out=plugins=grpc:. *.proto
生成 pb 文件后,目录中增加了两个文件,文件结构如下:
java
$ tree
.
├── make.sh
├── service.pb.go
├── service.proto
├── user.pb.go
└── user.proto
生成的文件基于 gRPC 调用的标准格式生成,这里就不具体列出了。我们接着看 user 服务的实现。
user 服务
由于 user 服务的实现代码比较多,这里我侧重讲解 Go-kit 集成使用 etcd 部分。我们先来看 user 服务的入口主函数:
go
var grpcAddr = flag.String("g", "127.0.0.1:8881", "grpcAddr")
var quitChan = make(chan error, 1)
func main() {
flag.Parse()
var (
etcdAddrs = []string{"127.0.0.1:2379"}
serName = "svc.user.agent"
grpcAddr = *grpcAddr
ttl = 5 * time.Second
)
utils.NewLoggerServer()
// 初始化 etcd 客户端
options := etcdv3.ClientOptions{
DialTimeout: ttl,
DialKeepAlive: ttl,
}
etcdClient, err := etcdv3.NewClient(context.Background(), etcdAddrs, options)
if err != nil {
utils.GetLogger().Error("[user_agent] NewClient", zap.Error(err))
return
}
// 基于 etcdClient 初始化 Registar
Registar := etcdv3.NewRegistrar(etcdClient, etcdv3.Service{
Key: fmt.Sprintf("%s/%s", serName, grpcAddr),
Value: grpcAddr,
}, log.NewNopLogger())
go func() {
golangLimit := rate.NewLimiter(10, 1)
server := src.NewService(utils.GetLogger())
endpoints := src.NewEndPointServer(server, golangLimit)
// 构造 EndPointServer
grpcServer := src.NewGRPCServer(endpoints, utils.GetLogger())
// 监听 tcp 地址和端口
grpcListener, err := net.Listen("tcp", grpcAddr)
if err != nil {
utils.GetLogger().Warn("[user_agent] Listen", zap.Error(err))
quitChan <- err
return
}
Registar.Register()
baseServer := grpc.NewServer(grpc.UnaryInterceptor(grpctransport.Interceptor))
pb.RegisterUserServer(baseServer, grpcServer)
if err = baseServer.Serve(grpcListener); err != nil {
utils.GetLogger().Warn("[user_agent] Serve", zap.Error(err))
quitChan <- err
return
}
}()
go func() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
quitChan <- fmt.Errorf("%s", <-c)
}()
utils.GetLogger().Info("[user_agent] run " + grpcAddr)
err = <-quitChan
// 注销连接
Registar.Deregister()
utils.GetLogger().Info("[user_agent] quit err", zap.Error(err))
}
user 服务集成 etcd 的主要步骤如下:
初始化 etcd 客户端;
基于 etcdClient 初始化 Registar;
Registar.Register() 注册 user 服务到 etcd,RegisterService 将服务及其实现注册到 gRPC 服务器,必须在调用服务之前调用 RegisterService;
服务关闭时,注销 etcd 连接。
客户端调用
在微服务架构中,用户登录的操作,一般由 user 服务校验其身份信息的合法性,如果合法则为该用法返回认证的令牌。我们的测试客户端就是模拟 auth 认证服务的实现。
go
func TestNewUserAgentClient(t *testing.T) {
// 初始化 UserAgent,返回的是一个 UserAgent
client, err := NewUserAgentClient([]string{"127.0.0.1:2379"}, logger)
if err != nil {
t.Error(err)
return
}
// 循环调用,为了测试 user 多实例注册到 etcd,客户端调用的情况
for i := 0; i < 6; i++ {
time.Sleep(time.Second)
userAgent, err := client.UserAgentClient()
if err != nil {
t.Error(err)
return
}
ack, err := userAgent.Login(context.Background(), &pb.Login{
Account: "aoho",
Password: "123456",
})
if err != nil {
t.Error(err)
return
}
t.Log(ack.Token)
}
}
上述代码示例是测试的主要代码,首先读取配置,初始化 UserAgent,其实就是得到指定服务的一个 etcdv3 客户端实例。这里获取了 etcd 中键为svc.user.agent
的值。
go
func NewUserAgentClient(addr []string, logger log.Logger) (*UserAgent, error) {
var (
etcdAddrs = addr
serName = "svc.user.agent"
ttl = 5 * time.Second
)
options := etcdv3.ClientOptions{
DialTimeout: ttl,
DialKeepAlive: ttl,
}
etcdClient, err := etcdv3.NewClient(context.Background(), etcdAddrs, options)
if err != nil {
return nil, err
}
instancerm, err := etcdv3.NewInstancer(etcdClient, serName, logger)
if err != nil {
return nil, err
}
return &UserAgent{
instancerm: instancerm,
logger: logger,
}, err
}
在 NewUserAgentClient 的实现中,根据传入的 etcdAddrs 构建 etcdClient,并通过 etcdClient 和 serName 构建 instancerm,指向的类型为 Instancer。
go
type Instancer struct {
cache *instance.Cache
client Client
prefix string
logger log.Logger
quitc chan struct{}
}
Instancer 选出存储在 etcd 键空间中的实例。同时将 watch 该键空间中的任何事件类型的更改,这些更改将更新实例器的实例信息。
至此,我们实现了 user 服务和调用 user 服务的客户端测试方法。
运行结果
我们启动 3 个服务地址,分别为:127.0.0.1:8881、127.0.0.1:8882、127.0.0.1:8883。
shell
$ ./user_agent -g 127.0.0.1:8881
2021-03-17 13:31:15 INFO utils/log_util.go:89 [NewLogger] success
2021-03-17 13:31:15 INFO user_agent/main.go:75 [user_agent] run 127.0.0.1:8881
$ ./user_agent -g 127.0.0.1:8882
2021-03-17 13:31:12 INFO utils/log_util.go:89 [NewLogger] success
2021-03-17 13:31:12 INFO user_agent/main.go:75 [user_agent] run 127.0.0.1:8882
$ ./user_agent -g 127.0.0.1:8883
2021-03-17 13:31:08 INFO utils/log_util.go:89 [NewLogger] success
2021-03-17 13:31:08 INFO user_agent/main.go:75 [user_agent] run 127.0.0.1:8883
依次运行服务端和测试函数,可以得到如下的结果:
shell
=== RUN TestNewUserAgentClient
ts=2021-03-17T05:31:22.605559Z caller=instancer.go:32 prefix=svc.user.agent instances=3
TestNewUserAgentClient: user_agent_test.go:44: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiYW9obyIsIkRjSWQiOjEsImV4cCI6MTYwMDMyMDcxMywiaWF0IjoxNjAwMzIwNjgzLCJpc3MiOiJraXRfdjQiLCJuYmYiOjE2MDAzMjA2ODMsInN1YiI6ImxvZ2luIn0.Eo-uytDEuAJyPGooXB2mC6uga-C-krVdthEQSYkqG-k
...
--- PASS: TestNewUserAgentClient (6.11s)
PASS
根据测试函数的运行结果,svc.user.agent 有三个服务实例。客户端 6 次调用 user 服务的登录结果都是成功的,TestNewUserAgentClient 输出了获取到的 JWT Token。同时在启动的三个 user 服务端控制台输出了如下的日志信息:
shell
// 8883
2021-03-17 13:31:24 DEBUG src/middleware_server.go:31 [9f4221fd-ec8c-53f2-b2ac-26e9cb4501ba] {"调用 Login logMiddlewareServer": "Login", "req": "Account:\"aoho\"assword:\"123456\" ", "res": "Token:\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiYW9obyIsIkRjSWQiOjEsImV4cCI6MTYwMDMyMDcxNCwiaWF0IjoxNjAwMzIwNjg0LCJpc3MiOiJraXRfdjQiLCJuYmYiOjE2MDAzMjA2ODQsInN1YiI6ImxvZ2luIn0.atzewyzrwRtBVCCg_4eZo7iiJKXGV6nJs-_BA9JDSLQ\" ", "time": "188.861 µ s", "err": null}
// 8882
2021-03-17 13:31:26 DEBUG src/middleware_server.go:31 [9ece68d5-9e56-515c-a417-77f371b04910] {"调用 Login logMiddlewareServer": "Login", "req": "Account:\"aoho\"assword:\"123456\" ", "res": "Token:\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiYW9obyIsIkRjSWQiOjEsImV4cCI6MTYwMDMyMDcxNiwiaWF0IjoxNjAwMzIwNjg2LCJpc3MiOiJraXRfdjQiLCJuYmYiOjE2MDAzMjA2ODYsInN1YiI6ImxvZ2luIn0.KLjK_mf11C_ssO_X5sKyzr55ftUEh2D5mfxS5xTKbP4\" ", "time": "195.477 µ s", "err": null}
2021-03-17 13:31:27 DEBUG src/middleware_server.go:31 [de1d3e65-d389-5232-9254-33e4cb6c9060] {"调用 Login logMiddlewareServer": "Login", "req": "Account:\"aoho\"assword:\"123456\" ", "res": "Token:\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiYW9obyIsIkRjSWQiOjEsImV4cCI6MTYwMDMyMDcxNywiaWF0IjoxNjAwMzIwNjg3LCJpc3MiOiJraXRfdjQiLCJuYmYiOjE2MDAzMjA2ODcsInN1YiI6ImxvZ2luIn0.2jkryvYTJVnsrXuNWB_SyYqKxQB-l5dos7bGUP2aLyo\" ", "time": "104.817 µ s", "err": null}
// 8881
2021-03-17 13:31:23 DEBUG src/middleware_server.go:31 [c521bfb2-5a48-58c8-aa74-fdf78adc443f] {"调用 Login logMiddlewareServer": "Login", "req": "Account:\"aoho\"assword:\"123456\" ", "res": "Token:\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiYW9obyIsIkRjSWQiOjEsImV4cCI6MTYwMDMyMDcxMywiaWF0IjoxNjAwMzIwNjgzLCJpc3MiOiJraXRfdjQiLCJuYmYiOjE2MDAzMjA2ODMsInN1YiI6ImxvZ2luIn0.Eo-uytDEuAJyPGooXB2mC6uga-C-krVdthEQSYkqG-k\" ", "time": "173.146 µ s", "err": null}
2021-03-17 13:31:25 DEBUG src/middleware_server.go:31 [9ffc9f63-d925-5999-9b9b-2bf544654010] {"调用 Login logMiddlewareServer": "Login", "req": "Account:\"aoho\"assword:\"123456\" ", "res": "Token:\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiYW9obyIsIkRjSWQiOjEsImV4cCI6MTYwMDMyMDcxNSwiaWF0IjoxNjAwMzIwNjg1LCJpc3MiOiJraXRfdjQiLCJuYmYiOjE2MDAzMjA2ODUsInN1YiI6ImxvZ2luIn0.OwMi33WbWz4SuIIRsTO0uOzg2d7qx5CDyISetnsbiiE\" ", "time": "174.443 µ s", "err": null}
2021-03-17 13:31:28 DEBUG src/middleware_server.go:31 [c5459a23-0999-5861-80d2-fea508815ac5] {"调用 Login logMiddlewareServer": "Login", "req": "Account:\"aoho\"assword:\"123456\" ", "res": "Token:\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiYW9obyIsIkRjSWQiOjEsImV4cCI6MTYwMDMyMDcxOCwiaWF0IjoxNjAwMzIwNjg4LCJpc3MiOiJraXRfdjQiLCJuYmYiOjE2MDAzMjA2ODgsInN1YiI6ImxvZ2luIn0.TR6gcjlZ7rb2PXQg5XJz1AX0cGJc706UAuT9VyWR1Wg\" ", "time": "68.345 µ s", "err": null}
从上面的日志信息可以知道,客户端根据 etcd 中存储的实例信息发起调用,成功实现了负载均衡。如果我们关闭某一个实例,客户端会监测到服务实例的变更,本地的服务实例列表会踢掉该实例,这种机制使得 Go-kit 的负载均衡依然奏效。
小结
这一讲我们主要介绍了在常见的两种微服务框架 go-micro 和 Go-kit 中集成 etcd 作为服务注册与发现组件。go-micro 把分布式系统的各种细节抽象出来,方便我们进行组件切换。go-micro 的新版本工具集弃用了 Consul,建议使用 etcd。Go-kit 是 Go 语言工具包的集合,可以帮助你构建强大、可靠和可维护的微服务,不过 Go 目前还不支持泛型,interface 的定义相对来说也比较烦琐。
本讲内容总结如下:
总的来说,两个微服务框架都支持方便地集成 etcd,但是微服务框架本身也有优缺点。通过两个常用的微服务框架集成 etcd 的案例学习,可以帮助你对 etcd 的使用有一个更深的理解,在此基础上自行封装适合业务场景的框架。
最后,我们来做一个互动:你在项目中使用的是哪种服务发现与注册组件,又使用什么样的微服务框架呢?欢迎你在留言区和我分享。下一讲我们将介绍 etcd 在 Kubernetes 中如何保证容器的调度。