监听DB配置变更之go-broadcast简单实现

2024-06-10 16:20

本文主要是介绍监听DB配置变更之go-broadcast简单实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1. 前言
  • 2. 分析
  • 3. 实现
  • 4. 问题
  • 5. 小结
  • 6. 参考

1. 前言

之前遇到一个需求,因为配置的查找是基于db的,而db的更改却无法实时通知到具体利用到这条数据的使用方,为了实现db数据变动时,能够尽快让使用方知道这条数据发生了变更,从而进行后续数据变更等相关逻辑的运行,就需要实现db数据变动时的通知。

在观察者模式中,因为观察者模式是一种一对多的关系模式,即多个观察者观察同一个主题对象,当主题对象发生变化时,会通知所有的观察者对象。

2. 分析

使用观察者模式来实现的话,则需要实现如下四个部分的结构:

  1. 抽象主题
  2. 具体主题
  3. 抽象观察者
  4. 具体观察者

举个例子,在我们日常使用微信公众号中,当你关注了一个公众号,这个公众号如果有更新的话,则会推送给每一个关注过这个公众号的用户。此时我们可以将具体的部分的接收映射到微信公众号中,即:

  1. 抽象主题:公众号,具备订阅、取消订阅和发送消息的功能
  2. 具体主题:具体某一个公众号
  3. 抽象观察者:用户(泛指使用微信公众号的用户受众)
  4. 具体观察者:某一个具体的用户

分析了以上四个结构之后,我们需要实现的功能部分就清楚了。即我们需要实现一个抽象主题,这个主题需要有提供注册、取消注册以及提交信息的能力,当提交信息到抽象主题的时候,抽象主题需要将这个消息通知到所有已经注册过的具体观察者。

3. 实现

在明确了需求之后, 就开始进行功能的实现,因为使用的是go语言,则第一时间肯定是希望通过chan这样的功能来实现,因为chan天生具备监听的能力,我们可以通过监听注册到抽象主题的chan,从而实现抽象主题消息的实时监听。

但秉持着“你需要的功能,基本都有人实现过”的方针,第一时间还是上到了github,看看是否有现成的开源方案,经过一番查找,还真发现了一个开源库可以使用,这个库的名称是go-broadcast。

下面就来说下broadcaster是如何实现上面的功能逻辑的,broadcaster这个库的代码很简单,主体实现逻辑只有110行代码左右,但符合我们的功能逻辑实现需要。

type broadcaster struct {input chan interface{}reg   chan chan<- interface{}unreg chan chan<- interface{}outputs map[chan<- interface{}]bool
}// The Broadcaster interface describes the main entry points to
// broadcasters.
type Broadcaster interface {// Register a new channel to receive broadcastsRegister(chan<- interface{})// Unregister a channel so that it no longer receives broadcasts.Unregister(chan<- interface{})// Shut this broadcaster down.Close() error// Submit a new object to all subscribersSubmit(interface{})// Try Submit a new object to all subscribers return false if input chan is fillTrySubmit(interface{}) bool
}

首先定义了一个接口叫做Broadcaster,然后定义了一个broadcaster实现了Broadcaster的所有方法逻辑。

func (b *broadcaster) Register(newch chan<- interface{}) {b.reg <- newch
}func (b *broadcaster) Unregister(newch chan<- interface{}) {b.unreg <- newch
}func (b *broadcaster) Close() error {close(b.reg)close(b.unreg)return nil
}// Submit an item to be broadcast to all listeners.
func (b *broadcaster) Submit(m interface{}) {if b != nil {b.input <- m}
}
  • Register方法主要实现了将注册的chan直接放入到reg这个chan中,用于后续注册
  • Register方法主要实现了将注册的chan直接让如到ureg这个chan中,用于后续注销
  • Close方法主要是关闭reg和ureg两个chan
  • Submit方法主要实现对抽象主题broadcaster发送消息,将消息放入input这个chan中

上面的方法都是基于chan作为通信的,而chan中有了数据,后续需要消费数据。

// NewBroadcaster creates a new broadcaster with the given input
// channel buffer length.
func NewBroadcaster(buflen int) Broadcaster {b := &broadcaster{input:   make(chan interface{}, buflen),reg:     make(chan chan<- interface{}),unreg:   make(chan chan<- interface{}),outputs: make(map[chan<- interface{}]bool),}go b.run()return b
}

这里的run()方法则是消费所有chan数据的地方。

