win10系统中配置rabbitmq
安装包提取码:
链接:https://pan.baidu.com/s/1zkSkM0VSphUzfbYCbjVAfg
提取码:0kmv
具体配置如下:
安装一路默认即可
1、安装erlang环境
在电脑环境变量中添加新的变量名
ERLANG_HOME=E:\Program Files\erl9.0
再path中添加
%ERLANG_HOME%\bin;%RABBITMQ_SERVER%\sbin
2、安装rabbitMq
在 E:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.10\sbin> 下运行 rabbitmq-plugins enable rabbitmq_management
浏览器运行:
http://localhost:15672/#/
用户名; guest 密码; guest
3、rabbitmq工作原理
组成部分说明:
producer 生产者: 生产者负责发送消息
channel 连接管道: 负责建立消费者或生产者的tcp长连接
Broker 服务器: 消息队列服务进程 此服务包括两个部分 exchange queue
Exchange 交换机: 负责消息的匹配和转发
queue 队列消息: 存储消息的队列
congsumer 消费者:负责消费队列中的消息
生产者生产消息过程:
1、生产者和rabbitmq建立管道连接
2、生产者指定对列或者交换机 路由或通配符等信息
3、生产者向管道中发送消息
4、关闭管道 和 rabbitmq连接
消费者消费消息过程
1、建立服务器tcp长连接
2、创建队列并设置队列的属性信息
3、绑定队列与交换机 通配符 路由key等信息
4、消费者获取消息
5、手动确认机制或者 自定ack确认机制
package common.projects.devlop.utils;
import com.rabbitmq.client.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.TimeoutException;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProducerSendMsg {
@ApiModelProperty(value = "rabbitmq连接客户端")
private Connection connection;
@ApiModelProperty(value = "消息对列")
private String queue;
@ApiModelProperty(value = "是否持久化存储消息")
private Boolean durable;
@ApiModelProperty(value = "是否独占连接")
private Boolean exclusive;
@ApiModelProperty(value = "是否自动删除")
private Boolean autoDelete;
@ApiModelProperty(value = "设置额为参数 如: 队列存活时间")
private Map<String,Object> arguments;
@ApiModelProperty(value = "队列消息")
private String queueMsg;
@ApiModelProperty(value = "rabbitmq交换机")
private String exchange;
@ApiModelProperty(value = "交换机类型")
private BuiltinExchangeType exchangeType;
@ApiModelProperty(value = "rabbitmq交换机路由key")
private String routingKey;
@ApiModelProperty(value = "消息的属性")
private String props;
public void sendMsg() throws IOException, TimeoutException, InterruptedException {
// 1、创建连接管道 建立tcp长连接管道
Channel channel = connection.createChannel();
/**
* 2、创建管道连接的消息队列
* @param queue 队列
* @param durable 是否持久化对列存储 如果设置为持久化存储 在重启rabbitmq以后该队列还会存在rabbitmq服务器中
* @param exclusive 是否独占连接 该对列只能在本次连接中使用 如果连接关闭则该对列自动删除 如果设置为true可用于创建临时对列
* @param autoDelete 是否自动删除 当对列为空的时候自动删除 配置exclusive使用可实现临时对列
* @param arguments 额外参数配置 一般可用于设置 对列存活时间
*/
channel.queueDeclare(queue,durable,exclusive,autoDelete,arguments); //队列名称 是否持久化对列 是否独占连接 是否自动删除 额外参数设置
/**
* rabbitmq生产者数据丢失问题解决方法
* 1、声明开启mq事务 保证生产者数据的完性 由于事务开启和事务关闭的两个速度差为149倍 一般很少使用rabbitmq事务
* try {
* channel.txSelect(); // 1.1、声明开启一个事务
* channel.basicPublish(exchange,queue,routingKey,queueMsg.getBytes(StandardCharsets.UTF_8));
* channel.txCommit(); // 1.2、事务的提交
* }catch (Exception ex){
* channel.txRollback(); // 1.3、发生错误事务回滚
* }finally {
* // 3、关闭通道和客户端的连接
* channel.close();
* connection.close();
* }
* 2、confirm发送方确认机制 confirm的三种确认机制
* 2.1 channel.waitForConfirms() 普通confirm普通消息机制
* channel.confirmSelect(); //开启confirm发送消息确认机制
* channel.basicPublish(exchange,queue,routingKey,queueMsg.getBytes(StandardCharsets.UTF_8));
* channel.waitForConfirms())
* 2.2 channel.waitForConfirmsOrDie(); 批量confirm确认机制
* channel.confirmSelect();
* for (int i=0;i<10;i++){
* channel.basicPublish(exchange,queue,routingKey,queueMsg.getBytes(StandardCharsets.UTF_8));
* }
* channel.waitForConfirmsOrDie();
* 2.3 channel.addConfirmListener 异步监听confirm机制
* channel.confirmSelect();
* for (int i=0;i<10;i++){
* channel.basicPublish(exchange,queue,routingKey,queueMsg.getBytes(StandardCharsets.UTF_8));
* }
* // confirm机制
* channel.addConfirmListener(new ConfirmListener() {
* @Override
* public void handleAck(long l, boolean b) throws IOException {
* System.out.println("已确认消息: "+l);
* }
*
* @Override
* public void handleNack(long l, boolean b) throws IOException {
* System.out.println("未确认消息: "+l);
* }
* });
*/
channel.confirmSelect();
// confirm机制
for (int i=0;i<1000;i++){
channel.basicPublish(exchange,queue,null,queueMsg.getBytes(StandardCharsets.UTF_8));
}
channel.waitForConfirmsOrDie();
channel.close();
connection.close();
}
/**
* Publish/subscribe(交换机类型:Fanout,也称为广播 )
* 交换机只负责消息的转发不负责消息的存储 发送到交换机中不再需要队列
*/
public void fanout() throws IOException, TimeoutException {
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchange,exchangeType);
channel.basicPublish(exchange,"",null,queueMsg.getBytes(StandardCharsets.UTF_8));
channel.close();
connection.close();
}
/**
* routing 路由发送消息模式
* @throws IOException
*/
public void routingProducer() throws IOException, TimeoutException {
// 1、创建连接队列
Channel channel = connection.createChannel();
// 2、创建交换 指定交换机名称和类型 类型为路由模式
channel.exchangeDeclare(exchange, exchangeType);
channel.txSelect(); // 3、开始事务 保证消息发送的可靠性防止消息的丢失
// 4、管道队列初始化 指定交换机 对列或routingKey queueMsg 消息
try {
channel.basicPublish(exchange,routingKey,null,queueMsg.getBytes(StandardCharsets.UTF_8));
channel.txCommit();
}catch (Exception ex){
channel.txRollback(); // 事务提交失败后进行回滚
}finally {
channel.close(); // 5、关闭连接管道和连接
connection.close();
}
}
/**
* topic 模式 设置绑定交换机类型为 topic 并向该交换机中推送数据
* @throws IOException
* @throws InterruptedException
*/
public void topicProducer() throws IOException, InterruptedException {
Channel channel = connection.createChannel();
// 绑定交换机
channel.exchangeDeclare(exchange,exchangeType);
channel.confirmSelect(); //开启消息自动确认机制
channel.basicPublish(exchange,routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN,queueMsg.getBytes(StandardCharsets.UTF_8));
channel.waitForConfirms();
}
}
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Connection;
import common.projects.devlop.ProducerApplication;
import common.projects.devlop.utils.MsgQue;
import common.projects.devlop.utils.ProducerSendMsg;
import common.projects.devlop.utils.RabbitMqCon;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ProducerApplication.class,webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class Test {
// 建立rabbitmq连接
@org.junit.Test
public void connection() throws IOException, TimeoutException {
Connection connection = RabbitMqCon.builder()
.host("127.0.0.1")
.port(5672)
.timeout(3000)
.username("guest")
.virtual("/")
.password("guest")
.build().getConnection();
System.out.println(connection);
}
/**
* worker 工作队列 需要设置对列参数 如 对列名称 是否持久化存储数据 是否独占连接 是否自动删除 额外参数等设置 不需要设置交换机 和 路由key等消息
* 持久化数据 rabbitmq服务器重启以后对列仍然存在不会出现消息丢失的情况
* 独占连接 只能有本地连接管道才能操作该对列中的数据 如果该连接断开则对列自动删除
* 是否自动删除 该对列为空的时候自动删除该对列 可以配置 独占连接实现一个临时对列
* 额外参数 类型为Map<String,Object> 可以设置对列过期时间等
* @throws IOException
* @throws TimeoutException
* @throws InterruptedException
*/
@org.junit.Test
public void sendMsg() throws IOException, TimeoutException, InterruptedException {
Connection connection = RabbitMqCon.builder()
.host("127.0.0.1")
.port(5672)
.timeout(3000)
.username("guest")
.virtual("/")
.password("guest")
.build().getConnection();
ProducerSendMsg.builder()
.connection(connection)
.queue(MsgQue.QUEUE_NAME.getQueueName())
.durable(true)
.exclusive(false)
.autoDelete(false)
.arguments(null)
.queueMsg("你好啊,小傻瓜")
.build().sendMsg();
}
/**
* FANOUT 发布订阅机制 需要绑定交换机 不需要绑定对列 交换机只负责转发消息 不存储消息
* 消费者消费的时候 需要绑定对列和交换机 可以存在多个消费者
* @throws IOException
* @throws TimeoutException
*/
@org.junit.Test
public void tes() throws IOException, TimeoutException {
Connection connection = RabbitMqCon.builder()
.host("127.0.0.1")
.port(5672)
.timeout(3000)
.username("guest")
.virtual("/")
.password("guest")
.build().getConnection();
ProducerSendMsg.builder().queueMsg("你好啊小傻瓜!").
exchange("EXCHANGE_NAME")
.connection(connection)
.exchangeType(BuiltinExchangeType.FANOUT)
.build().fanout();
}
/**
* 路由对列 Routing 路由模型(交换机类型:direct)需要绑定交换机 和 routerKey
* 不需要设置消息对列 交换机只负责转换消息 不存储消息
* 消费者可以有多个 但是需要绑定交换机 对列 和routerKey
* @throws IOException
* @throws TimeoutException
*/
@org.junit.Test
public void routingProducer() throws IOException, TimeoutException {
Connection connection = RabbitMqCon.builder()
.host("127.0.0.1")
.port(5672)
.timeout(3000)
.username("guest")
.virtual("/")
.password("guest")
.build().getConnection();
ProducerSendMsg.builder().queueMsg("你好啊小傻瓜!").
exchange("EXCHANGE_NAME_ROUTING")
.routingKey("msg")
.connection(connection)
.exchangeType(BuiltinExchangeType.DIRECT)
.build().fanout();
}
/**
* topic 通配符格式
* * 代表一个通配符 只能有一个参数
* # 代表后面可以有多个参数
* @throws IOException
* @throws TimeoutException
* @throws InterruptedException
*/
@org.junit.Test
public void topicProducer() throws IOException, TimeoutException, InterruptedException {
Connection connection = RabbitMqCon.builder()
.host("127.0.0.1")
.port(5672)
.timeout(3000)
.username("guest")
.virtual("/")
.password("guest")
.build().getConnection();
ProducerSendMsg.builder().queueMsg("你好啊小傻瓜!").
exchange("EXCHANGE_NAME_Topic")
.routingKey("exchange.rabbitmq.li")
.connection(connection)
.exchangeType(BuiltinExchangeType.DIRECT)
.build().topicProducer();
}
}
消费者消费消息
package common.projects.devlop.utils;
import com.rabbitmq.client.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.concurrent.TimeoutException;
@Data
@AllArgsConstructor
@Builder
@NoArgsConstructor
public class ConsumerMsg {
@ApiModelProperty(value = "连接rabbitmq客户端")
private Connection connection;
@ApiModelProperty(value = "消息队列名称")
private String queueName;
@ApiModelProperty(value = "是否持久化队列存储")
private Boolean durable;
@ApiModelProperty(value = "是否独占连接")
private Boolean exclusive;
@ApiModelProperty(value = "是否自动删除")
private Boolean autoDelete;
@ApiModelProperty(value = "设置额外参数")
private Map<String,Object> arguments;
@ApiModelProperty(value = "交换机")
private String exchange;
@ApiModelProperty(value = "routingKey")
private String routerKey;
@ApiModelProperty(value = "是否手动消费 设置ack")
private Boolean autoConsume;
/**
* worker消息模式
* @throws IOException
*/
public void consumer() throws IOException {
Channel channel = connection.createChannel();
/**
* 参数说明
* queueName 队列名称
* durable 是否连接持久化 如果设置为true 该列队为空或者断开后重新连接以后 队列不会消失
* exclusive 是否独占连接 如果设置为独占连接 该对列只能在本地连接中使用 则对列为空时自动删除 设置为true可以设置为临时对列 需要配合 autoDelete使用
* autoDelete 是否自动删除 如果该对列为空则自动删除
* arguments 额外配置参数 可配置对列的存活时间
*/
channel.queueDeclare(queueName,durable,exclusive,autoDelete,arguments);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消费者标签 用来标识消费者的身份信息 例如 admin
* envelope 信封
* properties 消息属性
* body 消息主体
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//交换机
String exchange = envelope.getExchange();
// body 即消息体
String msg = new String(body,"utf-8");
System.out.println(" [x] received : " + msg + "!");
if(autoConsume){
/**
* 手动消费ack
* multiple:是否批量.true:将一次性ack所有小于deliveryTag的消息。
* envelope.getDeliveryTag(): 消息id,mq在channel中用来标识消息的id,可用于确认消息已接收
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
// 判断是否手动消费ack
if(autoConsume){
channel.basicConsume(queueName,false,defaultConsumer); //手动确认ack
System.out.println("手动消费");
}else {
channel.basicConsume(queueName,true,defaultConsumer); //默认自动确认ack
System.out.println("自动消费");
}
}
/**
* 发布订阅机制监听消费消息 也称为广播
* @throws IOException
*/
public void fanout() throws IOException {
Channel channel = connection.createChannel();
channel.queueDeclare(queueName,durable,exclusive,autoDelete,arguments);
channel.queueBind(queueName,exchange,"");
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// body 即消息体
String msg = new String(body);
System.out.println(" [邮件服务] received : " + msg + "!");
}
};
channel.basicConsume(queueName,true,consumer);
}
/**
* routing路由模式消费消息
*/
public void routingConsumer() throws IOException, TimeoutException {
// 1、创建消费者连接通道
Channel channel = connection.createChannel();
channel.queueDeclare(queueName,durable,exclusive,autoDelete,arguments);
// 2、绑定管道初始化 绑定 交换机 对列 routerKey
channel.queueBind(queueName,exchange,routerKey);
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// body 即消息体
String msg = new String(body);
System.out.println(" [邮件服务] received : " + msg + "!");
}
};
channel.basicConsume(queueName,true,consumer); // 消费者消费 绑定对列 ack设置为自动消费 默认消费消息
channel.close();
connection.close();
}
/**
* 通配符消费消息
* * 代表后面跟一个参数
* # 代码后面可以跟多个参数
*/
public void topic_consumer() throws IOException {
Channel channel = connection.createChannel();
channel.queueDeclare(queueName,durable,exclusive,autoDelete,arguments);
// 绑定消息队列 交换机通配符
channel.queueBind(queueName,exchange,routerKey);
// 创建消息默认消费
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("消费者消息为: "+msg);
}
};
channel.basicConsume(queueName,true,consumer);
}
}
import com.rabbitmq.client.Connection;
import common.projects.devlop.ConsumerApplication;
import common.projects.devlop.utils.ConsumerCon;
import common.projects.devlop.utils.ConsumerMsg;
import common.projects.devlop.utils.MsgQue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ConsumerApplication.class,webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ConsumerTest {
@Test
public void test() throws IOException, TimeoutException {
Connection connection = ConsumerCon.builder()
.host("127.0.0.1")
.port(5672)
.timeout(3000)
.username("guest")
.virtual("/")
.password("guest")
.build().getCon();
ConsumerMsg.builder()
.connection(connection)
.queueName(MsgQue.QUEUE_NAME.getQueueName())
.durable(true)
.exclusive(false)
.autoDelete(false)
.arguments(null)
.autoConsume(true)
.build().consumer();
}
@Test
public void tests() throws IOException, TimeoutException {
Connection connection = ConsumerCon.builder()
.host("127.0.0.1")
.port(5672)
.timeout(3000)
.username("guest")
.virtual("/")
.password("guest")
.build().getCon();
ConsumerMsg.builder()
.connection(connection)
.queueName(MsgQue.QUEUE_NAME.getQueueName())
.durable(true)
.exclusive(false)
.autoDelete(false)
.arguments(null)
.autoConsume(false)
.build().consumer();
}
@Test
public void fanoutConsumer() throws IOException, TimeoutException {
Connection connection = ConsumerCon.builder()
.host("127.0.0.1")
.port(5672)
.timeout(3000)
.username("guest")
.virtual("/")
.password("guest")
.build().getCon();
ConsumerMsg.builder()
.connection(connection)
.queueName("QUEUEName")
.exchange("EXCHANGE_NAME")
.durable(false)
.exclusive(false)
.autoDelete(false)
.arguments(null)
.autoConsume(false)
.build().fanout();
}
@Test
public void fanoutConsumers() throws IOException, TimeoutException {
Connection connection = ConsumerCon.builder()
.host("127.0.0.1")
.port(5672)
.timeout(3000)
.username("guest")
.virtual("/")
.password("guest")
.build().getCon();
ConsumerMsg.builder()
.connection(connection)
.queueName("QUEUENAME")
.exchange("EXCHANGE_NAME")
.durable(false)
.exclusive(false)
.autoDelete(false)
.arguments(null)
.autoConsume(false)
.build().fanout();
}
@Test
public void routingConsumer() throws IOException, TimeoutException {
Connection connection = ConsumerCon.builder()
.host("127.0.0.1")
.port(5672)
.timeout(3000)
.username("guest")
.virtual("/")
.password("guest")
.build().getCon();
ConsumerMsg.builder()
.connection(connection)
.queueName("QUEUENAME_routing")
.exchange("EXCHANGE_NAME_ROUTING")
.durable(false)
.exclusive(false)
.autoDelete(false)
.arguments(null)
.autoConsume(false)
.routerKey("msg")
.build().routingConsumer();
}
@Test
public void topic_consumer() throws IOException, TimeoutException {
Connection connection = ConsumerCon.builder()
.host("127.0.0.1")
.port(5672)
.timeout(3000)
.username("guest")
.virtual("/")
.password("guest")
.build().getCon();
ConsumerMsg.builder()
.connection(connection)
.queueName("QUEUENAME_topic")
.exchange("EXCHANGE_NAME_Topic")
.durable(false)
.exclusive(false)
.autoDelete(false)
.arguments(null)
.autoConsume(false)
.routerKey("*.*.li")
.build().topic_consumer();
}
@Test
public void list() throws IOException, TimeoutException {
test();
tests();
}
}
第一问:
生产者是如何避免消息丢失的
1、开启rabbitmq的事务机制
rabbitmq生产者数据丢失问题解决方法
* 1、声明开启mq事务 保证生产者数据的完性 由于事务开启和事务关闭的两个速度差为149倍 一般很少使用rabbitmq事务
* try {
* channel.txSelect(); // 1.1、声明开启一个事务
* channel.basicPublish(exchange,queue,routingKey,queueMsg.getBytes(StandardCharsets.UTF_8));
* channel.txCommit(); // 1.2、事务的提交
* }catch (Exception ex){
* channel.txRollback(); // 1.3、发生错误事务回滚
* }finally {
* // 3、关闭通道和客户端的连接
* channel.close();
* connection.close();
* }
2、开启生产者消息确认机制 confirm
2、confirm发送方确认机制 confirm的三种确认机制
* 2.1 channel.waitForConfirms() 普通confirm普通消息机制
* channel.confirmSelect(); //开启confirm发送消息确认机制
* channel.basicPublish(exchange,queue,routingKey,queueMsg.getBytes(StandardCharsets.UTF_8));
* channel.waitForConfirms())
* 2.2 channel.waitForConfirmsOrDie(); 批量confirm确认机制
* channel.confirmSelect();
* for (int i=0;i<10;i++){
* channel.basicPublish(exchange,queue,routingKey,queueMsg.getBytes(StandardCharsets.UTF_8));
* }
* channel.waitForConfirmsOrDie();
* 2.3 channel.addConfirmListener 异步监听confirm机制
* channel.confirmSelect();
* for (int i=0;i<10;i++){
* channel.basicPublish(exchange,queue,routingKey,queueMsg.getBytes(StandardCharsets.UTF_8));
* }
* // confirm机制
* channel.addConfirmListener(new ConfirmListener() {
* @Override
* public void handleAck(long l, boolean b) throws IOException {
* System.out.println("已确认消息: "+l);
* }
*
* @Override
* public void handleNack(long l, boolean b) throws IOException {
* System.out.println("未确认消息: "+l);
* }
* });
第二问:
如何避免消息积压
采用worker工作消息队列,多个worker操作同一个queue
然后采用线程池异步对消息进行消费可以有效的避免消息的积压
第三问:
如何保证消息不丢失
消费者端采用ack机制可以将消息持久化存储,前提是交换机和消息队列必须先设置为持久化存储
消息发布方设置 exchange为持久化存储
channel.exchangeDeclare(exchange, exchangeType,true);
消息发布方设置 消息为持久化存储channel.basicPublish(exchange,routingKey,MessageProperties.PERSISTENT_TEXT_PLAIN,queueMsg.getBytes(StandardCharsets.UTF_8));