![Micro In Action](https://s1.ax1x.com/2020/03/28/Gkcgw6.png)
> 本文作者:Che Dan
>
> 原文链接:<https://medium.com/@dche423/micro-in-action-6-service-discovery-cn-c13c3e3829d>
本文是[Micro](https://micro.mu/)系列文章的第六篇。我们将从最基本的话题开始,逐步转到高级特性。
今天来谈谈Micro中的服务发现。
------
本系列的第一篇文章曾提到,Micro以插件的形式支持多种服务发现系统。默认情况下,服务发现基于组播DNS(mDNS)机制, 无需任何配置,非常便于在开发环境中使用。
但是在生产环境中, 我们需要可靠性更高的产品。高可靠的注册中心有很多, 例如 etcd/consul /zookeeper/eureka 等等。 针对这些产品, Micro都提供了相应的插件,它甚至提供了[Kubernetes插件](https://github.com/micro/go-plugins/tree/master/registry/kubernetes), 以便把Kubernetes用作注册中心。
在所有这些产品中, etcd是目前最主流的。它也是[Micro官方推荐](https://medium.com/microhq/deprecating-consul-in-favour-of-etcd-421941a538a6)并内置支持的方案。 因此若想在Micro 中使用etcd,与mDNS一样,无需任何插件即可使用。
------
## 连接etcd
下面我们将尝试在示例项目中使用etcd。
etcd本身的安装与配置不是本文的重点(可以[etcd官网](https://etcd.io/)找到更多相关信息),我们假设你已经拥有一个三节点的etcd集群, 地址分别是
- etcd1.foo.com:2379
- etcd2.foo.com:2379
- etcd3.foo.com:2379
由于Micro内置支持etcd,我们无需对**hello-srv**项目的源码作任何修改,只需在项目启动时指定两个参数:
1. registry,指定注册中心类型,这里我们指定为**etcd。**环境变量$MICRO_REGISTRY 作用与此参数相同。
2. registry_address,指定注册中心地址,多个地址用逗号分隔。环境变量$MICRO_REGISTRY_ADDRESS 作用与此参数相同。
指定参数并运行程序的结果如下:
```bash
$ go run main.go plugin.go --registry=etcd --registry_address=etcd1.foo.com:2379,etcd2.foo.com:2379,etcd3.foo.com:2379
2020-02-20 16:48:05.402863 I | Transport [http] Listening on [::]:54158
2020-02-20 16:48:05.402957 I | Broker [http] Connected to [::]:54159
2020-02-20 16:48:05.403169 I | Registry [etcd] Registering node: com.foo.srv.hello-2a891470-6cc0-45bc-88e2-c2ffe6cfbc65
2020-02-20 16:48:05.405109 I | Subscribing com.foo.srv.hello-2a891470-6cc0-45bc-88e2-c2ffe6cfbc65 to topic: com.foo.srv.hello
2020-02-20 16:48:05.405289 I | Subscribing com.foo.srv.hello-2a891470-6cc0-45bc-88e2-c2ffe6cfbc65 to topic: com.foo.srv.hello
```
注意其中的 **Registry** [**etcd**] 字样, 这说明此刻已经与etcd集群连接成功!
同样道理, 保持服务器运行的情况下, 运行本系列的[第三篇文章](https://medium.com/@dche423/micro-in-action-getting-start-cn-99c870e078f)中创建的客户端项目, 即可完成对服务的调用。
```bash
$ go run main.go plugin.go --registry=etcd --registry_address=etcd1.foo.com:2379,etcd2.foo.com:2379,etcd3.foo.com:2379Hello Bill 4
$
```
注: 除了命令行参数,我们也可以通过环境变量来控制注册中心的选择,以上面的程序为例,也可以用如下方式运行:
```bash
$ MICRO_REGISTRY=etcd MICRO_REGISTRY_ADDRESS=etcd1.foo.com:2379,etcd2.foo.com:2379,etcd3.foo.com:2379 go run main.go plugin.goHello Bill 4
$
```
可见,在Micro中使用etcd是非常简单的。**如果没有特殊理由, 推荐使用etcd作为注册中心。**
另外,由于 `micro`命令行工具与其它Micro项目采用相同架构, 所以micro命令行工具也可以用同样的方式指定注册中心。 例如:
```bash
$ micro web --registry=etcd --registry_address=etcd1.foo.com:2379,etcd2.foo.com:2379,etcd3.foo.com:2379...$ micro get service com.foo.srv.hello --registry=etcd --registry_address=etcd1.foo.com:2379,etcd2.foo.com:2379,etcd3.foo.com:2379...
```
不过这仅限于内置的etcd和mdns, 如果想使用其它插件, 则需要重新编译`micro`,这显然不是一个好方法。Micro 似乎也支持运行时加载插件,如果这是可行的, 那将是比较理想的方式,与此相关的细节会在后续文章中单独探讨
### 连接consul
虽然Micro推荐使用etcd,但并不限制你作其它选择。下面我们用实例说明Micro中consul的使用方法。
假设已经三节点的consul集群,其地址分别为
- consul1.foo.com:8300
- consul2.foo.com:8300
- consul3.foo.com:8300
指定相关参数,运行**hello-srv**:
```bash
$ go run main.go plugin.go --registry=consul --registry_address=consul1.foo.com:8300,consul2.foo.com:8300,consul3.foo.com:8300
NAME:
- a go-micro service
USAGE:
main [global options] command [command options] [arguments...]
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--client value Client for go-micro; rpc [$MICRO_CLIENT]
--client_request_timeout value Sets the client request timeout. e.g 500ms, 5s, 1m. Default: 5s [$MICRO_CLIENT_REQUEST_TIMEOUT]
...
```
**服务并没有顺利启动,**只打印一些参数提示信息。
这体现了Micro命令行处理的一个弊端: **当不能正确处理传入参数时 , 框架不能清晰地反馈错误所在**。这对于新手开发者非常不友好(曾经浪费了我不少时间)。
在本例中, 错误的原因是**需要在源码中引入consul插件**。
前面文章中提到, 每个项目中都有一个名为 **plugin.go** 的空文件, 这是Micro约定用来导入插件的地方。为解决上述问题, 我们修改此文件:
```go
package main
import (
_ "github.com/micro/go-plugins/registry/consul"
)
```
当然, 同时需要修改go.mod文件:
```bash
module hello
go 1.13
require (
github.com/golang/protobuf v1.3.2
github.com/micro/go-micro v1.18.0
github.com/micro/go-plugins v1.5.1
)
```
注意其中 **v1.5.1** 非常重要。 因为这是与micro **v1.18.0** 相匹配的插件版本。 若不指定, 会引来其它错误。
修改源码后再运行:
```bash
$ go run main.go plugin.go --registry=consul --registry_address=consul1.foo.com:8300,consul2.foo.com:8300,consul3.foo.com:8300
2020-02-20 16:48:05.402863 I | Transport [http] Listening on [::]:54158
2020-02-20 16:48:05.402957 I | Broker [http] Connected to [::]:54159
2020-02-20 16:48:05.403169 I | Registry [consul] Registering node: com.foo.srv.hello-2a891470-6cc0-45bc-88e2-c2ffe6cfbc65
2020-02-20 16:48:05.405109 I | Subscribing com.foo.srv.hello-2a891470-6cc0-45bc-88e2-c2ffe6cfbc65 to topic: com.foo.srv.hello
2020-02-20 16:48:05.405289 I | Subscribing com.foo.srv.hello-2a891470-6cc0-45bc-88e2-c2ffe6cfbc65 to topic: com.foo.srv.hello
```
至此, 我们的程序已经成功连接了consul集群。看起来也非常简单。
不过你可能会有疑惑, 为什么**registry**参数的值是**consul**,而不是**cnsl** 或其它什么? 到哪里能找到插件的名字呢?
这又回到了Micro一直以来作得不好的地方:文档不足。要解答上述疑惑只有一个办法: 看源码 。
不过我在这里会给你一些线索和规律, 希望可以提高找答案的速度。以consul插件为例, 相关的关键源码在 **github.com/micro/go-plugins/registry/consul/consule.go**里 :
```go
func init() {
cmd.DefaultRegistries["consul"] = NewRegistry
}
```
一般情况下, 每一个插件源码中都会有一个与插件同名的源文件。这算是插件的主文件。在这个主文件中的 `init` 函数是插件“注册”的地方。 这里可以看到插件的名字。在本例中,就是 **consul。** 其它插件也基本遵循这个约定。
------
上述两个示例充分体现了Micro插件架构的优越性。无论是使用内置支持的etcd还是consul, 我们**几乎无需修改代码。** 框架把不同产品的差异和复杂度完全隔离在了业务开发之外。 由于对业务代码没有侵入, 使得我们可以方便地集成和切换不同注册中心。
当然Micro的插件优势不仅体现在这里, 我们在后续的文章将看到更多示例。
## 总结
Micro几乎支持市面上所有注册中心产品,本文以etcd和consul为例进行了具体说明。
虽然有少量细节不尽如人意, 但总体上Micro在服务发现方面的功能是完备和优雅的。
由于有统一的理念贯穿整个Micro生态,使得我们可以用一致的方式操作官方提供的命令行工具和自己开发的项目。
如果你曾经“手工”处理过分布式系统的服务发现问题, 对比之下你会为Micro化繁为简的能力点赞。
有疑问加站长微信联系(非本文作者))