ZeroMQ的进阶
上一篇博文我们对ZeroMQ的经典模式做了写Demo让他跑起来了,但实际开发中我们可能面临一些远比上述复杂的场景。这时候我们需要进一步的对经典模式进行扩展,所幸ZeroMQ已经为我们做好了准备工作。
来吧,让我们继续在码上几行ZeroMQ的砖头。
ZeroMQ扩展模式
请求响应代理模式
请求响应模式绑定了请求端和响应端的联系,当我们添加一个新的响应端时则不得不修改响应的请求端配置,这是在是太不scalability了,想分布式必须解耦啊,想解耦就得添加第三方做代理,这个Proxy就是RouterSocet+DealerSokect。如果响应端是无状态的DealerSokect还能公平队列算法提供的负载均衡的效果。

Demo:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
class Program{ static void Main(string[] args) { using (var ctx = NetMQContext.Create()) { //proxy ThreadPool.QueueUserWorkItem((o) => { using (var front = ctx.CreateRouterSocket()) using (var back = ctx.CreateDealerSocket()) { front.Bind("tcp://127.0.0.1:9991"); back.Bind("tcp://127.0.0.1:9992"); var proxy = new Proxy(front, back, null); Task.Factory.StartNew(proxy.Start); using (var client = ctx.CreateRequestSocket()) using (var server = ctx.CreateResponseSocket()) { client.Connect("tcp://127.0.0.1:9991"); server.Connect("tcp://127.0.0.1:9992"); client.Send("hello"); Console.WriteLine("Expect hello, = {0}", server.ReceiveString()); server.Send("reply"); Console.WriteLine("Expect reply,= {0}", client.ReceiveString()); } } }); Console.Read(); } } static string Formate(byte[] input) { return System.Text.Encoding.Default.GetString(input); }} |
发布订阅同步模式
简单的发布订阅模式有个文档由于ZeroMQ收发的速率特别快,可能有些订阅方没来得及启动就已经广播了几条消息,或者中间一旦有些订阅端连接期间同样 会漏过一些消息。在比较苛刻的环境中可能会要求必须所有订阅端到齐才开始广播,或者一旦有订阅端断开连接马上停止广播。如果发布方知道所有的订阅方时,就 可以使用同步的发布订阅模式。这个模式实际启动两个Socket连接,一个是Pub - Sub,另一个是Req - Rep, 运行时订阅方首先启动请求应答队列向发布方告知自己已连接,并定期发送Ping消息告知自己在线,发布方发现所有的订阅方都已到齐,开始启动发布,并能够 在某个订阅方失去几次心跳请求后停止发布,并向MoniterSocket告知订阅方掉线。运行如下图:

发布订阅代理模式
发布订阅代理模式适用于跨网络广播时应用,只需要在一个网络入口设置一个和一个Pub对接的Sub客户端即可。

Sub客户端接收的消息通过Pub广播出去即可,唯一需要注意的是,如果一个消息是多帧消息,保证消息的完整转发即可。一般在在代理的处理代码中加上一个 嵌套的While处理。内层While只有确认一个多帧消息结束才可以break跳出,接受下一个新的消息。
代理简化代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
using (var ctx = NetMQContext.Create()){ var sub = ctx.CreateSubscriberSocket(); sub.Connect("tcp://127.0.0.1:5556"); var pub = ctx.CreatePublisherSocket(); sub.Bind("tcp://127.0.0.1:5557"); // 处理所有的消息帧 while (true) { while (true) { bool hasMore; var msg = sub.ReceiveString(out hasMore); pub.Send(msg,false,hasMore); if (!hasMore) break;// 到达最后一帧 } }} |
扩展推拉模式
扩展推拉模式在原来经典的推拉模式的Work上添加了订阅队列, 当Sink一方受到所有的worker推送过来的执行结果后,向所有额Work推送自杀消息,结束Worker线程,如下左图。
当然也可以进一步扩展在三者之外添加一个Manger角色托管一个ResponseSocket,负责Start Worker和Kill Worker。 具体为在Ventilator收到任务时向Manger发送消息告知有任务,Manger负责启动Worker,并向Ventilator返回 Worker线程已启动,在Sink收到全部Worker执行结果后,广播任务完成消息,Worker的订阅队列收到消息后自杀,如右图。
推 拉模式注意一点,在NetMQ中PushSocket的Send的动作时阻塞的,在没有连接到Pull时Send方法将不返回。也就是说他是一个同步发送 的过程,内容一直尝试发送直到超时。所以最好发送前做一次尝试发送这也是为什么Demo中首先发送一个0之后继续发送真正的Task的原因。
当Push连接多个Pull时会启动负载均衡策略保证尽可能平均的将信息分配给Pull,同样如果一个Pull连接了多个Push则会保证尽可能公平的从各个Push中接收信息叫公平队列,注意慢连接的情况,有时候可能在一个Worker首先连接上了Ventilator上,接下了所有任务这导致其他Worker的队列饥渴,所以要保证Work都启动后在开始分发任务。
注:PushSocket和PullSocket都能够Bind和Connect(1 VS N );但只有Push可以Send,只有Pull可以Receive;

Demo:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
class Program{ static ManualResetEvent rest = new ManualResetEvent(false); static void Main(string[] args) { for (int i = 0; i < 3; i++) { ThreadPool.QueueUserWorkItem((o) => { Worker(); }); } ThreadPool.QueueUserWorkItem((o) => { Sink(); }); ThreadPool.QueueUserWorkItem((o) => { Ventilator(); }); rest.Set(); Console.ReadKey(); } static void Ventilator() { using (var context = NetMQContext.Create()) { using (var sender = context.CreatePushSocket()) { sender.Bind("tcp://127.0.0.1:9005"); Console.WriteLine("Press enter when the workers are ready: "); rest.WaitOne(); Console.WriteLine("Sending tasks to workers…"); // 发送一个0标示开始; sender.Send("0"); var randomizer = new Random(DateTime.Now.Millisecond); const int tasksToSend = 100; int expectedTime = 0; for (int taskNumber = 0; taskNumber < tasksToSend; taskNumber++) { // Random workload from 1 to 100msecs int sleepTimeOnWorker = randomizer.Next(1, 100); expectedTime += sleepTimeOnWorker; sender.Send(sleepTimeOnWorker.ToString()); } Console.WriteLine("Ventilator -- Total expected time for 1 worker: {0} msec", expectedTime); } } } static void Worker() { using (var context = NetMQContext.Create()) { using (NetMQSocket receiver = context.CreatePullSocket(), sender = context.CreatePushSocket()) { receiver.Connect("tcp://127.0.0.1:9005"); sender.Connect("tcp://127.0.0.1:9006"); Console.WriteLine("Worker Running..."); while (true) { string task = receiver.ReceiveString(); Console.WriteLine("Task -- {0}.", task); int sleepTime = Convert.ToInt32(task); Thread.Sleep(sleepTime); // Send 'result' to the sink sender.Send("result"); } } } } static void Sink() { using (var context = NetMQContext.Create()) { using (var receiver = context.CreatePullSocket()) { receiver.Bind("tcp://127.0.0.1:9006"); Console.WriteLine("Sink Running..."); // Wait for start of batch Console.WriteLine("Sink -- {0}", receiver.ReceiveString()); var stopwatch = new Stopwatch(); stopwatch.Start(); const int tasksToConfirm = 100; for (int taskNumber = 0; taskNumber < tasksToConfirm; taskNumber++) { string message = receiver.ReceiveString(); Console.WriteLine(taskNumber % 10 == 0 ? ":" : "."); } stopwatch.Stop(); Console.WriteLine("Sink -- Total elapsed time: {0}", stopwatch.ElapsedMilliseconds); } } }} |
深入理解NetMQSocket
上文我们使用了各类的Socket,有Response/Request、Push/Pull、Router/Dealer等等,这些Socket都集成值同一个基类NetMQSocket。接下来我们到NetMQSocket里看看还为我们准备什么?
发送接收信息(Send / Receive)
毫无疑问Send和Receive是最重要的两个方法,没有这两个方法我嘛事也做不了。仔细看发现这两个方法都提供对了是否阻塞发送/接受和 HasMore的选项。而且最终发送的都是字节数组,所以如果你总是使用Send发送字符串而不是自己做Encoding工作这里会有一定的损耗,不过这 不是大问题,总要有人来做Encoding的不是。
另一个参数hasMore则是值当前收到消息帧是否还有关联的消息帧,如果hasMore = = true那么你需要收到所有的消息帧合并为一个消息才能解析
通信绑定和连接(Bind / Connecet)
Bind方法将向底层的Socket注册要是使用的通信方式和通信地址,一个NetMQSocket运行多次Bind从而使用不通的地址和协议进行信息交 换,一般来说大部分的NetMQSocket子类都同时支持Bind, Receive 方法,一般来说在通信双方处于比较稳定的一方总是使用Bind来确认通信地址和协议。另一方则使用Connect来执行连接。
轮询(Poll)
内部封装了一个轮询方法,在我们注册了 ReceiveReady / SendReady事件处理函数后负责同步或异步触发这些事件;
订阅(Subscribe)
订阅仅供SubscriberSocket和XSubscriberSocket使用,如果你发现你的额SubscriberSocket收不到订阅信 息,请检查是否代码中少加了Subscribe()方法,该方法提供一个Topic参数重载,允许你订阅特定主题的信息,但是信息根据主题筛选实际是在订 阅端筛分的,也就是说订阅端实际仍然接受了所有的发布方发出的消息。
监控(Monitor)
之前版本的NetMQ并没有提供像其他MQ那样明确的监控角色幸好在3.2版本添加了一个MonitorMQ的队列其他队列可以在发送延时,堆积通信异常 时将信息发往Monitor,这里的Monitor方法指定一个监控Socket,允许在NetMQSocket出现异常或正常事件是将事件信息发送到 MonitorSocket。
代理(Proxy)
Proxy其实是一个独立的类而不是NetMQSocet的方法或事件。 它的主要用处就是如果需要解耦通信双方时可以在Proxy内加入承上、启下的两个NetMQSocet对象在整个通信链路汇总充当代理的角色。如发布订阅代理,请求应答代理等。
简单的Demo:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
using (var sub = ctx.CreateSubscriberSocket())using (var pub = ctx.CreatePublisherSocket()){ using (var front = ctx.CreateXSubscriberSocket()) using (var back = ctx.CreateXPublisherSocket()) { front.Connect("tcp://127.0.0.1:9991"); front.Subscribe(""); back.Bind("tcp://127.0.0.1:9992"); var proxy = new Proxy(front, back, null); Task.Factory.StartNew(proxy.Start); pub.Bind("tcp://127.0.0.1:9991"); sub.Connect("tcp://127.0.0.1:9992"); sub.Subscribe(""); pub.Send("hello"); string msg = sub.ReceiveString(); Console.WriteLine("Expect hello= {0}", msg); } } |
ZeroMQ的进阶的更多相关文章
- 年薪20万Python工程师进阶(7):Python资源大全,让你相见恨晚的Python库
我是 环境管理 管理 Python 版本和环境的工具 pyenv – 简单的 Python 版本管理工具. Vex – 可以在虚拟环境中执行命令. virtualenv – 创建独立 Python 环 ...
- zeromq实践
zeromq简介 zeroMQ不是TCP,不是socket,也不是消息队列,而是这些的综合体. ZeroMQ以嵌入式网络编程库的形式实现了一个并行开发框架(concurrency framework) ...
- nodejs进阶(6)—连接MySQL数据库
1. 建库连库 连接MySQL数据库需要安装支持 npm install mysql 我们需要提前安装按mysql sever端 建一个数据库mydb1 mysql> CREATE DATABA ...
- nodejs进阶(4)—读取图片到页面
我们先实现从指定路径读取图片然后输出到页面的功能. 先准备一张图片imgs/dog.jpg. file.js里面继续添加readImg方法,在这里注意读写的时候都需要声明'binary'.(file. ...
- JavaScript进阶之路(一)初学者的开始
一:写在前面的问题和话 一个javascript初学者的进阶之路! 背景:3年后端(ASP.NET)工作经验,javascript水平一般般,前端水平一般般.学习资料:犀牛书. 如有误导,或者错误的地 ...
- nodejs进阶(3)—路由处理
1. url.parse(url)解析 该方法将一个URL字符串转换成对象并返回. url.parse(urlStr, [parseQueryString], [slashesDenoteHost]) ...
- nodejs进阶(5)—接收请求参数
1. get请求参数接收 我们简单举一个需要接收参数的例子 如果有个查找功能,查找关键词需要从url里接收,http://localhost:8000/search?keyword=地球.通过前面的进 ...
- nodejs进阶(1)—输出hello world
下面将带领大家一步步学习nodejs,知道怎么使用nodejs搭建服务器,响应get/post请求,连接数据库等. 搭建服务器页面输出hello world var http = require ...
- [C#] 进阶 - LINQ 标准查询操作概述
LINQ 标准查询操作概述 序 “标准查询运算符”是组成语言集成查询 (LINQ) 模式的方法.大多数这些方法都在序列上运行,其中的序列是一个对象,其类型实现了IEnumerable<T> ...
随机推荐
- linux下搭建mysql数据库
linux下搭建mysql数据库 1.下载mysql: http://dev.mysql.com/downloads/mysql/5.6.html#downloads wget http://dev. ...
- Java多线程学习——wait方法(管道法/生产者消费者模式)
简单介绍管道法: 生产者生产数据输送到管道,消费者从管道拿出数据,管道为空消费者等待,管道满生产者生产,消费者消费生产者生产,生产者生产消费者消费. public class Corn { //要生产 ...
- Balanced Binary Tree(平衡二叉树)
来源:https://leetcode.com/problems/balanced-binary-tree Given a binary tree, determine if it is height ...
- [转帖]IIS7.5应用程序池集成模式和经典模式的区别介绍
IIS7.5应用程序池集成模式和经典模式的区别介绍 之前转帖过一个 但是感觉不如这个说的细: https://www.jb51.net/article/31010.htm 关注脚本之家微信公众号(jb ...
- Kubernetes服务部署解决方案
学习了K8S的基础知识,我们的目的就是解决我们服务的迁移,那么接下去通过几个案例来感受一下K8s部署带来的便捷与效率. 环境准备: 3个节点,然后我这边也安装了 Ingress. 部署wordpres ...
- 在excel中如何计算两个时间之间的差[转]
因为时间是由序列号所代表的,用户可以用较晚的时间减去较早的时间以得到间隔.例如,单元格A3含有5:30,单元格B3含有14:00,下面的公式返回8:30(间隔8小时30分). =B3-A3 然而,如果 ...
- vue $forceUpdate() 强制重新渲染
vue $forceUpdate() 强制重新渲染:https://blog.csdn.net/z9061/article/details/94862047
- 05: jwt原理&使用
1.1 COOKIE使用和优缺点 参考博客:https://baijiahao.baidu.com/s?id=1608021814182894637&wfr=spider&for= ...
- 利用localStorage实现浏览器中多个标签页之间的通信
原理: localStorage是浏览器存储数据的容器,而且它是多页面共享的,利用localStorage多页面共享的特性,可以实现多个标签页的通信. 比如: 一个标签页发送消息(将发送的消息设置到l ...
- ThinkPHP无法打开或点击不了Trace的问题
首先先确认是否打开了Trace配置项,ThinkPHP3.*为'SHOW_PAGE_TRACE'=>true,ThinkPHP5.*为'app_trace'=>true. 如果已经确认开启 ...