docker安装RabbitMQ
#先拉取下来
docker pull rabbitmq
#给rabbitmq创建容器并启动
docker run -d -p 5671:5671 -p 5672:5672 -p 15672:15672 -p 15671:15671 -p 25672:25672 -v /data/rabbitmq-data/:/var/rabbitmq/lib --name rabbitmq b8956a8129ef
#进入rabbitmq
docker exec -it rabbitmq sh
#运行
rabbitmq-plugins enable rabbitmq_management
#浏览器访问:http://ip地址:15672
#用户名密码:guest
添加用户
Virtual Hosts管理 就相当于数据库
java连接RabbitMQ
引入maven依赖
<!-- 引入队列依赖 -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.10</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
- 生产者
- 队列 rabbitmq
- 消费者
简单队列(simple queue)
获取连接
public class ConnectionUtils {
//获取mq的连接
public static Connection getConnection() throws IOException, TimeoutException {
// 定义一个连接工厂
ConnectionFactory factory=new ConnectionFactory();
// 设置服务地址
factory.setHost("39.107.221.63");
// AMQP 5672
factory.setPort(5672);
// vhost
factory.setVirtualHost("/vhost_hzy");
factory.setUsername("user_hzy");
factory.setPassword("root");
return factory.newConnection();
}
}
生产者
//生产者生产消息
public class Send {
private static final String QUEUE_NAME="test_simple_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection=ConnectionUtils.getConnection();
// 从连接中获取一个通道
Channel channel=connection.createChannel();
// 创建队列 声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String msg="hello simple !";
// 负责生产消息
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
System.out.println("--send msg:"+msg);
channel.close();
connection.close();
}
}
消费者
//消费者获取消息
public class Recv {
private static final String QUEUE_NAME="test_simple_queue";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection = ConnectionUtils.getConnection();
Channel channel=connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
// 到达的消息
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("new api recv:" + msg);
}
};
// 监听队列
channel.basicConsume(QUEUE_NAME,true,consumer);
/** 不推荐
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列
channel.basicConsume(QUEUE_NAME,true,consumer);
while(true){
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String msgString=new String(delivery.getBody());
System.out.println("{recv} msg:"+msgString);
}
*/
}
}
简单队列的不足
耦合性高,生产者11对应消费者(如果多个消费者消费队列中消息,就不行了;队列名变更 必须同时变更)
Work queues 工作队列
P------Queue------C1,C2
(轮询方式)
先创建一个获取连接的方法(简单队列中有)
生产者
public class Send {
/**
* P------Queue------C1,C2
* */
private static final String QUEUE_NAME="test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection= ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
for (int i = 0; i < 50; i++) {
String msg="hello"+i;
System.out.println("{work send}:"+msg);
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
try {
Thread.sleep(i*20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
channel.close();
connection.close();
}
}
消费者1
public class Recv1 {
private static final String QUEUE_NAME="test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection= ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
Consumer consumer = new DefaultConsumer(channel) {
// 消息到达触发
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("[1] Recv msg:"+msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[1] done");
}
}
};
boolean autoAck=true;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
消费者2
复制消费者1代码进行修改
(公平分发 fair dipatch)
使用公平分发,必须关闭自动应答ack,改成手动
在生产者中生产消息之前加上
// 使用公平分发 每个消费者发送确认消息之前,消息队列不发送下一个消息到消费者 1次只处理一个消息
int prefetchCount=1;
channel.basicQos(prefetchCount);
消费者1
public class Recv1 {
private static final String QUEUE_NAME="test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection= ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//公平分发
channel.basicQos(1);//保证一次只分发一个
Consumer consumer = new DefaultConsumer(channel) {
// 消息到达触发
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("[1] Recv msg:"+msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[1] done");
// 公平分发 手动回复消息
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
// 公平分发 自动应答改为false
boolean autoAck=true;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
消费者2同理
消息应答与消息持久化
- 消息应答
boolean autoAck=true;(自动确认模式)一旦rabbitmq将消息分发给消费者,就会从内存中删除
这种情况下,如果杀死正在执行的消费者,就会丢失正在处理的消息。
boolean autoAck=false;(手动确认模式),如果有一个消费者挂掉,就会交付给其他消费者,rabbitmq支持消息应答,消费者发送一个消息应答,告诉rabbitmq这个消息我处理完了,然后rabbitmq就删除内存中的消息
消息应答默认是false
- 消息持久化
//声明队列
boolean durable=true;
channel.queueDeclare(QUEUE_NAME,durable,false,false,null);
我们将程序中的boolean durable改成true,就实现了持久化,注意:如果更改已存在的队列会报错!!!
注意:rabbitmq不允许重新定义(不同参数)一个已存在的队列
解决方法:更改队列名或者删除已存在的队列,重新创建
订阅模式publist/subscribe
发布订阅
- 一个生产者,多个消费者
- 每一个消费者都有自己的队列
- 生产者没有直接把消息发送到队列,而是发到了交换机 转发器 exchange
- 每个队列都要绑定到交换机上
- 生产者发送的消息 经过交换机 到达队列 就能实现 一个消息被多个消费者消费
注册->邮件->短信…
生产者
public class Send {
private static final String EXCHANGE_NAME="test_exchange_fanout";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection= ConnectionUtils.getConnection();
Channel channel=connection.createChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");//分类
// 发送消息
String msg="hello ps";
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
System.out.println("Send :"+msg);
channel.close();
connection.close();
}
}
注意:交换机没有存储能力,只有队列有存储能力!
消费者1
public class Recv1 {
private static final String QUEUE_NAME="test_queue_fanout_email";
private static final String EXCHANGE_NAME="test_exchange_fanout";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection= ConnectionUtils.getConnection();
final Channel channel=connection.createChannel();
// 队列声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 绑定队列到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//公平分发
channel.basicQos(1);//保证一次只分发一个
Consumer consumer = new DefaultConsumer(channel) {
// 消息到达触发
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("[1] Recv msg:"+msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[1] done");
// 公平分发 手动回复消息
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
// 公平分发 自动应答改为false
boolean autoAck=false;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
消费者2同上
路由模式
Exchange(交换机 转发器)
一方面是接收生产者的消息,另一方面是向队列推送消息
匿名转发
Fanout(不处理路由键)
Direct(处理路由键)
生产者
public class Send {
private static final String EXCHANGE_NAME="test_exchange_direct";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection= ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
// exchange
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
String msg="hello direct!";
String routingKey="info";//发送的key,绑定这人key的会收到
channel.basicPublish(EXCHANGE_NAME,routingKey,null,msg.getBytes());
System.out.println("[send]:"+msg);
channel.close();
connection.close();
}
}
消费者1
public class Recv1 {
private static final String EXCHANGE_NAME="test_exchange_direct";
private static final String QUEUE_NAME="test_queue_direct_1";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection= ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.basicQos(1);
// 绑定key,消费生产者中的key
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");
Consumer consumer=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("[Recv1]:"+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[done1]");
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck=false;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
在消费者2中会绑定多个key
`// 绑定多个key,哪个key发送消息,就接受哪个key的消息
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"info");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"warning");`
主题模式
Topic exchange
将路由键和某模式匹配
#匹配一个或者多个
*匹配一个
生产者
public class Send {
private static final String EXCHANGE_NAME="test_exchange_topic";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection= ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
// exchange
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
String msgString="商品....";
channel.basicPublish(EXCHANGE_NAME,"goods.upd",null,msgString.getBytes());
System.out.println("----send "+msgString);
channel.close();
connection.close();
}
}
消费者和上面的订阅模式基本一样
区别:将订阅模式中的key改为
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.#");
Rabbitmq的消息确认机制
问题:生产者将消息发送出去之后,消息到底有没有到达rabbitmq服务器,默认的情况是不知道的;
两种方式解决:
-
AMQP实现了事务机制
-
Confirm模式
AMQP事务机制
txSelect txCommit txRollback -
txSelect 用于将当前channel设置成transation模式
-
txCommit 用于提交事务
-
txRollback 用于回滚事务
生产者
public class TxSend {
private static final String QUEUE_NAME="test_queue_tx";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection= ConnectionUtils.getConnection();
Channel channel=connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String tx_msg="hello tx message";
try {
channel.txSelect();
channel.basicPublish("",QUEUE_NAME,null,tx_msg.getBytes());
channel.txCommit();
}catch (Exception e){
channel.txRollback();
System.out.println("执行了事务回滚!");
}
}
}
消费者
public class TxRecv {
private static final String QUEUE_NAME="test_queue_tx";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection= ConnectionUtils.getConnection();
Channel channel=connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.basicConsume(QUEUE_NAME,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("[recv[tx] msg]"+new String(body,"utf-8"));
}
});
}
}
Confirm模式
Confirm模式最大的好处在于他是异步
注意:队列不能重复设置AMQP事务和Confirm事务
Confirm单条消息
生产者
public class Send1 {
private static final String QUEUE_NAME="test_queue_confirm";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection= ConnectionUtils.getConnection();
Channel channel=connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String tx_msg="hello confirm message";
// 设置confirm事务
channel.confirmSelect();
channel.basicPublish("",QUEUE_NAME,null,tx_msg.getBytes());
// 确认发送消息
if(!channel.waitForConfirms()){
// 如果失败
System.out.println("message send failed");
}else{
System.out.println("message send ok");
}
channel.close();
connection.close();
}
}
消费者同AMQP事务
Confirm多条消息
同时发送一批量的消息
public class Send2 {
private static final String QUEUE_NAME="test_queue_confirm";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection= ConnectionUtils.getConnection();
Channel channel=connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String tx_msg="hello confirm message barch";
// 设置confirm事务
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
channel.basicPublish("",QUEUE_NAME,null,tx_msg.getBytes());
}
// 确认发送消息
if(!channel.waitForConfirms()){
// 如果失败
System.out.println("message send failed");
}else{
System.out.println("message send ok");
}
channel.close();
connection.close();
}
}
异步Confirm模式
public class Send3 {
private static final String QUEUE_NAME="test_queue_confirm3";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection= ConnectionUtils.getConnection();
Channel channel=connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 生产者调用confirmSelect将channel设置为confirm模式注意
final SortedSet<Long> confirmSet= Collections.synchronizedSortedSet(new TreeSet<Long>());
// 通道添加监听
channel.addConfirmListener(new ConfirmListener() {
// 没有问题的handleAck
@Override
public void handleAck(long l, boolean b) throws IOException {
if(b){
System.out.println("---handleAck---true");
confirmSet.headSet(l+1).clear();
}else{
System.out.println("---handleAck---false");
confirmSet.remove(l);
}
}
@Override
public void handleNack(long l, boolean b) throws IOException {
if(b){
System.out.println("---handleAck---true");
confirmSet.headSet(l+1).clear();
}else{
System.out.println("---handleAck---false");
confirmSet.remove(l);
}
}
});
String msgStr="message str";
while (true){
long seqNo=channel.getNextPublishSeqNo();
channel.basicPublish("",QUEUE_NAME,null,msgStr.getBytes());
confirmSet.add(seqNo);
}
}
}
spring整合rabbitmq
spring整合rabbitmq
<!-- 引入队列依赖 -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.10</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>1.7.5.RELEASE</version>
</dependency>
xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd" >
<!-- 定义RabbitMQ的连接工厂-->
<rabbit:connection-factory id="connectionFactory" host="39.107.221.63" port="5672" username="guest" password="guest" virtual-host="/vhost_hzy"/>
<!-- 定义Rabbit模板,指定连接工厂以及定义exchange-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory" exchange="fanoutExchange"/>
<!-- MQ的管理,包括队列、交换器声明等-->
<rabbit:admin connection-factory="connectionFactory"/>
<!-- 定义队列,自动声明-->
<rabbit:queue name="myQueue" durable="true"/>
<!-- 定义交换器,自动声明-->
<rabbit:fanout-exchange name="fanoutExchange">
<rabbit:bindings>
<rabbit:binding queue="myQueue"/>
</rabbit:bindings>
</rabbit:fanout-exchange>
<!-- 队列监听-->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="auto">
<rabbit:listener ref="foo" method="listen" queue-names="myQueue"/>
</rabbit:listener-container>
<!-- 消费者-->
<bean id="foo" class="com.hzy.rabbitmq.spring.MyConsumer"/>
</beans>
生产者
AbstractApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:spring-context.xml");
// RabbitMQ模板
RabbitTemplate template=applicationContext.getBean(RabbitTemplate.class);
// 发送消息
template.convertAndSend("Hello-World!");
Thread.sleep(1000);
applicationContext.destroy();//容器销毁
消费者
public void listen(String foo){
System.out.println("消费者:"+foo);
}
应用场景
在用户进行增加,删除,修改时,及时更改缓存中的数据!
当用户在数据库中增加,删除,修改时,告诉缓存(如redis)该更新数据了,然后进行更新!