Golang-----发布订阅

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

/**
发布者

事件驱动架构是计算机科学家中一种高度可扩展年的范例,它允许我们可以多方系统异步处理事件

事件总线是 发布/ 订阅模式的实现,其中发布者发布数据,并且感兴趣的订阅者可以监听这些数据并基于
这些数据作出处理,是发布者与订阅者松耦合,发布者将数据事件发布到事件总线,总线负责将他们发送给订阅者

传统的实现事件总线的方法会涉及到使用回调,订阅者通常实现接口,然后事件总线通过接口传播数据
使用Go的并发模型,大多数地方可以使用channel来代替回调,在本文中,我们将重点介绍如何使用channle 来实现事件总线

基于主题 topic 的事件 发布者发布到主题,订阅者可以收听他们

 */
 // 定义数据结构
 // 实现事件总线,我们需要定义要传递数据结构,
 // 我们可以使用struct 简单地创建一个新的数据类型,定义一个DataEvent的结构体如下.
 // 这里 我们已经将基础数据定义为接口,这意味着他可以是任何值,我们还将主题定义为结构的成员,
 // 订阅者可能会收听多个主题,因此,我们将通过主题让订阅者可以区分不同的事件做法 is good

type DataEvent struct {
	Data  interface{}
	Topic string
}
// 为事件总线定义了我们主要的数据结构,我们还需要一种方法传递它,
// 为此,我们可以定义一个可以传播DataEvent 和DataChannel 类型
//DataChannel 是一个能接受DataEvent的channel

type DataChannel chan DataEvent

// DataChannelSlice是一个包涵DataChannels 数据切片
//DataChannelSlice的创建是为了保留DataChannel 的切片并轻松引用他们
type DatachannelSlice [] DataChannel

// 事件总线
// EventBus 存储有关订阅者感兴趣的特定主题的信息
 /**
 EventBus有 subscribes, 这是一个包涵DataChannelSlice 的map 我们使用互斥锁来保护并发访问的读写

 通过使用map 和定义topics,它允许我们轻松组织事件.主题被视为map的键,当有人发布它时,
 可以通过键轻松找到主题,然后将事件传播到channnel 中以进行异步处理
  */
type  EventBus struct {
	subscribers map [string] DatachannelSlice
	rm 	sync.RWMutex
}
// 订阅主题
/**
对于订阅主题,使用channle 它像传统方法中的回调一样,当发布者向主题发布数据时channel 将接收数据
简单地说,我们将订阅者添加到channle 切片中然后给该结构加锁,最后在爱操作后将其解锁
 */
func (eb *EventBus)Subscribe (topic string,ch DataChannel)  {
	eb.rm.Lock()
	if prev,found := eb.subscribers[topic]; found{
		eb.subscribers[topic] = append(prev,ch)
	}else {
		eb.subscribers[topic] = append([] DataChannel{},ch)
	}
	eb.rm.Unlock()
}
// 发布主题
/**
 发布事件,发布者需要提供广播给订阅者所需的主题和数据
在次方法中,首先问你检查主题是否存在任何订阅者
然后我们只是简单遍历与主题有关的channel 切片 把事件发布给他们

注: 在发布方法中使用了Goroutine 来避免阻塞发布者
 */
func (eb *EventBus)Publish (topic string,data interface{}) {
	eb.rm.RLock()
	if chans,found:=eb.subscribers[topic] ;found{
		/**
			这样做是因为切片引用相同的数组,即使他们是按值传递的
		因此我们在使用我们的元素创建一个新切片,从而能正确地保持锁定
		 */
		 channels := append(DatachannelSlice{},chans...)
		 go func(data DataEvent,dataChannelSlice DatachannelSlice) {
		 	for _,ch := range dataChannelSlice{
		 		ch <- data
			}
		 }(DataEvent{Data:data,Topic:topic},channels)
	}
	eb.rm.RUnlock()
}
var eb = &EventBus{
	subscribers:map[string] DatachannelSlice{},
}

func printDataEvent(ch string,data DataEvent)  {
	fmt.Printf("Channel: %s; Topic: %s; DataEvent %v\n",ch,data.Topic,data.Data)
}

func publishersTo(topic string,data string)  {
	for  {
		eb.Publish(topic,data)
		time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
	}
}


// 测试
/**
	我们需要创建一个事件总线的实例,在实际场景中,
	可以从包中到处单个EventBus,使其像单例一样运行
 */
func main()  {
	ch1 := make(chan DataEvent)
	ch2 := make(chan DataEvent)
	ch3 := make(chan DataEvent)

	eb.Subscribe("topic1",ch1)
	eb.Subscribe("topic2",ch2)
	eb.Subscribe("topic3",ch3)

	go publishersTo("topic1","Hitopic 1")
	go publishersTo("topic2","Welcome to topic2")

	for {
		select {
		case d:= <-ch1:
				go printDataEvent("ch1",d)
		case d:= <-ch2:
			go printDataEvent("ch2",d)
		case d:= <-ch3:
			go printDataEvent("ch3",d)
	}
	}
}

 

Guess you like

Origin blog.csdn.net/qq_35361859/article/details/103611200