浅谈 rxgo 在项目中的使用方式
项目中使用到了 RxGo ,感觉现有的处理方式有一定的优势,当然也有一定的有劣势,遂记录下来,免得自己忘记。
本文介绍的只是 rxgo 的一种方式而已,如果你有不错的使用方式,请不吝赐教,谢谢。
对 rxgo 不清楚的同学可以看看我的另一篇文章,主要是介绍 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 在项目中的使用方式的更多相关文章
- 浅谈redux-form在项目中的运用
准则 先说一下redux的使用场景,因为如果没有redux,那更不会有redux-form. redux基于Flux架构思想,是一个状态管理框架,其目标是解决单页面应用中复杂的状态管理问题. 日常前端 ...
- 浅谈Log4net在项目中如何记录日志
一 引入背景 在软件开发周期中,无论是开发中,或是测试中,或是上线后,选择合适的工具监控程序的运行状态至关重要,只有如此,才能更好地排查程序问题和检测程序性能问题等.本篇文章主要与大家分享,如何 ...
- 【ASP.NET MVC系列】浅谈NuGet在VS中的运用
一 概述 在我们讲解NuGet前,我们先来看看一个例子. 1.例子: 假设现在开发一套系统,其中前端框架我们选择Bootstrap,由于选择Bootstrap作为前端框架,因此,在项目中,我们 ...
- 浅谈线程池(中):独立线程池的作用及IO线程池
原文地址:http://blog.zhaojie.me/2009/07/thread-pool-2-dedicate-pool-and-io-pool.html 在上一篇文章中,我们简单讨论了线程池的 ...
- 浅谈surging服务引擎中的rabbitmq组件和容器化部署
1.前言 上个星期完成了surging 的0.9.0.1 更新工作,此版本通过nuget下载引擎组件,下载后,无需通过代码build集成,引擎会通过Sidecar模式自动扫描装配异构组件来构建服务引擎 ...
- 浅谈如何检查Linux中开放端口列表
给大家分享一篇关于如何检查Linux中的开放端口列表的详细介绍,首先如果你想检查远程Linux系统上的端口是否打开请点击链接浏览.如果你想检查多个远程Linux系统上的端口是否打开请点击链接浏览.如果 ...
- $.ajax()方法详解 ajax之async属性 【原创】详细案例解剖——浅谈Redis缓存的常用5种方式(String,Hash,List,set,SetSorted )
$.ajax()方法详解 jquery中的ajax方法参数总是记不住,这里记录一下. 1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: 要求为Str ...
- 【转】浅谈常用的几种web攻击方式
浅谈常用的几种web攻击方式 一.Dos攻击(Denial of Service attack) 是一种针对服务器的能够让服务器呈现静止状态的攻击方式.有时候也加服务停止攻击或拒绝服务攻击.其原理就是 ...
- 浅谈boost.variant的几种访问方式
前言 variant类型在C++14并没有加入,在cppreference网站上可以看到该类型将会在C++17加入,若想在不支持C++17的编译器上使用variant类型,我们可以通过boost的va ...
- 【Unity游戏开发】浅谈Lua和C#中的闭包
一.前言 目前在Unity游戏开发中,比较流行的两种语言就是Lua和C#.通常的做法是:C#做些核心的功能和接口供Lua调用,Lua主要做些UI模块和一些业务逻辑.这样既能在保持一定的游戏运行效率的同 ...
随机推荐
- [转帖]Nginx四层负载均衡详解
https://developer.aliyun.com/article/885599?spm=a2c6h.24874632.expert-profile.315.7c46cfe9h5DxWK 202 ...
- mysql8 CentOS7 简要安装说明
1. 卸载mariadb rpm -qa |grep mariadb |xargs yum remove -y比较简单的卸载办法. 2. 安装所有的rpm包. yum localinstall *.r ...
- 京音平台-一起玩转SCRM之电销系统
作者:京东科技 李良文 一.前言 电销是什么?就是坐席拿着电话给客户打电话吗?no no no,让我们一起走进京音平台之电销系统. 京音平台2020年初开始建设,过去的两年多的时间里,经历了跌宕起伏, ...
- ABP vNext系列文章01---模块化
一.模块化应用 1.继承AbpModule 每个模块都应该定义一个模块类.定义模块类的最简单方法是创建一个派生自AbpModule的类,如下所示: 2.配置依赖注入和其他模块---ConfigSe ...
- Vue基础系列文章05----babel安装与使用
1.安装babel-node包,运行: 1) npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/node 2 ...
- Java 自增自减运算符和移位运算符介绍
摘自 JavaGuide (「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识.准备 Java 面试,首选 JavaGuide!) 自增自减运算符 在写代码的过程中,常见的 ...
- 驱动开发:Win10枚举完整SSDT地址表
在前面的博文<驱动开发:Win10内核枚举SSDT表基址>中已经教大家如何寻找SSDT表基地址了,找到后我们可根据序号获取到指定SSDT函数的原始地址,而如果需要输出所有SSDT表信息,则 ...
- C/C++ Npcap包实现数据嗅探
npcap 是Nmap自带的一个数据包处理工具,Nmap底层就是使用这个包进行收发包的,该库,是可以进行二次开发的,不过使用C语言开发费劲,在进行渗透任务时,还是使用Python构建数据包高效,这东西 ...
- 47从零开始用Rust编写nginx,配对还有这么多要求!负载均衡中的路径匹配
wmproxy wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 负载均衡, 静态文件服务器,websocket代理,四层TCP/UDP转发,内网穿透等,会将实 ...
- pandas教程02: 查找表中数据
在上篇教程中,我们介绍了pandas的安装.数据的导入与导出以及删除行列的操作.这次让我们一起研究下在pandas中如何根据指定的条件查找表中数据. 1. 数据准备 这次,我们使用一张学生成绩 ...