项目中使用到了 RxGo ,感觉现有的处理方式有一定的优势,当然也有一定的有劣势,遂记录下来,免得自己忘记。

本文介绍的只是 rxgo 的一种方式而已,如果你有不错的使用方式,请不吝赐教,谢谢。

对 rxgo 不清楚的同学可以看看我的另一篇文章,主要是介绍 rxgo 的基础使用。

Go中响应式编程库RxGo详细介绍

1、需求简介

现在项目的需求是,项目中有多个生成数据的地方,数据的消费是不确定何时何处消费的,数据消费的地方,随着版本的迭代可能增加,也可能减少。

我们将上述需求简化抽象后知道,即要有一个地方,接受所有的数据,消费者想什么时候去消费数据,自己去取就好了。使用过消息队列的同学都知道,这个场景太适合使用消息队列组件了,像Kafka、RabbitMQ、nats 等等。

是的,通常来说,使用消息队列是常见的方式。但是我们现在的项目,生成的数据主要是各个抽象出来的服务使用,如果其他系统需要,也可以抽象出来一个服务,将生成的数据发送到 Kafka、RabbitMQ 中去。

因为我们是工业软件,软件是部署到单个机器上的,我们不希望使用这么重的组件,增加我们部署运维的复杂度,那应该如何处理呢?

想必了解消息队列实现方式的同学知道,消息队列的实现方式,和设计模式中的 观察者模式 类似,那我们可以自己实现一个 观察者模式 的库,不就可以了嘛。

自己去实现一个符合现在需求的库,的确是可行的方案,但是对一般的公司而言,还没有到这一步,恰好 Go 中有 RxGo 这个库,符合现在的业务场景,简单说下使用此库的好处:

  • rxgo 作为一个响应式编程的库,提供了Observable和Observer两个核心概念,使得我们可以更容易地处理异步事件流和数据流。
  • 发送数据和消费数据都很简单,比如我们只关心生成观察者之后的数据,使用此库轻松实现

.....

还有很多好处,这里就不一一列举了。

2、rxgo 在项目中的使用方式

2.1 单仓库情景下

package main

import (
"fmt"
"github.com/reactivex/rxgo/v2"
"time"
) /*--------------------------------------- 观察对象 ---------------------------------------*/
var (
// 事件bus
eventCh chan rxgo.Item
eventObservable rxgo.Observable
) func init() {
//指令消息
eventCh = make(chan rxgo.Item)
// rxgo.FromEventSource 创建的是 热观察者, 即对从流的一开始产生的所有数据不感兴趣,只对我们开始观察之后的数据感兴趣。
// 没有观察者的Observable发出的项目将会丢失。对上面表示不理解的同学可以看看 rxgo github 上面的介绍
// 因为 rxgo.FromEventSource 默认在开始观察数据后, 是阻塞等待的
// rxgo.WithBackPressureStrategy(rxgo.Drop)
// 使用Drop策略意味着如果从FromEventSource后面的流程没有准备好消耗一个项目,这个项目会被丢弃。
eventObservable = rxgo.FromEventSource(eventCh, rxgo.WithBackPressureStrategy(rxgo.Drop)) //observe()时指定buffer } func PublishEvent(e interface{}) {
//发布事件
eventCh <- rxgo.Of(e)
} func GetObservable() rxgo.Observable {
return eventObservable
} func main() {
for i := 0; i < 3; i++ {
go consumer(i)
}
producer() time.Sleep(time.Second * 60)
} func consumer(number int) {
o := GetObservable().Filter(func(i interface{}) bool {
fmt.Printf("coming。。。。。。。。。。。。。, number: %v, data: %+v\n", number, i)
return true
})
o.DoOnNext(func(i interface{}) { })
} func producer() { for i := 0; i < 3; i++ {
fmt.Println(i)
PublishEvent(i)
}
}

特别注意,上述代码执行的结果是不确定的,因为 rxgo 采用的是异步事件流。

执行结果:

0
1
coming。。。。。。。。。。。。。, number: 1, data: 1
coming。。。。。。。。。。。。。, number: 2, data: 0
coming。。。。。。。。。。。。。, number: 0, data: 0
2
coming。。。。。。。。。。。。。, number: 1, data: 2
coming。。。。。。。。。。。。。, number: 0, data: 2
coming。。。。。。。。。。。。。, number: 2, data: 2

