快捷搜索:  汽车  科技

微服务为什么选golang(Golang微服务教程五)

微服务为什么选golang(Golang微服务教程五)发布与订阅模式事件驱动架构 理解起来比较简单,普遍认为好的软件架构都是解耦的,微服务之间不应该相互耦合或依赖。举个例子,我们在代码中调用微服务 go.srv.user-service 的函数,会先通过服务发现找到微服务的地址再调用,我们的代码与该微服务有了直接性的调用交互,并不算是完全的解耦。更多参考:软件架构入门正如前几节所说,go-micro 是一个拔插式的框架,能与很多优秀的开源软件进行对接,可参考插件列表:go-plugins,可看到已支持很多优秀组件。事件驱动概念

Golang 微服务教程(五)

发表于 2018-05-28 | 阅读次数: 2664 | 字数统计: 2 048

原文链接:ewanvalentine.io,翻译已获作者 Ewan Valentine 授权。

本文完整代码:github

在上节中,我们使用 JWT 在微服务之间进行了用户的认证。在本节中,我们将使用 go-micro 结合 nats 插件来完成用户创建事件的发布与订阅。

正如前几节所说,go-micro 是一个拔插式的框架,能与很多优秀的开源软件进行对接,可参考插件列表:go-plugins,可看到已支持很多优秀组件。

事件驱动

概念

事件驱动架构 理解起来比较简单,普遍认为好的软件架构都是解耦的,微服务之间不应该相互耦合或依赖。举个例子,我们在代码中调用微服务 go.srv.user-service 的函数,会先通过服务发现找到微服务的地址再调用,我们的代码与该微服务有了直接性的调用交互,并不算是完全的解耦。更多参考:软件架构入门

发布与订阅模式

为了理解事件驱动架构为何能使代码完全解耦,先了解事件的发布、订阅流程。微服务 X 完成任务 x 后通知消息系统说 “x 已完成”,它并不关心有哪些微服务正在监听这个事件、事件发生后会产生哪些影响。如果系统发生了某个事件,随之其他微服务都要做出动作是很容易的。

事件驱动

在事件驱动的架构下,user-service 只需向消息系统发布一条 topic 为 “user.created” 的消息,其他两个订阅了此 topic 的 service 能知道有用户注册了,拿到用户信息后他们自行发邮件、发短信。如下图:

微服务为什么选golang(Golang微服务教程五)(1)

本节中,我们将在 user-service 创建一个新用户时发布一个事件,使得 email-service 给用户发送邮件。

代码实现

go-micro NATS 插件

我们先将 NATS 插件集成到我们的代码中:

// user-service/main.go func main() { ... // 初始化命令行环境 srv.Init() // 获取 broker 实例 pubSub := srv.Server().Options().Broker // 注册 handler pb.RegisterUserServiceHandler(srv.Server() &handler{repo &t pubSub}) ... }

需要注意的是,当 go-micro 创建微服务时,srv.Init() 会加载该微服务的所有配置,比如使用到的插件、设置的环境变量、命令行参数等,这些配置项会作为微服务的一部分来运行。可使用 s.Server().Options() 来获取这些配置。

我们在 Makefile 中设置了 GO_MICRO_BROKER 环境变量,go-micro 会使用该地址指定的 NATS 消息系统做事件的订阅和发布。

Publish 事件发布

当创建一个新用户时我们发布一个事件,完整代码见:GitHub

// user-service/handler.go const topic = "user.created" type handler struct { repo Repository tokenService Authable PubSub broker.Broker } func (h *handler) Create(ctx context.Context req *pb.User resp *pb.Response) error { // 哈希处理用户输入的密码 hashedPwd err := bcrypt.GenerateFromPassword([]byte(req.Password) bcrypt.DefaultCost) if err != nil { return err } req.Password = string(hashedPwd) if err := h.repo.Create(req); err != nil { return nil } resp.User = req // 发布带有用户所有信息的消息 if err := h.publishEvent(req); err != nil { return err } return nil } // 发送消息通知 func (h *handler) publishEvent(user *pb.User) error { body err := json.Marshal(user) if err != nil { return err } msg := &broker.Message{ Header: map[string]string{ "id": user.Id } Body: body } // 发布 user.created topic 消息 if err := h.PubSub.Publish(topic msg); err != nil { log.Fatalf("[pub] failed: %v\n" err) } return nil } ...

在运行前请确保你的 Postgres 容器正常运行:

$ docker run -d -p 5432:5432 postgres $ make build $ make run

Subscribe 事件订阅

现在创建新的邮件服务:email-service,创建新用户时将通知它发邮件。

