本文章主要记录个人使用rabbitMQ,仅供参考
基本
介绍
MQ全称Message Queue(消息队列)
优势:应用解耦、异步提速、削峰填谷
RabbitMQ:基于Erlang语言开发,延迟低,专门为开发高并发和分布式系统的一种语言。
1.Producer和Consumer通过Connection(tcp)连接Server
2.在Connection中有很多管道类似mybatis的连接
3.server中的每个Virtual(虚拟机)中都有很多的Exchange(交换机)和Queue(队列),它们之间相互隔离,逻辑分区
4.Exchange可以绑定到很多的Queue上
工作模式:简单模式、work queues、Publish/Subscribe发布订阅模式、Routing路由模式、Topics主题模式、RPC远程调用模式(不太算Mq)
JMS:java消息服务接口,是java平台面向消息中间间的api
安装
rabbitMQ
官网:https://www.rabbitmq.com/
下载:https://github.com/rabbitmq/rabbitmq-server/releases
依赖:
https://github.com/rabbitmq/erlang-rpm/releases/
rpm -ivh erlang-25.3.2.6-1.el7.x86_64.rpm
rpm -ivh rabbitmq-server-3.12.4-1.el8.noarch.rpm
安装插件:
rabbitmq-plugins enable rabbitmq_management
yum install -y socat
启动、停止、重启、状态
service rabbitmq-server start
service rabbitmq-server stop
service rabbitmq-server restart
service rabbitmq-server stutas
开机启动:
chkconfig rabbitmq-server on
使用图形化页面:
http://hadoop100:15672/
密码and用户:guest
不能登录创建用户
查看用户列表、添加用户、设置角色、设置权限
//用户列表
[root@hadoop100 soft]# rabbitmqctl list_users
Listing users ...
user tags
admin [administrator]
guest [administrator]
//添加用户
[root@hadoop100 soft]# rabbitmqctl add_user admin 123456
Adding user "admin" ...
Done. Don't forget to grant the user permissions to some virtual hosts! See 'rabbitmqctl help set_permissions' to learn more.
//设置角色
[root@hadoop100 soft]# rabbitmqctl set_user_tags admin administrator
Setting tags for user "admin" to [administrator] ...
//设置权限
[root@hadoop100 soft]# rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
Setting permissions for user "admin" in vhost "/" ...
说明:设置权限:rabbitmqctl set_permissions [-p ]
javaAPI
简单模式
生产者
public class Producer {
public static void main(String[] args) {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(MQ_HOST);
connectionFactory.setUsername(MQ_USER);
connectionFactory.setPassword(MQ_PASSWORD);
try {
//创建连接
Connection connection = connectionFactory.newConnection();
//获取信道
Channel channel = connection.createChannel();
//生成一个队列
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//发消息
String message = "hello world";
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("发送完毕");
} catch (IOException e) {
throw new RuntimeException(e);
} catch (TimeoutException e) {
throw new RuntimeException(e);
}
}
}
消费者
public class Consumer {
public static void main(String[] args) {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(MQ_HOST);
connectionFactory.setUsername(MQ_USER);
connectionFactory.setPassword(MQ_PASSWORD);
//声明
DeliverCallback deliverCallback = (consumerTag, message) -> System.out.println("message->" + message);
CancelCallback cancelCallback = consumerTag -> System.out.println("消息消费中断");
try {
Channel channel = connectionFactory.newConnection().createChannel();
channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (TimeoutException e) {
throw new RuntimeException(e);
}
}
}
work queues
一对多,并且消费者们是竞争关系,一个消息只能被消费一次,并且消费者们轮训获取。
Utils
public class RabbitMQUtils {
public static Channel getChannel(){
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(MQ_HOST);
connectionFactory.setUsername(MQ_USER);
connectionFactory.setPassword(MQ_PASSWORD);
try {
Channel channel = connectionFactory.newConnection().createChannel();
return channel;
} catch (IOException e) {
throw new RuntimeException(e);
} catch (TimeoutException e) {
throw new RuntimeException(e);
}
}
}
work
public class Worker01 {
public static void main(String[] args) throws IOException {
Channel channel = RabbitMQUtils.getChannel();
DeliverCallback deliverCallback = (consumerTag,message)-> System.out.println("message"+new String(message.getBody()));
CancelCallback cancelCallback = consumerTag -> System.out.println("consumerTag"+"消费者消息接口回调逻辑");
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
System.out.println("c2__________________");
}
}
task
public class Task01 {
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
try {
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//从控制台中发送消息
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String message = scanner.next();
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("发送消息成功:"+message);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
消息应答:处理消费者可能异常的情况,保证了消息的安全。
只有接收到消费者的应答生产者才会处理这条消息。
消息自动重新入队:消费者由于某些原因失去连接,通道关闭,tcp连接丢失,导致消息未发送ACK确认。MQ将会对其重新排队。
multiple:批量应答
消费者修改
public class Work03 {
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
DeliverCallback deliverCallback = (consumerTag,message)-> {
System.out.println("接受到的消息"+new String(message.getBody(),"UTF-8"));
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
}
CancelCallback cancelCallback = consumerTag-> System.out.println("消费者取消消费");
try {
channel.basicConsume(TASK_QUEUE_NAME,false,deliverCallback,cancelCallback);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
队列持久化:
解决异常崩溃时,宕机时,数据安全问题。
生产者:
public class Task2 {
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
try {
channel.queueDeclare(TASK_QUEUE_NAME,true,false,false,null);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String s = scanner.next();
channel.basicPublish("",TASK_QUEUE_NAME,null,s.getBytes("UTF-8"));
System.out.println(s+"发送");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
效果:
消息持久化:
public class Task2 {
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
try {
channel.queueDeclare(TASK_QUEUE_NAME,true,false,false,null);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String s = scanner.next();
channel.basicPublish("",TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,s.getBytes("UTF-8"));
System.out.println(s+"发送");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
不公平分发(不使用轮训分发):
设置channel.basicQos(1),1不使用轮训分发,0使用(默认)
public class Work03 {
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
DeliverCallback deliverCallback = (consumerTag,message)-> {
System.out.println("接受到的消息"+new String(message.getBody(),"UTF-8"));
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
CancelCallback cancelCallback = consumerTag-> System.out.println("消费者取消消费");
try {
channel.basicQos(1);
channel.basicConsume(TASK_QUEUE_NAME,false,deliverCallback,cancelCallback);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
预取值:指定消费者分多少,指定几就在信道中可以堆积几条数据
channel.basicQos(2);>1就是设置预取值
public class Work03 {
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
DeliverCallback deliverCallback = (consumerTag,message)-> {
System.out.println("接受到的消息"+new String(message.getBody(),"UTF-8"));
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
CancelCallback cancelCallback = consumerTag-> System.out.println("消费者取消消费");
try {
channel.basicQos(2);
channel.basicConsume(TASK_QUEUE_NAME,false,deliverCallback,cancelCallback);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
发布确实:处理生产者在发送消息过程中数据丢失的情况。
1.单个发布确认,发一条确认一条,不确认不发送下一条
public static void publishMessahgeIndividually(){
Channel channel = RabbitMQUtils.getChannel();
try {
channel.queueDeclare(CONFIRM_QUEUE_NAME,true,false,false,null);
//开启发布确认
channel.confirmSelect();
String message = "发布的消息"
channel.basicPublish("",CONFIRM_QUEUE_NAME,null,message.getBytes());
//单个发布确认
boolean re = channel.waitForConfirms();
if (re) {
System.out.println("消息发送成功");
}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
2.批量发布确认,不能知道到底是哪一条消息出现了问题
public static void publishMessahgeBatch(){
Channel channel = RabbitMQUtils.getChannel();
try {
channel.queueDeclare(CONFIRM_QUEUE_NAME,true,false,false,null);
//开启发布确认
channel.confirmSelect();
for (int i = 0; i < 1000; i++) {
String message = "发布的消息"+i
channel.basicPublish("",CONFIRM_QUEUE_NAME,null,message.getBytes());
//批量发布确认
//批量确认消息大小
int batchSize = 100;
if (i%batchSize == 0) {
boolean re = channel.waitForConfirms();
if (re) {
System.out.println("消息发送成功");
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
3.异步确认发布:利用回调函数,broker异步通知哪些消息传达到了、哪些消息未传达到
public static void publishMessageAsync(){
Channel channel = RabbitMQUtils.getChannel();
try {
channel.queueDeclare(CONFIRM_QUEUE_NAME,true,false,false,null);
//开启发布确认
channel.confirmSelect();
//线程安全有序的一个hash表,1,轻松的将序号与消息关联,2,轻松批量删除,3,支持高并发
ConcurrentSkipListMap<Long,String> outstandingConfirms = new ConcurrentSkipListMap<>();
//准备消息监听器(开启新线程)
//消息确认成功回调函数
ConfirmCallback ackCallback = (deliveryTag,multiple)->{
System.out.println("确认的消息"+deliveryTag);
//删除确认到的消息
if(multiple){
ConcurrentNavigableMap<Long, String> confirmed = outstandingConfirms.headMap(deliveryTag);
confirmed.clear();
}else {
outstandingConfirms.remove(deliveryTag);
}
}
//消息确认失败回调函数
ConfirmCallback nackCallback = (deliveryTag,multiple)->{
System.out.println("未确认的消息"+deliveryTag);
}
channel.addConfirmListener(ackCallback,nackCallback);
//模拟发布消息
for (int i = 0; i < 1000; i++) {
String message = "发布的消息"+i
channel.basicPublish("",CONFIRM_QUEUE_NAME,null,message.getBytes());
//记录所有要发送的消息
outstandingConfirms.put(channel.getNextPublishSeqNo(),message);
}
//批量发布确认
//批量确认消息大小
} catch (IOException e) {
throw new RuntimeException(e);
}
}
交换机:
生产者只能将消息发送到交换机。
类型:
直接(direct):
主题(topic):
标题(headers):
扇出(fanout):
无名交换机为默认交换机:
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
消息发送到队列中其实是由routingKey(bindingkey)绑定key指定的。
临时队列:
一旦断开了消费者连接,队列就会自动删除。
channel.queueDeclare().getQueue();
绑定(binding)
交换机通过绑定队列后,当消息发送到交换机时,交换机就会将消息发送给所绑定的队列,通过RoutingKey可以指定发给谁。
Fanout
将接受到的所有消息广播到它所知的队列中,默认有Exahange类型
生产者:
public class EmitLog {
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
try {
channel.exchangeDeclare(EXCHANGE_QUEUE_NAME,"fauout");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
channel.basicPublish(EXCHANGE_QUEUE_NAME,"",null,scanner.next().getBytes("UTF-8"));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
消费者:
public class ReceiveLog01 {
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
//声明一个交换机
try {
channel.exchangeDeclare(EXCHANGE_QUEUE_NAME,"fanout");
//声明一个临时的队列
String queue = channel.queueDeclare().getQueue();
//交换机队列绑定
channel.queueBind(queue,EXCHANGE_QUEUE_NAME,"");
channel.basicConsume(queue,true,null,consumerTag -> {});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
direct
生产者:
public class DirectLogs {
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
try {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
channel.basicPublish(EXCHANGE_QUEUE_NAME,"info",null,scanner.next().getBytes("UTF-8"));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
消费者:
public class ReceiveLogsDirect01 {
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
try {
//交换机
channel.exchangeDeclare(EXCHANGE_QUEUE_NAME, BuiltinExchangeType.DIRECT);
//队列
channel.queueDeclare("console",false,false,false,null);
channel.queueBind("console",EXCHANGE_QUEUE_NAME,"info");
channel.queueBind("console",EXCHANGE_QUEUE_NAME,"warning");
DeliverCallback deliverCallback=(consumerTag,message)-> System.out.println("接受到的消息"+new String(message.getBody(),"UTF-8"));
channel.basicConsume("console",true,deliverCallback,consumerTag->{});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public class ReceiveLogsDirect02 {
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
try {
//交换机
channel.exchangeDeclare(EXCHANGE_QUEUE_NAME, BuiltinExchangeType.DIRECT);
//队列
channel.queueDeclare("disk",false,false,false,null);
channel.queueBind("disk",EXCHANGE_QUEUE_NAME,"error");
DeliverCallback deliverCallback=(consumerTag,message)-> System.out.println("接受到的消息"+new String(message.getBody(),"UTF-8"));
channel.basicConsume("disk",true,deliverCallback,consumerTag->{});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
topic
解决一个生产者不能同时发给多个队列
Topic交换机的routing_key必须是一个单词列表,以点分隔开:*可以代替一个单词,#可以代替0个或多个单词。单词不能超过255个字节。
生产者:
public class EmitLogTopic {
public static final String EXCHANGE_NAME="topic_log";
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
HashMap<String, String> bindingKeyMap = new HashMap<>();
bindingKeyMap.put("quick.orange.rabbit","被Q1Q2接受到");
bindingKeyMap.put("lazy.orange.elephant","被Q1Q2接受到");
bindingKeyMap.put("quick.orange.fox","被Q1接受到");
bindingKeyMap.put("lazy.brown.fox","被Q2接受到");
bindingKeyMap.put("lazy.pink.rabbit","Q2接受一次");
bindingKeyMap.put("quick.brown.fox","丢弃");
bindingKeyMap.put("quick.orange.male.rabbit","丢弃");
bindingKeyMap.put("lazy.orange.rabbit","匹配Q2");
for (Map.Entry<String, String> entry : bindingKeyMap.entrySet()) {
String key = entry.getKey();
String message = entry.getValue();
try {
channel.basicPublish(EXCHANGE_NAME, key, null,message.getBytes("UTF-8"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
消费者
public class ReceiveLogsTopic01 {
public static final String EXCHANGE_NAME="topic_log";
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
try {
//交换机
channel.exchangeDeclare(EXCHANGE_NAME,"topict");
//队列
channel.queueDeclare("Q1",false,false,false,null);
channel.queueBind("Q1",EXCHANGE_NAME,"*.orange.*");
DeliverCallback deliverCallback = (consumerTag, message)->{
System.out.println(new String(message.getBody(),"UTF-8"));
System.out.println("绑定的键:"+message.getEnvelope().getRoutingKey());
};
//接受消息
channel.basicConsume("Q1",deliverCallback,message->{});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public class ReceiveLogsTopic02 {
public static final String EXCHANGE_NAME="topic_log";
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
try {
//交换机
channel.exchangeDeclare(EXCHANGE_NAME,"topict");
//队列
channel.queueDeclare("Q2",false,false,false,null);
channel.queueBind("Q2",EXCHANGE_NAME,"*.*.rabbit");
channel.queueBind("Q2",EXCHANGE_NAME,"lazy.#");
DeliverCallback deliverCallback = (consumerTag, message)->{
System.out.println(new String(message.getBody(),"UTF-8"));
System.out.println("绑定的键:"+message.getEnvelope().getRoutingKey());
};
//接受消息
channel.basicConsume("Q1",deliverCallback,message->{});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
死信:
无法被消费的消息,由于某种原因导致queue中的某些消息无法被消费。
有死信,就有死信队列
1.消息TTL过期
2.队列达到最大长度
3.消息别拒绝
设置死信交换机和队列
消费者:
public static final String NORMAL_EXCHANGE="normal_exchange";
public static final String DEAD_EXCHANGE="dead_exchange";
public static final String NORMAL_QUEUE="normal_queue";
public static final String DEAD_QUEUE="dead_queue";
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
try {
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
//普通队列
Map<String, Object> arguments = new HashMap<>();
//过期时间10s,也可以由生产方设置
//arguments.put("x-message-ttl",10000);
//正常队列设置死信交换机
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
//设置死信RoutingKey
arguments.put("x-dead-letter-routing-key","lisi");
channel.queueDeclare(NORMAL_EXCHANGE,false,false,false,arguments);
//死信队列
channel.queueDeclare(DEAD_EXCHANGE,false,false,false,null);
//绑定普通的交换机与普通的队列
channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"zhangsan");
//绑定死信的交换机与死信的队列
channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"lisi");
DeliverCallback deliverCallback = (consumerTag, message)->{
System.out.println(new String(message.getBody(),"UTF-8"));
}
channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,consumerTag -> {});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
生产者:
public class Producer {
public static final String NORMAL_EXCHANGE="normal_exchange";
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
//死信消息,设置TTL时间
AMQP.BasicProperties props = new AMQP.BasicProperties()
.builder().expiration("10000")
.build();
for (int i = 0; i < 10; i++) {
String message = "info" + i;
try {
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",props,message.getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
死信队列:
public class Consumer02 {
public static final String DEAD_QUEUE="dead_queue";
public static void main(String[] args) {
Channel channel = RabbitMQUtils.getChannel();
try {
DeliverCallback deliverCallback = (consumerTag, message)->{
System.out.println(new String(message.getBody(),"UTF-8"));
}
channel.basicConsume(DEAD_QUEUE,true,deliverCallback,consumerTag -> {});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
整合springboot
延迟队列
是死信队列的一种,是指消息TTL过期中生产者和死信消费者之间的关系。
使用场景:
1.十分钟之内未支付自动取消
2.新建的店铺,十天内都没有上传商品,自动发送你消息提醒
3.注册成功后,三天内没有登录则进行短信提醒
4.用户发起退款,三天没有得到处理则通知相关人员
5.预定会议后,需要在预定的时间前十分钟通知各个与会人员参加会议
准备工作:
添加pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
</dependency>
yml:
spring:
rabbitmq:
host: "hadoop100"
port: "5672"
username: "admin"
password: "123456"
mvc:
pathmatch:
matching-strategy: ant_path_matcher
server:
port: 80
SwaggerConfig
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket webApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")
.apiInfo(webApiInfo())
.select()
.build();
}
private ApiInfo webApiInfo(){
return new ApiInfoBuilder()
.title("rabbitmq接口文档")
.description("本文档描述了rabbitmq微服务接口定义")
.version("1.0")
.contact(new Contact("GoiRhiy","txk.aisa","XXX@gmail.com"))
.build();
}
}
TtlQueueConfig(mq配置类)
@Configuration
public class TtlQueueConfig {
//声明普通交换机
@Bean("xExchange")
public DirectExchange xExchange(){
return new DirectExchange(X_EXCHANGE);
}
//声明死信交换机
@Bean("yExchange")
public DirectExchange yExchange(){
return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);
}
//声明普通队列
@Bean("queueA")
public Queue queueA(){
Map<String, Object> arguments = new HashMap<>(3);
//设置死信交换机
arguments.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
//设置死信routingkey
arguments.put("x-dead-letter-routing-key","YD");
//设置ttl为10s
arguments.put("x-message-ttl",10000);
return QueueBuilder.durable(QUEUE_A).withArguments(arguments).build();
}
@Bean("queueB")
public Queue queueB(){
Map<String, Object> arguments = new HashMap<>(3);
//设置死信交换机
arguments.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
//设置死信routingkey
arguments.put("x-dead-letter-routing-key","YD");
//设置ttl为10s
arguments.put("x-message-ttl",40000);
return QueueBuilder.durable(QUEUE_B).withArguments(arguments).build();
}
//声明死信队列
@Bean("queueD")
public Queue queueD(){
return QueueBuilder.durable(DEAD_LETTER_QUEUE).build();
}
//绑定
@Bean
public Binding queueABindingX(@Qualifier("queueA")Queue queueA,
@Qualifier("xExchange")DirectExchange xExchange){
return BindingBuilder.bind(queueA).to(xExchange).with("XA");
}
@Bean
public Binding queueBBindingX(@Qualifier("queueB")Queue queueB,
@Qualifier("xExchange")DirectExchange xExchange){
return BindingBuilder.bind(queueB).to(xExchange).with("XB");
}
@Bean
public Binding queueDBindingY(@Qualifier("queueD")Queue queueD,
@Qualifier("yExchange")DirectExchange yExchange){
return BindingBuilder.bind(queueD).to(yExchange).with("YD");
}
}
SendMsgController(生产者)
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMsgController {
@Resource
private RabbitTemplate rabbitTemplate;
//开发发消息
@GetMapping("/sendMsg/{message}")
public void sendMsg(@PathVariable String message){
log.info("当前时间:{},发送一条信息给两个ttl队列:{}",new Date().toString(),message);
rabbitTemplate.convertAndSend(X_EXCHANGE,"XA","消息来自ttl为10s的队列:"+message);
rabbitTemplate.convertAndSend(X_EXCHANGE,"XB","消息来自ttl为40s的队列:"+message);
}
}
DeadLet(死信队列)
@Slf4j
@Component
public class DeadLet {
@RabbitListener(queues = "QD")
public void receiveD(Message message, Channel channel) {
String msg = new String(message.getBody());
log.info("当前时间:{},收到死信队列的消息:{}", new Date().toString(), msg);
}
}
但是这种方式不能设置自定义ssl
@Bean("queueC")
public Queue queueC(){
Map<String, Object> arguments = new HashMap<>(3);
//设置死信交换机
arguments.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
//设置死信routingkey
arguments.put("x-dead-letter-routing-key","YD");
return QueueBuilder.durable(QUEUE_C).withArguments(arguments).build();
}
@Bean
public Binding queueCBindingX(@Qualifier("queueC")Queue queueC,
@Qualifier("xExchange")DirectExchange xExchange){
return BindingBuilder.bind(queueC).to(xExchange).with("XC");
}
生产者:
@GetMapping("/sendExpirationMsg/{message}/{ttlTime}")
public void sendMsg(@PathVariable String message,@PathVariable String ttlTime){
log.info("当前时间:{},发送一条时长是{}毫秒TTL信息给队列QC:{}",new Date().toString(),ttlTime,message);
rabbitTemplate.convertAndSend(X_EXCHANGE,"XC",message,msg->{
//发送消息的时长
msg.getMessageProperties().setExpiration(ttlTime);
return msg;
});
}
但是这种方式是排序的方式进行的,前面有一个等待长的消息,后面不管长短时间都得等待着。
并且只会检查第一个消息是否过期。
插件实现延迟队列(rabbitmq-delayed-message-exchange)
下载:https://www.rabbitmq.com/community-plugins.html
选择与mq一样的版本并将文件放入:/usr/lib/rabbitmq/lib/rabbitmq_server-3.12.4/plugins/
执行安装:rabbitmq-plugins enable rabbitmq_delayed_message_exchange
重启mq:systemctl restart rabbitmq-server.service
在web中有如下选项,一般来说就是好了
说明:有了这个插件就可以使用延迟交换机,也就是说延迟操作在交换机中实现。
以下是一个案例:
配置类:
@Configuration
public class DelayedQueueConfig {
/**
* @Description: 自定义交换机类型
* 1.交换机名
* 2.交换机类型
* 3.是否需要持久化
* 4.是否需要自动删除
* 5.其他的参数
* @version v2.0
* @author TianXinKun
*
*/
@Bean
public CustomExchange delayedExchange(){
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-delayed-type","direct");
return new CustomExchange(DELAYED_EXCHANGE_NAME,"x-delayed-message",true,false,arguments);
}
//队列
@Bean
public Queue delayedQueue(){
return QueueBuilder.durable(DELAYED_QUEUE_NAME).build();
}
//绑定
@Bean
public Binding delayedQueueBingDelayeExchange(@Qualifier("delayedQueue") Queue delayedQueue,
@Qualifier("delayedExchange") CustomExchange delayedExchange){
return BindingBuilder.bind(delayedQueue).to(delayedExchange).with(DELAYED_ROUTINGKEY_NAME).noargs();
}
}
生产者:
@GetMapping("/sendDelayMsg/{message}/{delayTime}")
public void sendMsg(@PathVariable String message,@PathVariable Integer delayTime){
log.info("当前时间:{},发送一条时长是{}毫秒信息给延迟队列delayed.queue:{}",new Date().toString(),delayTime,message);
rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME,DELAYED_ROUTINGKEY_NAME,message,msg->{
//发送消息的时长,ms
msg.getMessageProperties().setDelay(delayTime);
return msg;
});
}
消费者:
@Component
@Slf4j
public class DelayQueueConsumer {
@RabbitListener(queues = DELAYED_QUEUE_NAME)
public void receiverDelayQueue(Message message){
String msg = new String(message.getBody());
log.info("当前时间:{},收到延迟队列的消息:{}", new Date().toString(), msg);
}
}
发布确认高级
生产者消息投递失败,导致消息丢失(rabbitMQ宕机)
在yml添加:
spring:
rabbitmq:
host: "hadoop100"
port: "5672"
username: "admin"
password: "123456"
# none默认,禁用发布确认模式 correlated,发布消息成功到交换机后回触发回调方法
# simple(同步确认消息) ,其一效果和correlated会触发回调方法,
# 其二发布消息成功后使用rabbitTemplate调用waitForCOnfirms或waitForConfirmsOrDie方法等待broker节点发送结果,
# 注意:waitForCOnfirms返回false则会关闭channel,则接下无法发送消息到broker
publisher-confirm-type: correlated
配置类:
@Configuration
public class ConfirmConfig {
@Bean("confirmExchange")
public DirectExchange confirmExchange(){
return new DirectExchange(CONFIRM_EXCHANGE_NAME);
}
@Bean("confirmQueue")
public Queue confirmQueue(){
return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
}
@Bean
public Binding queueBindingExchange(@Qualifier("confirmQueue")Queue confirmQueue,
@Qualifier("confirmExchange")DirectExchange confirmExchange){
return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_ROUTINGKEY_NAME);
}
}
回调配置类:
@Component
@Slf4j
public class MyCallBack implements RabbitTemplate.ConfirmCallback {
@Resource
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
//注入
rabbitTemplate.setConfirmCallback(this);
}
//交换机确认回调方法
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
String id = correlationData != null ? correlationData.getId() : "";
if (ack) {
log.info("交换机已经收到了消息id为{}的消息",id);
}else {
log.info("交换机还未收到ID为{},原因为{}",id,cause);
}
}
}
生产者:
@Slf4j
@RestController
@RequestMapping("/confirm")
public class Producer {
@Resource
private RabbitTemplate rabbitTemplate;
@GetMapping("/sendMessage/{message}")
public void sendMessage(@PathVariable String message){
CorrelationData correlationData = new CorrelationData("1");
rabbitTemplate.convertAndSend(CONFIRM_EXCHANGE_NAME,CONFIRM_ROUTINGKEY_NAME,message,correlationData);
log.info("发送消息内容为:{}",message);
}
}
消费者:
@Component
@Slf4j
public class Consumer {
@RabbitListener(queues = CONFIRM_QUEUE_NAME)
public void receiveConfirmMessage(Message message){
String msg = new String(message.getBody());
log.info("收到队列confirm.queue的消息:{}", msg);
}
}
回退消息
解决不可路由导致的消息丢失
添加yml:
publisher-returns: true
回退配置:
@Component
@Slf4j
public class MyCallBack implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {
@Resource
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init() {
//注入
rabbitTemplate.setConfirmCallback(this);
}
//交换机确认回调方法
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
String id = correlationData != null ? correlationData.getId() : "";
if (ack) {
log.info("交换机已经收到了消息id为{}的消息", id);
} else {
log.info("交换机还未收到ID为{},原因为{}", id, cause);
}
}
//消息不可达回调,只有失败时才会调用
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
Message message = returnedMessage.getMessage();
int replyCode = returnedMessage.getReplyCode();
String replyText = returnedMessage.getReplyText();
String exchange = returnedMessage.getExchange();
String routingKey = returnedMessage.getRoutingKey();
log.info("消息{},code是:{}被交换机{}退回,退回原因{},路由key为:{}",new String(message.getBody()),replyCode,exchange,replyText,routingKey);
}
}
备份交换机
配置类:
@Configuration
public class ConfirmConfig {
@Bean("confirmExchange")
public DirectExchange confirmExchange(){
return ExchangeBuilder
.directExchange(CONFIRM_EXCHANGE_NAME)
.durable(true)
.withArgument("alternate-exchange",BACKUP_EXCHANGE_NAME).build();
}
@Bean("confirmQueue")
public Queue confirmQueue(){
return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
}
@Bean
public Binding queueBindingExchange(@Qualifier("confirmQueue")Queue confirmQueue,
@Qualifier("confirmExchange")DirectExchange confirmExchange){
return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_ROUTINGKEY_NAME);
}
//备份交换机
@Bean("backupExchange")
public FanoutExchange backupExchange(){
return new FanoutExchange(BACKUP_EXCHANGE_NAME);
}
//备份队列
@Bean("backupQueue")
public Queue backupQueue(){
return QueueBuilder.durable(BACKUP_QUEUE_NAME).build();
}
//报警队列
@Bean("warningQueue")
public Queue warningQueue(){
return QueueBuilder.durable(WARNING_QUEUE_NAME).build();
}
//备份绑定
@Bean
public Binding backupBindingbackupExchange(@Qualifier("backupQueue")Queue backupQueue,
@Qualifier("backupExchange")FanoutExchange backupExchange){
return BindingBuilder.bind(backupQueue).to(backupExchange);
}
//报警绑定
@Bean
public Binding warningBindingbackupExchange(@Qualifier("warningQueue")Queue warningQueue,
@Qualifier("backupExchange")FanoutExchange backupExchange){
return BindingBuilder.bind(warningQueue).to(backupExchange);
}
}
报警消费者:
@Component
@Slf4j
public class WarningConsumer {
@RabbitListener(queues = WARNING_QUEUE_NAME)
public void receiveWarningMsg(Message message){
String msg = new String(message.getBody());
log.error("报警发现不可路由消息:{}",msg);
}
}
注意:当回退消息和备份交换机都启用的时候,备份交换机的优先级交高
其他
冥等性
说明,在用户付款之后,业务由于某种原因导致rabbitmq多次扣款。
解决方式:使用redis的set类型
优先级队列
场景:在用户下单后,如果在设定的时间内未付款那么就会给用户推送一条短信,但是商铺对平台而言,分大客户和小客户,大客户给我们带来很大的利润,因此他们的订单必须得到优先处理。
1.在队列中其他参数中添加优先级范围值
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-max-priority",10);
2.消息中代码添加优先级priority(5)
AMQP.BasicProperties props = new AMQP.BasicProperties()
.builder().expiration("10000").priority(5)
.build();
惰性队列
在消费者下线、宕机、维护时,长时间不能消费消息时,需要使用惰性队列
default模式:默认模式,正常模式
lazy模式:惰性模式,可以使用channel.queueDeclare和Policy两种方式设置,如果都设置Policy有更高的优先级
惰性队列的消息放在磁盘中,但是消费速度慢于正常模式
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-queue-mode","lazy");
集群
1.将所有节点的cookie保持一致
scp -r /var/lib/rabbitmq/.erlang.cookie root@hadoop101:/var/lib/rabbitmq/
2.需要同时启动server和erlang虚拟机
rabbitmq-server -detached
3.在以第一个节点为集群,将其他节点加入进来
在其他节点关闭rabbitmq服务不关闭erlang虚拟机,然后重置,接着加入第一个节点,最后启动rabbitmq服务
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster rabbit@hadoop100
rabbitmqctl start_app
查看集群状态:
rabbitmqctl cluster_status
为集群创建账号
//添加用户
[root@hadoop100 soft]# rabbitmqctl add_user admin 123456
Adding user "admin" ...
Done. Don't forget to grant the user permissions to some virtual hosts! See 'rabbitmqctl help set_permissions' to learn more.
//设置角色
[root@hadoop100 soft]# rabbitmqctl set_user_tags admin administrator
Setting tags for user "admin" to [administrator] ...
//设置权限
[root@hadoop100 soft]# rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
Setting permissions for user "admin" in vhost "/" ...
解除集群节点
rabbitmqctl stop_app
rabiitmqctl reset
rabbitmqctl start_app
在其他集群节点执行(忘记需要解除的机器):
forget_cluster_node rabbit@刚刚删除机器的ip
镜像队列
说明:在集群中某一个节点创建的队列,一旦宕机其他节点又没有这个队列,这个队列里面消息直接就丢失了
说明:单备份的节点中有一台宕机,那么会有新的节点再备份一份。
负载均衡
连接mq负载均衡
使用haproxy实现:
Federation Exchange
当节点相隔较远时,网络延迟高,因此设置不同地区访问不同地区的mq,但是需要数据同步。因此需要使用federation插件。
默认内置了插件,只需要安装启用:
rabbitmq-plugins enable rabbitmq_federation
rabbitmq-plugins enable rabbitmq_federation_management
1.在需要同步的节点创建交换机,并且绑定队列
2.在同步的节点创建联邦队列
Federation Queue
说明:联邦队列可以在多个节点或者集群之间为单个队列提供负载均衡的功能,一个联邦队列可以连接一个或多个上游队列。
Shovel
可以将源端的数据发送到目的地队列
安装开启shovel
rabbitmq-plugins enable rabbitmq_shovel
rabbitmq-plugins enable rabbitmq_shovel_management