本文是Micro[1]系列文章的第二篇。我们将以实际开发微服务为主线,顺带解析相关功能。从最基本的话题开始,逐步转到高级特性。
项目结构
在上篇文章中我们创建了一个简单的项目, 并过将它运行起来。本篇将继续这个旅程,先介绍项目结构及其中每个文件的用途。
注:由于本系列文章的主题是 Micro,所以不会讨论无关话题,
项目结构如下:
. ├── 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
每个文件的说明为:
- main.go ,项目主文件,后面会详细说明
- generate.go ,只包含一行 //go:generate make proto ,实现与 go generate 命令的集成。在运行 go generate 命令时自动调用 make proto
- plugins.go,目前是空文件, 根据 Micro 的约定[2], 建议在这里管理所需 plugin 的导入, 后续会用到。
- proto/hello/hello.proto,gRPC 服务定义[3]文件, 定义了 rpc 服务Hello,服务中提供 3 种典型 gRPC 调用:单向 RPC,单向 Stream 和双向 Stream
- **proto/hello/hello.pb.go,**根据上述 proto 文件, 由protoc 生成 gRPC 相关代码
- proto/hello/hello.pb.micro.go,由前文提到的 protoc-gen-micro 生成的, 进一步简化开发者的工作。其中定义了HelloSerivce 接口, 以及HelloHandler 接口。后者是我们需要去实现、完成业务逻辑的接口
- handler/hello.go ,实现 gRPC 业务逻辑的地方。其中定义了 Hello 对象, 此对象实现了前面提到 HelloHandler 接口。
- subscriber/hello.go,实现异步消息接收并处理的地方。其中展示了用两种不同方式处理消息,一是以对象方法处理, 二是以一个函数来处理。
- Dockerfile,定义如何构建 Docker 镜像
- go.mod / go.sum , Go Module 相关文件
- Makefile,包含了几个常用任务定义, 编译、测试、生在 Docker 镜像等
- 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。逐一加以说明:
- micro.Name(n string) Option , 指定服务名称。命名规则一般是“type.$name”。其中 namespace 代表项目的名称空间, type 代表服务类型(例如 gRPC 和 web),一般会把 gRPC service 类型缩写成 srv。服务实例运行后, 此名称将自动注册到 Registry, 成为服务发现的依据。默认为“go.micro.server”。注:因此此项必须要指定, 否则所有节点使用相同的默认名称,会导致调用混乱
- micro.Version(v string) Option,指定服务版本。默认为启动时间格式化的字符串。恰当地选择版本号再配合相应的 Selector, 可以实现优雅的轮转升级、灰度发布、A/B 测试等功能。
- micro.Address(addr string) Option,指定 gRPC 服务地址。默认为随机端口。由于客户端是通过注册中心来定位服务, 所以随机端口并不影响使用。但实践中经常是指定固定端口号的, 这会有利于运维管理和安全控制
- micro.RegisterTTL(t time.Duration) Option,指定服务注册信息在注册中心的有效期。默认为一分种
- micro.RegisterInterval(t time.Duration) Option,指定服务主动向注册中心报告健康状态的时间间隔, 默认为 30 秒。这两个注册中心相关的 Option 结合起来用,可以避免因服务意外宕机而未通知注册中心,产生“无效注册信息”
- micro.WrapHandler(w …server.HandlerWrapper) Option,包装服务 Handler, 概念上类似于 Gin Middleware[5], 集中控制 Handler 行为。可包装多层,执行顺序由外到内(后续会有实例)
- micro.WrapSubscriber(w …server.SubscriberWrapper) Option,与 WrapHandler 相似,不同之处在于它用来包装异步消费处理中的“订阅者”。
- micro.WrapCall(w …client.CallWrapper) Option,包装客户端发起的每一次方法调用。
- micro.WrapClient(w …client.Wrapper) Option,包装客户端,可包装多层, 执行顺序由内到外。
- micro.BeforeStart(fn func() error) Option,设置服务启动前回调函数,可设置多个。
- micro.BeforeStop(fn func() error) Option,设置服务关闭前回调函数,可设置多个。
- micro.AfterStart(fn func() error) Option,设置服务启动后回调函数,可设置多个。
- micro.AfterStop(fn func() error) Option,设置服务关闭后回调函数,可设置多个。
- micro.Action(a func(*cli.Context)) Option,处理命令行参数。支持子命令及控制标记。详情请见 micro/cli[6]
- micro.Flags(flags …cli.Flag) Option,快捷支持命令行控制标记, 详情请见micro/cli[7]
- micro.Cmd(c cmd.Cmd) Option, 指定命令行处理对象。默认由 newCmd[8]生成,此对象包含了一系列默认的环境变量、命令行参数支持。可以看作是多个内置 cli.Flag 的集合。注:go-micro 框架对命令行处理的设计方案有利有弊。利是提供大量默认选项,可以节省开发者时间。弊是此设计对用户程序的有强烈的侵入性:框架要求开发者必须以 micro/cli 统一要求的方式来处理命令行参数。如若不然, 程序会报错无法运行。例如,我们运行 ./hello-srv --foo=bar 就会报出“Incorrect Usage. flag provided but not defined: -foo=bar”的错误。好在有这个 Option,可以弥补这种强侵入性带来的弊端。假如一个现存项目想引入 Micro ,而它已经有自己的参数处理机制, 那么就需要使用此 Option 覆盖默认行为(同时丢掉一些默认的参数处理能力)。关于命令行参数, 本文后面部分有进一步解释。
- micro.Metadata(md map[string]string) Option,指定服务元数据。元数据时常被用来为服务标记与分组, 实现特定的负载策略等
- micro.Transport(t transport.Transport) Option,指定传输协议, 默认为 http 协议
- micro.Selector(s selector.Selector) Option ,指定节点选择器, 实现不同负载策略。默认为随机 Selector
- micro.Registry(r registry.Registry) Option,指定用于服务发现的注册机制, 默认为基于 mDNS 的注册机制
- micro.Server(s server.Server) Option, 指定自定义 Server, 用于默认 Server 不满足业务要求的情况。默认为 rpcServer
- micro.HandleSignal(b bool) Option, 是否允许服务自动响应 TERM, INT, QUIT 等信号。默认为 true
- micro.Context(ctx context.Context) Option,指定服务初始 Context,默认为 context.BackGround(),可用于控制服务生存期及其它
- micro.Client(c client.Client) Option,指定对外调用的客户端。默认为 rpcClient
- micro.Broker(b broker.Broker) Option, 指定用于 发布/订阅 消息通讯的 Broker。默认为 http broker
因此,通过在创建时指定恰当的 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 个典型的注册操作:
- 注册 gRPC handler。创建handler.Hello对象, 并注册到 Server 上。由于handler.Hello实现了HelloHandler 接口, 所以它才可以作为hello.RegisterHelloHandler 的方法参数被传入,否则会报错。一个服务中可以注册多个 Handler 以完成不同业务功能。
- 注册消息处理对象。第一个参数为消息 Topic, 第二个参数是 Server, 第三个参数是消息处理对象。
- 注册消息处理函数。与对象注册相似, 只是第三个参数是对应的消息处理函数
关于消息处理的更多细节, 我们将在后续文章中专门说明。
运行服务
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 将能以网页形式查看服务状态。截图如下:
从上图中, 我们可以看到该服务的各种关键信息:
- 服务名称。
- 服务节点列表。如果此服务有多个节点同时运行, 此处会看到多行
- 每个节点中显示了版本号, 名称,编一 ID,地址,元数据等
- Endpoints。服务的接口定义, 方法名,参数结构与数据类型等等
可见通过 micro web 可以很方便的了解各种运行时状态。你可能会问, 我们的服务与 micro web 之间并没有互相调用, 它是怎么知道这些信息的呢?答案在于前文提到的服务发现。 Micro 内置支持服务发现, 在未作特别设置的情况下, 默认的服务发现是基于 mDNS 的, 因此只要在同一个局域内, 就可以自动发现彼此。
当然 micro web 的功能不只于此,我们只是展现与本篇主题相关的内容。后续文章会展开介绍。
总结
本文是 Micro in Action 系列的第二篇文章, 我们作了几件事:
- 介绍了上篇文章所创建的项目结构, 说明每一个文件的用途。
- 对照源码逐行分析一个 Micro 服务的启动过程。
- 考虑到 Micro 文档的缺失, 本文完整介绍了创建 Micro 服务所支持的全部 Option
- 最后用 micro web 查看了服务的运行时状态
推荐阅读
扫码二维码 获取免费视频学习资料
- 本文固定链接: http://phpxs.com/post/7141/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料