RabbitMQ 入门 (Go) - 5. 使用 Fanout Exchange 做服务发现(下)

到目前为止,我一直专注于如何让消息进出消息代理,也就是RabbitMQ。
实际上,我们可以继续使用 RabbitMQ 和它的 Exchanges 来连接这个应用程序的其他部分,但是我想探索一个稍微不同的模型:我想使用协调器来跟踪哪些类型的消费者得到消息通知。
这样的话,我断开了传感器数据生成器和数据使用者之间的连接。
同时为了处理这些数据通信,我决定使用事件(event)来通知用户系统中正在发生的事情,并让他们决定是否要处理数据。
其原理大致如下:
在协调器内部,我们有构建好的 QueueListener。
我还需要构建另外一个类型,我叫它 EventAggregator。
来自RabbitMQ 的消息,它将通过一个异步的goroutine 进入QueueListener
goroutine 将把消息传输到一个事件对象(event object)中,并通过事件聚合对象(event aggregation object)进行广播。
该对象将维护任何对事件感兴趣的使用者的注册表,并向其发送事件对象的副本。
这使我们能够通过将数据转储到下游的 Queue 来为这些事件注册其他应用程序,但它也可以让使用者能够在协调器内部进行设置,例如日志系统。
最后,如果使用者最终要通过 Queue 将数据发送到另一个应用程序,则可以对其进行预处理,以添加有用的附加数据,而最终使用者不必知道这些附加信息是如何到达那里的。
编写代码
创建 EventAggregator
在 coordinator 目录下添加 eventaggregator.go,代码如下:

第 28 行,建立 EventData struct,目前它的字段碰巧和 SensorMessage 是一样的,但是两个 struct 的职责不同,所以我们不复用 SensorMessage,而是单独建立 EventData,以便它们以后可以独立的进化;
第 5 行,建立了 EventAggregator struct,也就是事件聚合,它只有一个 listeners 字段,是一个 map,它的 key 是事件的名称,它的值是回调函数的集合。当事件发生的时候,EventAggregator 就轮流调用为该事件注册的回调函数;
第 9 行,就是 EventAggregator 的构造函数;
第 16 行,AddListener 方法,使用者通过该方法可以向 EventAggregator 注册回调函数;
第 20 行,PublishEvent 方法用来发布事件。它接收事件名称和事件的数据作为参数。这里需要判断 EventAggregator 里是否已经注册了该事件,如果注册了,那么遍历其对应的回调函数,并使用事件数据进行调用。
调用回调函数时,使用的不是 EventData 的指针,而是 EventData 的副本,这可以保证使用者不会把事件数据搞乱,影响其它使用者
取消订阅的功能我就不做了。
把 EventAggregator 连接到 QueueListener
打开 queuelistener.go,添加代码:

第19 行,在QueueListener struct 里面添加字段ea,类型是 *EventAggregator;
第 25 行,在 QueueListener 的构造函数里为 ea 自读赋初始值。

在 AddListener 方法里,原来只是把原始数据打印到控制台。现在添加如下代码:
创建一个 EventData,其字段内容目前和传感器的消息内容一样;
使用 QueueListener 上的 EventAggregator 发布事件:
事件的名称是 MessageReceived_传感器名称
第二个参数就是事件数据
发现早已运行的传感器
最后我们要做的就是如何让协调器发现在协调器上线前就已经在运行的传感器。

目前我们的做法是这样的:首先协调器先运行,然后传感器在上线的时候立即把它们的数据Queue 发送过去,使用的是 Fanout Exchange,这样多个协调器都可以被通知到。
但是,如果传感器先运行,协调器后运行,那么协调器就无法知道传感器的存在,为了解决这个问题,我这样做:
我在消息代理中也就是 RabbitMQ 里,建立一个新的 Exchange,它是一个 Fanout Exchange,它和其它信息流的方向正好相反。
在这里,协调器将会向这个 Fanout Exchange 发出一个“发现”请求,这个信息将会发送给所有的传感器。
传感器接收到这个“发现”请求信息后,将会响应,将它们的数据 Queue 的名称发送给我们以前建立的那个 Fanout Exchange(中间黄色的)。
这里会出现一些冗余的信息,但协调器里有过滤机制,所以就这样吧。
我们首先测试一下先运行传感器项目,再运行协调器项目的效果:

