Feed 实现一对多订阅,其中事件的载体是通道。发送到 Feed 的值会同时传送到所有订阅的通道。
与Typemux的对比
链接: link
TypeMux是一个同步的事件框架,当有一个被订阅的事件发生的时候,会遍历该事件对应的订阅者通道,通知其中的订阅者,但是当订阅者1没有接受该消息的时候,发送进程会被阻塞,会影响对订阅者2的发送。
所以Feed作为流式事件框架,是否是异步的取决于是否有缓存通道,当设计有缓存通道的时候是异步的,否则就是同步的。
type Feed struct {
once sync.Once // ensures that init only runs once
sendLock chan struct{} // sendLock has a one-element buffer and is empty when held.It protects sendCases.
removeSub chan interface{} // interrupts Send
sendCases caseList // the active set of select cases used by Send
// The inbox holds newly subscribed channels until they are added to sendCases.
mu sync.Mutex
inbox caseList
etype reflect.Type
}
once
字段是用来确保init
只会执行一次
sendLock
是一个缓存通道,
这是一个名为Feed的结构体,它包含以下字段:
once
:sync.Once类型,确保init
只运行一次。
sendLock
:chan struct{}类型,sendLock
具有一个元素缓冲区,当持有时为空。可以保护sendCases
。
removeSub
:chan interface{}类型,用于中断Send
。
sendCases
:caseList类型,Send
使用的活动选择集。
mu
:sync.Mutex类型,用于保护inbox
。
inbox
:caseList类型,用于存储新订阅的通道,直到它们被添加到sendCases
中。
etype
:reflect.Type类型,表示结构体的反射类型。
func (f *Feed) init(etype reflect.Type) {
f.etype = etype
f.removeSub = make(chan interface{})
f.sendLock = make(chan struct{}, 1)
f.sendLock <- struct{}{}
f.sendCases = caseList{{Chan: reflect.ValueOf(f.removeSub), Dir: reflect.SelectRecv}}
}
初始化一个名为Feed
的结构体。该方法接受一个参数etype
,表示结构体的反射类型。
etype
赋值给f.etype
,表示结构体的类型。f.removeSub
的通道,用于接收移除订阅的通知。f.sendLock
的通道,并设置其缓冲区大小为1,以确保只有一个发送操作可以同时进行。f.sendLock
通道发送一个空的结构体,以解锁该通道。f.sendCases
的caseList
类型的变量,并将其初始化为包含一个元素的列表。这个元素是一个case
结构体,其中Chan
字段表示要监听的通道(即f.removeSub
),Dir
字段表示监听的方向(即接收方向)。Feed
结构体,并设置其相关属性和状态。func (f *Feed) Subscribe(channel interface{}) Subscription
订阅将channel添加到feed中,添加一个订阅。 未来的发送将在通道上传送,直到subscription为止。 添加的所有通道必须具有相同的元素类型。
通道应该有足够的缓冲空间以避免阻塞其他订阅者。
慢速订阅者不会被丢弃。
这里大量用到了reflect类型,针对不同类型数据的相同处理,就可以用到反射。反射中的send,recv,default分别对应case中的不同情况。而reflect中还有其他类型,Bool、String 和 所有数字类型的基础类型;Array 和 Struct 对应的聚合类型;Chan、Func、Ptr、Slice 和 Map 对应的引用类型。
chanval
chantyp
chantyp.ChanDir()&reflect.SendDir == 0
用来判断是不是发送方向,有一个有错就会报错if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.SendDir == 0 {
panic(errBadChannel)
}
feedSub
类型的对象sub,并将其与当前的Feed对象和channel关联起来。同时,初始化一个错误通道err,用于接收可能的错误信息。sub := &feedSub{feed: f, channel: chanval, err: make(chan error, 1)}
func (f *Feed) remove(sub *feedSub)
首先会从inbox中删除,其中涵盖尚未添加到 f.sendCases 的频道。
该方法的作用是从Feed
对象的内部数据结构中移除一个订阅对象(feedSub
)。这个方法使用了Go语言的反射机制和并发控制机制来实现多通道的同步发送操作。
ch := sub.channel.Interface()
是将订阅对象的通道中的值给变量ch,可以方便后续处理index
中查找要移除的订阅对象index := f.inbox.find(ch)
(参考3.1),如果可以找到,就将其从index
中删除f.inbox = f.inbox.delete(index)
(参考3.2),释放锁,并返回index := f.inbox.find(ch)
if index != -1 {
f.inbox = f.inbox.delete(index)
f.mu.Unlock()
return
}
f.removeSub
,表示将要执行移除订阅对象sendCases
通道的信号,如果有正在发送的操作,这个sendLock
就不会为空,就无法获得这个锁,只有没有正在发送的操作的时候才会得到该锁进入执行:sendCases
中查找该订阅f.sendCases.find(ch)
(参考3.1),删除f.sendCases = f.sendCases.delete(f.sendCases.find(ch))
(参考3.2)sendLock
锁func (f *Feed) Send(value interface{}) (nsent int)
Send 同时发送到所有订阅的频道,返回发送到的订阅者数量。
rvalue := reflect.ValueOf(value)
sendLock
,以确保在发送过程中不会发生竞争条件<-f.sendLock
inbox
中的值添加到sendCases
列表中,并清空inbox
f.mu.Lock()
f.sendCases = append(f.sendCases, f.inbox...)
f.inbox = nil
f.mu.Unlock()
sendCases
列表,将每个通道的发送值设置为rvalue
,将一个值发送给所有订阅者cases = cases.deactivate(i)
(参考3.3),且计数nsent加一reflect.Select(cases)
语句,类似case语句,获得返回值chosen, recv, _ := reflect.Select(cases)
chosen
的值为0,表示通道关闭,就需要找到该对象的位置(参考3.1),并将该对象从sendCases中删除(参考3.2),并且缩小大小,重新赋值给casesif chosen == 0 /* <-f.removeSub */ {
index := f.sendCases.find(recv.Interface())
f.sendCases = f.sendCases.delete(index)
if index >= 0 && index < len(cases) {
// Shrink 'cases' too because the removed case was still active.
cases = f.sendCases[:len(cases)-1]
}
}
cases.deactivate(i)
从cases中删除(参考3.3),并且nsent计数加一f.sendCases[i].Send = reflect.Value{}
来清空要发送的数据f.sendLock
通道发送一个空的结构体,以解锁该通道nsent
。是Subscription的实现,Subscription中有两个函数:Unsubscribe(取消订阅)和Err(错误)。
type feedSub struct {
feed *Feed
channel reflect.Value
errOnce sync.Once
err chan error
}
feed
:一个指向Feed
类型的指针。channel
:一个反射值,表示通道类型。errOnce
:一个同步原语,用于确保错误处理只执行一次。err
:一个错误通道,用于传递错误信息。func (sub *feedSub) Unsubscribe() {
sub.errOnce.Do(func() {
sub.feed.remove(sub)
close(sub.err)
})
}
remove
(参考1.3)从订阅对象列表中删除func (sub *feedSub) Err() <-chan error {
return sub.err
}
从通道中获取错误信息并返回
type caseList []reflect.SelectCase
caseList是反射机制中的selectcase,类似case,但是可以动态添加所有管道
func (cs caseList) find(channel interface{}) int {
for i, cas := range cs {
if cas.Chan.Interface() == channel {
return i
}
}
return -1
}
find返回包含给定通道的事例的索引。
func (cs caseList) delete(index int) caseList {
return append(cs[:index], cs[index+1:]...)
}
从cs列表中删除指定位置的case并返回新的caselist,使用append来进行
func (cs caseList) deactivate(index int) caseList {
last := len(cs) - 1
cs[index], cs[last] = cs[last], cs[index]
return cs[:last]
}
deactivate将索引处的case移动到cs切片的不可访问部分。
先将index和最后一部分last的case进行对换,然后将cs的长度减一,这样就相当于将最后一部分删除了,也就是函数传入的index部分。
-----------------------------subscription.go----------------------------------------
type Subscription interface {
Err() <-chan error // returns the error channel
Unsubscribe() // cancels sending of events, closing the error channel
}
只有两个方法,一个是取消订阅,而是错误函数
代码注释:
subscription表示一系列事件。事件的载体通常是一个通道,但不是接口的一部分。
建立订阅时可能会失败。故障通过错误通道报告。如果订阅存在问题(例如,传递事件的网络连接已关闭)。只发送一个值。当订阅成功结束时(即,当事件源关闭时),错误通道将关闭。当调用“取消订阅”时,它也会关闭。
Unsubscribe方法取消发送事件。在任何情况下,您都必须调用Unsubscribe以确保与订阅相关的资源得到释放。它可以被调用任意次数。
func NewSubscription(producer func(<-chan struct{}) error) Subscription
NewSubscription在新的goroutine中作为订阅运行生产者函数。当调用取消订阅时,提供给制作人的频道将关闭。如果fn返回错误,则在订阅的错误通道上发送。
该函数接受一个参数producer
,该参数是一个生产者函数,返回值是error
。这个函数的作用是创建一个新的订阅对象,并启动一个协程来执行生产者函数。通过通道将数据发送给消费者。
s
的指针变量,指向一个名为funcSub
的结构体实例(参考5)make(chan struct{})
创建一个名为unsub
的通道,用于取消订阅make(chan error, 1)
创建一个名为err
的错误通道,用于传递错误信息defer close(s.err)
确保错误通道在协程结束时被关闭producer(s.unsub)
,使用的通道是unsub通道,并将返回的错误赋值给变量err
s.mu.Lock()
和s.mu.Unlock()
对结构体实例进行加锁和解锁操作s.unsubscribed
设置为true
表示已取消订阅s
。封装匿名函数
type funcSub struct {
unsub chan struct{}
err chan error
mu sync.Mutex
unsubscribed bool
}
func (s *funcSub) Unsubscribe() {
s.mu.Lock()
if s.unsubscribed {
s.mu.Unlock()
return
}
s.unsubscribed = true
close(s.unsub)
s.mu.Unlock()
// Wait for producer shutdown.
<-s.err
}
bool unsubscribed
来确定是否已经完成了取消订阅s.mu.Unlock()
释放锁func (s *funcSub) Err() <-chan error {
return s.err
}
用于返回错误信息
type resubscribeSub struct {
fn ResubscribeErrFunc
err chan error
unsub chan struct{}
unsubOnce sync.Once
lastTry mclock.AbsTime
lastSubErr error
waitTime, backoffMax time.Duration
}
fn
:一个类型为ResubscribeErrFunc
的函数err
:一个错误通道,用于接收错误信息unsub
:一个结构体通道,用于取消订阅unsubOnce
:一个sync.Once
类型的变量,确保unsub
只被执行一次lastTry
:一个mclock.AbsTime
类型的变量,表示上次尝试的时间lastSubErr
:一个error
类型的变量,表示上次订阅时发生的错误waitTime
:一个time.Duration
类型的变量,表示等待时间backoffMax
:一个time.Duration
类型的变量,表示最大退避时间Resubscribe会反复调用fn以保持订阅已建立。当订阅建立时,Resubscribe会等待它失败,然后再次调用fn。此过程重复进行,直到调用“取消订阅”或活动订阅成功结束。
重新订阅在对fn的调用之间应用回退。调用之间的时间根据错误率进行调整,但永远不会超过backoffMax。
ResubscribeErr反复调用fn以保持订阅已建立。当订阅建立时,ResubscribeErr等待它失败并再次调用fn。此过程重复进行,直到调用“取消订阅”或活动订阅成功结束。
Resubscribe和ResubscribeErr之间的区别在于,使用ResubscripteErr,回调可以使用失败订阅的错误进行日志记录。
ResubscribeErr在对fn的调用之间应用回退。调用之间的时间根据错误率进行调整,但永远不会超过backoffMax。
type ResubscribeFunc func(context.Context) (Subscription, error)
func ResubscribeErr(backoffMax time.Duration, fn ResubscribeErrFunc) Subscription {
s := &resubscribeSub{
waitTime: backoffMax / 10,
backoffMax: backoffMax,
fn: fn,
err: make(chan error),
unsub: make(chan struct{}),
}
go s.loop()
return s
}
resubscribeSub
对象:waitTime
是等待时间,是backoffMax的十分之一backoffMax
是最大的时间限制fn
是传入的重新订阅函数ResubscribeErrFunc
类型type ResubscribeErrFunc func(context.Context, error) (Subscription, error)
err
是错误通道,用于接收错误信息unsub
是取消订阅通道func (s *resubscribeSub) Unsubscribe() {
s.unsubOnce.Do(func() {
s.unsub <- struct{}{}
<-s.err
})
}
取消订阅
unsubOnce sync.Once
所以取消订阅只能执行一次s.unsub
中func (s *resubscribeSub) Err() <-chan error {
return s.err
}
用于返回错误信息
func (s *resubscribeSub) loop() {
defer close(s.err)
var done bool
for !done {
sub := s.subscribe()
if sub == nil {
break
}
done = s.waitForError(sub)
sub.Unsubscribe()
}
}
defer close(s.err)
用于延迟关闭一个名为 s.err 的错误通道。当函数执行完成时,defer 关键字会确保 close(s.err)
被调用,从而关闭错误通道并释放相关资源sub := s.subscribe()
返回订阅对象(参考6.6)done = s.waitForError(sub)
(参考6.7)sub.Unsubscribe()
取消订阅func (s *resubscribeSub) subscribe() Subscription
实现一个自动重试订阅的逻辑,当订阅失败时会进行一定的退避等待,并在等待结束后再次尝试订阅。
subscribed
的错误通道,用于接收订阅结果sub
的Subscription
变量(参考4)(取消订阅+错误)s.lastTry
为当前时间ctx
和一个取消函数cancel
s.fn(ctx, s.lastSubErr)
来获取订阅结果(参考6.2),并将结果赋值给rsub
和err
。rsub
赋值给sub
,并将err
信息发送到subscribed
通道。select
语句等待以下情况之一发生:
subscribed
通道接收到信号,取消上下文并检查错误是否为nil
。如果是,则返回sub
作为订阅结果,没有错误;否则,根据s.backoffWait()
的结果决定是否继续尝试订阅(参考6.8)s.unsub
通道接收到取消信号,取消上下文并,使用<-subscribed
接受消息但不 使用。然后返回nil
表示已取消订阅。func (s *resubscribeSub) waitForError(sub Subscription) bool
defer sub.Unsubscribe()
确保函数执行完成的时候会执行取消订阅函数sub.Err()
通道中接收到错误时,将错误赋值给s.lastSubErr
,并返回是否有错误func (s *resubscribeSub) backoffWait() bool
用于在订阅失败时进行重试等待,这个方法通常用于网络通信中,当订阅失败时,通过退避算法逐渐增加等待时间,直到达到最大等待时间或收到取消信号为止。
s.lastTry
是否大于退避的最大时间s.backoffMax
。如果是,则将等待时间设置为退避最大时间的1/10;否则,将等待时间翻倍,但不超过退避最大时间s.waitTime *= 2
if s.waitTime > s.backoffMax {
s.waitTime = s.backoffMax
}
t
,并设置等待时间为更新后的等待时间,它会在 s.waitTime
时间后触发。select
语句等待定时器超时或收到取消信号false
表示继续重试;如果收到取消信号,返回true
表示已取消type SubscriptionScope struct {
mu sync.Mutex
subs map[*scopeSub]struct{}
closed bool
}
type scopeSub struct {
sc *SubscriptionScope
s Subscription
}
SubscriptionScope提供了一种同时取消订阅多个订阅的功能。
对于处理多个订阅的代码,可以使用作用域通过一个调用方便地取消订阅所有订阅。该示例演示了在大型程序中的典型使用。
SubscriptionScope
的结构体,它包含以下字段:mu
:一个sync.Mutex
类型的变量,用于实现互斥锁,确保在多线程环境下对subs
和closed
字段的操作是线程安全的。subs
:一个map[*scopeSub]struct{}
类型的变量,用于存储订阅者信息。键为scopeSub
类型的指针,值为空结构体。closed
:一个布尔类型的变量,表示订阅范围是否已关闭。如果为true
,则表示已关闭;如果为false
,则表示未关闭。scopeSub
的结构体,它包含以下字段:sc
:一个指向SubscriptionScope
类型的指针,表示订阅范围。s
:一个Subscription
类型的变量,表示订阅信息。func (sc *SubscriptionScope) Track(s Subscription) Subscription {
sc.mu.Lock()
defer sc.mu.Unlock()
if sc.closed {
return nil
}
if sc.subs == nil {
sc.subs = make(map[*scopeSub]struct{})
}
ss := &scopeSub{sc, s}
sc.subs[ss] = struct{}{}
return ss
}
Track开始跟踪订阅。如果作用域已关闭,Track将返回nil。返回的订阅是一个包装。取消订阅包装会将其从作用域中删除。
sc.subs
为nil,就要重新创建一个scopeSub
的对象func (sc *SubscriptionScope) Close() {
sc.mu.Lock()
defer sc.mu.Unlock()
if sc.closed {
return
}
sc.closed = true
for s := range sc.subs {
s.s.Unsubscribe()
}
sc.subs = nil
}
Close调用所有跟踪订阅的Unsubscribe,并阻止进一步添加到跟踪集。关闭后对Track的调用返回nil。
func (sc *SubscriptionScope) Count() int {
sc.mu.Lock()
defer sc.mu.Unlock()
return len(sc.subs)
}
Count返回跟踪的订阅数
func (s *scopeSub) Unsubscribe() {
s.s.Unsubscribe()
s.sc.mu.Lock()
defer s.sc.mu.Unlock()
delete(s.sc.subs, s)
}
Subscription
类型的s的Unsubscribe()
函数来取消订阅delete
从sc.subs中删除sfunc (s *scopeSub) Err() <-chan error {
return s.s.Err()
}
返回错误信息
----------------------------event.go-----------------------------------
type Event struct {
once sync.Once
feeds map[string]*Feed
feedsLock sync.RWMutex
feedsScope map[string]*SubscriptionScope
}
once sync.Once
:一个只执行一次的同步原语,用于确保某个操作只执行一次。feeds map[string]*Feed
:一个字符串到Feed
指针的映射,用于存储订阅信息。feedsLock sync.RWMutex
:一个读写互斥锁,用于保护feeds
字段的并发访问。feedsScope map[string]*SubscriptionScope
:一个字符串到SubscriptionScope
指针的映射,用于存储订阅范围信息。func (e *Event) init() {
e.feeds = make(map[string]*Feed)
e.feedsScope = make(map[string]*SubscriptionScope)
}
初始化函数,创建feeds和feedsscope
func (e *Event) initKey(key string) {
e.feedsLock.Lock()
defer e.feedsLock.Unlock()
if _, ok := e.feeds[key]; !ok {
e.feeds[key] = new(Feed)
e.feedsScope[key] = new(SubscriptionScope)
}
}
初始化一个键值对,其中键为传入的字符串参数key,值为一个新的Feed
对象和一个新的SubscriptionScope
对象
Feed
对象和一个新的SubscriptionScope
对象func (e *Event) Subscribe(channel interface{}) Subscription
是订阅一个通道(channel),并返回一个订阅对象(Subscription)。
e.once.Do(e.init)
来确保在第一次调用Subscribe
方法时执行e.init()
函数进行初始化操作且只执行一次reflect.TypeOf(channel).Elem().String()
获取通道的类型字符串作为键值key
e.initKey(key)
方法对键值进行初始化,传入key
作为参数e.feedsLock.RLock()
加锁和延迟释放锁e.feedsScope[key].Track(e.feeds[key].Subscribe(channel))
创建一个订阅对象sub
,并将其与对应的通道关联起来。sub
。func (e *Event) Send(value interface{}) int
向订阅对象发送消息
e.once.Do(e.init)
来确保在第一次调用Subscribe
方法时执行e.init()
函数进行初始化操作且只执行一次key := reflect.TypeOf(value).String()
获取value的类型字符串作为键值key
e.initKey(key)
方法对键值进行初始化,传入key
作为参数e.feedsLock.RLock()
加锁和延迟释放锁func (e *Event) Close() {
e.feedsLock.Lock()
defer e.feedsLock.Unlock()
for _, scope := range e.feedsScope {
scope.Close()
}
}