2.2 多仓库场景下

上面我们已经实现了一个生产者消费者模式的demo,在实际开发中,我们可能会将某些功能单独抽离出来作为一个仓库,那么这个时候应该怎么将单独仓库中产生的数据发送到全局的事件 Bus 中呢,接下来,一起看下下面的代码就知道如何处理了。

这里为了方便演示、测试,直接将代码写在了同一份文件中,理解到其中的含义后,我们可以自行拆解。

package main

import (
"fmt"
"github.com/reactivex/rxgo/v2"
"time"
) /*--------------------------------------- 观察对象 ---------------------------------------*/
var (
// 事件bus
eventCh chan rxgo.Item
eventObservable rxgo.Observable
forwardEventObservable rxgo.Observable
) func init() {
//指令消息
eventCh = make(chan rxgo.Item)
// rxgo.FromEventSource 创建的是 热观察者, 即对从流的一开始产生的所有数据不感兴趣,只对我们开始观察之后的数据感兴趣。
// 没有观察者的Observable发出的项目将会丢失。对上面表示不理解的同学可以看看 rxgo github 上面的介绍
// 因为 rxgo.FromEventSource 默认在开始观察数据后, 是阻塞等待的
// rxgo.WithBackPressureStrategy(rxgo.Drop)
// 使用Drop策略意味着如果从FromEventSource后面的流程没有准备好消耗一个项目,这个项目会被丢弃。
eventObservable = rxgo.FromEventSource(eventCh, rxgo.WithBackPressureStrategy(rxgo.Drop)) //observe()时指定buffer initOther() // 因为同一份文件的 init 函数是从上到下执行的, 为了 forwardEvent 不报错, 这里手动调用
forwardEvent()
} func PublishEvent(e interface{}) {
//发布事件
eventCh <- rxgo.Of(e)
} func GetObservable() rxgo.Observable {
return eventObservable
} func forwardEvent() {
forwardEventObservable = GetOtherObservable()
forwardEventObservable.DoOnNext(func(i interface{}) {
PublishOtherEvent(i)
}, rxgo.WithBufferedChannel(2))
} func main() {
for i := 0; i < 1; i++ {
go consumer(i)
}
go producer() // 这里类似于其他仓库中生成数据
go otherProducer() time.Sleep(time.Second * 60)
} func consumer(number int) {
o := GetObservable().Filter(func(i interface{}) bool {
fmt.Printf("coming。。。。。。。。。。。。。, number: %v, data: %+v\n", number, i)
return true
})
o.DoOnNext(func(i interface{}) { })
} func producer() { for i := 0; i < 3; i++ {
fmt.Println(i)
PublishEvent(i)
}
} func otherProducer() { for i := 0; i < 3; i++ {
fmt.Println("other is sending")
PublishEvent(fmt.Sprintf("other: %d", i))
}
} /*
以下代码是其他仓库中的 rxgo 配置
*/ var (
// 事件bus
otherEvent chan rxgo.Item
otherObservable rxgo.Observable
) func initOther() {
//指令消息
otherEvent = make(chan rxgo.Item)
// rxgo.FromEventSource 创建的是 热观察者, 即对从流的一开始产生的所有数据不感兴趣,只对我们开始观察之后的数据感兴趣。
// 没有观察者的Observable发出的项目将会丢失。对上面表示不理解的同学可以看看 rxgo github 上面的介绍
// 因为 rxgo.FromEventSource 默认在开始观察数据后, 是阻塞等待的
// rxgo.WithBackPressureStrategy(rxgo.Drop)
// 使用Drop策略意味着如果从FromEventSource后面的流程没有准备好消耗一个项目,这个项目会被丢弃。
otherObservable = rxgo.FromEventSource(otherEvent, rxgo.WithBackPressureStrategy(rxgo.Drop)) //observe()时指定buffer } func PublishOtherEvent(e interface{}) {
//发布事件
otherEvent <- rxgo.Of(e)
} func GetOtherObservable() rxgo.Observable {
return otherObservable
}

特别注意,上述代码执行的结果是不确定的。

执行结果:

0
1
other is sending
other is sending
coming。。。。。。。。。。。。。, number: 0, data: 0
2
coming。。。。。。。。。。。。。, number: 0, data: 2
other is sending
coming。。。。。。。。。。。。。, number: 0, data: other: 2

3、rxgo 配置简要说明

这里主要讲解下我们在实际项目中用到的几个参数配置,其他的等待你的自行发掘。

  • rxgo.WithBufferedChannel(3): 设置 channel 的缓存大小;

  • rxgo.WithBackPressureStrategy(rxgo.*Drop*): 使用Drop策略意味着如果后面的流程没有准备好消耗一个数据,这个数据会被丢弃。

演示案例:

package main

import (
"fmt"
"github.com/reactivex/rxgo/v2"
"time"
) /*--------------------------------------- 观察对象 ---------------------------------------*/
var (
// 事件bus
eventCh chan rxgo.Item
eventObservable rxgo.Observable
) func init() {
//指令消息
eventCh = make(chan rxgo.Item)
// rxgo.FromEventSource 创建的是 热观察者, 即对从流的一开始产生的所有数据不感兴趣,只对我们开始观察之后的数据感兴趣。
// 没有观察者的Observable发出的项目将会丢失。对上面表示不理解的同学可以看看 rxgo github 上面的介绍
// 因为 rxgo.FromEventSource 默认在开始观察数据后, 是阻塞等待的
// rxgo.WithBackPressureStrategy(rxgo.Drop)
// 使用Drop策略意味着如果从FromEventSource后面的流程没有准备好消耗一个项目,这个项目会被丢弃。
eventObservable = rxgo.FromEventSource(eventCh, rxgo.WithBackPressureStrategy(rxgo.Drop)) //observe()时指定buffer } func PublishEvent(e interface{}) {
//发布事件
eventCh <- rxgo.Of(e)
} func GetObservable() rxgo.Observable {
return eventObservable
} func main() {
for i := 0; i < 1; i++ {
go consumer(i)
}
producer() time.Sleep(time.Second * 60)
} func consumer(number int) {
// 为什么是在 消费数据的地方设置 channel 的容量?
// 这样可以根据消费者自身情况灵活调整
o := GetObservable().Filter(func(i interface{}) bool {
fmt.Printf("coming。。。。。。。。。。。。。, number: %v, data: %+v\n", number, i)
return true
}, rxgo.WithBufferedChannel(3))
o.DoOnNext(func(i interface{}) { })
} func producer() { for i := 0; i < 3; i++ {
fmt.Println(i)
PublishEvent(i)
}
}

这次测试结果就是固定的了,大家可以想想为什么。

执行结果:

0
1
2
coming。。。。。。。。。。。。。, number: 0, data: 0
coming。。。。。。。。。。。。。, number: 0, data: 1
coming。。。。。。。。。。。。。, number: 0, data: 2

特别注意:

为什么是在 消费数据的地方设置 channel 的容量?

答:这样可以根据消费者自身情况灵活调整

