上一篇博文我们对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的进阶的更多相关文章

  1. 年薪20万Python工程师进阶(7):Python资源大全,让你相见恨晚的Python库

    我是 环境管理 管理 Python 版本和环境的工具 pyenv – 简单的 Python 版本管理工具. Vex – 可以在虚拟环境中执行命令. virtualenv – 创建独立 Python 环 ...

  2. zeromq实践

    zeromq简介 zeroMQ不是TCP,不是socket,也不是消息队列,而是这些的综合体. ZeroMQ以嵌入式网络编程库的形式实现了一个并行开发框架(concurrency framework) ...

  3. nodejs进阶(6)—连接MySQL数据库

    1. 建库连库 连接MySQL数据库需要安装支持 npm install mysql 我们需要提前安装按mysql sever端 建一个数据库mydb1 mysql> CREATE DATABA ...

  4. nodejs进阶(4)—读取图片到页面

    我们先实现从指定路径读取图片然后输出到页面的功能. 先准备一张图片imgs/dog.jpg. file.js里面继续添加readImg方法,在这里注意读写的时候都需要声明'binary'.(file. ...

  5. JavaScript进阶之路(一)初学者的开始

    一:写在前面的问题和话 一个javascript初学者的进阶之路! 背景:3年后端(ASP.NET)工作经验,javascript水平一般般,前端水平一般般.学习资料:犀牛书. 如有误导,或者错误的地 ...

  6. nodejs进阶(3)—路由处理

    1. url.parse(url)解析 该方法将一个URL字符串转换成对象并返回. url.parse(urlStr, [parseQueryString], [slashesDenoteHost]) ...

  7. nodejs进阶(5)—接收请求参数

    1. get请求参数接收 我们简单举一个需要接收参数的例子 如果有个查找功能,查找关键词需要从url里接收,http://localhost:8000/search?keyword=地球.通过前面的进 ...

  8. nodejs进阶(1)—输出hello world

    下面将带领大家一步步学习nodejs,知道怎么使用nodejs搭建服务器,响应get/post请求,连接数据库等. 搭建服务器页面输出hello world var  http  =  require ...

  9. [C#] 进阶 - LINQ 标准查询操作概述

    LINQ 标准查询操作概述 序 “标准查询运算符”是组成语言集成查询 (LINQ) 模式的方法.大多数这些方法都在序列上运行,其中的序列是一个对象,其类型实现了IEnumerable<T> ...

随机推荐

  1. 前端使用Git 切换分支 查看线上远程,本地切换

    想要使用Git切换线上分支时先 得先查看线上分支 git branch -a //查看线上分支 git branch //查看本地分支 这是线上的分支图(当前是master) 知道有那些分支就可以进行 ...

  2. 【MM系列】SAP MM模块-配置PO的创建时间

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[MM系列]SAP MM模块-配置PO的创建时间 ...

  3. Chapter03 第二节 const限定符的使用

    3.2 const限定符 const的作用:替代#define作为有类型检查的常量来使用.他的值被初始化后就固定了,成为一个只读变量,不能更改.(推荐使用特殊的命名规范来区分常量和非常量). cons ...

  4. 应用安全 - 工具 - freefloatftpserver - 漏洞汇总

    Freefloat FTP Server 1.0 Date 类型栈溢出导致远程代码执行 复现(1)启动服务 (2)FTP连接(账号密码任意) 分析(1)正常运行调试 (1)pwntools发送expl ...

  5. 基于 Timer是一种定时器工具

    没有依赖 通过Timer中的schedule方法启动定时任务 一般不采用此方法 /** * ------------------------------------------------------ ...

  6. 【转】MySQL查询缓存详解

    [转]MySQL查询缓存详解 转自:https://www.cnblogs.com/Alight/p/3981999.html 相关文章:http://www.zsythink.net/archive ...

  7. C++中的深拷贝和浅拷贝构造函数

    1,对象的构造在实际工程开发当中是相当重要的,C++ 中使用类就要创建对象,这 就涉及了对象的构造,本节课讲解对象的构造和内存操作方面的问题: 2,实际工程开发中,bug 产生的根源,必然的会有内存操 ...

  8. chrome浏览器截长图的方法

    1.首先打开一个你想要截图的长页面 2.然后按下F12 3.按Ctrl+Shift+P打开console菜单 4.在有个红对勾的位置输入Capture full size screenshot,找到C ...

  9. git flow 基础了解

    git flow 软件开发中的一个分支管理流程.利用它可以让软件开发有条不紊的进行,先对它进行一个大概的了解吧,后面工作了实际用到了在深入研究一下. 先看下它的工作流程: 这张图看着一脸茫然,先放在这 ...

  10. Scala学习笔记(5)类

    1.简单类和无参方法 calss Counter{ private var value = 0  //必须初始字段 def increment(){value +=1} //方法默认是公有的 def ...