在上篇文章中我们创建了一个简单的项目, 并过将它运行起来。本篇将继续这个旅程,先介绍项目结构及其中每个文件的用途。
项目结构如下:
.
├── main.go
├── generate.go
├── plugin.go
├── proto/hello
│ └── hello.proto
│ └── hello.pb.go
│ └── hello.pb.micro.go
├── handler
│ └── hello.go
├── subscriber
│ └── hello.go
├── Dockerfile
├── go.mod
├── go.sum
├── Makefile
└── README.md
每个文件的说明为:
注:文件夹 proto有特殊含义。虽然在技术上没有限制, 但在 Micro 的约定中,每个项目根目录下的proto文件夹专门用来存放“接口”文件。这既包含本项目需要对外暴露的接口, 也包含本项目所依赖其它接口。举例来说, 假如我们实现业务逻辑时需要依赖另外一个服务 foo。那么我们会建立proto/foo 文件夹,并在其中放置 foo.proto, foo.pb.go, foo.pb.micro.go 三个文件,供业务代码调用。
接下来看一看启动代码,main.go:
package main
import (
"github.com/micro/go-micro/util/log"
"github.com/micro/go-micro"
"hello/handler"
"hello/subscriber"
hello "hello/proto/hello"
)
func main() {
// New Service
service := micro.NewService(
micro.Name("com.foo.srv.hello"),
micro.Version("latest"),
)
// Initialise service
service.Init()
// Register Handler
hello.RegisterHelloHandler(service.Server(), new(handler.Hello))
// Register Struct as Subscriber
micro.RegisterSubscriber("com.foo.srv.hello", service.Server(), new(subscriber.Hello))
// Register Function as Subscriber
micro.RegisterSubscriber("com.foo.srv.hello", service.Server(), subscriber.Handler)
// Run service
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
代码大体分 4 个部分,分别是导入依赖、创建及初始化服务、注册业务处理 Handler 和运行服务。
导入依赖
这部分只有一行代码值得单独说明:
hello "hello/proto/hello"
导入时定义了别名。这也是 Micro 的一个习惯约定:对所有接口导入包设置别名。这样就可以避免依赖导入代码的包名。实践中, 如果不作特别设置,自动生成代码的包名会比较长, 以 hello.pb.go 为例, 它的包名是 com_foo_srv_hello。显然设置一个别名是更好的选择
创建及初始化服务
// New Service
service := micro.NewService(
micro.Name("com.foo.srv.hello"),
micro.Version("latest"),
)
创建服务用到了 micro.NewService(opts …Option) Service 方法。此方法可接收多个 micro.Option 为参数, 生成并返回 micro.Service 接口实例。
可见 micro.Option 是控制服务的关键。示例代码用 Option 分别指定了服务的名称和版本号。目前共有 25 个 Option 可供使用, 能够控制服务的方方面面。有些 Option 可以指定多次,形成叠加效果(后面会提到)。
但是, 如此重要的选项竟没有任何一份说明文档,想要学习只能去查看源码[4]。而很多 Option 的源码中连注释也没有,这进一步提高了学习的难度。虽然本文并不打算成为完备的 Micro 参考手册,但这些 Option 对于理解和使用 Micro 非常重要,又没有其它资料可参考, 所以我决定列出 v1.18.0 版本中全部 25 个 Option。逐一加以说明:
因此,通过在创建时指定恰当的 Option,便可以高度定制服务的行为。例如要想修改注册信息有效期:
...
// New Service
service := micro.NewService(
micro.Name("foo.bar"),
micro.Version("v1.0"),
// change default TTL value
micro.RegisterTTL(5 * time.Minute),
...
)
...
注:上述大部分 Option 可以通过多种方式指定。在源码中硬编码只是几种其中之一。事实上, Micro 建议用户优先通过环境变量来指定某些 Option, 因为这样可以提供更大的灵活性。以micro.RegisterTTL 为例 , 我们可以在运行时通过环境变量 **$**MICRO_REGISTER_TTL或者命令行参数 --register_ttl value 来指定(单位是秒)。运行 ./hello-srv -h 可以看到这些内置参数的简要说明。如果想了解全部细节,目前没有完整文档,需要自行查看 newCmd[9] 源码。本系列后续文章对此话题会作进一步解读。
创建之后就可以初始化服务了:
// Initialize service
service.Init()
service.Init 方法可以接收与 micro.NewService 相同的参数。所以上述 25 个 Option 也可以用在 service.Init方法中。他们效果相同只是时机有差异。由于此时服务已经创建, 我们可以使用服务实例的某些信息。例如,可自动读取随机端口:
// Initialize service
service.Init(
// print log after start
micro.AfterStart(func() error {
log.Infof("service listening on %s!",
service.Options().Server.Options().Address,
)
return nil
}),
)
注册业务处理 Handler
// Register Handler
hello.RegisterHelloHandler(service.Server(), new(handler.Hello))
// Register Struct as Subscriber
micro.RegisterSubscriber("com.foo.srv.hello", service.Server(), new(subscriber.Hello))
// Register Function as Subscriber
micro.RegisterSubscriber("com.foo.srv.hello", service.Server(), subscriber.Handler)
只有在完成 Handler 注册后, 我们的业务代码才能真正对外提供服务。这里展示了 3 个典型的注册操作:
关于消息处理的更多细节, 我们将在后续文章中专门说明。
运行服务
if err := service.Run(); err != nil {
log.Fatal(err)
}
至此, 服务便真正运行起来了
上一篇文章提到, micro 这个命令行工具可以用来在运行时查看和操作服务。下面我们来试一下。
在服务启动之后, 运行 micro web命令:
$ micro web
2020/01/15 18:13:25 : [web] HTTP API Listening on [::]:8082
2020/01/15 18:13:25 : [web] Transport [http] Listening on [::]:59005
2020/01/15 18:13:25 : [web] Broker [http] Connected to [::]:59006
2020/01/15 18:13:25 : [web] Registry [mdns] Registering node: go.micro.web-950a8b2b-003d-47c1-a512-53aedebc9d12
可见此命令已在本机 8082 端口上服务。注:8082 端口是默认值,可以通过环境变量或命令行参数修改。具体可以运行 micro web -h查看说明
从浏览器访问 http://127.0.0.1:8082/registry?service=com.foo.srv.hello 将能以网页形式查看服务状态。截图如下:
从上图中, 我们可以看到该服务的各种关键信息:
可见通过 micro web 可以很方便的了解各种运行时状态。你可能会问, 我们的服务与 micro web 之间并没有互相调用, 它是怎么知道这些信息的呢?答案在于前文提到的服务发现。 Micro 内置支持服务发现, 在未作特别设置的情况下, 默认的服务发现是基于 mDNS 的, 因此只要在同一个局域内, 就可以自动发现彼此。
当然 micro web 的功能不只于此,我们只是展现与本篇主题相关的内容。后续文章会展开介绍。
本文是 Micro in Action 系列的第二篇文章, 我们作了几件事:
[1]
Micro: https://micro.mu/
[2]
约定: https://micro.mu/docs/plugins.html#usage
[3]
服务定义: https://grpc.io/docs/guides/concepts/
[4]
源码: https://github.com/micro/go-micro/blob/v1.18.0/options.go
[5]
Gin Middleware: https://github.com/gin-gonic/gin#using-middleware
[6]
micro/cli: https://github.com/micro/cli
[7]
micro/cli: https://github.com/micro/cli
[8]
newCmd: https://github.com/micro/go-micro/blob/v1.18.0/config/cmd/cmd.go#L263
[9]
newCmd: https://github.com/micro/go-micro/blob/v1.18.0/config/cmd/cmd.go#L263
[10]
https://mp.weixin.qq.com/s/WG3wv-FWPLoJIWYEVNdNsA