func (b *broadcaster) broadcast(m interface{}) {for ch := range b.outputs { // 遍历所有注册的chan,将消息发送到注册的chan中ch <- m}
}func (b *broadcaster) run() {for {select {case m := <-b.input: // 如果有消息输入,则广播出去b.broadcast(m)case ch, ok := <-b.reg: // 如果有新注册的,则进行output的添加if ok {b.outputs[ch] = true} else {return}case ch := <-b.unreg: // 如果有注销的,则进行output的删除delete(b.outputs, ch)}}
}

整体的运行图如下:

在这里插入图片描述

  • 对应chan通过reg进行注册,注册后的chan记录在outputs中
  • 对应chan通过ureg进行注销,注销后的chan从output中移除
  • 对应的信息通过input输入,输入后的msg通过遍历outputs注册列表,从而通知到每一个注册者

4. 问题

在使用go-broadcast的过程中,看到之前有个pr加了一个TrySubmit的逻辑,这个逻辑主要是解决当input被装满了以后,broadcast会被阻塞,这个时候如果有新的消息进来,如何办呢?

// TrySubmit attempts to submit an item to be broadcast, returning
// true iff it the item was broadcast, else false.
func (b *broadcaster) TrySubmit(m interface{}) bool {if b == nil {return false}select {case b.input <- m:return truedefault:return false}
}

解决办法是采用select的方法尝试去塞入,塞入不成功则意味着消息提交失败,返回false,让使用者根据消息提交的结果进行后续的逻辑处理。

但这里还存在另外一个问题,库中给了一个样本case,这个样本case基于的条件都是消息传递给chan的时候没有阻塞。如下代码所示:

func (b *broadcaster) broadcast(m interface{}) {for ch := range b.outputs {ch <- m}
}

但一旦有注册的chan消费的时候阻塞了,这时候就会产生问题,会导致其它正常消费的chan因为一个异常chan而全部被阻塞住,导致其他chan都无法正常消费。

这个时候就会导致在input没有满的时候,即消息可以放入,但是消息无法被正常的消费,进而又反向导致input逐渐被塞满,最终导致input无法被塞入,消息也无法被发送到对应的chan中,导致run方法逻辑卡在broadcast中,导致整个运行出现问题。

解决办法:

func (b *broadcaster) broadcast(m interface{}) {for ch := range b.outputs {// if exist one output consume the chan message is too slow,// will block other output receive the msg.select {case ch <- m:default:}}
}

但这种虽然解决了一个chan满消费block其他chan的问题,随之也引入了丢消息的问题了,即有些消费慢的chan,由于chan消费慢导致无法接收新的消息,进而导致新消息丢失的问题。

5. 小结

因为需要实时监听db配置的变更,所以去探寻了一下方案,最终采用了go-broadcast的方案,但在使用go-broadcast的过程中,发现在broadcast消息的时候存在阻塞的行为,为了保证整个服务不被某个chan阻塞而停止运行,在broadcast消息的时候添加了select default条件来规避这个问题。

6. 参考

  • go-broadcast

这篇关于监听DB配置变更之go-broadcast简单实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1048658

相关文章

Spring Boot拦截器Interceptor与过滤器Filter深度解析(区别、实现与实战指南)

《SpringBoot拦截器Interceptor与过滤器Filter深度解析(区别、实现与实战指南)》:本文主要介绍SpringBoot拦截器Interceptor与过滤器Filter深度解析... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实

C#实现访问远程硬盘的图文教程

《C#实现访问远程硬盘的图文教程》在现实场景中,我们经常用到远程桌面功能,而在某些场景下,我们需要使用类似的远程硬盘功能,这样能非常方便地操作对方电脑磁盘的目录、以及传送文件,这次我们将给出一个完整的... 目录引言一. 远程硬盘功能展示二. 远程硬盘代码实现1. 底层业务通信实现2. UI 实现三. De

SpringBoot后端实现小程序微信登录功能实现

《SpringBoot后端实现小程序微信登录功能实现》微信小程序登录是开发者通过微信提供的身份验证机制,获取用户唯一标识(openid)和会话密钥(session_key)的过程,这篇文章给大家介绍S... 目录SpringBoot实现微信小程序登录简介SpringBoot后端实现微信登录SpringBoo

MySQL Workbench工具导出导入数据库方式

《MySQLWorkbench工具导出导入数据库方式》:本文主要介绍MySQLWorkbench工具导出导入数据库方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录mysql Workbench工具导出导入数据库第一步 www.chinasem.cn数据库导出第二步

使用Vue-ECharts实现数据可视化图表功能

《使用Vue-ECharts实现数据可视化图表功能》在前端开发中,经常会遇到需要展示数据可视化的需求,比如柱状图、折线图、饼图等,这类需求不仅要求我们准确地将数据呈现出来,还需要兼顾美观与交互体验,所... 目录前言为什么选择 vue-ECharts?1. 基于 ECharts,功能强大2. 更符合 Vue

使用WPF实现窗口抖动动画效果

《使用WPF实现窗口抖动动画效果》在用户界面设计中,适当的动画反馈可以提升用户体验,尤其是在错误提示、操作失败等场景下,窗口抖动作为一种常见且直观的视觉反馈方式,常用于提醒用户注意当前状态,本文将详细... 目录前言实现思路概述核心代码实现1、 获取目标窗口2、初始化基础位置值3、创建抖动动画4、动画完成后

uniapp小程序中实现无缝衔接滚动效果代码示例

《uniapp小程序中实现无缝衔接滚动效果代码示例》:本文主要介绍uniapp小程序中实现无缝衔接滚动效果的相关资料,该方法可以实现滚动内容中字的不同的颜色更改,并且可以根据需要进行艺术化更改和自... 组件滚动通知只能实现简单的滚动效果,不能实现滚动内容中的字进行不同颜色的更改,下面实现一个无缝衔接的滚动

Vue 2 项目中配置 Tailwind CSS 和 Font Awesome 的最佳实践举例

《Vue2项目中配置TailwindCSS和FontAwesome的最佳实践举例》:本文主要介绍Vue2项目中配置TailwindCSS和FontAwesome的最... 目录vue 2 项目中配置 Tailwind css 和 Font Awesome 的最佳实践一、Tailwind CSS 配置1. 安

C#通过进程调用外部应用的实现示例

《C#通过进程调用外部应用的实现示例》本文主要介绍了C#通过进程调用外部应用的实现示例,以WINFORM应用程序为例,在C#应用程序中调用PYTHON程序,具有一定的参考价值,感兴趣的可以了解一下... 目录窗口程序类进程信息类 系统设置类 以WINFORM应用程序为例,在C#应用程序中调用python程序

利用Python实现可回滚方案的示例代码

《利用Python实现可回滚方案的示例代码》很多项目翻车不是因为不会做,而是走错了方向却没法回头,技术选型失败的风险我们都清楚,但真正能提前规划“回滚方案”的人不多,本文从实际项目出发,教你如何用Py... 目录描述题解答案(核心思路)题解代码分析第一步:抽象缓存接口第二步:实现两个版本第三步:根据 Fea