RabbitMQ - Start Up
- 开始之前
rabbitmq是一个被广泛使用的消息队列,它是由erlang编写的,根据AMQP协议设计实现的。
AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
RabbitMQ是一个开源的AMQP实现,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
下载rabbitMq
http://www.rabbitmq.com/download.html
erlang环境
http://www.erlang.org/
在启动rabbitMq之前需要安装erlang环境,并且erlang环境和rabbitMq版本要匹配
- AMQP
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件同产品,不同的开发语言等条件的限制。
AMQ Model
Message Queue
消息队列会将消息存储到内存或者磁盘中,并将这些消息按照一定顺序转发给一个或者多个消费者,每个消息队列都是独立隔离的,相互不影响。
消息队列具有不同的属性:私有,共享,持久化,临时,客户端定义 或者服务端定义等,可以基于实际需求选择对应的类型,以 RabbitMQ 队列特性为例:
共享持久化消息队列:将发送的消息存储到磁盘,然后将消息转发给订阅该队列的所有消费者;
私有临时消息队列:RabbitMQ 支持 rpc 调用,再调用过程中消费者都会临时生成一个消息队列,只有当前消费者可见,且由服务端生成,调用完就会销毁队列。
Exchange
交换机收到生产者投递的消息,基于路由规则及队列绑定关系匹配到投递对应的交换机或者队列进行分发,交换机不存储消息,只做转发。
AMQP定义了许多标准交换类型,基本涵盖了消息传递所需的路由类型,一般 AMQP 服务器都会提供默认的交换机基于交换机类型命名,AMQP 的应用程序也可以创建自己的交换机用于绑定指定的消息队列发布消息。
RabbitMQ
- 概念
channel
channel是定义每一个生产者或者消费者与Mq的连接,Mq的各种事务都是以channel为基本来扩展的,它类似于一个socket连接。
exchanger
交换器。在rabbitMq设计的内部,是通过exchanger来决定消息怎么分发到queue上的。定义了交换器需要把队列绑定到交换器上,并设置规则,这样交换器会将消息分发到对应的queue上。通常的交换器有四种:direct,fanout,topic,header。
routekey
路由键,可以看做是对交换器的一种补充。给消息设置一个路由键,队列会拿到带有它感兴趣的路由键消息。routing key设定的长度限制为255 bytes。
queue
队列。作为消费者需要关心的元素,它承载着消息的存放与管理功能。所有的消息最终会投递到队列上,而队列上的消费者会取走它们并执行消费者的任务。消费者可以拒收消息,并告诉队列把它重新入队或者丢弃,队列可以是channel独占的,也可以是共享的。并且队列提供了持久化的功能。
- 开始搬砖
引入amqp客户端
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.4.1</version>
</dependency>
amqp客户端提供的api都是按照以上的概念设计的。
首先我们需要建立与消息队列的连接,并拿到这个连接。
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(Properties.host);
//创建一个新的连接
Connection connection = factory.newConnection();
然后我们拿到一个channel。
//创建一个通道
Channel channel = connection.createChannel();
接着我们就在这个channel上做各种操作了。
这时候需要用到我们上述介绍的概念了,绑定交换器,设置路由键,并且把队列绑定到交换器上。
如下几种交换器
direct
此类交换器是直接往队列发送的一种交换器,从名字上很容易理解。
// 声明一个队列
channel.exchangeDeclare("basic","direct");
channel.queueBind(queueName, "basic",Properties.routeKey);
在rabbitMq管理页面上可以看到绑定的情况。
fanout
扇形交换器,类似于一对多发送的交换器。它将详细发送到具有相同特征的queue上,是一种广播特性的交换器。
//扇形交换器 广播
channel.exchangeDeclare(exchangeName,"fanout");
channel.queueBind(queueName1, exchangeName,routeKey);
在管理页面上现实了设置的队列。
topic
主题交换器,将消息分发到订阅了某个主题的queue里。设置主题的关键是在于路由键是否符合一定的通配。
// 声明转发器
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
//定义绑定键
String[] routing_keys = new String[] { "kernal.info", "cron.warning",
"auth.info", "kernel.critical" };
for (String routing_key : routing_keys)
{
channel.basicPublish(EXCHANGE_NAME, routing_key, null, msg.getBytes());
}
在做消息发送的时候,围绕着basicPublisher在做;做消息消费的时候,是围绕着basicConsumer来做。
/**
* Publish a message.
*
* Publishing to a non-existent exchange will result in a channel-level
* protocol exception, which closes the channel.
*
* Invocations of <code>Channel#basicPublish</code> will eventually block if a
* <a href="http://www.rabbitmq.com/alarms.html">resource-driven alarm</a> is in effect.
*
* @see com.rabbitmq.client.AMQP.Basic.Publish
* @see <a href="http://www.rabbitmq.com/alarms.html">Resource-driven alarms</a>
* @param exchange the exchange to publish the message to
* @param routingKey the routing key
* @param props other properties for the message - routing headers etc
* @param body the message body
* @throws java.io.IOException if an error is encountered
*/
void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
/**
* Start a non-nolocal, non-exclusive consumer, with
* a server-generated consumerTag.
* @param queue the name of the queue
* @param autoAck true if the server should consider messages
* acknowledged once delivered; false if the server should expect
* explicit acknowledgements
* @param callback an interface to the consumer object
* @return the consumerTag generated by the server
* @throws java.io.IOException if an error is encountered
* @see com.rabbitmq.client.AMQP.Basic.Consume
* @see com.rabbitmq.client.AMQP.Basic.ConsumeOk
* @see #basicConsume(String, boolean, String, boolean, boolean, Map, Consumer)
*/
String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException;
生产者关注的是exchanger和路由键,消费者关注的是queue。
//基础生产者
public void basicPublish(String queueName,String message){
try{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(Properties.host);
//创建一个新的连接
Connection connection = factory.newConnection();
//创建一个通道
Channel channel = connection.createChannel();
channel.confirmSelect();
// 声明一个队列
channel.exchangeDeclare("basic","direct");
channel.queueBind(queueName, "basic",Properties.routeKey);
//添加一个监听器
channel.addConfirmListener(new ConfirmListener() {
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("Producer Send :"+deliveryTag+", ack, multiple :"+multiple);
} public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("Producer Send :"+deliveryTag+", nack");
}
});
//发送消息到队列中
for(int i=0;i<5;i++){
channel.basicPublish("basic",Properties.routeKey, null, message.getBytes("UTF-8"));
boolean isok = channel.waitForConfirms();
if(isok){
System.out.println("Producer Send Message :" + message);
}
}
}catch (Exception e){
e.printStackTrace();
}
}
//基础消费者
public void basicConsumer(String queueName){
try{
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置RabbitMQ地址
factory.setHost(Properties.host);
//创建一个新的连接
Connection connection = factory.newConnection();
//创建一个通道
Channel channel = connection.createChannel();
channel.confirmSelect();
//声明要关注的队列
channel.queueBind(queueName,"basic",Properties.routeKey);
System.out.println("Customer Waiting Received messages");
//实现一个消费者逻辑
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
System.out.println("Message tag:"+envelope.getDeliveryTag()+",Customer Received '" + message + "'");
//手动设置
if(envelope.getDeliveryTag() % 2 == 0){
this.getChannel().basicAck(envelope.getDeliveryTag(),false);
}else {
this.getChannel().basicReject(envelope.getDeliveryTag(),false);
}
}
};
//自动回复队列应答 -- RabbitMQ中的消息确认机制
channel.basicConsume(queueName, false, consumer);
}catch (Exception e){
e.printStackTrace();
}
}
- 消息确认
rabbitMq的消息确认,无论生产者和消费者,消息确认的对象都是broker本身。生产者确保消息准确投递到broker server,server返回了ack则表示消息已投递。消费者则是签收方,broker server确认消息投递成功后,则在内存中移除这个消息。
为了确保消息准确的投递,rabbitMq提供了两种消息确认机制。
confirmSelect
如果生产者将channel设置成confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(deliverId),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker回传给生产者的确认消息中delivery-tag域包含了确认消息的序列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理;
confirm模式最大的好处在于他是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理该nack消息;
生产者可以对这两种响应做出监听处理。
//添加一个监听器
channel.addConfirmListener(new ConfirmListener() {
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("Producer Send :"+deliveryTag+", ack, multiple :"+multiple);
} public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("Producer Send :"+deliveryTag+", nack");
}
});
开启这种模式,只需要在channel中调用confirmSelect。
channel.confirmSelect();
txSelect
事务模式是一种增加可靠性的手段,它必然会牺牲性能,与数据库事务的理解差不多。在事务范围内,保证消息投递成功。
try {
channel.txSelect(); // 声明事务
// 发送消息
channel.basicPublish("", _queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8"));
channel.txCommit(); // 提交事务
} catch (Exception e) {
channel.txRollback();
}
rollback之后,等于是丢弃了消息。
//发送消息到队列中
for(int i=0;i<5;i++){
try{
channel.txSelect();
channel.basicPublish("basic",Properties.routeKey, null, message.getBytes("UTF-8"));
if(i % 3 == 0){
throw new Exception();
}
channel.txCommit();
}catch (Exception e){
e.printStackTrace();
channel.txRollback();
}
}
改变了代码之后,失败的两条并未进入Ready。
- 结尾
由于时间问题,没有对各种消息确认机制进行抓包分析,性能分析。以及了解rabbitMq的cluster模式,高可用模式等,只是基本上了解了它的api以及概念原理。对于深入学习有几个方向,一是集群扩展横向扩展方案,二是exchanger集群同步方案,三是消息堆积处理问题等,因为没有在实际工作中使用,很多场景都未曾考虑到。消息队列最典型的应用就是使用来解耦事务,做成基于MQ的2PC分布式事务,以及实现最终一致方案。在未来会在这方面尝试。
RabbitMQ - Start Up的更多相关文章
- 消息队列——RabbitMQ学习笔记
消息队列--RabbitMQ学习笔记 1. 写在前面 昨天简单学习了一个消息队列项目--RabbitMQ,今天趁热打铁,将学到的东西记录下来. 学习的资料主要是官网给出的6个基本的消息发送/接收模型, ...
- RabbitMq应用二
在应用一中,基本的消息队列使用已经完成了,在实际项目中,一定会出现各种各样的需求和问题,rabbitmq内置的很多强大机制和功能会帮助我们解决很多的问题,下面就一个一个的一起学习一下. 消息响应机制 ...
- 如何优雅的使用RabbitMQ
RabbitMQ无疑是目前最流行的消息队列之一,对各种语言环境的支持也很丰富,作为一个.NET developer有必要学习和了解这一工具.消息队列的使用场景大概有3种: 1.系统集成,分布式系统的设 ...
- RabbitMq应用一的补充(RabbitMQ的应用场景)
直接进入正题. 一.异步处理 场景:发送手机验证码,邮件 传统古老处理方式如下图 这个流程,全部在主线程完成,注册->入库->发送邮件->发送短信,由于都在主线程,所以要等待每一步完 ...
- RabbitMq应用一
RabbitMq应用一 RabbitMQ的具体概念,百度百科一下,我这里说一下我的理解,如果有少或者不对的地方,欢迎纠正和补充. 一个项目架构,小的时候,一般都是传统的单一网站系统,或者项目,三层架构 ...
- 缓存、队列(Memcached、redis、RabbitMQ)
本章内容: Memcached 简介.安装.使用 Python 操作 Memcached 天生支持集群 redis 简介.安装.使用.实例 Python 操作 Redis String.Hash.Li ...
- 消息队列性能对比——ActiveMQ、RabbitMQ与ZeroMQ(译文)
Dissecting Message Queues 概述: 我花了一些时间解剖各种库执行分布式消息.在这个分析中,我看了几个不同的方面,包括API特性,易于部署和维护,以及性能质量..消息队列已经被分 ...
- windows下 安装 rabbitMQ 及操作常用命令
rabbitMQ是一个在AMQP协议标准基础上完整的,可服用的企业消息系统.它遵循Mozilla Public License开源协议,采用 Erlang 实现的工业级的消息队列(MQ)服务器,Rab ...
- RabbitMQ + PHP (三)案例演示
今天用一个简单的案例来实现 RabbitMQ + PHP 这个消息队列的运行机制. 主要分为两个部分: 第一:发送者(publisher) 第二:消费者(consumer) (一)生产者 (创建一个r ...
- RabbitMQ + PHP (二)AMQP拓展安装
上篇说到了 RabbitMQ 的安装. 这次要在讲案例之前,需要安装PHP的AMQP扩展.不然可能会报以下两个错误. 1.Fatal error: Class 'AMQPConnection' not ...
随机推荐
- process_进程池
from multiprocessing import Poolimport os,timeimport multiprocessingdef func(msg): print("msg:& ...
- SqlServer和MySql允许脏读的实现方式,提高查询效率
--Sql Server 允许脏读查询sqlselect * from category with(nolock) --MySql 允许脏读查询sql Mysql没有语法糖,需要原生的sqlSET S ...
- 2018.09.12 hdu2473Junk-Mail Filter(并查集)
传送门 一开始开题还以为是平衡树. 仔细想了一想并查集就可以了. 合并操作没什么好说的. 删除操作:对于每个点记录一个pos值表示原来的点i现在的下标是什么. 每次删除点i是就新建一个点cnt,然后令 ...
- 2018.09.02 bzoj1003: [ZJOI2006]物流运输(dp+最短路转移)
传送门 dp好题. 每一天要变更路线一定还是走最短路. 所以l~r天不变更路线的最优方案就是把l~r天所有不能走的点都删掉再求最短路.显然是可以dp的. 设f[i]表示第i天的最优花销.那么我们枚举在 ...
- fortran write格式
advance="no",就是输出不换行. write(*,"(f10.1)",advance="no")A 格式化输出的控制字符非常的丰富 ...
- php文件上传代码解析
php文件上传代码解析 is_uploaded_file() //函数判断指定的文件是否是通过 HTTP POST 上传的,返回一个布尔值. $_FILES['upfile']['tmp_name' ...
- mysql操作说明,插入时外键约束,快速删除
快速删除: CMD命令 SET FOREIGN_KEY_CHECKS=0;去除外键约束 truncate table 表名;
- Series转成list
直接list(series)就可以的 最佳的方式是将列表转换成Python中的科学计算包numpy包的array类型,再进行加减. 1 2 3 4 import numpy as np a = np. ...
- Python中的replace方法
replace 方法:返回根据正则表达式进行文字替换后的字符串的复制. stringObj.replace(rgExp, replaceText) 参数 stringObj必选项.要执行该替换的 St ...
- BSD Socket (java)
服务器 import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java ...