Golang学习-发布订阅(二)使用redis

247 阅读2分钟

接上一篇实现的发布订阅功能,由于之前的订阅事件触发,在发布消息时就直接触发了,导致发布者和订阅者需要在同一进程内。现在通过Redis来做消息的订阅和发布,可以实现分布式的事件总线。

Golang学习-发布订阅(一)

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
}