Rabbitmq实现消息的异步和通信
本文以商品服务和订单服务之间的异步通信为基础进行搭建*首先启动RabbitMQ 服务器*运行 rabbitmq 容器RabbitMQ 官方已经提供了自己的 Docker 容器,先下载 rabbitmq:3-management 镜像来启动 RabbitMQ 容器, 之所以选择这个镜像是因为它拥有一个 web 控制台,可以通过浏览器来访问。docker pull rabbitmq:3-ma...
本文以商品服务和订单服务之间的异步通信为基础进行搭建
*
首先启动RabbitMQ 服务器*
运行 rabbitmq 容器
RabbitMQ 官方已经提供了自己的 Docker 容器,先下载 rabbitmq:3-management 镜像来启动 RabbitMQ 容器, 之所以选择这个镜像是因为它拥有一个 web 控制台,可以通过浏览器来访问。
docker pull rabbitmq:3-management
RabbitMQ 除了控制台,还提供了 HTTP API 方式,可方便应用程序使用。
下面使用如下 Docker 命令启动 RabbitMQ
docker run -d -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin --name rabbitmq rabbitmq:3-management
在启动 RabbitMQ 容器时,它对宿主机暴露了两个端口号
15672: 表示RabbitMQ 控制台端口号,可在浏览器中通过控制台来执行 RabbitMQ 的相关操作
5672 表示 RabbitMQ 监听的TCP 端口号,应用程序可以通过该端口号与 RabbitMQ 建立 TCP 连接,并完成后续的异步消息通信
此外,启动时还有两个环境变量
RABBITMQ_DEFAULT_USER : 设置控制台默认用户名, 默认为 guest
RABBITMQ_DEFAULT_PASS: 设置控制台默认密码,默认为 guest
RabbitMQ 控制台
RabbitMQ 容器启动完毕后,打开浏览器,并在地址栏中输入 http://localhost:15672/ ,并且输入登录的用户名和密码,就可以看到控制台如下所示
在上面管理界面中,包含 6 个功能菜单
Overview: 用于查看 RabbitMQ 基本信息
Connections: 用于查看 RabbitMQ 客户端的连接信息
Channels: 用于查看 RabbitMQ 的通道
Exchanges:用于查看 RabbitMQ 的交换机
Queues: 用于查看 RabbitMQ 的队列
Admin: 用于管理 RabbitMQ 的用户,虚拟主机,策略等数据
Exchange 和 Queue
RabbitMQ 只有 Queue, 没有 Topic,因为可通过 Exchange 与 Queue 的组合来实现 Topic 所具备的功能。RabbitMQ 的消息模型如下图所示
在 Exchange 和 Queue 间有一个 Binding 关系,当消息从 Producer 发送到 Exchange 中时,会根据 Binding 来路由消息的去向。
如果 Binding 各不相同,那么该消息将路由到其中一个 Queue 中,随后将被一个 Consumer 所消费,此时实现了 "点对点"的消息通信模型。
如果 Binding 完全相同,那么该消息就会路由到每个 Queue 中,随后将会被每个 Consumer 消费,这样就实现了 “发布与订阅” 的消息通信模型
因此可将 Binding 理解为 Exchange 到 Queue 的路由规则,这些规则可通过 RabbitMQ 所提供的客户端 API 来控制,也可通过 RabbitMQ 提供的控制台来管理。
RabbitMQ 提供了一个默认的 Exchange(AMQP default),在控制台的 Exchange 菜单中就可以看到它,简单情况下,只需要使用默认的 Exchange 即可,当需要提供发布与订阅功能时才会使用自定义的 Exchange。
代码的开发
1.pom文件引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.在配置文件里配置:
spring
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
3.接收端代码:
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/*
接受mq消息
*/
@Slf4j
@Component
public class MqReceiver {
//1. @RabbitListener(queues = "myQueue")
//2自动创建队列
//@RabbitListener(queuesToDeclare = @Queue("myQueue"))
//3.自动创建,exchange和bindings绑定,目前采用第三种
@RabbitListener(bindings = @QueueBinding(
value = @Queue("myQueue123"),
exchange = @Exchange("myExchange")
))
public void process(String message){
log.info("MqReceiver:{}",message);
}
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange("myOrder"),
key = "computer",
value = @Queue("computerOrder")
))
public void processComputer(String message){
log.info("computer MqReceiver:{}",message);
}
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange("myOrder"),
key = "fruit",
value = @Queue("fruitOrder")
))
public void processFruit(String message){
log.info("fruit MqReceiver:{}",message);
}
}
4.测试一下,模拟发送端
import org.junit.Test;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
/*
发送mq消息
*/
@Component
public class MqSenderTest extends OrderApplicationTests {
@Autowired
private AmqpTemplate amqpTemplate;
@Test
public void send(){
amqpTemplate.convertAndSend("myQueue123","now: "+new Date());
}
@Test
public void sendOrder(){
amqpTemplate.convertAndSend("myOrder","computer","now: "+new Date());
}
}
5.在真实项目中演示(附部分代码):
(1)在消息生产者端product微服务中(关键代码):
@Override
public void decreaseStock(List<DecreaseStockInput> decreaseStockInputList) {
List<ProductInfo> productInfoList=decreaseStockProcess(decreaseStockInputList);
//发送mq消息
List<ProductInfoOutput> productInfoOutputList= productInfoList.stream().map(e->{
ProductInfoOutput productInfoOutput=new ProductInfoOutput();
BeanUtils.copyProperties(e,productInfoOutput);
return productInfoOutput;
}).collect(Collectors.toList());
System.out.println("分界线------------------------------------------------------");
amqpTemplate.convertAndSend("productInfo", JsonUtil.toJson(productInfoOutputList));
}
@Transactional
public List<ProductInfo> decreaseStockProcess(List<DecreaseStockInput> decreaseStockInputList) {
List<ProductInfo> productInfoList=new ArrayList<>();
for(DecreaseStockInput decreaseStockInput:decreaseStockInputList){
Optional<ProductInfo> productInfoOptional= productInfoRepository.findById(decreaseStockInput.getProductId());
//判断商品是否存在
if(!productInfoOptional.isPresent()){
throw new ProductException(ResultEnum.PRODUCT_NOT_EXIT);
}
//库存是否足够
ProductInfo productInfo=productInfoOptional.get();
Integer result=productInfo.getProductStock()-decreaseStockInput.getProductQuantity();
if(result<0){
throw new ProductException(ResultEnum.PRODUCT_STOCK_ERROR);
}
productInfo.setProductStock(result);
productInfoRepository.save(productInfo);
productInfoList.add(productInfo);
}
return productInfoList;
}
(2)在消息消费者端order微服务中(关键代码):
import com.fasterxml.jackson.core.type.TypeReference;
import com.imooc.order.utils.JsonUtil;
import com.imooc.product.common.ProductInfoOutput;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class ProductInfoReceiver {
private static final String PRODUCT_STOCK_TEMPLATE="product_stock_%s";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RabbitListener(queuesToDeclare = @Queue("productInfo"))
public void process(String message){
//message->productInfoOutput
List<ProductInfoOutput> productInfoOutputList=(List<ProductInfoOutput>)JsonUtil.fromJson(message,
new TypeReference<List<ProductInfoOutput>> (){}
);
log.info("从队列【{}】中接受消息:{}","productInfo",productInfoOutputList);
//存储到redis中去
for(ProductInfoOutput productInfoOutput:productInfoOutputList) {
stringRedisTemplate.opsForValue().set(String.format(PRODUCT_STOCK_TEMPLATE, productInfoOutput.getProductId()),
String.valueOf(productInfoOutput.getProductStock()));
}
}
}
大家可以结合自己的项目灵活运用,到这里就要结束了,各位小伙伴,下期见!
更多推荐
所有评论(0)