前面已经介绍了消息生产消费中间类库(OSS.DataFlow)的简单使用,这篇主要介绍内部的设计实现。主要内容包含:

  1. 消息生产消费的抽象设计。

  2. 具体使用示例

一. 消息生产消费的抽象设计。

  需要首先强调的是,这里的生产消费抽象主要在业务使用层面,抛开具体的RabbitMQ之类的消息队列产品。可能说起来比较模糊,我们先看下常见的业务调用消息队列的情况:

  上边是一个常见的通过消息队列将业务异步拆解的模型,按道理结构已经十分简单了,特别是对于一个相对稳定的业务,代码基本变动不大的情况下,这个模型基本足够了。不过对于一个爱折腾的开发人,不玩点花里胡哨的,存在感就没了,经过各种假设论证之后,终于找到了这么几个不满意的地方:

  1.  开发,测试,生产,都需要搭建对应的Rabbit实例,特别是开发测试环境抢消息,再加上多人开发,大麻烦可能没有,但小麻烦一定是不断的。

  2.  业务代码中,直接调用了具体消息队列的产品,当某一个模块消息量快速上升,无法局部切换队列产品。(当然你也可以切分出独立的服务,但是耗时,代价相对较大)。

  3.  对于同一解决方案内的异步消息,或多或少的会出现生产消息调用和消费消息调用代码分散(比如用户登录后添加日志)。

  4. 当前的项目代码依赖外部消息队列或者数据库(如果公司偏项目型,新项目无法轻装上阵)

  以上臆测仅个人偏见,仅供参考。在我的角度,我希望在业务调用消息中转的过程中,需要面向的是接口,在需要的时候适配即可,所以我尝试添加一个轻简的中间层。

  这个中间层第一件事就是隔离,草图设计如下:

  通过全局 DataFlowFactory 能够创建消息发布者(IDataPublisher),并能够注入订阅者(IDataSubscriber) ,业务层只需要通过 IDataPublisher,IDataSubscriber 接口交互,和具体消息存储设施脱离。顺着这个思路,当业务需要生产写入消息时,创建发布者,并通过发布者写入消息,并完成订阅者的回调,这个环路即可完成。

  现在只需要解决两个问题:

  1. 创建的发布者,如何实现不同场景的扩展。

  2.  如何完成对应的订阅者(支持同一消息类型,多个订阅者)的回调。

  这里我引入一个全局管理对象(DataFlowManager),内部的调用过程图示如下:

  在 DataFlowManager 中提供了  PublisherProvider 公开属性,可用来扩展不同消息设施的发布者实现。同时,提供了 NotifySubscriber 方法,作为已注册消息订阅者的统一触发入口(内部完成了多个订阅者的调度,当然如果针对特殊消费者调用,用户也可以跳过注册订阅者,自由实现订阅处理)。

  通过上边的整个过程,完全实现了消息中间层的功能,以插件的形式将具体的消息设施在程序的全局入口注入,这样就可以针对不同环境不同业务模块(入口的参数 source_name 控制)做定制化。同时,在大多数的项目中(包括在开发环境中)并无需立即使用独立的消息设施,所以在中间件的内部,提供了一个默认的内存消息队列实现,这样也保证了类库的即引即用,扩展后的图示如下:

  根据上边的过程图可以看出,内部的默认队列,和外部队列所处统一层级,当没有提供用户自定义 PublisherProvider时(或者Provider 返回的 DataPublisher为空),系统会执行内部的默认队列实现。

二. 具体使用示例

  上边展示了内部设计,这里介绍具体的代码使用层面,看在实际的使用中是如何简化内敛整个消息的处理。

  1.  业务侧调用:

  上边演示了消息Key为 “ P-S-Msg ” ,有两个订阅者(这里使用了委托方法,也可以传入继承IDataSubscriber<MsgData> 接口的实例),并且创建了一个名为 _publisher 的发布者(如果没有注册其他消息存储适配实现,会走默认内部消息队列实现,即创建一个名为 NewSource 的队列)。除了上边的使用方式,有些时候我们的生产和消费代码都是同一个服务内部,比如用户登录和添加登录日志,这个时候提供了一个更简单的方式:

  可以看出,具体的业务代码相对很清晰,不需要关注具体的消息底层实现,或者什么触发方式(定时,webhook或者内部消费线程)。而这些内容全部转移到全局模块插件化适配。

二. 消息的底层适配:

  上边的代码,自定义了一个 CustomMsgStorage 消息存储适配器,并在全局初始化时,赋值给  DataFlowManager.PublisherProvider ,在这个适配器里,约定了当  source_name 等于 CustomStorageQueue 时返回消息发布者 CustomMsgStoragePublisher,当然这个具体实现可以替换成 RabbitMQ,Redis,Mysql 等等,当消息实际消费触发时,调用 DataFlowManager.NotifySubscriber() 方法即可,在上边的测试用例里,我简化了这个过程,直接调用,实际场景根据情况调整即可(比如放在RabbitMQ的消费监控线程,或者读取Mysql数据的定时任务中)。

  简单来说,DataFlowFactory处理业务使用接口(通过 msgkey 关联生产者和消费订阅者),DataFlowManager 控制具体的消息适配(通过 source_name 来控制底层适配), 基本解决了前面我所顾虑的问题。

如果你已经看到这里,并且感觉还行的话可以在下方点个赞,或者也可以关注我的公总号(见二维码)


消息抽象层设计和实现-OSS.DataFlow的更多相关文章

  1. SDK接入(U8SDK)——SDK抽象层的设计

    上一篇文章,我们总体地分析并设计了一套高效的SDK接入方案,也罗列出这套方案,我们需要完成的工作.这里再罗列并回顾下: 1.统一抽象的SDK接入框架 2.各个SDK接入实现 3.一键打包工具 4.统一 ...

  2. 事件消息生产消费中间件-OSS.DataFlow

    系统重构解耦的过程涉及不同领域服务分拆,或同一服务下实时响应部分和非响应部分分拆,分解后的各部分通过异步消息的流转传递,完成整体的业务逻辑,但是频繁的在业务层面直接调用不同消息队列的SDK,个人感觉不 ...

  3. Java消息系统简单设计与实现

    前言:由于导师在我的毕设项目里加了消息系统(本来想水水就过的..),没办法...来稍微研究研究吧..简单简单... 需求分析 我的毕设是一个博客系统,类似于简书这样的,所以消息系统也类似,在用户的消息 ...

  4. 【开源】OSharp框架解说系列(5.1):EntityFramework数据层设计

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  5. ENode 1.0 - 消息队列的设计思路

    开源地址:https://github.com/tangxuehua/enode 上一篇文章,简单介绍了enode框架内部的整体实现思路,用到了staged event-driven architec ...

  6. enode框架step by step之消息队列的设计思路

    enode框架step by step之消息队列的设计思路 enode框架系列step by step文章系列索引: enode框架step by step之开篇 enode框架step by ste ...

  7. 《细说PHP》第四版 样章 第18章 数据库抽象层PDO 1

    现在,如果你已经能熟练地使用MySQL客户端软件来操作数据库中的数据,就可以开始学习如何使用PHP来显示和修改数据库中的数据了.PHP提供了标准的函数来操作数据库.在PHP 5以上的版本中可以使用My ...

  8. 网络编程之网络架构及其演变过程、互联网与互联网的组成、OSI七层协议、socket抽象层

    目录 网络架构及其演变过程 单机架构 CS架构 BS架构 BS架构和CS架构的区别 C/S架构的优缺点: B/S架构的优缺点: 互联网与互联网的组成 互联网的组成(教科书版) 互联网的组成(科普版) ...

  9. redis源码分析(二)-rio(读写抽象层)

    Redis io抽象层 Redis中涉及到多种io,如socket与file,为了统一对它们的操作,redis设计了一个抽象层,即rio,使用rio可以实现将数据写入到不同的底层io,但是接口相同.r ...

随机推荐

  1. go 连接MSSQLServer数据库【遇到的坑】

    前言:项目测试需要用到mssqlserver数据库连接,遇到坑,自己爬直接上代码:       package main import ( "database/sql" " ...

  2. RabbitMQ持久化机制、内存磁盘控制(四)

    一.持久化 如果看到这一篇文章的朋友,都是有经验的开发人员,对持久化的概念就不用再做过多的解析了,经过前面的几篇文章,其实不难发现RabbitMQ 的持久化其实就只分交换器持久化.队列持久化和消息持久 ...

  3. Visual Studio Docker调试端口设置

    一.前言 在Visual Studio 调试程序时,Docker中的容器端口和主机端口映射随机生成,导致每次调试都需要修改前端API接口的地址 二.解决方案 1.修改Docker调试启动参数,找到启动 ...

  4. gin 跨域问题

    package middlewares import ( "github.com/gin-gonic/gin" "net/http" ) func Cors() ...

  5. transformers---BERT

    transformers---BERT BERT模型主要包括两个部分,encoder和decoder,encoder可以理解为一个加强版的word2vec模型,以下是对于encoder部分的内容 预训 ...

  6. Spring事件,ApplicationEvent在业务中的应用

    前言 关于事件驱动模型,百度百科在有明确的解释.在JDK的Util包里抽象了事件驱动,有兴趣的朋友可以自行去看下相关类的定义.Spring事件模型ApplicationEvent是基于JDK里的事件模 ...

  7. 云原生的弹性 AI 训练系列之三:借助弹性伸缩的 Jupyter Notebook,大幅提高 GPU 利用率

    Jupyter Notebooks 在 Kubernetes 上部署往往需要绑定一张 GPU,而大多数时候 GPU 并没有被使用,因此利用率低下.为了解决这一问题,我们开源了 elastic-jupy ...

  8. PAT (Basic Level) Practice (中文)1017 A除以B (20分)

    1017 A除以B (20分) 本题要求计算 A/B,其中 A 是不超过 1000 位的正整数,B 是 1 位正整数.你需要输出商数 Q 和余数 R,使得 A=B×Q+R 成立. 输入格式: 输入在一 ...

  9. NX开发库版本问题

    有做NX二次开发的朋友经常问我这样的问题:我在NX8.0上开发的程序,可以在NX9.0上运行吗? 由于NX的开发库随着版本的更新也会不断更新,会增加新的方法,同时有些也会过时或者消失. 如下图:NX8 ...

  10. 【Java虚拟机8】自定义类加载器、类加载器命名空间、类的卸载

    前言 学习类加载器就一定要自己实现一个类加载器,今天就从一个简单的自定义类加载器说起. 自定义类加载器 例1 一个简单的类加载器,从一个给定的二进制名字读取一个字节码文件的内容,然后生成对应的clas ...