项目中使用到了 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. SPECJVM2008的简单结果

    SPECJVM2008的简单结果 摘要 前面两天学习了SPECJVM2008简单使用. 今天进行一下简单的数据采集. 需要说明一下SPECJVM2008貌似仅兼容JDK1.8 更新的LTS版本都不兼容 ...

  2. 基于Apache PDFBox的PDF数字签名

    在Java语言环境中完成数字签名主要基于itext-pdf.PDFBox两种工具,itext-pdf受商业限制,应用于商业服务中需要购买授权.PDFBox是apache基金会开源项目,基于apache ...

  3. JRC Flink流作业调优指南

    # 作者:京东物流 康琪 本文综合Apache Flink原理与京东实时计算平台(JRC)的背景,详细讲述了大规模Flink流作业的调优方法.通过阅读本文,读者可了解Flink流作业的通用调优措施,并 ...

  4. vue3.2中setup语法糖父组件如何调用子组件中的方法

    父组件如何调用子组件中的方法 父组件.vue <template> <div> <aa ref="testRef"></aa> &l ...

  5. 【APP 逆向百例】Frida 初体验,root 检测与加密字符串定位

    声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容.敏感网址.数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 本文章未经许 ...

  6. Flask四剑客

    目录 Flask四剑客 Flask四剑客 ''' 响应字符串 响应html页面 跳转页面 返回json字符串 ''' from flask import Flask, render_template, ...

  7. 开源项目02-OSharp

    项目名称:OSharp 项目所用技术栈: osharp netstandard aspnetcore osharpns ng-alain angular等 项目简介: OSharp是一个基于.NetC ...

  8. lua开发和调试环境

    Lua开发环境搭建 Lua官网提供源码下载需要自己编译,Lua官网:https://www.lua.org/ftp/ lua for windows.exe(占二十多MB那个) 目前在网络上没有找到 ...

  9. Markdown常用书写语法合集

    1. 文字设置 1.1 文字颜色 中常用的文字颜色有: 红色文字:<font color="red">红色文字</font> 浅红色文字:<font ...

  10. Python Selenium 库使用技巧

    Selenium 是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样.支持的浏览器包括IE,Mozilla Firefox,Safari,Google ...