起因:在实际项目开发过程中,需要使用RabbitMQ来实现消息队列的功能,比如说我发布一个动态之后,需要在30分钟使用默认用户给他点几个赞,之前考虑使用redis的zset对他进行操作,之后决定使用RabbitMQ,专业的事情使用专业的工具来操作。
第一种单体模式,即发送亦接收
1.引入RabbitMQ
org.springframework.boot
spring-boot-starter-amqp
2.yml添加相关配置
virtual-host 如何配置后文讲解,请看我的另一篇博文
rabbitmq:
host: 101.132.111.*(真实ip)
port: 5672
virtual-host: /mall
username: mall
password: mall
3.添加消息队列枚举类配置
@Getter
public enum QueueEnum {
/**
* 点赞 消息通知队列 EachPraise
*/
QUEUE_DYNAMIC_PRAISE("mall.dynamic.praise.direct", "mall.dynamic.praise.cancel", "mall.dynamic.praise.cancel"),
/**
* 点赞 ttl队列
*/
QUEUE_TTL_DYNAMIC_PRAISE("mall.dynamic.praise.direct.ttl", "mall.dynamic.praise.cancel.ttl", "mall.dynamic.praise.cancel.ttl");
/**
* 交换名称
*/
private String exchange;
/**
* 队列名称
*/
private String name;
/**
* 路由键
*/
private String routeKey;
QueueEnum(String exchange, String name, String routeKey) {
this.exchange = exchange;
this.name = name;
this.routeKey = routeKey;
}
}
4.添加消息队列相关配置
package com.ptdot.portal.config;
import com.ptdot.portal.domain.QueueEnum;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 消息队列相关配置
*
* @author macro
* @date 2018/9/14
*/
@Configuration
public class RabbitMqConfig {
// ============================ 点赞
/**
* 订单消息实际消费队列所绑定的交换机
*/
@Bean
DirectExchange dynamicPraiseDirect() {
return (DirectExchange) ExchangeBuilder
.directExchange(QueueEnum.QUEUE_DYNAMIC_PRAISE.getExchange())
.durable(true)
.build();
}
/**
* 订单延迟队列队列所绑定的交换机
*/
@Bean
DirectExchange dynamicPraiseTtlDirect() {
return (DirectExchange) ExchangeBuilder
.directExchange(QueueEnum.QUEUE_TTL_DYNAMIC_PRAISE.getExchange())
.durable(true)
.build();
}
/**
* 订单实际消费队列
*/
@Bean
public Queue dynamicPraiseQueue() {
return new Queue(QueueEnum.QUEUE_DYNAMIC_PRAISE.getName());
}
/**
* 订单延迟队列(死信队列)
*/
@Bean
public Queue dynamicPraiseTtlQueue() {
return QueueBuilder
.durable(QueueEnum.QUEUE_TTL_DYNAMIC_PRAISE.getName())
.withArgument("x-dead-letter-exchange", QueueEnum.QUEUE_DYNAMIC_PRAISE.getExchange())//到期后转发的交换机
.withArgument("x-dead-letter-routing-key", QueueEnum.QUEUE_DYNAMIC_PRAISE.getRouteKey())//到期后转发的路由键
.build();
}
/**
* 将订单队列绑定到交换机
*/
@Bean
Binding dynamicPraiseBinding(DirectExchange dynamicPraiseDirect,Queue dynamicPraiseQueue){
return BindingBuilder
.bind(dynamicPraiseQueue)
.to(dynamicPraiseDirect)
.with(QueueEnum.QUEUE_DYNAMIC_PRAISE.getRouteKey());
}
/**
* 将订单延迟队列绑定到交换机
*/
@Bean
Binding dynamicPraiseTtlBinding(DirectExchange dynamicPraiseTtlDirect,Queue dynamicPraiseTtlQueue){
return BindingBuilder
.bind(dynamicPraiseTtlQueue)
.to(dynamicPraiseTtlDirect)
.with(QueueEnum.QUEUE_TTL_DYNAMIC_PRAISE.getRouteKey());
}
}
5.服务提供者
/**
* @ClassName DynamicTimingSender
* @Description TODO
* @Author liulinfang
* @Date 2020/9/21 15:40
* @Version 1.0
*/
@Component
@Slf4j
public class DynamicPraiseSender {
@Resource
private AmqpTemplate amqpTemplate;
public void sendMessage(Long dynamicId,final long delayTimes){
//给延迟队列发送消息
amqpTemplate.convertAndSend(QueueEnum.QUEUE_TTL_DYNAMIC_PRAISE.getExchange(), QueueEnum.QUEUE_TTL_DYNAMIC_PRAISE.getRouteKey(), dynamicId, message -> {
//给消息设置延迟毫秒值
message.getMessageProperties().setExpiration(String.valueOf(delayTimes));
return message;
});
log.info("send dynamicId:{}",dynamicId);
}
}
6.服务消费者
/**
* @ClassName DynamicTimingReceiver
* @Description TODO
* @Author liulinfang
* @Date 2020/9/21 16:59
* @Version 1.0
*/
@Slf4j
@Component
@RabbitListener(queues = "mall.dynamic.praise.cancel")
public class DynamicPraiseReceiver {
//消息处理器
@RabbitHandler
public void handle(Long dynamicId){
System.out.println("Receiver:"+dynamicId);
log.error("process dynamicId:{}",dynamicId);
}
}
7.单元测试
package com.ptdot.portal.service.impl;
import com.ptdot.portal.component.DynamicTimingSender;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import javax.annotation.Resource;
import java.util.Date;
@RunWith(SpringRunner.class)
@SpringBootTest
//由于是Web项目,Junit需要模拟ServletContext,因此我们需要给我们的测试类加上@WebAppConfiguration。
@WebAppConfiguration
public class RabbitmqdemoApplicationTests {
@Resource
DynamicTimingSender dynamicTimingSender;
@Test
public void contextLoads() {
System.out.println(System.currentTimeMillis());
System.out.println(new Date());
dynamicTimingSender.sendMessage(1L,10 * 1000);
}
}
第二种任务发送接收分离模式
1.1. 创建工程
- 提供消息对象的项目:放消息对象的
import lombok.Data;
import java.io.Serializable;
@Data
public class OrderInfo implements Serializable {
//消息对象需要序列化
private static final long serialVersionUID = 4084996990296644842L;
private String id;
private String order_name;
//消息id是用来生成一个消息的唯一id,通过消息id能找到这个消息的业务信息
private String message_id;
}
provider项目:消息发送方
receive项目:消息接收方
1.2. 发送方的设置
POM依赖
消息体的工程依赖
com.icoding.basic
rmq-basic
1.0-SNAPSHOT
AMQP的依赖
org.springframework.boot
spring-boot-starter-amqp
Yaml的配置
spring:
rabbitmq:
host: 39.99.219.219
username: guest
password: guest
virtual-host: /
connection-timeout: 15000
编写发送类
import com.icoding.basic.po.OrderInfo;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class OrderSender {
@Autowired
RabbitTemplate rabbitTemplate;
public void sendOrder(OrderInfo orderInfo) throws Exception{
/**
* exchange: 交换机名字,是个你自己定义的字符串
* routingkey: 队列关联的key,是个你自己定义的字符串
* object: 要传输的消息对象
* correlationData: 消息的唯一id
*/
CorrelationData correlationData = new CorrelationData();
correlationData.setId(orderInfo.getMessage_id());
rabbitTemplate.convertAndSend("order-exchange","order.update",orderInfo,correlationData);
}
}
这个时候还不能发送消息,因为还没有创建exchange,可以在控制台创建exchange
type是exchage的routingkey的绑定类型
Durability:消息是否持久化
Auto delete:如果设置为yes则当exchange最后一个绑定的队列被删除后,就会自动删除
Internal:如果设置为yes,是RabbitMQ的内部使用,不提供给外部,自己编写erlang语言做扩展时使用
Arguments:扩展AMQP的自定义参数
创建消息队列
在exchange里创建Binding并输入routingkey
- 这里的routingkey就是我们的一个接收规则
这个时候再启动消息的发送MQ就能接收到消息了
import com.icoding.basic.po.OrderInfo;
import com.icoding.provider.provider.OrderSender;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RmqProviderApplicationTests {
@Autowired
OrderSender orderSender;
@Test
void contextLoads() {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setId("10001");
orderInfo.setOrder_name("消息队列RabbitMQ从入门到精通");
orderInfo.setMessage_id("MS99999");
try {
System.out.println("***********开始发送************");
orderSender.sendOrder(orderInfo);
System.out.println("-----------发送完成------------");
}catch (Exception ex){
ex.printStackTrace();
}
}
}
1.3. 接收方的设置
POM依赖
org.springframework.boot
spring-boot-starter-amqp
com.icoding.basic
rmq-basic
1.0-SNAPSHOT
yaml设置
spring:
rabbitmq:
host: 39.99.219.219
username: guest
password: guest
virtual-host: /
connection-timeout: 15000
listener: #消费端配置
simple:
concurrency: 5 #初始化并发数
max-concurrency: 10 #最大并发数据
auto-startup: true #自动开启监听
prefetch: 1 #每个并发连接同一时间最多处理几个消息,限流设置
acknowledge-mode: manual #签收模式,设置为手动
编写接收的实现类
import com.icoding.basic.po.OrderInfo;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class OrderReceiver {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "order-queue",durable = "true",autoDelete = "false"),
exchange = @Exchange(value = "order-exchange",durable = "true",type = "topic"),
key = "order.update"
)
)
@RabbitHandler
public void onOrderMessage(@Payload OrderInfo orderInfo, @Headers Map headers, Channel channel) throws Exception{
System.out.println("************消息接收开始***********");
System.out.println("Order Name: "+orderInfo.getOrder_name());
}
}
完结,之后对细节进行补充
不要以为每天把功能完成了就行了,这种思想是要不得的,互勉~!