RabbitMQ
1消息队列解决了什么问题
流量削峰
应用解耦
日志处理
异步处理
2docker安装
rabbit是lang语言编写的 docker 会将lang语言一块安装
docker pull rabbitmq:3-management
management 代表有图形界面的
5672端口 客户端通讯端口
15672 web页面的端口
docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq 479479d8e188
启动之后访问
账号密码默认都是guest
3添加用户
virtual hosts 相当于数据库
4添加数据库
授权
4Java操作RabbitMQ
1)、简单队列
模型
p:消息的生产者
红色:消息队列
c:消费者
3个对象 生产者 队列 消费者
1导包
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.0.2</version>
</dependency>
<
2 创建获取连接的工具
package com.lijiawei;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*
获取MQ的连接
*/
public class ConnectionUtil {
public static Connection getConnection() throws IOException, TimeoutException {
//定义一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//定义连接信息
factory.setHost("192.168.75.129:15672");
factory.setPort(5672);
//连接那个库
factory.setVirtualHost("/vhost");
//设置用户
factory.setUsername("user_name");
factory.setPassword("123456");
return factory.newConnection();;
}
}
2 生产者
package com.lijiawei;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtil.getConnection();
//获取一个通道 用这个通道传消息
Channel channel = connection.createChannel();
//声明消息队列test
channel.queueDeclare("test",false,false,false,null);
//使用管道发消息到test队列 消息必须转成字节
channel.basicPublish("","test",null,"helloworld".getBytes());
channel.close();
connection.close();
}
}
运行程序之后 生产者 会发送一个消息 我们去 web网站去看一眼
3消费者
用于接收消息队列里的消息
package com.lijiawei;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*
消费着
*/
public class Obtain {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//获取连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
Channel channel = connection.createChannel();
//定义消费者
QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
//监听队列
channel.basicConsume("test",true,queueingConsumer);
while (true){
QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
String s= new String(delivery.getBody());
System.out.println(s);
}
}
}
消费者会一直处于运行状态 监听消息队列
4消费者新API
上面的消费之 里的方法过时了 下面是用新API
package com.lijiawei;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*
消费着
*/
public class Obtain {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//获取连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
Channel channel = connection.createChannel();
//队列声明
channel.queueDeclare("test",false,false,false,null);
//定义消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String s = new String(body);
System.out.println(s);
}
};
//监听消息队列test 用消费者处理
channel.basicConsume("test",true,defaultConsumer);
}
}
5简单队列的不足
耦合性高 生产者与消费者一一对应 如果想多个消费者消费队列的消息
队列名变更 消费者和生产者要同时变更
2)、Work Queues
1模型
1 生产50个消息
package com.lijiawei;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
public static void main(String[] args) throws IOException, TimeoutException{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取 通道
Channel channel = connection.createChannel();
//队列声明
channel.queueDeclare("test",false,false,false,null);
//发消息 消息必须转成字节
for(int i=0;i<50;i++){
channel.basicPublish("","test",null,(i+"msg").getBytes());
System.out.println("发送消息"+i+"msg");
}
channel.close();
connection.close();
}
}
只需要写俩个消费者就行
通过测试发现 俩个消费者处理的速度是一样的 平均都处理25个消息
这种方式就叫轮询分发 不管谁忙或谁清闲都不会多给一个或少给一个
任务消息总是你一个我一个
3)、公平分发
轮询分发 效率低 处理慢的和处理快的都处理一样的数量 导致效率低 所以使用公平分发
消息队列只给每个消费者分发一个消息 处理完之后消费者回应一下 消息队列才会在分配消息 最后的效果就是能者多劳
使用公平分发 必须满足下面条件
消费者关闭自动应答 改成手动
通道指定一次最多发几条消息
1消费者
package com.lijiawei;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*
消费着
*/
public class Obtain1 {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//获取连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//队列声明
channel.queueDeclare("test",false,false,false,null);
channel.basicQos(1);
//定义消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String s = new String(body);
System.out.println(s);
//处理完之后告诉消息队列
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//监听test队列 如果有消息调用defaultConsumer处理 false关闭自动应答
channel.basicConsume("test",false,defaultConsumer);
}
}
2生产者
package com.lijiawei;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
public static void main(String[] args) throws IOException, TimeoutException{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取 通道
Channel channel = connection.createChannel();
//队列声明
channel.queueDeclare("test",false,false,false,null);
//一次最多个消费者分发1个消息
channel.basicQos(1);
//发消息 消息必须转成字节
for(int i=0;i<50;i++){
channel.basicPublish("","test",null,(i+"msg").getBytes());
System.out.println("发送消息"+i+"msg");
}
channel.close();
connection.close();
}
}
每个类都加 channel.basicQos(1); //指定一次发一个消息
消费者类加 channel.basicAck(envelope.getDeliveryTag(),false); //告诉消息队列处理完了一个消息
消费者类加 channel.basicConsume("test",false,defaultConsumer);//关闭自动应答
4)、消息应答与消息持久化
boolean autoAck =false
消费者类加 channel.basicConsume(“test”,autoAck ,defaultConsumer);//false关闭自动应答
boolean autoAck =true(自动确认模式) 一旦消息队列把消息给消费者 这个消息就会从内存中删除(不管你处理成功),如果杀死正在处理消息的消费者 这个消息就会丢失
boolean autoAck =true(手动确认模式) 如果有一个消费者挂掉 那么正在处理的这个消息就会给其他消费者处理,处理成功之后 给消息队列一个响应 消息队列才会删除这个消息
如果消息队列挂了 所有消息都丢了 那怎么办?持久化?
在代码中有这么一句话 现在解释
boolean durable =false
channel.queueDeclare(“test”,durable,false,false,null);
如果设置过一次 就不能更改 rabbitMQ不允许重新定义一个已存在的队列
5)、订阅消息
1模型
一个生产者 多个消费者 每个消费者都有自己的队列 生产者将消息发送给交换机 每个队列都要绑定交换机
2代码
消息不再发给队列还是发给交换机
代码就不用定义队列 只要定义交换机 给交换机发消息即可
package com.lijiawei;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
public static void main(String[] args) throws IOException, TimeoutException{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取 通道
Channel channel = connection.createChannel();
//声明交换机 类型为fanout分发 名字叫myExchange w
channel.exchangeDeclare("myExchange","fanout");
//发消息给交换机
channel.basicPublish("myExchange","",null,"helloworld".getBytes());
channel.close();
connection.close();
}
}
发送之后 web管理界面就会有一个交换机
交换机是有了 消息呢?丢失了 因为交换机没有存储能力 只有队列有存储能力
消费者
定义队列 并且将队列和交换机绑定
package com.lijiawei;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*
消费着
*/
public class Obtain {
public static void main(String[] args) throws IOException, TimeoutException{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//队列声明
channel.queueDeclare("test",false,false,false,null);
//队列与交换机绑定
channel.queueBind("test","myExchange","");
//定义消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String s = new String("1:"+body);
System.out.println(s);
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//监听test队列 如果有消息调用defaultConsumer处理
channel.basicConsume("test",false,defaultConsumer);
}
}
web
6)、交换机
简单模式下
生产者通过这个代码发送消息给test队列 “” 代表匿名交换机
channel.basicPublish("",“test”,null,“helloworld”.getBytes());
订阅模式下
这个代码指定了交换机 下面的“” 代表路由路由键 空代表不处理路由
channel.basicPublish(“myExchange”,"",null,“helloworld”.getBytes());
在声明队列的时候 fanout是什么? 不处理路由键
channel.exchangeDeclare(“myExchange”,“fanout”);
6)、路由模式
1模型
路由模式必须是direct
channel.exchangeDeclare(“myExchange”,“direct”);
生产者
package com.lijiawei;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
public static void main(String[] args) throws IOException, TimeoutException{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取 通道
Channel channel = connection.createChannel();
//声明交换机 类型direct 名字叫myExchange_direct
channel.exchangeDeclare("myExchange_direct","direct");
//发消息给交换机
channel.basicPublish("myExchange_direct","info",null,"helloworld".getBytes());
channel.close();
connection.close();
}
}
消费者
//队列与交换机绑定 写key
channel.queueBind(“test”,“myExchange”,"");
消费者1
package com.lijiawei;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*
消费着
*/
public class Obtain {
public static void main(String[] args) throws IOException, TimeoutException{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//队列声明
channel.queueDeclare("test3",false,false,false,null);
channel.basicQos(1);
//队列与交换机绑定
channel.queueBind("test3","myExchange_direct","error");
//定义消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("1收到了消息");
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//监听test队列 如果有消息调用defaultConsumer处理
channel.basicConsume("test3",false,defaultConsumer);
}
}
消费者2
package com.lijiawei;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*
消费着
*/
public class Obtain1 {
public static void main(String[] args) throws IOException, TimeoutException{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//队列声明
channel.queueDeclare("test4",false,false,false,null);
channel.basicQos(1);
//队列与交换机绑定这个队列有3个路由key
channel.queueBind("test4","myExchange_direct","error");
channel.queueBind("test4","myExchange_direct","info");
channel.queueBind("test4","myExchange_direct","warning");
//定义消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("2收到了消息");
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//监听test队列 如果有消息调用defaultConsumer处理
channel.basicConsume("test4",false,defaultConsumer);
}
}
7)、主题模式
#匹配多个
*匹配一个
主题模式 交换机必须是topic类型
生产者
package com.lijiawei;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
public static void main(String[] args) throws IOException, TimeoutException{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取 通道
Channel channel = connection.createChannel();
//声明交换机 类型为fanout分发 名字叫myExchange w
channel.exchangeDeclare("myExchange_topic","topic");
//发消息给交换机
channel.basicPublish("myExchange_topic","good.add",null,"helloworld".getBytes());
channel.close();
connection.close();
}
}
消费者1
package com.lijiawei;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*
消费着
*/
public class Obtain {
public static void main(String[] args) throws IOException, TimeoutException{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//队列声明
channel.queueDeclare("test5",false,false,false,null);
channel.basicQos(1);
//队列与交换机绑定
channel.queueBind("test5","myExchange_topic","a.#");
//定义消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("1收到了消息");
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//监听test队列 如果有消息调用defaultConsumer处理
channel.basicConsume("test5",false,defaultConsumer);
}
}
消费者2
package com.lijiawei;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*
消费着
*/
public class Obtain1 {
public static void main(String[] args) throws IOException, TimeoutException{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//队列声明
channel.queueDeclare("test6",false,false,false,null);
channel.basicQos(1);
//队列与交换机绑定
channel.queueBind("test6","myExchange_topic","good.#");
//定义消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("2收到了消息");
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//监听test队列 如果有消息调用defaultConsumer处理
channel.basicConsume("test6",false,defaultConsumer);
}
}
一共五种
5.消息确认机制
如何确定生产会将消息发送出去之后到达了rabbitMQ 服务器默认是不知道的
通过俩种方式解决
AMQP事务
Confirm模式
1)、事务机制
用下面三个方法
txSelect:用户将当前channel设置成transation模式 开启事务
txCommit:提交
txRollback:回滚事务
通过事务机制 如果执行成功呢么一定会传到消息队列 失败就会报错
生产者
package com.lijiawei;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
public static void main(String[] args) throws IOException, TimeoutException{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("test7",false,false,false,null);
try {
//开启事务
channel.txSelect();
channel.basicPublish("","test7",null,"helloworld".getBytes());
int a = 1/0;
channel.txCommit();
}catch (Exception e){
channel.txRollback();
System.out.println("send message failure");
}finally {
channel.close();
connection.close();
}
}
}
消费者
package com.lijiawei;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Obtain {
public static void main(String[] args) throws IOException, TimeoutException{
Connection connection = ConnectionUtil.getConnection();
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare("test7",false,false,false,null);
channel.basicQos(1);
//定义消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String s= new String(body);
System.out.println(s);
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//监听test队列 如果有消息调用defaultConsumer处理
channel.basicConsume("test7",true,defaultConsumer);
}
}
这中事务机制会降低吞吐量 所以有了第二种处理方式
2)、Confirm模式
Conifrm 最大的好处是异步
confirmSelect 开启Confirm模式
1)、发送一条消息
package com.lijiawei;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("test8",false,false,false,null);
//将channel设置成confirm模式
channel.confirmSelect();
channel.basicPublish("","test8",null,"helloworld".getBytes());
//该模式发送消息后会返回boolean值 true代表入队成功
if(channel.waitForConfirms()){
System.out.println("入队成功");
}else {
System.out.println("入队失败");
}
channel.close();
connection.close();
}
}
2)、批量发送
全部发送之后判断
package com.lijiawei;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("test8",false,false,false,null);
//将channel设置成confirm模式
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
channel.basicPublish("","test8",null,("helloworld"+i).getBytes());
}
//该模式发送消息后会返回boolean值 true代表入队成功
if(channel.waitForConfirms()){
System.out.println("入队成功");
}else {
System.out.println("入队失败");
}
channel.close();
connection.close();
}
}