进程内的异步“消息队列” - EventBus 事件总线(Go 实现)

980 阅读1分钟

概述

EventBus 像消息队列一样,可以用于异步处理消息数据,但实际上是单进程中的消息队列

go get github.com/tzq0301/eventbus

欢迎 Star 👏 GitHub | tzq0301/eventbus

快速上手

EventBus 库对外暴露两个接口,用于事件订阅与发布

PublishAsync(eventName string, data any) // 异步
Subscribe(eventName string, callback func(data any))

可以先定义事件名称与数据:

const (
	EventCreate = "event.create"
	EventUpdate = "event.update"
	EventDelete = "event.delete"
)

type EventCreateData struct {
	EventId int64
}

type EventUpdateData struct {
	EventId      int64
	OperatorName string
}

type EventDeleteData struct {
	EventId      int64
	OperatorName string
}

然后在程序中根据事件名称对事件进行订阅,并提供一个处理事件数据的函数:

func InitLark() {
	SubScribe(EventCreate, func(data any) {
		eventCreateData := data.(EventCreateData)
		larkNotify(fmt.Sprintf("Create Event, Id = %v", eventCreateData.EventId))
	})

	SubScribe(EventUpdate, func(data any) {
		eventUpdateData := data.(EventUpdateData)
		larkNotify(fmt.Sprintf("Update Event, Id = %v, Updater = %v", eventUpdateData.EventId, eventUpdateData.OperatorName))
	})

	SubScribe(EventDelete, func(data any) {
		eventDeleteData := data.(EventDeleteData)
		larkNotify(fmt.Sprintf("Delete Event, Id = %v, Deleter = %v", eventDeleteData.EventId, eventDeleteData.OperatorName))
	})
}

func InitResource() {
	SubScribe(EventCreate, func(data any) {
		eventCreateData := data.(EventCreateData)
		createResource(eventCreateData.EventId)
	})

	SubScribe(EventDelete, func(data any) {
		eventDeleteData := data.(EventDeleteData)
		deleteResource(eventDeleteData.EventId)
	})
}

在运行时,调用 PublishAsync 即可发布事件:

InitLark()
InitResource()

var wg sync.WaitGroup

wg.Add(4)
for i := 0; i < 4; i++ {
	i64 := int64(i)
	go func() {
		PublishAsync(EventCreate, EventCreateData{
			EventId: i64,
		})
		wg.Done()
	}()
}

wg.Wait()

得到结果:

Create Resource, id = 3
Create Resource, id = 0
Create Resource, id = 1
Lark: Create Event, Id = 1
Create Resource, id = 2
Lark: Create Event, Id = 2
Lark: Create Event, Id = 3
Lark: Create Event, Id = 0

代码实现

GitHub - tzq0301/eventbus

EventBus 接口

// eventbus/eventbus.go

type EventBus interface {
	PublishAsync(eventName string, data any)
	Subscribe(eventName string, callback SubscribeCallback)
}

type eventBusImpl struct {
	// eventName -> callback functions
	eventNameToSubscribeCallbacks map[string][]SubscribeCallback
	// 按照 EventBus 的逻辑,Subscribe 事件订阅一般都在服务端初始化时调用,
	// 因此,在运行时,基本只有 PublishAsync 只读调用,因此在运行时基本只有读锁,
	// 基本不会产生冲突,性能较高
	mu                            sync.RWMutex
}

func (e *eventBusImpl) PublishAsync(eventName string, data any) {
	// 异步处理事件发布,不会阻塞函数调用
	go func() {
		e.mu.RLock()
		defer e.mu.RUnlock()

		for _, callback := range e.eventNameToSubscribeCallbacks[eventName] {
			go callback(data) // 所有的 Callback 回调函数并行调用(而非按序)
		}
	}()
}

func (e *eventBusImpl) Subscribe(eventName string, callback SubscribeCallback) {
	e.mu.Lock()
	defer e.mu.Unlock()

	e.eventNameToSubscribeCallbacks[eventName] = append(e.eventNameToSubscribeCallbacks[eventName], callback)
}

提供一个默认的 EventBus 实例 & API 调用

为了易用性,创建一个默认的实例:

// eventbus/default.go

var DefaultEventBus = NewEventBus()

同样为了易用性,默认暴露该实例的 API 作为库函数的默认 API

// eventbus/api.go

var (
	PublishAsync = DefaultEventBus.PublishAsync
	SubScribe    = DefaultEventBus.Subscribe
)