Go Micro(4)——基于消息队列NATS构建微服务
这篇文章我们会讨论基于 NATS 使用 Micro。讨论包括了服务发现,同步通信和异步通信。
NATS是什么?
NATS
是一个开源的消息系统,或者说消息队列。NATS
的作者是 Derek Collison, Apcera 的作者。它起源于 VMWare
,最开始是一个 ruby
的系统。后来使用 golang
进行重写,逐步的成为了一个高扩展性的高性能消息系统。
为什么是NATS?
为什么不是呢?过去我使用过很多消息队列,很明显 NATS
是鹤立鸡群的,在过去消息系统似乎成了企业的灵丹妙药了,结果是每个人参与的每个系统都离不开消息系统。导致了很多无效的承诺、高昂的研发投入,制造出比它解决的更多的麻烦。
NATS
,相比之下,就十分专注,在难以置信的简洁之下,解决了性能问题,解决了高可用行问题。它的口号是『always on and available』,使用了一种『fire and forget』的消息模式。它简单、专注、轻量的特性使它在微服务的候选中脱颖而出。我们相信,在服务间的消息传递领域,它很快会变成主要的候选者。
NATS能提供什么?
- 高性能、高扩展
- 高可用
- 极致的轻量级
- 一次部署
NATS不支持什么?
- 持久化
- 事务
- Enhanced delivery modes
- Enterprise queueing
NATS
是否适合 Micro
呢?我们接着讨论
Micro on NATS
Micro
采用插件化的架构设计,用户可以替换底层的实现,而不更改任何底层的代码。每个 Go-Micro
框架的底层模块定义了相应的接口,registry
是作为服务发现,transport
作为同步通信,broker
作为异步通信。
type Transport interface {
Dial(addr string, opts ...DialOption) (Client, error)
Listen(addr string, opts ...ListenOption) (Listener, error)
String() string
}
type Registry interface {
Register(*Service, ...RegisterOption) error
Deregister(*Service) error
GetService(string) ([]*Service, error)
ListServices() ([]*Service, error)
Watch() (Watcher, error)
String() string
}
type Broker interface {
Options() Options
Address() string
Connect() error
Disconnect() error
Init(...Option) error
Publish(string, *Message, ...PublishOption) error
Subscribe(string, Handler, ...SubscribeOption) (Subscriber, error)
String() string
}
为每个组件创建一个插件很简单,只需要实现这些组件的接口即可。我们会在未来的文章里详细讨论,怎样写插件,如果你想详细了解 NATS
的插件或者其他的类似 etcd
作为服务发现,kafka
作异步通信,rabbitmq
作同步通信,在这里可以看到: github.com/micro/go-plugins
Micro on NATS
本质上是实现一系列的插件,将 NATS
消息系统整合到 Micro
的框架中来。通过为 Go Micro
提供不同的插件,我们可以创造出很多灵活的组合。
在实践过程中,不能一套系统打天下,Micro on NATS
的可以灵活的定义各种模型。
下面我们会讨论一下 NATS
插件怎样在 transport
、 broker
和 registry
下工作。
Transport
[图片上传失败…(image-a3bc83-1513577247790)]
transport
是 go-micro
定义的同步通信接口,它使用了泛型的语法描述,类似其他 golang
代码,比如 Liesten,Dial,Accept
。这些理念和模式在类似 TCP
和 HTTP
这样的同步通信中很容易理解,但是在消息系统中要怎样兼容呢?通信过程中,连接是与消息队列建立的,而不是服务本身。为了达到目的,我们使用消息系统中的 topics
和 channels
来创造虚连接。
它是怎样工作起来的?
一个服务使用 transport.Listen
来监听消息,这会与 NATS
之间建立连接。当 transport.Accept
被调用时,一个唯一的 topic
会被创建并订阅。这个唯一的 topic
地址会作为服务的地址,注册到 go-micro
的注册器中。每个收到的消息会被虚连接处理,如果一个连接已经存在,而且回复的地址是一样的,我们会简单的把消息积压在连接上。
客户端通过 transport.Dial
来连接服务端,其实它是建立了与 NATS
的连接,然后创建了唯一的 topic
并且订阅这个 topic
。这个 topic
用于接收服务端的返回。任何时候客户端发送消息到服务端时,它都会把回复地址设置成这个 topic
。(译注:服务端的地址是固定的,而客户端的每个请求,都会生成一个客户端地址,唯一的请求与唯一的地址实现一一关联)
当任何一边想要关闭连接时,简单的调用 transport.Close
就会断开与 NATS
的连接。
[图片上传失败…(image-a17b4e-1513577247790)]
使用 transport
插件
引用插件
import _ "github.com/micro/go-plugins/transport/nats"
启动时传入参数
go run main.go --transport=nats --transport_address=127.0.0.1:4222
或者直接在代码中创建
transport := nats.NewTransport()
go-micro
的 transport
接口:
type Transport interface {
Dial(addr string, opts ...DialOption) (Client, error)
Listen(addr string, opts ...ListenOption) (Listener, error)
String() string
}
Broker
[图片上传失败…(image-14ecc9-1513577247790)]
broke
是 go-micro
的异步通信接口,它定义了泛型的、高等级的通用接口。NATS
本身作为消息系统,是可以作为消息 broker
的。这里只有一个警告:nats
并不持久化消息。虽然在某些场景有风险,但我们相信 NATS
可以也应该用于 go-micro
的 broker
。持久化并不是必须的,它提供了高扩展的发布和订阅架构。
NATS
通过 Topics
和 Channels
的理念,非常直接的实现了发布和订阅机制。这里并没有其他工作需要做。消息可以被发布到任何异步的 fire and forget manner。通过 NATS
的 Queue Group,订阅方可以通过 channel
的名字就行订阅。实现消息自动的分发的多个订阅者。
[图片上传失败…(image-be955d-1513577247790)]
使用 broker
插件
引入包
import _ "github.com/micro/go-plugins/broker/nats"
(译注:注意此时引用的是 broker
中的 nats
包,上面引用的是 transport
中的 nats
包)
通过参数启动
go run main.go --broker=nats --broker_address=127.0.0.1:4222
也可以直接启动
broker := nats.NewBroker()
go-micro
的 broker
需要实现的接口:
type Broker interface {
Options() Options
Address() string
Connect() error
Disconnect() error
Init(...Option) error
Publish(string, *Message, ...PublishOption) error
Subscribe(string, Handler, ...SubscribeOption) (Subscriber, error)
String() string
}
Register
[图片上传失败…(image-f5229e-1513577247790)]
Register
是 go-micro
定义的服务发现接口,你也许想过。用消息系统做服务发现?这能工作吗?事实上它不仅能工作,还工作的很好。许多人在服务发现中使用消息系统,避免了不一致的发现机制。这是因为消息队列通过 topics
和 channels
实现了路由,Topics
定义为服务的名字可以作为路由的 key
,订阅 topics
的服务自动实现了负载均衡。
Go-micro
把服务发现和传输机制看做两个不同的领域,任何时候,客户端向服务发起请求,都会首先在注册器中根据服务的名字查询到正在运行的服务节点,然后客户端通过 transport
与节点进行通信。
一般来说,最常见的存储服务发现信息的方式是通过分布式 key-value store
,比如 zookeeper、etcd
等等。正如你了解到的,NATS
不是一个分布式的 KV store
,我们将做一下改动。。。
广播查询!
广播查询正如你想象的那样,服务都会监听一个特点的 topic
,任何需要服务发现信息的都需要订阅这个 topic
,然后对这个 topic
做一个反馈。
因为我们并不知道有多少服务正在运行,我们对响应时间设置一个上限。这是一个粗糙的服务发现机制,但因为 NATS
本身的高扩展性和高性能,它运行的反而非常好。它也间接的提高了过滤服务节点的响应时间。未来我们会试着提高底层的实现。
我们总结一下它是怎么工作的:
- 创建一个
reply topic
并订阅 - 向广播
topic
发起查询,附带上reply topic
- 监听回复,超过限制时间就注销订阅
- 聚合响应并返回结果
[图片上传失败…(image-9adecd-1513577247790)]
使用 register
插件
引入包
import _ "github.com/micro/go-plugins/registry/nats"
通过参数启动
go run main.go --registry=nats --registry_address=127.0.0.1:4222
或者直接在代码中设置
registry := nats.NewRegistry()
go-micro
中的 register
接口
type Registry interface {
Register(*Service, ...RegisterOption) error
Deregister(*Service) error
GetService(string) ([]*Service, error)
ListServices() ([]*Service, error)
Watch() (Watcher, error)
String() string
}
大规模在 Micro 中使用 NATS
在上面的例子中,我们只是用了单个的 NATS
服务,但是在实际情况下,我们一般都是使用 NATS
集群来实现高可用和容错。你可以在这里看看更多 NATS
集群的知识。
Micro
在启动时可以接收一系列的参数,如环境变量等。如果直接使用 client
的库,也可以在创建时指定参数。
在目前云计算的架构下,我们过去的经验是,每个 AZ
都应该有集群。大部分的云计算公司,不同的 AZ
之间的延迟大概是3到5毫秒,集群的通信是没有任何问题的。当我们运行一个高可用的配置,你的系统必须要能在 AZ
宕机、甚至整个 region
崩溃时还能提供服务。我们不建议跨 region
部署集群。理想的高等级工具应该是用于管理多集群和多 region
系统。
Micro
是一个极其灵活的微服务系统,它本身就被设计成运行在任何配置的任何地方。整个 Micro
世界是通过服务发现进行指导,服务的集群可以运行在机器集群中,AZ
或者 regions
。再考虑到 NATS
集群,它可以让你构建高可用的服务。
[图片上传失败…(image-92f6b7-1513577247790)]
总结
NATS
是一个高可用和高性能的消息系统,在我们的微服务生态系统中运行的很好。它与 Micro
配合的非常的好,我们可以把它用在 register,transport,broker
中,我们也已经全部实现了这些插件。
NATS
在 Micro
中的使用只是一个示例,它展示了 Micro
高度的插件化架构,每个 go-micro
的包都可以被替换,底层不需要改动代码,上层也只需要改动非常少的代码。在未来我们还会看到更多的 Micro on X
,下一个可能是 Micro on kubernetes
。
希望这可以鼓励你来使用 Micro on NATS
,或者编写自己的插件并回馈给社区。
在这里看更多的NATS插件 github.com/micro/go-plugins