[从RPC到Go-Micro 捌]Go-Micro解耦利器--事件驱动机制

之前的文章中我们已经学习了使用go-micro创建微服务,并实现了服务的调用。我们具体的实现是实例化了client对象,并调用了对应服务的相关方法。这种方式可以实现系统功能,但有比较大的缺点。

我们通过举例来说明:在某个系统中存在用户服务(user service)、产品服务(product service)和消息服务(message service)。如果用户服务中要调用消息服务中的功能方法,则具体的实现方式可用下图所示方法表示:
无

按照正常的实现是在user service模块的程序中实例化message service的一个client,然后进行RPC调用,调用sendMessage来实现发送消息。

缺点

这种实现方式代码耦合度高,用户服务的模块中出现了消息服务模块的代码,不利于系统的扩展和功能的迭代开发。

发布/订阅机制

事件驱动

依然是上述的案例,用户服务在用户操作的过程中,需要调用消息服务的某个方法,假设为发送验证码消息的一个方法。为了使系统代码能够实现解耦,用户服务并不直接调用消息服务的具体的方法,而是将用户信息等相关数据发送到一个中间组件,该组件负责存储消息,而消息服务会按照特定的频率访问中间的消息存储组件,并取出其中的消息,然后执行发送验证码等操作。具体的示意图如下所示:
事件驱动
在上述的架构图中,我们可以看到,相较于之前的实现,多了一个中间的消息组件系统。

事件发布

只有当用户服务中的某个功能执行时,才会触发相应的事件,并将对应的用户数据等消息发送到消息队列组件中,这个过程我们称之为事件发布。

事件订阅

与事件发布对应的是事件订阅。我们增加消息队列组件的目的是实现模块程序的解耦,原来是程序调用端主动进行程序调用,现在需要由另外一方模块的程序到消息队列组件中主动获取需要相关数据并进行相关功能调用。这个主动获取的过程称之为订阅。

基于消息发布/订阅的消息系统有很多种框架的实现,常见的有:Kafka、RabbitMQ、ActiveMQ、Kestrel、NSQ等。

Broker

在我们介绍go-micro的时已经提到过,go-micro整个框架都是插件式的设计。没错,这里的发布/订阅也是通过接口设计来实现的。

定义

type Broker interface {
	Init(...Option) error
	Options() Options
	Address() string
	Connect() error
	Disconnect() error
	Publish(topic string, m *Message, opts ...PublishOption) error
	Subscribe(topic string, h Handler, opts ...SubscribeOption) (Subscriber, error)
	String() string
}

如果我们要具体实现事件的发布和订阅功能,只需要安装对应支持的go-plugins插件实现就可以了。go-plugins里支持的消息队列方式有:kafka、nsq、rabbitmq、redis等。同时,go-micro本身支持三种broker,分别是http、nats、memory,默认的broker是http,在实际使用过程中往往使用第三方的插件来进行消息发布/订阅的实现。

使用Rabbitmq插件实现的事件订阅和发布机制

消息组件初始化

如果要想使用消息组件完成消息的发布和订阅,首先应该让消息组件正常工作。因此,需要先对消息组件进行初始化。我们可以在服务创建时,对消息组件进行初始化,并进行可选项配置,设置使用rabbitmq作为消息组件。代码实现如下:

service := micro.NewService(
		micro.Name("go.micrio.server"),
		micro.Version("1.0.0"),
		micro.Broker(rabbitmq.NewBroker(broker.Addrs("amqp://guest:guest@10.10.10.116:5672"))),
		)
订阅事件
// 订阅事件
	pubSub := service.Server().Options().Broker
	if err := pubSub.Connect(); err != nil {
		log.Fatalf("broker connect failed, err: %v", err)
		return
	}

	_, err := pubSub.Subscribe("go.micro.server.message", func(event broker.Event) error {
		var req *message.Student
		if err := json.Unmarshal(event.Message().Body, &req); err != nil {
			return err
		}
		fmt.Println(" 接收到信息:", req)
		//去执行其他操作
		return nil
	})
