Rabbitmq基础篇

文章目录

RabbitMq基础篇

1. 初识Mq

1.1同步与异步

正常情况下,我们调用一个服务,调用者发起请求后需要等待 服务提供者执行业务返回结果后,才能继续执行后面的业务。也就是说调用者在调用过程中处于阻塞状态,因此我们成这种调用方式为同步调用 ,也可以叫同步通讯 。但在很多场景下,我们可能需要采用异步通讯的方式,为什么呢?

解读:

  • 同步通讯:就如同打视频电话,双方的交互都是实时的。因此同一时刻你只能跟一个人打视频电话。
  • 异步通讯:就如同发微信聊天,双方的交互不是实时的,你不需要立刻给对方回应。因此你可以多线操作,同时跟多人聊天。

两种方式各有优劣,打电话可以立即得到响应,但是你却不能跟多个人同时通话。发微信可以同时与多个人收发微信,但是往往响应会有延迟。

所以,如果我们的业务需要实时得到服务提供方的响应,则应该选择同步通讯(同步调用)。而如果我们追求更高的效率,并且不需要实时响应,则应该选择异步通讯(异步调用)。

1.2 消息队列的作用

基本上消息队列主要有以下几个作用:异步,解耦,削峰。

异步调用

异步处理指的是将一些不需要立即返回结果的操作,通过消息队列延后处理,从而提高系统的响应速度。

例如:用户注册后需要发送欢迎邮件,这个操作不需要立即完成,可以异步处理。
用户 注册服务 消息队列 邮件服务 提交注册信息 保存用户信息 发送"发送邮件"消息 注册成功响应 异步处理邮件 发送欢迎邮件 用户 注册服务 消息队列 邮件服务

系统解耦

解耦是指系统各个模块之间不直接依赖,通过消息队列进行通信,从而降低模块间的耦合度,提高系统的可维护性和扩展性。

例如:订单服务不直接调用库存服务,而是通过消息队列通知库存服务扣减库存。
发布消息 订阅消息 订阅消息 订阅消息 订单服务 消息队列 库存服务 物流服务 短信服务

流量削峰

削峰是指在高并发场景下,通过消息队列缓冲瞬时大量请求,避免后端服务被压垮,实现流量的平滑处理。

例如:秒杀活动中,大量用户同时下单,消息队列将请求排队,后端服务按处理能力逐步消费。
瞬时高峰 平稳流出 逐步处理 用户大量请求 消息队列 订单服务 数据库

1.3消息队列技术选型

几种常见MQ的对比:

RabbitMQ ActiveMQ RocketMQ Kafka
公司/社区 Rabbit Apache 阿里 Apache
开发语言 Erlang Java Java Scala&Java
协议支持 AMQP,XMPP,SMTP,STOMP OpenWire,STOMP,REST,XMPP,AMQP 自定义协议 自定义协议
可用性 一般
单机吞吐量 一般 非常高
消息延迟 微秒级 毫秒级 毫秒级 毫秒以内
消息可靠性 一般 一般

追求可用性:Kafka、 RocketMQ 、RabbitMQ

追求可靠性:RabbitMQ、RocketMQ

追求吞吐能力:RocketMQ、Kafka

追求消息低延迟:RabbitMQ、Kafka

据统计,目前国内消息队列使用最多的还是RabbitMQ,再加上其各方面都比较均衡,稳定性也好,因此接下来我们将详细介绍一下RabbitMq。

2. 安装RabbitMq

下面介绍一下在Ubuntu24.x系统上安装RabbitMq的步骤

2.1系统准备

bash 复制代码
sudo apt update && sudo apt -y upgrade
sudo apt install -y curl gnupg apt-transport-https lsb-release

2.2 安装 Erlang & RabbitMQ

bash 复制代码
sudo apt update
# 安装 Erlang/OTP 26 全量包
sudo apt install -y erlang-base \
  erlang-asn1 erlang-crypto erlang-eldap erlang-ftp erlang-inets \
  erlang-mnesia erlang-os-mon erlang-parsetools erlang-public-key \
  erlang-runtime-tools erlang-snmp erlang-ssl erlang-syntax-tools \
  erlang-tftp erlang-tools erlang-xmerl

# 安装 RabbitMQ Server
sudo apt install -y rabbitmq-server

安装完成后服务已自动启动并设为开机自启:

bash 复制代码
sudo systemctl status rabbitmq-server

2.3 启用 Web 管理插件

bash 复制代码
sudo rabbitmq-plugins enable rabbitmq_management

2.4创建管理员用户

bash 复制代码
# 建议删除默认 guest 用户(仅 localhost 可访问)
sudo rabbitmqctl delete_user guest

# 创建新管理员
sudo rabbitmqctl add_user admin YOUR_PASSWORD
sudo rabbitmqctl set_user_tags admin administrator
sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

2.5开放防火墙端口

bash 复制代码
# 如启用了 ufw
sudo ufw allow 5672/tcp  # AMQP
sudo ufw allow 15672/tcp # Web UI
sudo ufw reload

云主机请同步在 安全组 放行上述端口。

2.6访问管理界面

浏览器打开:

复制代码
http://<服务器IP>:15672

使用刚才创建的 admin / YOUR_PASSWORD 登录。

3. 快速入门

注意:一下操作均在SpringBoot3.5x版本使用

3.1依赖配置

引入RabbitMq的依赖:

xml 复制代码
<!--引入RabbitMQ : AMQP(高级消息队列协议) Advanced-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置:

yml 复制代码
# 在生产者或者消费者引入MQ服务端信息
spring:
  rabbitmq:
    host: <Your Host>
    port: 5672 
    virtual-host: "/" # 虚拟主机
    username: <Your User Name>
    password: <Your Password>

3.2 消息发送与接受

1)配置RabbitConfig配置类。用于初始化队列,交换机等

java 复制代码
@Configuration
public class RabbitMQConfig {

    // 声明队列
    @Bean
    public Queue helloQueue1() {
        return new Queue("hello.queue1", true); // durable=true 持久化队列
    }

    @Bean
    public Queue chengfuNewsQueue() {
        return new Queue("chengfu.news", true);
    }

    // 声明交换机
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange("direct.exchange");
    }

    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange("topic.exchange");
    }

    // 声明绑定关系
    @Bean
    public Binding bindingHelloQueue(Queue helloQueue1, DirectExchange directExchange) {
        return BindingBuilder.bind(helloQueue1).to(directExchange).with("hello.queue1");
    }

    @Bean
    public Binding bindingChengfuNews(Queue chengfuNewsQueue, TopicExchange topicExchange) {
        return BindingBuilder.bind(chengfuNewsQueue).to(topicExchange).with("chengfu.news.#");
    }
}

2)编写消息发送与接受方法

java 复制代码
@Slf4j
@RestController
@RequestMapping("/rabbit")
public class Controller {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @PostMapping("/send")
    public void send() {
        rabbitTemplate.convertAndSend("chengfu.news", "hello rabbitmq");
        log.info("发送消息成功");
    }

    @RabbitListener(queues = {"chengfu.news"})
    public void listen(String message) {
        System.out.println("接收到消息:" + message);
    }
}

3)进行测试

bash 复制代码
###
POST http://localhost:8080/rabbit/send

日志:

复制代码
2025-10-29T15:47:43.645+08:00  INFO 16948 --- [rabbitmq-demo] [nio-8080-exec-6] com.chengfu.rabbitmqdemo.Controller      : 发送消息成功
接收到消息:hello rabbitmq

4.业务改造

案例需求:改造余额支付功能,将支付成功后基于OpenFeign的交易服务的更新订单状态接口的同步调用,改为基于RabbitMQ的异步通知。

如图:

说明,我们只关注交易服务,步骤如下:

  • 定义topic类型交换机,命名为pay.topic
  • 定义消息队列,命名为mark.order.pay.queue
  • mark.order.pay.queuepay.topic绑定,BindingKeypay.success
  • 支付成功时不再调用交易服务更新订单状态的接口,而是发送一条消息到pay.topic,发送消息的RoutingKeypay.success,消息内容是订单id
  • 交易服务监听mark.order.pay.queue队列,接收到消息后更新订单状态为已支付

4.1.配置MQ

不管是生产者还是消费者,都需要配置MQ的基本信息。分为两步:

1)添加依赖:

xml 复制代码
  <!--消息发送-->
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-amqp</artifactId>
  </dependency>

2)配置MQ地址:

yaml 复制代码
spring:
  rabbitmq:
    host: 192.168.150.101 # 你的虚拟机IP
    port: 5672 # 端口
    virtual-host: /hmall # 虚拟主机
    username: hmall # 用户名
    password: 123 # 密码

4.2.接收消息

在trade-service服务中定义一个消息监听类

其代码如下:

java 复制代码
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.core.ExchangeTypes;
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;

@Component
@RequiredArgsConstructor
public class PayStatusListener {

    private final IOrderService orderService;

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "mark.order.pay.queue", durable = "true"),
            exchange = @Exchange(name = "pay.topic", type = ExchangeTypes.TOPIC),
            key = "pay.success"
    ))
    public void listenPaySuccess(Long orderId){
        orderService.markOrderPaySuccess(orderId);
    }
}

4.3.发送消息

修改pay-service服务下的com.hmall.pay.service.impl.PayOrderServiceImpl类中的tryPayOrderByBalance方法:

java 复制代码
private final RabbitTemplate rabbitTemplate;

@Override
@Transactional
public void tryPayOrderByBalance(PayOrderDTO payOrderDTO) {
    // 1.查询支付单
    PayOrder po = getById(payOrderDTO.getId());
    // 2.判断状态
    if(!PayStatus.WAIT_BUYER_PAY.equalsValue(po.getStatus())){
        // 订单不是未支付,状态异常
        throw new BizIllegalException("交易已支付或关闭!");
    }
    // 3.尝试扣减余额
    userClient.deductMoney(payOrderDTO.getPw(), po.getAmount());
    // 4.修改支付单状态
    boolean success = markPayOrderSuccess(payOrderDTO.getId(), LocalDateTime.now());
    if (!success) {
        throw new BizIllegalException("交易已支付或关闭!");
    }
    // 5.修改订单状态
    // tradeClient.markOrderPaySuccess(po.getBizOrderNo());
    try {
        rabbitTemplate.convertAndSend("pay.topic", "pay.success", po.getBizOrderNo());
    } catch (Exception e) {
        log.error("支付成功的消息发送失败,支付单id:{}, 交易单id:{}", po.getId(), po.getBizOrderNo(), e);
    }
}


BizIllegalException("交易已支付或关闭!");
    }
    // 5.修改订单状态
    // tradeClient.markOrderPaySuccess(po.getBizOrderNo());
    try {
        rabbitTemplate.convertAndSend("pay.topic", "pay.success", po.getBizOrderNo());
    } catch (Exception e) {
        log.error("支付成功的消息发送失败,支付单id:{}, 交易单id:{}", po.getId(), po.getBizOrderNo(), e);
    }
}

技术不要为了用而用,适合场景才重要。

相关推荐
君陌社区·网络安全防护中心2 分钟前
基于Mininet模拟SDN环境
网络
Porco.w8 分钟前
C#与三菱PLC FX5U通信
网络·c#
枷锁—sha16 分钟前
Burp Suite 抓包全流程与 Xray 联动自动挖洞指南
网络·安全·网络安全
云飞云共享云桌面19 分钟前
高性能图形工作站的资源如何共享给10个SolidWorks研发设计用
linux·运维·服务器·前端·网络·数据库·人工智能
袁煦丞 cpolar内网穿透实验室25 分钟前
远程调试内网 Kafka 不再求运维!cpolar 内网穿透实验室第 791 个成功挑战
运维·分布式·kafka·远程工作·内网穿透·cpolar
爱学习的程序媛32 分钟前
PSTN(公共交换电话网)的起源与发展
网络·信息与通信
人间打气筒(Ada)36 分钟前
GlusterFS实现KVM高可用及热迁移
分布式·虚拟化·kvm·高可用·glusterfs·热迁移
xu_yule36 分钟前
Redis存储(15)Redis的应用_分布式锁_Lua脚本/Redlock算法
数据库·redis·分布式
roman_日积跬步-终至千里1 小时前
【Java并发】Java 线程池实战:警惕使用CompletableFuture.supplyAsync
java·开发语言·网络
2的n次方_2 小时前
Runtime 内存管理深化:推理批处理下的内存复用与生命周期精细控制
c语言·网络·架构