接上一篇实现的发布订阅功能,由于之前的订阅事件触发,在发布消息时就直接触发了,导致发布者和订阅者需要在同一进程内。现在通过Redis来做消息的订阅和发布,可以实现分布式的事件总线。
Redis的事件总线, 使用如下:
只要保证redis链接("127.0.0.1:6379")和redis_topic一致,就可以在分布式的环境中实现事件总线
//创建Event bus
client := InitializeRedis("127.0.0.1:6379")
bus := NewRedisEventBus(client, "redis_topic")
//创建订阅者
ch := make(events.EventChannel, 64)
bus.Subscribe("topic", ch)
//通过Event bus 发布消息
bus.Publish("topic", "hello world!!!")
bus.Publish("topic", "hello golang!!!")
//订阅者接收和处理消息事件
go func() {
for item := range ch {
//处理消息事件
fmt.Printf("sub: %#v\n", item)
}
}()
具体实现
部分代码在Golang学习-发布订阅(一)中,由于只是修改消息发布者即可,因此本地的代码较为简单。
首先创建一个Redis的Client连接
import (
"context"
"log"
"github.com/go-redis/redis/v8"
"go.uber.org/zap"
)
// 初始化redis客户端, redisUrl = 127.0.0.1:6379
func InitializeRedis(redisUrl string) *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: redisUrl,
Password: "", // no password set
DB: 1, // use default DB
})
_, err := client.Ping(context.Background()).Result()
if err != nil {
log.Panicln("Redis connect ping failed, err:", zap.Any("err", err))
return nil
}
return client
}
实现redis的发布者
可以回顾上文的发布者定义
// 发布接口
type EventPublisher interface {
Publish(topic string, item *EventItem, bus EventBus) error
}
这里我们只需要实现订阅者接口即可,实现代码如下:
type EventRedisPublisher struct {
client *redis.Client //redis client
pubsub *redis.PubSub //redis pubsub
cancel context.CancelFunc
redisTopic string //在redis中订阅的topic,和EventBus中的topic不是同一个
}
func (s *EventRedisPublisher) Publish(topic string, item *EventItem, bus EventBus) error {
buf, _ := json.Marshal(item) //用json做序列化
//推送消息给redis
err := s.client.Publish(context.Background(), s.redisTopic, buf).Err()
if err != nil {
return err
}
return nil
}
增加一个创建Redis Event bus的函数
func NewRedisEventBus(client *redis.Client, redisTopic string) EventBus {
if client == nil {
return nil
}
pubsub := client.Subscribe(context.Background(), redisTopic)
ctx, cancel := context.WithCancel(context.Background())
publisher := &EventRedisPublisher{
client: client,
pubsub: pubsub,
cancel: cancel,
redisTopic: redisTopic,
}
bus := &EventBusBase{
subscriber: make(map[string][]EventChannel),
publisher: publisher,
}
go func() {
for {
select {
case <-ctx.Done():
log.Printf("RedisEventBus done !!!")
return
default:
msg, err := pubsub.ReceiveMessage(ctx)
if err != nil {
log.Printf("ReceiveMessage error: %v", err)
continue
}
var item EventItem
err = json.Unmarshal([]byte(msg.Payload), &item)
if err != nil {
log.Printf("bad redis payload: %s\n", msg.Payload)
continue
}
//获取到redis的消息后,用来触发Event bus
bus.Trigger(item.Topic, &item)
}
}
}()
return bus
}