• 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);
            }
        }
  • 相关阅读:
    创建 SSH Keys
    idea创建管理项目
    springboot拦截器之验证登录
    SpringBoot防XSS攻击
    String,StringBuffer与StringBuilder的区别|线程安全与线程不安全
    算法的时间复杂度和空间复杂度详解
    switch语句以及三种循环语句的总结
    kafka原理简介并且与RabbitMQ的选择
    Kafka、RabbitMQ、RocketMQ等消息中间件的对比 —— 消息发送性能和区别
    RabbitMQ和kafka从几个角度简单的对比
  • 原文地址:https://www.cnblogs.com/shi-meng/p/4566394.html
Copyright © 2020-2023  润新知