事件驱动架构 理解起来比较简单,普遍认为好的软件架构都是解耦的,微服务之间不应该相互耦合或依赖。举个例子,我们在代码中调用微服务 go.srv.user-service 的函数,会先通过服务发现找到微服务的地址再调用,我们的代码与该微服务有了直接性的调用交互,并不算是完全的解耦。
源码地址
发布与订阅模式
为了理解事件驱动架构为何能使代码完全解耦,先了解事件的发布、订阅流程。微服务 X 完成任务 x 后通知消息系统说 “x 已完成”,它并不关心有哪些微服务正在监听这个事件、事件发生后会产生哪些影响。如果系统发生了某个事件,随之其他微服务都要做出动作是很容易的。
举个例子,user-service 创建了一个新用户,email-service 要给该用户发一封注册成功的邮件,message-service 要给网站管理员发一条用户注册的通知短信。
一般实现
在 user-service 的代码中实例化另两个微服务 Client 后,调用函数发邮件和短信,代码耦合度很高。如下图:
事件驱动
在事件驱动的架构下,user-service 只需向消息系统发布一条 topic 为 “user.created” 的消息,其他两个订阅了此 topic 的 service 能知道有用户注册了,拿到用户信息后他们自行发邮件、发短信。如下图:
代码实现
Publish 事件发布
package main
import (
"context"
"github.com/asim/go-micro/v3"
"github.com/asim/go-micro/v3/metadata"
"github.com/asim/go-micro/v3/server"
"github.com/asim/go-micro/v3/util/log"
proto "go-micro-examples/pubsub/proto"
)
// Sub All methods of Sub will be executed when
// a message is received
type Sub struct{}
// Process Method can be of any name
func (s *Sub) Process(ctx context.Context, event *proto.Event) error {
md, _ := metadata.FromContext(ctx)
log.Logf("[pubsub.1] Received event %+v with metadata %+v\n", event, md)
// do something with event
return nil
}
// Alternatively a function can be used
func subEv(ctx context.Context, event *proto.Event) error {
md, _ := metadata.FromContext(ctx)
log.Logf("[pubsub.2] Received event %+v with metadata %+v\n", event, md)
// do something with event
return nil
}
func main() {
// create a service
service := micro.NewService(
micro.Name("go.micro.srv.pubsub"),
)
// parse command line
service.Init()
// register subscriber
if err := micro.RegisterSubscriber("example.topic,pubsub.1", service.Server(), new(Sub)); err != nil {
log.Fatal(err)
}
// register subscriber with queue, each message is delivered to a unique subscriber
if err := micro.RegisterSubscriber("example.topic.pubsub.2", service.Server(), subEv, server.SubscriberQueue("queue.pubsub")); err != nil {
log.Fatal(err)
}
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
运行效果如下:
可以看到,直接使用 go-micro 的 NewEvent 订阅即可,成功订阅 example.topic.pubsub.1、example.topic.pubsub.2
Subscribe 事件订阅
事件既然有订阅,那么当然会有订阅
package main
import (
"context"
"fmt"
"github.com/asim/go-micro/v3"
"github.com/asim/go-micro/v3/util/log"
"github.com/pborman/uuid"
proto "go-micro-examples/pubsub/proto"
"time"
)
// send events using the publisher
func sendEv(topic string, p micro.Publisher) {
t := time.NewTimer(time.Second)
for _ = range t.C {
// crate new event
ev := &proto.Event{
Id: uuid.NewUUID().String(),
Timestamp: time.Now().Unix(),
Message: fmt.Sprintf("Messaging you all day on %s", topic),
}
log.Logf("publishing %+v\n", ev)
// publish an event
if err := p.Publish(context.Background(), ev); err != nil {
log.Logf("error publishing %v", err)
}
}
}
func main() {
// create a service
service := micro.NewService(
micro.Name("go.micro.cli.pubsub"),
)
// parse command line
service.Init()
// create publisher
pub1 := micro.NewEvent("example.topic.pubsub.1", service.Client())
pub2 := micro.NewEvent("example.topic.pubsub.2", service.Client())
// pub to topic 1
go sendEv("example.topic.pubsub.1", pub1)
// pub to topic 2
go sendEv("example.topic.pubsub.2", pub2)
// block forever
select {}
}
运行效果如下:
从图中可以看出,成功订阅事件并调用成功