AMQP
AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在。
AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写。用于在分布式系统中存储转发消息。
Topic模式
(主题模式/通配符模式)
TopicExchange
主题交换机,这个交换机其实跟直连交换机流程差不多,但是它的特点就是在它的路由键和绑定键之间是有规则的。
通配符使用规则
Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。
只不过 Topic 类型 Exchange 可以让队列在绑定 Routingkey 的时候使用通配符!
* (星号)用来表示一个单词 (必须出现的,不多不少)
# (井号)用来表示任意数量(零个或多个)单词
使用举例:
有如下三个队列:
队列 Q1 绑定键为 *.TT.*
队列 Q2 绑定键为 TT.#
队列 Q3 绑定键为 TT.*
使用方法:
如果一条消息携带的路由键为 A.TT.B,那么队列Q1将会收到;
如果一条消息携带的路由键为 TT.AA.BB,那么队列Q2将会收到;
如果一条消息携带的路由键为 TT.AA,那么队列Q2、Q3将会收到;
添加依赖
<!--rabbitmq-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.1.2</version>
</dependency>
生产者
关闭自动确认:能者多劳
public class Producer {
//队列名称
private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
//交换机名称
private static final String EXCHANGE_TOPICS_INFORM = "exchange_topics_inform";
//短信RoutingKey
private static final String ROUTINGKEY_SMS = "inform.sms";
//邮件RoutingKey
private static final String ROUTINGKEY_EMAIL = "inform.email";
public static void main(String[] args) {
//连接
Connection connection = null;
//通道
Channel channel = null;
try {
//连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
//rabbitmq默认虚拟机名称为“/”,虚拟机相当于一个独立的mq服务器
factory.setVirtualHost("/");
//创建与RabbitMQ服务的TCP连接
connection = factory.newConnection();
//创建与Exchange的通道,每个链接可以创建多个链接,每个通道到表一个会话任务
channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
/***
* 声明队列,如果Rabbit中没有此队列将自动创建
* param1:队列名称
* param2:队列是否持久化(重启MQ服务,队列不会消失)
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列附加参数
*/
channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
//交换机绑定队列
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPICS_INFORM,ROUTINGKEY_SMS);
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPICS_INFORM,ROUTINGKEY_EMAIL);
//发送短信
for (int i=1; i<5; i++){
String message = "第 " +i+ " 条短信";
/**
* 消息发布方法
* param1:Exchange的名称,如果没有指定,则使用Default Exchange
* param2:routingKey,消息的路由Key,是用于Exchange(交换机)将消息转发到指定的消息队列
* param3:消息包含的属性(与消费者属性保持一致)
* (设置为MessageProperties.PERSISTENT_TEXT_PLAIN作用:消息持久化,重启MQ消息依然存在)
* param4:消息体
*/
channel.basicPublish(EXCHANGE_TOPICS_INFORM,"inform.sms",null,message.getBytes());
System.out.println("发送: "+message+" 路由RoutingKey名称: "+ROUTINGKEY_SMS);
}
//发送邮件
for (int i=1; i<5; i++){
String message = "第 " +i+ " 封邮件";
channel.basicPublish(EXCHANGE_TOPICS_INFORM,"inform.email",null,message.getBytes());
System.out.println("发送: "+message+" 路由RoutingKey名称: "+ROUTINGKEY_EMAIL);
}
//发送短信和邮件
for (int i=1;i<5;i++){
String message = "第 " +i+ " 个短信和邮件";
channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.sms.email", null, message.getBytes());
System.out.println("发送: "+message+" 路由RoutingKey名称: inform.sms.email");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
短信消费者
public class SMSConsumer {
//队列名称
private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
//交换机
private static final String EXCHANGE_TOPICS_INFORM = "exchange_topics_inform";
//短信routingKey(可使用通配符)
private static final String ROUTINGKEY_SMS = "inform.#.sms.#";
public static void main(String[] args) throws IOException, TimeoutException {
//创建一个与MQ的链接,连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");
//创建与RabbitMQ服务的TCP连接
Connection connection = factory.newConnection();
//创建与Exchange的通道,每个链接可以创建多个链接,每个通道到表一个会话任务
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
//声明队列
channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
//交换机绑定ssm队列
/**
* 参数1:queue:队列的名字。
* 参数2:exchange:交换器的名字。
* 参数3:routingKey:用于绑定的路由键(inform.#表示接收短信和邮件)。
*/
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPICS_INFORM,ROUTINGKEY_SMS);
//一次只接受一条未确认的消息
channel.basicQos(1);
//定义消费方法
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
/***
* 消费者接收消息调用此方法
* @param consumerTag 消费者的标签,在channel.basicConsume()去指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志 (收到消息失败后是否需要重新发送)
* @param properties
* @param body
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
//手动确认消息
channel.basicAck(envelope.getDeliveryTag(),false);
//消息id,mq在channel中用来标识消息的id,可用于确认消息已接收
long deliveryTag = envelope.getDeliveryTag();
//交换机
String exchange = envelope.getExchange();
//路由key
String routingKey = envelope.getRoutingKey();
//消息内容
String message = new String(body,"utf-8");
System.out.println("短信消费者接收消息: "+message+" routingKey: "+ROUTINGKEY_SMS);
try {
Thread.sleep(9000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
/***
* 监听队列
* 参数String queue, boolean autoAck,Consumer callback
* 参数明细
* 1、队列名称
* 2、是否自动回复,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置 为false则需要手动回复
* 3、消费消息的方法,消费者接收到消息后调用此方法
*/
channel.basicConsume(QUEUE_INFORM_SMS,true,defaultConsumer);
}
}
邮件消费者
public class EmailConsumer {
//队列名称
private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
//交换机
private static final String EXCHANGE_TOPICS_INFORM = "exchange_topics_inform";
//短信routingKey(可使用通配符)
private static final String ROUTINGKEY_EMAIL = "inform.#.email.#";
public static void main(String[] args) throws IOException, TimeoutException {
//创建一个与MQ的链接,连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");
//创建与RabbitMQ服务的TCP连接
Connection connection = factory.newConnection();
//创建与Exchange的通道,每个链接可以创建多个链接,每个通道到表一个会话任务
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
//声明队列
channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
//交换机绑定email队列
/**
* 参数1:queue:队列的名字。
* 参数2:exchange:交换器的名字。
* 参数3:routingKey:用于绑定的路由键(inform.#表示接收短信和邮件)。
*/
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPICS_INFORM,ROUTINGKEY_EMAIL);
//定义消费方法
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
/***
* 消费者接收消息调用此方法
* @param consumerTag 消费者的标签,在channel.basicConsume()去指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志 (收到消息失败后是否需要重新发送)
* @param properties
* @param body
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
//消息id,mq在channel中用来标识消息的id,可用于确认消息已接收
long deliveryTag = envelope.getDeliveryTag();
//交换机
String exchange = envelope.getExchange();
//路由key
String routingKey = envelope.getRoutingKey();
//消息内容
String message = new String(body,"utf-8");
System.out.println("邮件消费者接收消息: "+message+" routingKey: "+ROUTINGKEY_EMAIL);
}
};
/***
* 监听队列
* 参数String queue, boolean autoAck,Consumer callback
* 参数明细
* 1、队列名称
* 2、是否自动回复,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置 为false则需要手动回复
* 3、消费消息的方法,消费者接收到消息后调用此方法
*/
channel.basicConsume(QUEUE_INFORM_EMAIL,true,defaultConsumer);
}
}
整合SpringBoot
添加依赖pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐test</artifactId>
</dependency>
配置文件application.yml
server:
prot: 44000
spring:
application:
name: test-rabbitmq-producer
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtualHost: /
启动类
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
定义RabbitConfig类
配置Exchange、Queue、及绑定交换机。(配置Topic交换机)
//配置Topic交换机
@Configuration
public class RabbitmqConfig {
//队列名称
public static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
public static final String QUEUE_INFORM_SMS = "queue_inf orm_sms";
//交换机名称
public static final String EXCHANGE_TOPICS_INFORM = "exchange_topic_inform";
//交换机配置
@Bean(EXCHANGE_TOPICS_INFORM)
public Exchange EXCHANGE_TOPICS_INFORM(){
//durable(true)持久化,消息队列重启后交换机仍然存在
return ExchangeBuilder.topicExchange(EXCHANGE_TOPICS_INFORM).durable(true).build();
}
//声明队列
@Bean(QUEUE_INFORM_EMAIL)
public Queue QUEUE_INFORM_EMAIL(){
Queue queue = new Queue(QUEUE_INFORM_EMAIL);
return queue;
}
//声明队列
@Bean(QUEUE_INFORM_SMS)
public Queue QUEUE_INFORM_SMS(){
Queue queue = new Queue(QUEUE_INFORM_SMS);
return queue;
}
//绑定队列到交换机
@Bean
public Binding BINDING_QUEUE_INFORM_EMAIL(@Qualifier(QUEUE_INFORM_EMAIL) Queue queue, @Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("inform.#.email.#").noargs();
}
@Bean
public Binding BINDING_QUEUE_INFORM_SMS(@Qualifier(QUEUE_INFORM_SMS) Queue queue, @Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("inform.#.sms.#").noargs();
}
}
消费者
使用@RabbitListener注解监听队列。
//消费者
@Component
public class ReceiveHandler {
//监听email队列
@RabbitListener(queues = {RabbitmqConfig.QUEUE_INFORM_EMAIL})
public void receive_email(String msg, Message message, Channel channel){
System.out.println("消息内容: "+msg);
}
//监听SMS队列
@RabbitListener(queues = {RabbitmqConfig.QUEUE_INFORM_SMS})
public void receive_sms(String msg,Message message,Channel channel){
System.out.println(msg);
}
}
生产者
测试类
//生产者
@SpringBootTest
@RunWith(SpringRunner.class)
public class Producer {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
public void testSendByTopics(){
for (int i=1; i<5; i++){
String message = "第 " +i+ " 条短信";
rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPICS_INFORM,"inform.sms.email",message);
System.out.println("发送: "+message);
}
}
}
学成项目使用到RabbitMQ
配置类 RabbitMQConfig
定义RabbitmqConfig类,配置Exchange、Queue、及绑定交换机。
@Configuration
public class RabbitMQConfig {
//添加选课任务交换机
public static final String EX_LEARNING_ADDCHOOSECOURSE = "ex_learning_addchoosecourse";
//添加选课消息队列
public static final String XC_LEARNING_ADDCHOOSECOURSE = "xc_learning_addchoosecourse";
//完成添加选课消息队列
public static final String XC_LEARNING_FINISHADDCHOOSECOURSE = "xc_learning_finishaddchoosecourse";
//添加选课路由key
public static final String XC_LEARNING_ADDCHOOSECOURSE_KEY = "addchoosecourse";
//完成添加选课路由key
public static final String XC_LEARNING_FINISHADDCHOOSECOURSE_KEY = "finishaddchoosecourse";
/**
* 交换机配置
* @return the exchange
*/
@Bean(EX_LEARNING_ADDCHOOSECOURSE)
public Exchange EX_DECLARE() {
return ExchangeBuilder.directExchange(EX_LEARNING_ADDCHOOSECOURSE).durable(true).build();
}
//声明队列
@Bean("xc_learning_addchoosecourse")
public Queue Queue_xc_learning_addchoosecourse(){
Queue queue = new Queue(XC_LEARNING_ADDCHOOSECOURSE,true,false,true);
return queue;
}
//声明队列
@Bean("xc_learning_finishaddchoosecourse")
public Queue QUEUE_DECLARE() {
Queue queue = new Queue(XC_LEARNING_FINISHADDCHOOSECOURSE,true,false,true);
return queue;
}
/**
* 绑定队列到交换机 .
* @param queue the queue
* @param exchange the exchange
* @return the binding
*/
@Bean
public Binding binding_queue_media_addchoosecourse(@Qualifier("xc_learning_addchoosecourse") Queue queue,@Qualifier(EX_LEARNING_ADDCHOOSECOURSE) Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with(XC_LEARNING_ADDCHOOSECOURSE_KEY).noargs();
}
@Bean
public Binding binding_queue_media_processtask(@Qualifier("xc_learning_finishaddchoosecourse") Queue queue, @Qualifier(EX_LEARNING_ADDCHOOSECOURSE) Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(XC_LEARNING_FINISHADDCHOOSECOURSE_KEY).noargs();
}
}
接收信息
/**
* 接收选课任务
*/
@RabbitListener(queues = {RabbitMQConfig.XC_LEARNING_ADDCHOOSECOURSE})
public void receiveChoosecourseTask(XcTask xcTask, Message message, Channel channel) throws
IOException {
LOGGER.info("receive choose course task,taskId:{}",xcTask.getId());
//接收到 的消息id
String id = xcTask.getId();
//添加选课
try {
String requestBody = xcTask.getRequestBody();
Map map = JSON.parseObject(requestBody, Map.class);
String userId = (String) map.get("userId");
String courseId = (String) map.get("courseId");
String valid = (String) map.get("valid");
Date startTime = null;
Date endTime = null;
SimpleDateFormat dateFormat = new SimpleDateFormat("YYYY‐MM‐dd HH:mm:ss");
if(map.get("startTime")!=null){
startTime =dateFormat.parse((String) map.get("startTime"));
}
if(map.get("endTime")!=null){
endTime =dateFormat.parse((String) map.get("endTime"));
}
//添加选课
ResponseResult addcourse = learningService.addcourse(userId, courseId,
valid,startTime, endTime,xcTask);
//选课成功发送响应消息
if(addcourse.isSuccess()){
//发送响应消息
rabbitTemplate.convertAndSend(RabbitMQConfig.EX_LEARNING_ADDCHOOSECOURSE,
RabbitMQConfig.XC_LEARNING_FINISHADDCHOOSECOURSE_KEY, xcTask );
LOGGER.info("send finish choose course taskId:{}",id);
}
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("send finish choose course taskId:{}", id);
}
}