浅谈 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模块和一些业务逻辑.这样既能在保持一定的游戏运行效率的同 ...
随机推荐
- SPECJVM2008的简单结果
SPECJVM2008的简单结果 摘要 前面两天学习了SPECJVM2008简单使用. 今天进行一下简单的数据采集. 需要说明一下SPECJVM2008貌似仅兼容JDK1.8 更新的LTS版本都不兼容 ...
- 基于Apache PDFBox的PDF数字签名
在Java语言环境中完成数字签名主要基于itext-pdf.PDFBox两种工具,itext-pdf受商业限制,应用于商业服务中需要购买授权.PDFBox是apache基金会开源项目,基于apache ...
- JRC Flink流作业调优指南
# 作者:京东物流 康琪 本文综合Apache Flink原理与京东实时计算平台(JRC)的背景,详细讲述了大规模Flink流作业的调优方法.通过阅读本文,读者可了解Flink流作业的通用调优措施,并 ...
- vue3.2中setup语法糖父组件如何调用子组件中的方法
父组件如何调用子组件中的方法 父组件.vue <template> <div> <aa ref="testRef"></aa> &l ...
- 【APP 逆向百例】Frida 初体验,root 检测与加密字符串定位
声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容.敏感网址.数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 本文章未经许 ...
- Flask四剑客
目录 Flask四剑客 Flask四剑客 ''' 响应字符串 响应html页面 跳转页面 返回json字符串 ''' from flask import Flask, render_template, ...
- 开源项目02-OSharp
项目名称:OSharp 项目所用技术栈: osharp netstandard aspnetcore osharpns ng-alain angular等 项目简介: OSharp是一个基于.NetC ...
- lua开发和调试环境
Lua开发环境搭建 Lua官网提供源码下载需要自己编译,Lua官网:https://www.lua.org/ftp/ lua for windows.exe(占二十多MB那个) 目前在网络上没有找到 ...
- Markdown常用书写语法合集
1. 文字设置 1.1 文字颜色 中常用的文字颜色有: 红色文字:<font color="red">红色文字</font> 浅红色文字:<font ...
- Python Selenium 库使用技巧
Selenium 是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样.支持的浏览器包括IE,Mozilla Firefox,Safari,Google ...