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