代码改变世界

RabbitMq入门详解

2018-01-24 17:04  糯米粥  阅读(1856)  评论(0编辑  收藏  举报

     因为项目中需要用到RabbitMq,所有花时间研究了下,虽然博客园已经有前辈写了关于RabbitMq的文章。但还是有必要研究下!

 

什么是RabbitMq?

   百度解释:MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过 队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。其中较为成熟的MQ产品有IBM WEBSPHERE MQ等等。https://baike.baidu.com/item/rabbitmq/9372144?fr=aladdin

 

安装RabbitMq

RabbitMq 官网:http://www.rabbitmq.com/

ErLang:http://www.erlang.org/download.html

RabbitMQ:http://www.rabbitmq.com/download.html

 

安装后,开启Rabbit MQ管理后台

打开RabbitMQ Server的开始菜单安装目录

选择RabbitMQ Command Prompt 命令行并打开,输入

rabbitmq-plugins enable rabbitmq_management

 

 

 

然后重启服务:

 

 

 

然后在浏览器输入:http://localhost:15672

默认用户名和密码:guest

 

Overview:概述,

Connection:连接

Channels:消息通道,在客户端的每个连接里,可建立多个channel.

Exchanges:消息交换机,它指定消息按什么规则,路由到哪个队列

Queues:队列,消息的载体,每个消息都会被投到一个或多个队列。

Admin:管理

 

几个概念说明:
Broker:它提供一种传输服务,它的角色就是维护一条从生产者到消费者的路线,保证数据能按照指定的方式进行传输,
Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来.
Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
vhost:虚拟主机,一个broker里可以有多个vhost,用作不同用户的权限分离。
Producer:消息生产者,就是投递消息的程序.
Consumer:消息消费者,就是接受消息的程序.

 

 

现在开始编写发送消息代码:

ConnectionFactory factory = new ConnectionFactory();
            factory.HostName = "192.168.0.39";
            //factory.Port = 5672;
            factory.UserName = "zhangsan";
            factory.Password = "123";
            factory.AutomaticRecoveryEnabled = true;//自动连接
            //factory.VirtualHost = "test2";
            //factory.RequestedHeartbeat = 60; //心跳超时时间

            using (IConnection conn = factory.CreateConnection())
            {
                using (IModel channel = conn.CreateModel()) //创建一个通道
                {
                    //在MQ上定义一个持久化队列,该队列不存在时,才会创建,声明队列是一次性的
                    //channel.QueueDeclare("myMq", true, false, false, null);
                  var qu =  channel.QueueDeclare(queue: "myMq", //队列名称
                                    durable: true, //是否是持久化
                                    exclusive: false,
                                    autoDelete: false,
                                    arguments: null); //参数

            uint count = qu.MessageCount; //获取消息队列数量
                   string name = qu.QueueName; //消息队列名称
          //设置消息持久化 
          IBasicProperties properties = channel.CreateBasicProperties(); properties.DeliveryMode = 2; //2 消息持久化 1 非持久化

        //properties.SetPersistent(true); //跟上面同理

        var post = new { name = "lishi", age = 201 };
      
string json = JsonConvert.SerializeObject(post); byte[] bytes = Encoding.UTF8.GetBytes(json); //发布消息
      channel.BasicPublish("", "myMq", properties, bytes); Console.WriteLine("消息发送成功" + json); } }


订阅消息者,或者加接收,消费者

ConnectionFactory factory = new ConnectionFactory
            {
                HostName = "localhost",
                Port = 5672,
                UserName = "guest",
                Password = "guest"
            };

            using (IConnection conn = factory.CreateConnection())
            {
                using (IModel model = conn.CreateModel())
                {
                    //在MQ上定义一个持久化队列,如果名称相同不会重复创建
                   var qu =  model.QueueDeclare("myMq", true, false, false, null);
  uint count = qu.MessageCount; //获取消息队列数量
                   string name = qu.QueueName; //消息队列名称
//输入1,那如果接收一个消息,但是没有应答,则客户端不会收到下一个消息 model.BasicQos(0, 1, false); Console.WriteLine("Listering....."); //在队列上定义一个消费者 QueueingBasicConsumer consumer = new QueueingBasicConsumer(model); //消费队列,并设置应答模式为程序主动应答,意思是等我回复你。你再删除 // //noAck设置false,告诉broker,发送消息之后,消息暂时不要删除,等消费者处理完成再说 model.BasicConsume("myMq", false, consumer); while (true) { //阻塞函数,获取队列中的消息 BasicDeliverEventArgs ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue(); byte[] bytes = ea.Body; string str = Encoding.UTF8.GetString(bytes); var json = JsonConvert.DeserializeObject<dynamic>(str); Console.WriteLine("我叫" + json.name + "我今年" + json.age); //回复确认,告诉服务器,我处理完了。你可以删除了 model.BasicAck(ea.DeliveryTag, false); } } }

 

在运行程序之前。我们看看。队列是没有的

 

 

 

当运行发送消息客户端

 

此时,队列已经存在,消息已经存在队列里面了

 

 

 

此时还没有消费者连接来消费

 

我们可以先手动去看看消息。单击队列名称。到队列详细界面,可以获取信息。

 

 

 

 

当然。你手动消费了。自然就没有了

 

现在运行消息客户端,2个

 

 

管理界面显示:

 

通道界面:

 

然后发送消息:

 

额。奇怪。怎么只有一个消费者受到消息呢?这里默认是Direct类型

这是由Exchange 类型 类型决定的

 

1.Direct exchange
Direct exchange 完全根据 key 进行投递,只有 key 与绑定时的 routing key 完全一致的消息才会收到消息,参考官网提供的图 4 更直观地了解 Direct exchange。

 

 

2.Fanount exchange
Fanount 完全不关心 key,直接采取广播的方式进行消息投递,与该交换机绑定的所有队列都会收到消息

 

 

.Topic exchange
Topic exchange 会根据 key 进行模式匹配然后进行投递,与设置的 routing key 匹配上的队列才能收到消息。

4.Headers exchange
Header exchange 使用消息头代替 routing key 作为关键字进行路由,不过在实际应用过程中这种类型的 exchange 使用较少。


用fanout方式发布消息:
ConnectionFactory factory = new ConnectionFactory();
            factory.HostName = "192.168.0.39";
            //factory.Port = 5672;
            factory.UserName = "zhangsan";
            factory.Password = "123";

            using (IConnection conn = factory.CreateConnection())
            {
                using (IModel channel = conn.CreateModel()) //创建一个通道
                {
                    /*
                     * 交换机类型
                      fanout(扇出):传播它收到的所有消息去它知道所有的队列中,会给所有在线的消费者发送所有信息,可以忽略:routingKey值。即队列名称
                     *direct(直接):把消息发送到指定的交换机下的指定的队列中
                     */
                    //channel.ExchangeDelete("message",true);
                    //不能改变已经存在交换机的类型 比如: channel.ExchangeDeclare("message", "direct")
                    channel.ExchangeDeclare("message", "fanout"); //type要小写
                    //在MQ上定义一个持久化队列,该队列不存在时,才会创建,声明队列是一次性的,这里不需要申明队列了。因为定义了交换机
                    //channel.QueueDeclare(queue: "myMq", //队列名称
                    //                durable: true, //是否是持久化
                    //                exclusive: false, //是否排除其他,即存在则不会在创建
                    //                autoDelete: false, //是否自动删除
                    //                arguments: null); //参数
                    //设置消息持久化
                    //IBasicProperties properties = channel.CreateBasicProperties();
                    //properties.DeliveryMode = 2; //2 消息持久化 1 非持久化
                    //properties.SetPersistent(true); //跟上面同理 
                    var post = new
                    {
                        name = "交换机",
                        age = 20
                    };

                    string json = JsonConvert.SerializeObject(post);
                    byte[] bytes = Encoding.UTF8.GetBytes(json);


                    //发布消息
                    channel.BasicPublish("message", "", null, bytes);
                    Console.WriteLine("消息发送成功" + json);
                }
            }

 

 

接收代码:

 ConnectionFactory factory = new ConnectionFactory
            {
                HostName = "localhost",
                Port = 5672,
                UserName = "guest",
                Password = "guest",
                //VirtualHost="test2"
            };

            using (IConnection conn = factory.CreateConnection())
            {
                using (IModel channel = conn.CreateModel())
                {

                    var name = channel.QueueDeclare().QueueName;

                    //如果该交换机不存在。则会报错
                    channel.QueueBind(name, "message", "");  //把交换机和队列绑定,没有指定队列名称,所以是所有队列

                    //在MQ上定义一个持久化队列,如果名称相同不会重复创建
                    //channel.QueueDeclare(name, true, false, false, null);
                    //输入1,那如果接收一个消息,但是没有应答,则客户端不会收到下一个消息
                    channel.BasicQos(0, 1, false);
                    Console.WriteLine("Listering.....");

                    var consumer = new EventingBasicConsumer(channel);
                    channel.BasicConsume(name, false, consumer);

                    consumer.Received += (m, ea) =>
                    {
                        var body = ea.Body;
                        string str = Encoding.UTF8.GetString(body);
                        var json = JsonConvert.DeserializeObject<dynamic>(str);
                        Console.WriteLine("我叫" + json.name + "我今年" + json.age);
                        //处理完成,告诉Broker可以服务端可以删除消息,分配新的消息过来
                        channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
                    };

                    Console.Read();
                }

            }

运行结果


 

 

接收消息一端是需要一直运行的接收消息的,不建议用

while(true){} 一个死循环建议用一个多线程

Task.Factory.StartNew(Method);

 不过用了多线程,那么,就不能用using语句

比如:

 

   ConnectionFactory factory = new ConnectionFactory
            {
                HostName = "localhost",
                Port = 5672,
                UserName = "guest",
                Password = "guest",
                //VirtualHost="test2"
            };

            IConnection conn = factory.CreateConnection();

            IModel channel = conn.CreateModel();

 

 

VirtualHost虚拟机

接收消息和发送消息,必须在同一个虚拟机。才能收到消息。默认虚拟机是   "/"  可以通过factory.VirtualHost = "test2"; 设置 

当然。以上代码是可以封装的。比如:

 

public class RabbitMqConsumerFactory
    {
       public static IConnection CreateConnection()
       {
           var factory = new ConnectionFactory()
           {
               HostName = ConfigurationManager.AppSettings["MqHostName"],
               UserName = ConfigurationManager.AppSettings["MqUserName"],
               Password = ConfigurationManager.AppSettings["MqPassword"],
               Port = int.Parse(ConfigurationManager.AppSettings["MqPort"].ToString()),
               RequestedHeartbeat = 60, //心跳超时时间
               AutomaticRecoveryEnabled = true //自动重连
           };

           return factory.CreateConnection(); //创建连接对象
       }
    }

调用: using (IConnection conn = RabbitMqConsumerFactory.CreateConnection()){}

 

这仅仅是在控制台应用程序中使用,那么你 有没有想过在Web中使用,

借助于 RabbitMQ 的 Web STOMP 插件,实现浏览器与服务端的全双工通信。

从本质上说,RabbitMQ 的 Web STOMP 插件也是利用 WebSocket 对 STOMP 协议进行了一次桥接,从而实现浏览器与服务端的双向通信。官网有个列子

例子:http://www.rabbitmq.com/web-stomp.html

例子下载:https://github.com/rabbitmq/rabbitmq-web-stomp-examples

 

我在官网的列子上研究了下

 

先启用STOMP插件  rabbitmq-plugins enable rabbitmq_management rabbitmq_web_stomp rabbitmq_stomp

重启服务:service rabbitmq-server restart

开通后的插件,可以在web页面中overview中查看

 

下载官网的列子后,里面有个stomp.js

 

以下是主要js代码

接收消息

 <script>
        //画图 start
        var draw;
        send = draw = function () { };

        var lines = [];

        var canvas = document.getElementById('cnvs');

        if (canvas.getContext) {
            var ctx = canvas.getContext('2d');

            var img = new Image();
            img.onload = function () {
                ctx.drawImage(img, 230, 160);
            };
            //bunny
            img.src = 'bunny.png';

            draw = function (p) {
                ctx.beginPath();
                ctx.moveTo(p.x1, p.y1);
                ctx.lineTo(p.x2, p.y2);
                ctx.stroke();
                ctx.drawImage(img, 230, 160);
            };

            var do_resize = function () {
                canvas.width = window.innerWidth;
                canvas.height = window.innerHeight;

                ctx.font = "bold 20px sans-serif";
                ctx.fillStyle = "#444";
                ctx.fillText("Draw wings on the bunny!", 260, 100);
                ctx.font = "normal 16px sans-serif";
                ctx.fillStyle = "#888";
                ctx.fillText("(For more fun open a second browser)", 255, 130);

                ctx.drawImage(img, 230, 160);

                ctx.strokeStyle = "#fa0";
                ctx.lineWidth = "10";
                ctx.lineCap = "round";

                $.map(lines, function (p) {
                    draw(p);
                });
            };

            $(window).resize(do_resize);
            $(do_resize);


            var pos = $('#cnvs').position();
            var prev = null;
            $('#cnvs').mousedown(function (evt) {
                evt.preventDefault();
                evt.stopPropagation();
                $('#cnvs').bind('mousemove', function (e) {
                    var curr = { x: e.pageX - pos.left, y: e.pageY - pos.top };
                    if (!prev) {
                        prev = curr;
                        return;
                    }
                    if (Math.sqrt(Math.pow(prev.x - curr.x, 2) +
                                  Math.pow(prev.y - curr.y, 2)) > 8) {
                        var p = { x1: prev.x, y1: prev.y, x2: curr.x, y2: curr.y }
                        lines.push(p);
                        draw(p);
                        send(JSON.stringify(p));
                        prev = curr;
                    }
                });
            });
            $('html').mouseup(function () {
                prev = null;
                $('#cnvs').unbind('mousemove');
            });
        }
        else {
            document.write("Sorry - this demo requires a browser with canvas tag support.");
        }

        //画图 end

        var has_had_focus = false;
        var pipe = function (el_name, send) {
            var div = $(el_name + ' div');
            var inp = $(el_name + ' input');
            var form = $(el_name + ' form');

            var print = function (m, p) {
                p = (p === undefined) ? '' : JSON.stringify(p);
                div.append($("<code>").text(m + ' ' + p));
                div.scrollTop(div.scrollTop() + 10000);
            };

            if (send) {
                form.submit(function () {
                    send(inp.val());
                    inp.val('');
                    return false;
                });
            }
            return print;
        };

        // Stomp.js boilerplate
        var client = Stomp.client('ws://' + window.location.hostname + ':15674/ws');
        client.debug = pipe('#second'); //显示log

        var print_first = pipe('#first', function (data) {
            //发送消息 myMq
            client.send('test1', { "content-type": "text/plain" }, data);
            //client.send('message', { "content-type": "text/plain" }, data);
            //client.send('/topic/test', { "content-type": "text/plain" }, data);
        });

        //建立连接后,订阅消息
        var on_connect = function (x) {

            //获取消息(订阅)testTomp
            /*
            订阅改队列的消息,如果没有该队列。会创建一个/topic/
            */
            id = client.subscribe("/topic/test1", function (d) {
                //d.body 是接收到的消息
                print_first(d.body); //输出到界面

                var p = JSON.parse(d.body);
                lines.push(p);
                draw(p, true);
            });
        };
        var on_error = function () {
            console.log('error');
        };

        //  "/"代表 VirtualHost
        client.connect('zhangsan', '123', on_connect, on_error, '/');
        client.onreceive = function (m) {
            alert(m.body);
            //$('#first div').append($("<code>").text(m.body));
        }
        $('#first input').focus(function () {
            if (!has_had_focus) {
                has_had_focus = true;
                $(this).val("");
            }
        });
    </script>

 

发送消息:

        var ws = new WebSocket('ws://' + window.location.hostname + ':15674/ws');
        //建立连接
        var client = Stomp.over(ws);

        //client.heartbeat.outgoing = 0;
        //client.heartbeat.incoming = 0;
        
        //日志
        client.debug = function (e) {
            $('#second div').append($("<code>").text(e));
        };

        //定义连接成功回调函数
        var on_connect = function (x) {

            //自己订阅自己
            //id = client.subscribe("/topic/testTopic", function (m) {
                // reply by sending the reversed text to the temp queue defined in the "reply-to" header
                //var reversedText = m.body.split("").reverse().join("");
                //client.send(m.headers['reply-to'], { "content-type": "text/plain" }, reversedText);
            //});
        };
        var on_error = function () {
            console.log('error');
        };//guest
        client.connect('zhangsan', '123', on_connect, on_error, '/');
        
        $(function () {
            $("#btnSend").click(function () {
                var text = $("#post").val();
                if (text == "") { alert("请输入要发送的数据"); return; }
                sendData(text);
            });
        });
        //myMq  testTomp
        function sendData(text) {
            /*当没有队列名称的时候,会创建一个队列。不会重复创建
            格式:/topic/test1 指定交换机类型是 topic
            */
            client.send('test1', { "content-type": "text/plain" }, text);
            $('#post').val("");
        }
        $('#first input').focus(function () {
            if (!has_had_focus) {
                has_had_focus = true;
                $(this).val("");
            }
        });

 

 

 

 

这里用的:client.send('test1', { "content-type": "text/plain" }, text); 

Exchanges是direct ,所有只会有一个消费者

 

 

如果想改成广播的方式。只要修改: client.send('/topic/test1', { "content-type": "text/plain" }, text);

看看效果:

 

 

 

 

接收有效的类型格式是:/temp-queue, /exchange, /topic, /queue, /amq/queue, /reply-queue/.

在消费端,因为也订阅了。


        var print_first = pipe('#first', function (data) {
            //发送消息 myMq
            client.send('/topic/test1', { "content-type": "text/plain" }, data);
        });

所有可以实现消费端互相推送消息

 

 

 

 

你会发现,我们发送的"content-type": "text/plain" 是文本类,但很多时候,我们发送的时候是json

比如:

 var p = { name: "张三", addres: "深圳", age: 20 };
            var json = JSON.stringify(p);
            client.send('/topic/test1', { "content-type": "application/json" }, json);
            //client.send('/topic/test1', {}, json); //传{} 也行

 

接收端:

 

 

 接收端获取值可以直接反序列化获取值

  var p = JSON.parse(d.body);
   print_first("我的名字是:"+p.name);

 

 官网的例子中还有一个功能,就是发送到画画,把数据传递给消费端。然后在消费端重现

 

 

 

最后。关于RabbitMq的应用场景。这里说得比较好:

 

 

public class RabbitMqProducerFactory
    {
        public static IConnection CreateConnection()
        {
            var factory = new ConnectionFactory()
            {
                VirtualHost = ConfigurationManager.AppSettings["MqVirtualHost"],
                HostName = ConfigurationManager.AppSettings["MqHostName"],
                UserName = ConfigurationManager.AppSettings["MqUserName"],
                Password = ConfigurationManager.AppSettings["MqPassword"],
                Port = int.Parse(ConfigurationManager.AppSettings["MqPort"].ToString()),
                RequestedHeartbeat = 60, //心跳超时时间
                AutomaticRecoveryEnabled = true //自动重连
            };

            return factory.CreateConnection(); //创建连接对象
        }
    }
-------------------------------------------------------------------------------
public class RabbitMqSendMessage
    {
        static string QueueName = ConfigurationManager.AppSettings["MqQueueName"];
        public static void SendMessage(string message)
        {
            using (IConnection conn = RabbitMqProducerFactory.CreateConnection())
            {
                using (IModel channel = conn.CreateModel())
                {
                    //在MQ上定义一个持久化队列,如果名称相同不会重复创建
                    channel.QueueDeclare(queue: QueueName,
                                durable: true,
                                exclusive: false,
                                autoDelete: false,
                                arguments: null);

                    var body = Encoding.UTF8.GetBytes(message);

                    var properties = channel.CreateBasicProperties();
                    properties.DeliveryMode = 2;//消息持久化

                    channel.BasicPublish(exchange: "",
                                routingKey: QueueName,
                                basicProperties: properties,
                                body: body);
                }
            }
        }
    }
-------------------------------------------------------------------------------

 

 

 

http://blog.csdn.net/whoamiyang/article/details/54954780

https://blog.csdn.net/gb4215287/article/details/79457445

 

 http://blog.csdn.net/zyz511919766/article/details/41946521

 

 参考资料:

http://www.cnblogs.com/PatrickLiu/tag/RabbitMQ/

https://www.jianshu.com/p/4a8336bc3428

https://www.jianshu.com/p/494dc4ca6fb9

https://www.cnblogs.com/ericli-ericli/p/5902270.html
https://www.cnblogs.com/yangh965/p/5862347.html
http://blog.csdn.net/u011642663/article/details/54691788
http://www.cnblogs.com/PatrickLiu/p/6807019.html#commentform
http://www.cnblogs.com/Andon_liu/p/5401961.html
https://www.cnblogs.com/ericli-ericli/p/5917018.html
http://www.erlang.org/downloads
http://blog.csdn.net/wangqingpei557/article/details/47864761
http://blog.csdn.net/wangqingpei557/article/details/47864761

 http://www.80iter.com/blog/1437455520862503

http://blog.csdn.net/u012631731/article/category/7240883

Demo下载