发布事件
brok := service.Server().Options().Broker
	if err := brok.Connect(); err != nil {
		log.Fatalf("connect broker failed, err: %v", err)
		return
	}
	student := &message.Student{Name: "davie", Classes: "软件工程专业", Grade: 80}

	msgBody, err := json.Marshal(student)
	if err != nil {
		log.Fatal(err.Error())
		return
	}

	msg := &broker.Message{
		Header: map[string]string{
			"Name": student.Name,
		},
		Body: msgBody,
	}

	err = brok.Publish("go.micro.server.message", msg)

	if err != nil {
		log.Fatalf("消息发布失败: %s\n", err.Error())
	} else {
		log.Println("消息发布成功")
	}

完整代码

client.go

import (
	"GoCode/example/goMicroEventDemo/message"
	"encoding/json"
	"github.com/micro/go-micro"
	"github.com/micro/go-micro/broker"
	"github.com/micro/go-plugins/broker/rabbitmq"
	"log"
)

func main() {
	service := micro.NewService(
		micro.Name("client_demo"),
		micro.Broker(rabbitmq.NewBroker(broker.Addrs("amqp://guest:guest@10.10.10.116:5672"))),
	)
	service.Init()

	brok := service.Server().Options().Broker
	if err := brok.Connect(); err != nil {
		log.Fatalf("connect broker failed, err: %v", err)
		return
	}
	student := &message.Student{Name: "davie", Classes: "软件工程专业", Grade: 80}

	msgBody, err := json.Marshal(student)
	if err != nil {
		log.Fatal(err.Error())
		return
	}

	msg := &broker.Message{
		Header: map[string]string{
			"Name": student.Name,
		},
		Body: msgBody,
	}

	err = brok.Publish("go.micro.server.message", msg)

	if err != nil {
		log.Fatalf("消息发布失败: %s\n", err.Error())
	} else {
		log.Println("消息发布成功")
	}

	defer brok.Disconnect()
}


server.go

import (
	"GoCode/example/goMicroDemo/message"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/micro/go-micro"
	"github.com/micro/go-micro/broker"
	"github.com/micro/go-plugins/broker/rabbitmq"
	"log"
)

//学生服务管理实现
type StudentManager struct {
}

func (s StudentManager) GetStudent(c context.Context, request *message.StudentRequest, student *message.Student) error {
	studentMap := map[string]message.Student{
		"davie":  {Name: "davie", Classes: "软件工程专业", Grade: 80},
		"steven": {Name: "steven", Classes: "计算机科学与技术", Grade: 90},
		"tony":   {Name: "tony", Classes: "计算机网络工程", Grade: 85},
		"jack":   {Name: "jack", Classes: "工商管理", Grade: 96},
	}

	if request.Name == "" {
		return errors.New(" 请求参数错误,请重新请求。")
	}

	fmt.Println(request.Name)
	studentInfo := studentMap[request.Name]

	fmt.Println(studentInfo)

	if studentInfo.Name != "" {
		*student = studentInfo
		return nil
	}
	return errors.New(" 未查询当相关学生信息 ")
}


func main() {

	service := micro.NewService(
		micro.Name("go.micrio.server"),
		micro.Version("1.0.0"),
		micro.Broker(rabbitmq.NewBroker(broker.Addrs("amqp://guest:guest@10.10.10.116:5672"))),
		)

	// 初始化
	service.Init()

	// 订阅事件
	pubSub := service.Server().Options().Broker
	if err := pubSub.Connect(); err != nil {
		log.Fatalf("broker connect failed, err: %v", err)
		return
	}

	_, err := pubSub.Subscribe("go.micro.server.message", func(event broker.Event) error {
		var req *message.Student
		if err := json.Unmarshal(event.Message().Body, &req); err != nil {
			return err
		}
		fmt.Println(" 接收到信息:", req)
		//去执行其他操作
		return nil
	})


	// 启动
	err = service.Run()
	if err != nil {
		log.Fatal(err)
	}
}

参考链接

Golang - 100天从新手到大师

源码地址

源码地址

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值