消息总线授权设计

我曾在之前的一篇文章中对比过消息队列跟消息总线。它们其中的一个不同点就是:消息总线更关注通信安全,消息总线可以管控通信双方,对通信的管控是建立在授权的基础上。因此授权模型的设计是消息总线必须考虑的问题。所谓的授权,就是校验通信双方有没有建立可信任的通信关系。这篇文章我们来谈谈消息总线的权限设计。

消息总线使用场景及RabbitMQ通信简介

在介绍授权设计之前,我们先了解一些必要信息。通常我们将消息总线应用于以下这些场景:

  1. 缓冲类——自生产自消费
  2. 解耦类、异步类——生产者消费者模型
  3. 服务调用类(RPC)——请求/响应
  4. 管控协调类——发布/订阅
  5. 通知类——广播

RabbitMQ原始的通信机制是非常简单的:publish(发送),consume(接收)。其他的模型都是在这个基础上构造出来的。因此我们可以将通信的API分为两类:

  • Publish(发送方):只需要了解exchange,routingkey
  • Consume(接收方):需要了解queue,routingkey

也就是说理论上作为单纯的发送方,可以不需要队列。

申请方app跟通信队列建立关联关系

其实所谓的授权,对应到数据库的模型里就是建立通信双方的数据库记录之间的关系。在建立关系的时候接收方肯定是以queueId作为队列主键来创建关系。而发送方呢?如果像上面假设的那样:它不需要队列就只能以appId作为创建关系了。这种授权方式也很普遍,比如在服务注册中心申请调用某个服务时,以一个appId的身份向服务管控台申请调用服务。但这种建立关系的方式,在消息总线的授权上遇到如下两个方面的问题。

多重身份问题

第一个场景是缓冲类的场景,很多消息都是自生产自消费的。对于这种场景,只需依靠本应用下的一个队列,而不需要跟外部应用通信;第二个场景是pipeline-filter模型下的某个stage或component。如图示:

它接收来自上一个stage的中间结果,处理完数据之后,再将最新的中间结果发送给下一个stage。

这两种情况下,发送方必须拥有队列。

request/response模型的问题

对web服务而言,一次调用从通信模型上看是一个request/response模型。但这个通信依靠的是http协议,而RabbitMQ的消费基于的是队列(队列的好处就是缓冲、异步、解耦这些特征与阻塞等待是对立的)。在这种情况下,我们要基于RabbitMQ模拟出request/response模型(注意这不是为了违背队列的初衷,而是为了简化特定业务场景的通信),就必须依赖一个接收response响应的队列,因此这种模型注定了request端也必须拥有一个队列。当然或许你觉得,如果基于一个临时生成的队列,接收完响应消息立即销毁队列,就如同http的无状态特征一样,也是可行的。我不是没想过这种做法,只是这取决于request/response模型使用的频度,如果频度不高是可以接收的;但如果非常频繁的话,创建队列的成本肯定远大于一直维持一个固定队列的成本。

虚拟队列

看起来给发送方也分配一个队列是比较可行的解决方案。但对于单纯的发送方,分配队列造成了消息服务器资源的浪费,因为从通信的角度来看,完全不需要一个真实队列的存在,而在这里它存在的目的却是为了契合对等授权模型。因此,我们可以选择一种折中的处理方案——对单纯的发送方而言,用虚拟队列代替真实队列。

当某个app申请队列时,我们将其分为三类:单纯的发送方、单纯的接收方、既是发送方同时也是接收方。只有后两者才会在RabbitMQServer上创建真实的队列,单纯的发送方不会在RabbitMQServer上创建真实队列,只在数据库中为其创建一个队列记录而已。以下我们谈及的队列既可能是真实队列,也可能是虚拟队列。每个队列都会被分配有一个secret,以唯一全局标识该队列,并且从安全角度而言,该队列的secret只有申请者自身以及管控台知道。

队列按通信模型进行隔离

一个队列在创建的时候,它必须选择是作为发送方还是接收方还是两者都有以及是哪种通信类型。只有指定了通信类型,才能让它正确地绑定到合适的exchange下去。另外通信的两个队列只能处于相同的模型内,比如,永远不可能出现produce/response或者request/consume这些交叉的通信模型。

这种隔离既是技术上的约束也是语义上的约束。

授权实现

上面谈到一些设计的前提以及理由。下面我们谈谈,我们是如何来实现授权的。授权主要分为如下三个方面。

队列申请

首先,必须经历的一步是审核队列申请。也就是一个应用要跟别的队列进行通信,它必须来申请一个队列,然后管控台的审核人员需要对其进行审核。其实这里的审核主要是针对publish以及broadcast。因为produce/consume、request/response这两组点对点的通信模型,它们的通信还需要经过一次建立连接的审核。而broadcast,publish作为公共或半公开的服务,在它们发送消息时是不需要被授权的。

信道

根据通信模型不同,我们对发送方跟接收方建立的关系也有所不同。对于produce/consume以及request/response这两个模型而言,创建的是点对点的通信链路,这里我们称其为信道(Sink)。

一个信道是一条单向的链路,基于源队列跟目的队列建立。关于接收方有一个原则:因为每个接收方只能消费自己队列里的消息,因此我们主要把控从发送方到接收方这条有向线路的发送权限,这就跟http请求一样,通常你只在server校验请求发起方是否是可信任的。

信道的控制台展示:

对于这种模型下的通信,发送者需要持有:

  1. 自身队列的标识:secret
  2. 目标队列的名称:targetQueueName(该名称默认对外公开)
  3. 信道授权后获取到的token

接收者只需要持有自身队列的标识:secret即可消费自身队列。

这里有个特殊的地方,那就是缓冲场景的自生产自消费模型。这种场景只围绕一个队列通信,这种情况是信道的一种特例,我们可以借鉴TCP/IP里处理“环回地址”(loopbackip,127.0.0.1,意思是代表本机,从网卡上过一下就回来,不需要从网络上绕一圈)的方式为:申请队列时,必须申请produce-consume模型(即同时兼具生产和消费的队列),然后secret跟token填一样的来表明其特殊性,targetQueueName填自身队列的公共名称即可。

频道

对于publish/subscribe模型而言,创建的是一对多的关系,我们称其为频道(Channel)。基于一些考虑,我们将publish/subscribe实现为精准发送模型,关于另一种实现方式,可见我之前的一篇文章。这里的授权是针对接收方授权来产生pub/sub关系。频道在管控台的UI展示:

基于这种模型的通信,发送方(publish)只需要持有:自身队列的标识:secret就可以了。授权体现在publish的时候,查找要发送的订阅者的过程。

而订阅方只需要持有自身队列的标识:secret,即可消费自己订阅的发送方的消息。

以上就是消息总线的权限设计,总的来说是基于队列到队列的对等授权模型。代码实现见:https://github.com/yanghua/banyan

时间: 2024-10-05 22:02:45

消息总线授权设计的相关文章

Linux进程内消息总线设计

文章目录 Windows平台进程内消息总线 如果没有消息总线,会产生什么问题 死循环包含关系 高耦合.低内聚 消息总线 结构图 原理 生产者与总线的关系 总线与消费者的关系 Linux进程内消息总线设计 使用进程间实时信号来实现进程内消息总线 参考文档 整体流程 主线程注册总线消息处理函数 生产者线程产生并发送消息到总线 接收并处理消息 总线接收到消息 总线消息处理函数 消费者总线消息处理虚函数 消费者消息处理实函数 核心原理 sigqueue发送消息 sigaction处理消息 存在的问题 消

关于消息总线使用哪种消息队列中间件的调查

几种MQ产品说明: ZeroMQ :  扩展性好,开发比较灵活,采用C语言实现,实际上他只是一个socket库的重新封装,如果我们做为消息队列使用,需要开发大量的代码 RabbitMQ :结合erlang语言本身的并发优势,性能较好,但是不利于做二次开发和维护 ActiveMQ: 历史悠久的开源项目,已经在很多产品中得到应用,实现了JMS1.1规范,可以和spring-jms轻松融合,实现了多种协议,不够轻巧(源代码比RocketMQ多).,支持持久化到数据库,对队列数较多的情况支持不好,不过我

消息总线之模型重构

前段时间重新对消息总线的通信模型进行设计&重构,这篇文章谈谈其中的一些想法. RabbitMQ简介 消息总线对RabbitMQ的官方java client进行了定制.简化.这里首先谈谈RabbitMQ作为一个Message Broker的两个核心概念: Exchange Exchange(交换器),其实你可以简单得将它看成是Router(路由器),路由就是它的主要职责.它支持非常灵活的 Exchange-Exchange.Exchange-Queue 之间的绑定,并提供了4种Exchange类型

消息总线扩展之主动转发

问题简述 消息总线目前为Java编程语言提供了SDK,同时针对其他语言提供了一个称之为httpBridge的http代理.这基本可以满足大部分主流编程语言对消息总线的使用需求,但这也仅仅是对技术层面上的需求的满足.在业务层面上,尤其是面对老的业务系统的适配一直都是个难题,这篇文章谈谈面对一个在线上运行的业务系统,如何使得引入消息总线的总体成本尽可能得低. 就消息总线的两种使用方式而言,无论是SDK的方式还是httpBridge的方式,都需要往第三方系统引入对消息总线的依赖,这些依赖包括但不仅限于

消息总线重构之简化客户端

这段时间对消息总线进行了再次重构.本次重构主要针对消息总线的pubsub组件以及对client的简化,同时谈谈对消息总线的一些想法. 简化client的复杂度 之前的client需要同时连接两个分布式组件.消息总线的访问需要用户提供pubsuberHost,pubsuberPort参数,因此它首先连接的就是pubsuber.而消息总线是基于RabbitMQ构建的,因此它必然还需要连接RabbitMQ.而之所以没有需要用户程序提供RabbitMQ Server的地址信息,是因为它是通过pubsub

谈消息总线的路由模型

最近在写一个基于RabbitMQ的消息总线.虽然RabbitMQ提供了plugin的机制可以实现对其进行扩展,但是由于对erlang语言不熟,考虑到上手成本高,因此放弃实现plugin,转而基于Smart client + 树形拓扑路由的模型.当然这也大大降低了我们实现功能的灵活性,后面我会找个时间开篇新文章,谈谈Smart Client的限制. 预备知识 RabbitMQ对于消息的通信只提供了几个非常简单的API:Channel#basicPublish:Channel#basicConsum

消息总线VS消息队列

前段时间实现了一个基于RabbitMQ的消息总线,实现的过程中自己也在不断得思考.总结以及修正.需要考虑各个维度:效率.性能.网络.吞吐量.甚至需要自己去设想API可能的使用场景.模式.不过能有一件事情,自己愿意去做,在走路.吃饭.坐公交的时候都在思考如何去改进它,然后在实践的过程中,促使去思考并挖掘自己知识面的空白,也是一件让人开心的事情. 借此记录下自己在实现的过程中,以及平时的一些想法. 这是第一篇,先谈谈消息总线跟消息队列的区别,以及对于企业级应用需要将消息队列封装成消息总线的必要性.

消息总线的应用场景

应用场景 分布式事务 分布式系统组件相互通信 数据复制 日志同步 delay queue 广播通知 介绍 消息总线是一种通信工具,可以在机器之间互相传输消息.文件等. 消息总线扮演着一种消息路由的角色,拥有一套完备的路由机制来决定消息传输方向.发送段只需要向消息总线发出消息而不用管消息被如何转发,为了避免消息丢失,部分消息总线提供了一定的持久化存储和灾备的机制. [多机房同步方案] 通过消息广播方式将数据多点分布 数据提交给一个代理,这个代理帮我们把这些数据同步到多个机房,那我们应用不需要关心这

SOA实践之基于服务总线的设计

在上文中,主要介绍了SOA的概念,什么叫做“服务”,“服务”应该具备哪些特性.本篇中,我将介绍SOA的一种很常见的设计实践--基于服务总线的设计. 基于服务总线的设计 基于总线的设计,借鉴了计算机内部硬件组成的设计思想(通过总线传输数据).在分布式系统中,不同子系统之间需要实现相互通信和远程调用,比较直接的方式就是“点对点”的通信方式,但是这样会暴露出一些很明显的问题:系统之间紧密耦合.配置和引用混乱.服务调用关系错综复杂.难以统一管理.异构系统之间存在不兼容等.而基于总线的设计,正是为了解决上