可以看到,协调器运行起来以后,没有接收到该传感器的数据。
修改 queuetools
我们要解决的就是这个问题,下面看代码,首先看 queuetools.go:

这里改动不多,就是把要新建立的 Fanout Exchange 的名称作为常量存在这里。
注意之前在这里定义的 SensorListQueue 已经不需要了,可以删掉。
修改 queuelistener
然后看 queuelistener.go,在这里为 QueueListener 添加一个DiscoverSensors 方法:

该方法中首先我使用了 ExchangeDeclare 方法来声明这个新的 Exchange,并进行设置。
虽然项目中还没用过这个方法,但是里面大多数参数的作用你应该能够猜得出来:
name:Exchange 的名称
kind:Exchange 的类型,可以是 direct、topic、header 或者 fanout,这里使用 fanout
durable:表示这个 Exchange 是否可持久
autoDelete:表示在没有绑定的情况下是否删除 Exchange
internal:这个参数我们还没见过,如果想拒绝外部的发布请求,就把这个设为 true。这可以在高级场景中使用,在高级场景中,Exchange 绑定在一起,在消息代理中形成更复杂的拓扑。
noWait 和 args 就不介绍了。
现在,协调器可以向这个 Exchange 发布消息了。而我们只需要向它发送一个消息即可,并没有什么具体的内容要发送,所以我发布了一个空的 Publishing,这就可以告诉浏览器我在寻找它们了。
修改传感器
下面我们让传感器(sensor.go)对上面发布的“发现”请求进行响应,不过首先,需要重构一下。
把 main 函数里面当传感器上面时,发布数据 Queue 名称那部分代码提取出来放在单独的一个函数里面:

然后在 main 函数相应的位置进行调用:

第 39 行,对重构的函数进行调用。
第 41 行,创建一个 Queue
第 42 行,使用 QueueBind 方法将这个 Queue 和 SensorDiscovery Exchange
第 48 行,创建goroutine 运行一个将要新建的函数 listenForDiscoveryRequests。通过使用 goroutine,无论当请求什么时候进来,这部分逻辑都将可用,而且不会阻塞系统的其余部分。这里需要传入 Queue 的名称和 Channel。
然后看一下 listenForDiscoveryRequests 函数:

这里使用 Channel 的 Consume 方法对 Channel进行设置以便能接收“发现”请求。
然后用 for range 来接收“发现”请求。这里忽略消息本身即可,因为该消息就是一个触发而已。当消息进来时,调用刚刚重构出来的 publishQueueName 函数即可。
在 queuelistener 里调用发现方法
在 queuelistener.go 的 ListenForNewSource 方法里,在如下位置调用 DiscoverSensors 方法:

为什么在这里调用?因为这是可以保证协调器正在监听传感器路由的消息的第一个地方。
运行测试
先运行一个传感器,然后在运行协调器:

传感器这里我使用了 freq 参数,让其每两秒钟生成一个数据。
可以看到,在这种情况下协调器也可以发现已经运行的传感器并接收数据了。
你可以运行多个传感器和多个协调器,应该也会好用的。
这也是一种非常简单的分布式应用吧。
RabbitMQ 入门 (Go) - 5. 使用 Fanout Exchange 做服务发现(下)的更多相关文章
- RabbitMQ 入门 (Go) - 4. 使用 Fanout Exchange 做服务发现(上)
到目前为止,我们项目的结果大致如下: 传感器生成的模拟数据(包含传感器名称.数据.时间戳)是通过传感器在运行时动态创建的 Queue 来发送的.这些 Queue 很难直接被发现. 为了解决这个问题,我 ...
- 阿里巴巴为什么不用 ZooKeeper 做服务发现?
阿里巴巴为什么不用 ZooKeeper 做服务发现? http://jm.taobao.org/2018/06/13/%E5%81%9A%E6%9C%8D%E5%8A%A1%E5%8F%91%E7%8 ...
- 使用Consul做服务发现的若干姿势
从2016年起就开始接触Consul,使用的主要目的就是做服务发现,后来逐步应用于生产环境,并总结了少许使用经验.最开始使用Consul的人不多,为了方便交流创建了一个QQ群,这两年微服务越来越火,使 ...
- Consul做服务发现
使用Consul做服务发现的若干姿势 https://www.cnblogs.com/bossma/p/9756809.html 从2016年起就开始接触Consul,使用的主要目的就是做服务发现,后 ...
- Go | Go 使用 consul 做服务发现
Go 使用 consul 做服务发现 目录 Go 使用 consul 做服务发现 前言 一.目标 二.使用步骤 1. 安装 consul 2. 服务注册 定义接口 具体实现 测试用例 3. 服务发现 ...
- Api网关Kong集成Consul做服务发现及在Asp.Net Core中的使用
写在前面 Api网关我们之前是用 .netcore写的 Ocelot的,使用后并没有完全达到我们的预期,花了些时间了解后觉得kong可能是个更合适的选择. 简单说下kong对比ocelot打动我的 ...
- etcd学习(3)-grpc使用etcd做服务发现
grpc通过etcd实现服务发现 前言 服务注册 服务发现 负载均衡 集中式LB(Proxy Model) 进程内LB(Balancing-aware Client) 独立 LB 进程(Externa ...
- go-micro使用Consul做服务发现的方法和原理
go-micro v4默认使用mdns做服务发现.不过也支持采用其它的服务发现中间件,因为多年来一直使用Consul做服务发现,为了方便和其它服务集成,所以还是选择了Consul.这篇文章将介绍go- ...
- RabbitMQ入门:主题路由器(Topic Exchange)
上一篇博文中,我们使用direct exchange 代替了fanout exchange,这次我们来看下topic exchange. 一.Topic Exchange介绍 topic exchan ...
随机推荐
- git alias all in one
git alias all in one workspace:工作区 staging area:暂存区/缓存区 local repository:或本地仓库 remote repository:远程仓 ...
- js 构造函数 & 静态方法 & 原型 & 实例方法
js 构造函数 & 静态方法 & 原型 & 实例方法 ES5 "use strict"; /** * * @author xgqfrms * @licens ...
- TypeScript 如何编写类库声明文件 .d.ts
TypeScript 如何编写类库声明文件 .d.ts how to write a d.ts file declaration-files/ https://www.typescriptlang.o ...
- project config generator
project config generator React, Vue, https://createapp.dev/ parcel https://parceljs.org/ https://git ...
- 【Python核心编程笔记】一、Python中一切皆对象
Python中一切皆对象 本章节首先对比静态语言以及动态语言,然后介绍 python 中最底层也是面向对象最重要的几个概念-object.type和class之间的关系,以此来引出在python如何做 ...
- JavaScriptBOM操作
BOM(浏览器对象模型)主要用于管理浏览器窗口,它提供了大量独立的.可以与浏览器窗口进行互动的功能,这些功能与任何网页内容无关.浏览器窗口的window对象是BOM的顶层对象,其他对象都是该对象的子对 ...
- Vue学习笔记-vue调试工具vue-devtools安装及使用
一 使用环境: windows 7 64位操作系统 二 vue调试工具vue-devtools安装及使用 1.下载: 百度中查找 "vue-devtools下载" 找到最新 ...
- OSS对象储存
简介 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量.安全.低成本.高可靠的云存储服务. 使用流程 名词解释 Endpoint(访问域名) Ac ...
- 由剑指offer引发的思考——对象中虚函数指针的大小
先看一个简单的问题: 一.定义一个空的类型,对于其对象我们sizeof其大小,是1字节.因为我们定义一个类型,编译器必须为其分配空间,具体分配多少是编译器决定,vs是1字节,分配在栈区. 那,这一个字 ...
- 1.3.1 apache的配置(下)
(1)httpd.conf的配置 使用文本编辑工具(推荐使用Editplus.UltraEdit等工具),打开httpd.conf. 其中,行首为#的部分为注释部分,不会被apache服务器程序进行读 ...