浅谈 rxgo 在项目中的使用方式的更多相关文章

  1. 浅谈redux-form在项目中的运用

    准则 先说一下redux的使用场景,因为如果没有redux,那更不会有redux-form. redux基于Flux架构思想,是一个状态管理框架,其目标是解决单页面应用中复杂的状态管理问题. 日常前端 ...

  2. 浅谈Log4net在项目中如何记录日志

    一    引入背景 在软件开发周期中,无论是开发中,或是测试中,或是上线后,选择合适的工具监控程序的运行状态至关重要,只有如此,才能更好地排查程序问题和检测程序性能问题等.本篇文章主要与大家分享,如何 ...

  3. 【ASP.NET MVC系列】浅谈NuGet在VS中的运用

    一     概述 在我们讲解NuGet前,我们先来看看一个例子. 1.例子: 假设现在开发一套系统,其中前端框架我们选择Bootstrap,由于选择Bootstrap作为前端框架,因此,在项目中,我们 ...

  4. 浅谈线程池(中):独立线程池的作用及IO线程池

    原文地址:http://blog.zhaojie.me/2009/07/thread-pool-2-dedicate-pool-and-io-pool.html 在上一篇文章中,我们简单讨论了线程池的 ...

  5. 浅谈surging服务引擎中的rabbitmq组件和容器化部署

    1.前言 上个星期完成了surging 的0.9.0.1 更新工作,此版本通过nuget下载引擎组件,下载后,无需通过代码build集成,引擎会通过Sidecar模式自动扫描装配异构组件来构建服务引擎 ...

  6. 浅谈如何检查Linux中开放端口列表

    给大家分享一篇关于如何检查Linux中的开放端口列表的详细介绍,首先如果你想检查远程Linux系统上的端口是否打开请点击链接浏览.如果你想检查多个远程Linux系统上的端口是否打开请点击链接浏览.如果 ...

  7. $.ajax()方法详解 ajax之async属性 【原创】详细案例解剖——浅谈Redis缓存的常用5种方式(String,Hash,List,set,SetSorted )

    $.ajax()方法详解   jquery中的ajax方法参数总是记不住,这里记录一下. 1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: 要求为Str ...

  8. 【转】浅谈常用的几种web攻击方式

    浅谈常用的几种web攻击方式 一.Dos攻击(Denial of Service attack) 是一种针对服务器的能够让服务器呈现静止状态的攻击方式.有时候也加服务停止攻击或拒绝服务攻击.其原理就是 ...

  9. 浅谈boost.variant的几种访问方式

    前言 variant类型在C++14并没有加入,在cppreference网站上可以看到该类型将会在C++17加入,若想在不支持C++17的编译器上使用variant类型,我们可以通过boost的va ...

  10. 【Unity游戏开发】浅谈Lua和C#中的闭包

    一.前言 目前在Unity游戏开发中,比较流行的两种语言就是Lua和C#.通常的做法是:C#做些核心的功能和接口供Lua调用,Lua主要做些UI模块和一些业务逻辑.这样既能在保持一定的游戏运行效率的同 ...

随机推荐

  1. 多模态 GPT-V 出世!36 种场景分析 ChatGPT Vision 能力,LMM 将全面替代大语言模型?

    LMM将会全面替代大语言模型?人工智能新里程碑GPT-V美国预先公测,医疗领域/OCR实践+166页GPT-V试用报告首发解读 ChatGPT Vision,亦被广泛称为GPT-V或GPT-4V,代表 ...

  2. 【解决了一个小问题】macbook m2 下交叉编译 musl-gcc 支持的 gozstd 库

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 我的 golang 项目中使用了 gozstd, 在 ma ...

  3. 【解决了一个小问题】vm-agent中,如何对envoy这样的特殊expoter路径做处理?

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 envoy这个组件的expoter路径为 /stats/p ...

  4. ILRuntime的TestCase

    基于ILRuntime 1.6.3版本,在ILRuntime中提供测试用例,建议在下载ILRuntime之后先跑一遍官方的测试用例,对比自己使用ILRuntime的性能和官方数据是否一致 测试工具 测 ...

  5. github clone或访问慢

    做技术的我们经常会访问github.com,有时出现github访问非常慢或者git clone速度很慢,git push也很慢 原因很简单:github被高高的墙屏蔽了. 所以解决方案就是手动把 c ...

  6. Go语言的100个错误使用场景(一)|代码和项目组织

    目录 前言 1. Go: Simple to learn but hard to master 1.1 Go 语言概述 1.2 简单不等于容易 1.3 使用 Go 的100个错误 2. Code an ...

  7. 守护线程(Python)

    import time from threading import Thread def son(): while True: print('in son') time.sleep(1) def so ...

  8. AI自动生成视频保姆级教程,还能赚包辣条哦~

    友友们,小卷今天给大家分享下如何通过AI自动生成视频,只需要3分钟就能做出一个视频,把视频发到B站.抖音.西瓜上,还能赚包辣条哦~ 文末给大家准备了AI变现的案例及AIGC知识库,记得领取哦! 1.收 ...

  9. Delphi原子操作函数介绍

    一.Delphi的原子操作函数 在System.SyncObjs单元中,有一个TInterlocked的密封类,其十多个类函数(class function)其实都是调用的System单元的原子操作函 ...

  10. Eclipse安装配置、卸载教程(Windows版)

    Eclipse是一个开放源代码的集成开发环境(IDE),最初由IBM公司开发,现在由Eclipse基金会负责维护.它是一个跨平台的工具,可以用于开发多种编程语言,如Java.C/C++.Python. ...