package main import ( userPb "shippy/user-service/proto/user" "github.com/micro/go-micro" "log" "github.com/micro/go-micro/broker" _ "github.com/micro/go-plugins/broker/nats" "encoding/json" ) const topic = "user.created" func main() { srv := micro.NewService( micro.Name("go.micro.srv.email") micro.Version("latest") ) srv.Init() pubSub := srv.Server().Options().Broker if err := pubSub.Connect(); err != nil { log.Fatalf("broker connect error: %v\n" err) } // 订阅消息 _ err := pubSub.Subscribe(topic func(pub broker.Publication) error { var user *userPb.User if err := json.Unmarshal(pub.Message().Body &user); err != nil { return err } log.Printf("[Create User]: %v\n" user) go senEmail(user) return nil }) if err != nil { log.Printf("sub error: %v\n" err) } if err := srv.Run(); err != nil { log.Fatalf("srv run error: %v\n" err) } } func senEmail(user *userPb.User) error { log.Printf("[SENDING A EMAIL TO %s...]" user.Name) return nil }

在运行邮件服务之前确保 NATS 已运行:

1 $ docker run -d -p 4222:4222 nats

现在把 user-service 和 email-service 都运行起来,使用 user-cli 创建新用户 Ewan,能看到给用户 Ewan 发了一封邮件:

微服务为什么选golang(Golang微服务教程五)(2)

切换消息代理插件

值得一提的是基于 JSON 的 NATS 性能消耗会比 gRPC 更高一点,需要额外处理 JSON 字符串,只在少数应用场景十分适合。go-micro 也支持很多已被广泛使用的消息队列 / 发布订阅技术,参考:消息代理插件列表,因为 go-micro 做了抽象,在它们之间进行切换是十分容易的。比如你想将 nats 换为 googlepubsub:

// 修改容器的环境变量 // MICRO_BROKER=nats MICRO_BROKER=googlepubsub // 修改 user-service 导入的包 // _ "github.com/micro/go-plugins/broker/nats" _"github.com/micro/go-plugins/broker/googlepubsub"

如果你不使用 go-micro,可使用 Go 实现的 NATS,事件发布:

nc _ := nats.Connect(nats.DefaultURL) // Simple Publisher nc.Publish("user.created" userJsonString)

事件订阅:

// Simple Async Subscriber nc.Subscribe("user.created" func(m *nats.Msg) { user := convertUserString(m.Data) go sendEmail(user) })

使用如 NATS 的第三方消息代理插件,会让微服务失去使用 protobuf 进行二进制数据通信的优势(微服务之间不再直接调用),反而多了处理 JSON 数据的开销,不过 go-micro 对此早有对策。

Pubsub 层

go-micro 内置有 pubsub 层,其位于代理层的顶端,无需第三方 NATS 消息代理,恰如其分的使用了我们定义好的 protobuf,更新 user-service 使用 pubsub 代替 NATS:

// user-service/main.go func main() { ... publisher := micro.NewPublisher(topic srv.Client()) pb.RegisterUserServiceHandler(srv.Server() &service{repo tokenService publisher}) ... } // user-service/handler.go func (h *handler) Create(ctx context.Context req *pb.User resp *pb.Response) error { // 哈希处理用户输入的密码 hashedPwd err := bcrypt.GenerateFromPassword([]byte(req.Password) bcrypt.DefaultCost) if err != nil { return err } req.Password = string(hashedPwd) if err := h.repo.Create(req); err != nil { return nil } resp.User = req // 发布带有用户所有信息的消息 if err := h.Publisher.Publish(ctx req); err != nil { return err } return nil }

更新邮件微服务:

// email-service/main.go type Subscriber struct{} func main() { ... micro.RegisterSubscriber(topic srv.Server() new(Subscriber)) ... } func (sub *Subscriber) Process(ctx context.Context user *userPb.User) error { log.Println("[Picked up a new message]") log.Println("[Sending email to]:" user.Name) return nil }

现在我们微服务底层就使用了 User Protobuf 定义,并没有使用第三方消息代理。运行效果如下:

微服务为什么选golang(Golang微服务教程五)(3)

总结

本节先使用 go-micro 的 NATS 消息代理插件,使 user-service 在创建新用户时发布一个带有用户信息且 topic 为 “user.created” 的消息事件,订阅了此 topic 的 email-service 接收到消息后取出用户信息来发送邮件。之后使用 go-micro 自带的 pubsub 层代替了 NATS 充分发挥 protobuf 通信的优势。

下节我们将使用 React 编写微服务的管理界面,并研究一下 web 端如何与微服务交互。

猜您喜欢: