环境:
centos6.8,jdk1.8.0_u172
1、环境搭建
官网:https://www.rabbitmq.com
准备:
erlang-21.0.7-1.el6.x86_64.rpm,rabbitmq-server-3.7.7-1.el6.noarch.rpm
先安装erlang,再安装rabbit-server
1 cd /home/rui/softwarerepo
2
3 rpm -ivh erlang-21.0.7-1.el6.x86_64.rpm #安装erlang
4
5 rpm -q erlang-21.0.7-1.el6.x86_64 #查询是否安装erlang
6
7 rpm -qi erlang-21.0.7-1.el6.x86_64 #显示erlang的详细信息
8
9 rpm -ql erlang-21.0.7-1.el6.x86_64 #查看erlang的安装位置和包含的文件,/usr/lib64和/usr/share/doc/
socat是一个网络工具,官网:http://www.dest-unreach.org/socat/
从http://www.dest-unreach.org/socat/download/下载socat,选择 socat-1.7.3.2.tar.gz
1 tar –zxvf socat-1.7.3.2.tar.gz
2
3 cd socat-1.7.3.2
4
5 ./configure
6
7 make && make install
安装rabbit-server
1 rpm -ivh rabbitmq-server-3.7.7-1.el6.noarch.rpm --nodeps
2
3 rpm -ql rabbitmq-server-3.7.7-1.el6.noarch #查看安装详情,/etc/logrotate.d/,/etc/profile.d/,/etc,/etc/rc.d/init.d/,/usr/lib/ocf/resource.d/,/usr/lib/,/usr/sbin/,/usr/share/doc/
4
5 cd /usr/share/doc/rabbitmq-server-3.7.7/ #拷贝配置文件到 /etc/rabbitmq
6
7 cp rabbitmq.config.example /etc/rabbitmq/rabbit.config
启动rabbitmq
1 service rabbitmq-server start
查看rabbitmq进程是否存在
1 ps -ef|grep rabbitmq
rabbitmq命令
1 service rabbitmq-server start #启动
2
3 service rabbitmq-server restart #重启动
4
5 service rabbitmq-server stop #关闭
6
7 service rabbitmq-server status #查看状态
配置rabbitmq的管理权限,通过rabbitmqctl命令行来配置,更多详细信息参考文档:https://www.rabbitmq.com/rabbitmqctl.8.html
1 #配置用户管理
2 rabbitmqctl add_user 目标用户名 目标用户密码
3
4 rabbitmqctl set_user_tags 目标用户名 标签名(比如:administrator)
5
6 #配置用户访问控制权限
7 rabbitmqctl set_permissions -p / 目标用户名 “.*” “.*” “.*” #后面的.*分别表示针对所有资源拥有配置权限,写入权限,读取权限;/是指虚拟主机的名字,这是默认设置
8
9 示例: 10 rabbitmqctl add_user admin admin 11 12 rabbitmqctl set_user_tags admin administrator 13 14 rabbitmqctl set_permissions -p / admin “.*” “.*” “.*” 15 16 rabbitmqctl list_users
通过web 浏览器的方式管理和查看rabbitmq,详细内容查看文档:https://www.rabbitmq.com/management.html
1)激活rabbitmq_management插件,不用重新启动rabbitmq
1 rabbitmq-plugins enable rabbitmq_management
2)开放防火墙端口15672,浏览器访问 http://192.168.0.102:15672/
1 -A INPUT -m state --state NEW -m tcp -p tcp --dport 15672 -j ACCEPT
------------------------------------------------------我是分割线-----------------------------------------------------------------
2、基本概念 ,参考文档:https://www.rabbitmq.com/getstarted.html
producer:发送消息的用户应用程序。
queue:存储消息的缓存区。
consumer:接收消息的用户应用程序。
exchange:它接收来自生产者的消息,然后将它们推送到队列。exchange有四种类型:direct,topic,headers,fanout。
1)fanout:将它收到的所有消息广播到它知道的所有队列。
2)direct:消息的routing key和direct exchange与queue之间的binding key相匹配时,消息将被送到相匹配的队列。direct --> fanout :当全部queue和exchange direct之间的binding key相同时,此时direct exchange等价于fanout exchange。
3)topic:使用特定routing key发送的消息将传递到使用匹配的binding key绑定的所有队列。routing key的规则:必须使用以点分隔开的字符串;大小不能超过255bytes;binding key的规则:可以使用符号“ * ”和“ # ”,“ * ”替代一个单词,“ # ”替代零个或者多个单词。topic exchange在某些情况下可以表现出direct exchange和fanout exchange的特性:topic --> direct:binding key不使用特殊字符“ * ”和“ # ”时;topic --> fanout:binding key只使用特殊字符“ # ”时。
binding:exchange和queue的关系称为绑定。绑定通常还会和routingKey参数一起使用,称为binding key。
temporary queue:具有随机名称的队列。它可以通过queueDeclare()方法生成,可以使用这个生成的随机对列名创建一个非持久,独立而且会自动删除的队列。
routing key:与producer发布的消息绑定的路由信息。
binding key:queue和exchange之间的绑定关系。
Round-robin dispatching:循环调度,默认地,rabbitmq会发送每一个消息给下一个消费者。按平均来算,每个消费者将会得到相同数量的消息。
Message acknowledgment:ack:消费者发回ack(nowledgement)告诉RabbitMQ已收到,处理了特定消息,RabbitMQ可以自由删除它。如果消费者死亡(其channal关闭,connection关闭或TCP连接丢失)而不发送ack,RabbitMQ将理解消息未完全处理并将重新排队。如果其他消费者同时在线,则会迅速将其重新发送给其他消费者。这样我们就可以确保没有消息丢失,即使worker(消费者)偶尔会死亡。
Message durability:通过设置durable=true确保rabbitmq的队列不会丢失,必须同时在生产者和消费者之间设置;接着通过设置 MessageProperties.PERSISTENT_TEXT_PLAIN保证消息持久化。
Fair dispatch:公平调度,通过设置prefetchCount=1实现一次发送一条信息给worker,直到worker处理并且确认之后再发送下一条消息。
deliveryMode:标记消息的发布模式,两种选择, persistent 或者 transient。
contentType:描述编码的lmine类型。比如指定为application/json.
replyTo:回调队列的名字。
correlationId:rpc中,响应和请求对应关系的关联纽带。每个请求的CorrelationId是唯一的。
3、框架
1)Publish/Subscribe,图片来自:https://www.rabbitmq.com/tutorials/tutorial-three-java.html
2)topic,图片来自:https://www.rabbitmq.com/tutorials/tutorial-five-java.html
3)rpc,图片来自:https://www.rabbitmq.com/tutorials/tutorial-six-java.html
说明:在rpc中使用rabbitmq时,请求会发送replyTo和correlationId两个属性,replyTo,表示为请求创建匿名的独占队列,称为reply queue。当worker等到请求,完成了相关的工作后,worker把处理结果和correlationId发送到reply queue。客户端等到reply queue出现数据时,会检查correlationId是否是之前请求中的correlationId,如果不是,将丢弃消息;如果是一样的,则响应给应用程序。
4、支持的协议
AMQP 0-9-1
5、java操作 参考文档:https://www.rabbitmq.com/getstarted.html
maven依赖:
12 com.rabbitmq 3amqp-client 44.0.2 5
1)简单的producer-queue-consumer
Send.java
0 private final static String QUEUE_NAME = "hello"; //声明一个队列的名称
1 ConnectionFactory factory = new ConnectionFactory(); //创建连接工厂
2 factory.setHost("localhost"); // 表示连接本地的rabbitmq,如果是远程,必须指定ip
3 Connection connection = factory.newConnection(); //创建与rabbitmq server的连接
4 Channel channel = connection.createChannel(); //创建用于发送消息的通道
5
6 channel.queueDeclare(QUEUE_NAME, false, false, false, null);//声明一个队列,名称指定为“hello”
7 String message = "Hello World!";
8 channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));//将消息编码,发送消息到指定名称的队列
9 System.out.println(" [x] Sent '" + message + "'");
10
11 channel.close(); //关闭通道
12 connection.close(); //关闭连接
Rec.java
0 private final static String QUEUE_NAME = "hello"; //声明队列名称
1 ConnectionFactory factory = new ConnectionFactory(); 2 factory.setHost("localhost"); 3 Connection connection = factory.newConnection(); 4 Channel channel = connection.createChannel(); 5 6 channel.queueDeclare(QUEUE_NAME, false, false, false, null); //上面代码作用见Send.java 7 System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); 8 9 Consumer consumer = new DefaultConsumer(channel) { //异步发送消息,以对象的形式提供回调方法,缓冲消息,直到我们准备使用消息 10 @Override 11 public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) 12 throws IOException { 13 String message = new String(body, "UTF-8"); //消息的内容 14 System.out.println(" [x] Received '" + message + "'"); 15 } 16 }; 17 channel.basicConsume(QUEUE_NAME, true, consumer); //指定消费者消费指定的队列
2)wokers queues
NewTask.java
1 private static final String TASK_QUEUE_NAME = "task_queue";//声明队列名称
2
3 //同Send.java:创建连接工厂,设置主机,创建连接,创建通道,声明队列
4
5 boolean durable = true; //声明队列为持久化,producer和consumer必须同时声明
6 channel.queueDeclare(TASK_QUEUE_NAME , durable, false, false, null);
7
8 //dosomething
9
10 channel.basicPublish("", TASK_QUEUE_NAME ,
11 MessageProperties.PERSISTENT_TEXT_PLAIN, //将消息设置为可以持久化
12 message.getBytes("UTF-8"));
13
14
15 //关闭通道,关闭连接
Worker.java
1 private static final String TASK_QUEUE_NAME = "task_queue";//声明队列的名称
2
3 //同Rec.java:创建连接工厂,设置主机,创建连接,创建通道,声明队列
4
5 boolean durable = true; //声明队列为持久化,producer和consumer必须同时声明
6 channel.queueDeclare(TASK_QUEUE_NAME , durable, false, false, null);
7
8 int prefetchCount = 1;
9 channel.basicQos(prefetchCount); //一次只接受一个未确认的消息,实现公平调度
10
11
12 final Consumer consumer = new DefaultConsumer(channel) {
13 //....
14
15 try{
16 //dosomething
17 }finally{
18 channel.basicAck(envelope.getDeliveryTag(), false);//确认消息,通过提供的标签
19 } 20 }; 21 22 boolean autoAck = false; //设置worker,即consumer可发送ack给rabbitmq,表示消息已被接收和处理,rabbitmq可以任意删除消息 23 channel.basicConsume(TASK_QUEUE_NAME , autoAck, consumer);
3)Publish/Subscribe
EmitLog.java
1 private static final String EXCHANGE_NAME = "logs";//exchange的名称 2 3 //同Send.java:创建连接工厂,设置主机,创建连接,创建通道 4 5 channel.exchangeDeclare(EXCHANGE_NAME , BuiltinExchangeType.FANOUT);//声明exchange,类型为fanout 6 7 //dosomething 8 9 channel.basicPublish(EXCHANGE_NAME , "", null, message.getBytes("UTF-8")); // 发布消息到exchange 10 11 12 //关闭通道,关闭连接
ReceiveLogs.java
1 private static final String EXCHANGE_NAME = "logs";//exchange的名称
2
3 //同Rec.java:创建连接工厂,设置主机,创建连接,创建通道
4
5 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);//创建exchange,类型为fanout
6 String queueName = channel.queueDeclare().getQueue();//声明一个有名称的队列,这个队列非持久,独占,可自动删除
7 channel.queueBind(queueName, EXCHANGE_NAME, "");//队列和exchange进行绑定,exchange会将消息附加到队列
8
9
10 final Consumer consumer = new DefaultConsumer(channel) {
11
12 //dosomething
13
14 };
15
16 channel.basicConsume(queueName, true, consumer);
4)routing
EmitLogDirect.java
1 private static final String EXCHANGE_NAME = "direct_logs";//exchange的名称
2
3 //同Send.java:创建连接工厂,设置主机,创建连接,创建通道
4
5 channel.exchangeDeclare(EXCHANGE_NAME , BuiltinExchangeType.DIRECT);//声明exchange,类型为direct
6
7 //dosomething
8
9 String routingKey = "...";
10
11 channel.basicPublish(EXCHANGE_NAME , routingKey, null, message.getBytes("UTF-8")); // 发布消息到exchange,消息上绑定有routingKey
12
13
14 //关闭通道,关闭连接
ReceiveLogsDirect.java
1 private static final String EXCHANGE_NAME = "direct_logs";//exchange的名称
2
3 //同Rec.java:创建连接工厂,设置主机,创建连接,创建通道
4
5 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);//创建exchange,类型为direct
6 String queueName = channel.queueDeclare().getQueue();//声明一个有名称的队列,这个队列非持久,独占,可自动删除
7
8 String routingKey = "..."
9
10 channel.queueBind(queueName, EXCHANGE_NAME, routingKey);//队列和exchange进行绑定,exchange会将消息附加到队列,暂且把routingKey称为 binding key,在exchange和queue增加binding key进行绑定
11
12
13 final Consumer consumer = new DefaultConsumer(channel) {
14
15 //dosomething
16
17 };
18
19 channel.basicConsume(queueName, true, consumer);
5)topic
EmitLogTopic.java
1 private static final String EXCHANGE_NAME = "topic_logs";//exchange的名称
2
3 //同Send.java:创建连接工厂,设置主机,创建连接,创建通道
4
5 channel.exchangeDeclare(EXCHANGE_NAME , BuiltinExchangeType.TOPIC);//声明exchange,类型为TOPIC
6
7 //dosomething
8
9 String routingKey = "...";
10
11 channel.basicPublish(EXCHANGE_NAME , routingKey, null, message.getBytes("UTF-8")); // 发布消息到exchange,消息上绑定有routingKey
12
13
14 //关闭通道,关闭连接
ReceiveLogsTopic.java
private static final String EXCHANGE_NAME = "topic_logs";//exchange的名称
//同Rec.java:创建连接工厂,设置主机,创建连接,创建通道
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);//创建exchange,类型为topic
String queueName = channel.queueDeclare().getQueue();//声明一个有名称的队列,这个队列非持久,独占,可自动删除
String routingKey = "..."; // 把routingkey命名成bindingKey
channel.queueBind(queueName, EXCHANGE_NAME, routingKey);//队列和exchange进行绑定,exchange会将消息附加到队列,暂且把routingKey称为 binding key,在exchange和queue增加binding key进行绑定
final Consumer consumer = new DefaultConsumer(channel) {
//dosomething
};
channel.basicConsume(queueName, true, consumer);
6)rpc
RPCClient.java
1 private String requestQueueName = "rpc_queue";//定义一个routing key,本质是队列的名称
2
3 public RPCClient() throws IOException, TimeoutException {
4 //同Rec.java:创建连接工厂,设置主机,创建连接,创建通道
5 }
6
7
8 public String call(String message) throws IOException, InterruptedException {
9
10 final String corrId = UUID.randomUUID().toString();//用来关联rpc的响应和请求,必须是唯一的
11
12 String replyQueueName = channel.queueDeclare().getQueue(); // 回调队列的名称,使用默认的queue,是用来接收worker的响应
13 AMQP.BasicProperties props = new AMQP.BasicProperties //这是消息额外的属性
14 .Builder()
15 .correlationId(corrId) // 设置correlationId属性到消息体中
16 .replyTo(replyQueueName) //设置replyTo属性到消息体中
17 .build();
18
19 channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8")); //发送消息(包括了额外的属性props)到名称为“rpc_queue”的队列
20
21 final BlockingQueue response = new ArrayBlockingQueue(1); 22 23 String ctag = channel.basicConsume(replyQueueName, true, new DefaultConsumer(channel) { //consumer对象的回调接口 24 @Override 25 public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { 26 if (properties.getCorrelationId().equals(corrId)) { //判断响应的信息中是否corrId和请求的corrId相等,如果相等,放到response队列中 27 response.offer(new String(body, "UTF-8")); 28 } 29 } 30 }); 31 32 String result = response.take(); 33 channel.basicCancel(ctag); // 取消消费者,一旦取消消费者,回调消费者的接口方法Consumer#handleCancelOk取出取处理 34 //ctag 是和消费者相关联的消费者标签ConsumerTag 35 return result; 36 } 37 38 //关闭通道,关闭连接
RPCServer.java
1 private static final String RPC_QUEUE_NAME = "rpc_queue";
2
3 //同Rec.java:创建连接工厂,设置主机,创建连接,创建通道
4
5
6 channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
7 channel.queuePurge(RPC_QUEUE_NAME); //清除队列rpc_queue的内容。
8
9 channel.basicQos(1); //一次只接受一个未确认的消息,实现公平调度
10
11 Consumer consumer = new DefaultConsumer(channel) {
12 @Override
13 public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
14 AMQP.BasicProperties replyProps = new AMQP.BasicProperties 15 .Builder() 16 .correlationId(properties.getCorrelationId()) //取出CorrId,构建消息的额外属性 17 .build(); 18 19 String response = ""; 20 21 try{ 22 23 }catch{ 24 25 }finally{ 26 channel.basicPublish( "", properties.getReplyTo(), replyProps, response.getBytes("UTF-8")); //发送消息给回调队列,消息内容包括response和replyProps中的CorrId 27 channel.basicAck(envelope.getDeliveryTag(), false);//确认消息,通过提供的标签 28 // RabbitMq consumer worker thread notifies the RPC server owner thread 29 synchronized(this) { 30 this.notify(); 31 } 32 } 33 } 34 }; 35 36 channel.basicConsume(RPC_QUEUE_NAME, false, consumer); 37 // 等待并准备消费RPC client发送过来的消息 38 while (true) { 39 synchronized(consumer) { 40 try { 41 consumer.wait(); 42 } catch (InterruptedException e) { 43 e.printStackTrace(); 44 } 45 } 46 } 47 48 //关闭通道,关闭连接
3、RabbitMQ使用场景 参考下面博文
CSDN https://blog.csdn.net/whoamiyang/article/details/54954780
知乎 https://www.zhihu.com/question/34243607
http://www.cnblogs.com/luxiaoxun/p/3918054.html
4、RabbitMQ工作机制的理解 参考下面文章
https://www.2cto.com/kf/201612/575219.html