B站视频相关-4-mq

我爱海鲸 2025-11-28 21:07:31 暂无标签

简介面试、mq

1、15年Java老司机,用架构思维视角拆解0到1实现一个消息队列_哔哩哔哩_bilibili

# 消息队列架构设计实战:从双十一高并发场景谈起

## 引言

你们有没有想过,像双十一这种**高并发场景**,系统是怎么去扛住压力的呢?其实背后离不开**消息中间件**。它在整个架构中起到了**流量削峰和填谷**的作用。

今天我们就从架构视角来聊一聊,如果让你来实现一个小型的消息队列,你会考虑到哪些关键问题。

## 一、核心需求分析

在设计整个组件之前,我们需要去搞清楚消息队列的核心需求是什么。

### 1.1 基本要求

- **生产者和消费者**:支持消息的生产和消费
- **高吞吐、低延迟**:能够处理高并发场景下的海量消息
- **高可用**:系统需要具备容错能力
- **高性能**:保证消息处理的效率
- **可扩展**:支持水平扩展,应对业务增长

## 二、消息存储设计

### 2.1 持久化策略

**问题:消息怎么存?**

消息得持久化,对不对?不然宕机时消息会丢失。

**技术方案:**

1. **磁盘存储 + WAL(Write-Ahead Log)**
   - 如果直接用磁盘来存,IO又是个瓶颈
   - 可以考虑用**WAL(预写日志)**去保证写入效率,像Kafka那样
   - WAL采用顺序写入,大幅提升IO性能

2. **内存队列**
   - 内存队列很快,但是有风险的话会丢失数据
   - 适合对可靠性要求不高的场景

3. **混合存储架构**
   - 在架构上,我们可以用**内存 + 磁盘**来混合存储
   - 结合业务场景来做权衡:
     - **高频的消息可以用内存**:追求极致性能
     - **低频可以落盘**:保证数据可靠性

## 三、消息消费模式

### 3.1 推模式 vs 拉模式

**问题:消息怎么去消费?是推还是拉呢?**

#### 推模式(Push)

- **优点**:实时性高,消息能够及时推送给消费者
- **缺点**:如果消费慢了的话,队列就得扛压,可能导致消息积压

#### 拉模式(Pull)

- **代表**:Kafka采用拉模式
- **优点**:
  - 消费者主动去获取消息,队列的压力小了
  - 消费者可以根据自身处理能力控制拉取速度
- **缺点**:延迟会比较高,需要轮询

### 3.2 混合模式(推荐)

**我的经验是混合模式:**

- **重要的消息**:我们用推,保证实时性
- **普通消息**:我们可以用拉,兼顾实时性和可靠性

**核心思想:**

> 在技术上面说,技术本身没有银弹,我们的技术方案是由业务场景来决定的。

## 四、可靠性保证

### 4.1 ACK确认机制

**问题:可靠性怎么保证呢?**

- **消息不能丢**:需要有持久化机制
- **消息不能重复**:需要有幂等性设计
- **ACK机制**:消费者确认消费才能去删除消息

### 4.2 分布式场景下的容错

**问题:分布式场景下节点故障怎么办呢?**

1. **主从复制**
   - 需要有主从复制机制
   - 像RocketMQ的多节点同步
   - 保证数据的高可用

2. **幂等性消费**
   - 还要考虑到**幂等性消费**
   - 防止重复消费导致的数据问题

3. **CAP理论权衡**
   - 在架构上,CAP理论得权衡
   - 优先一致性还是可用性,得看业务场景
   - 不同业务场景需要不同的取舍

## 五、扩展性设计

### 5.1 分区机制

**问题:消息量暴涨,单机顶不住怎么办呢?**

- **支持分区**:像Kafka的Partition
- **水平扩展**:通过分区实现消息的水平扩展
- 单机性能不足时,可以通过增加分区和节点来提升整体吞吐量

### 5.2 消费组和负载均衡

**问题:消费者多了怎么分组呢?**

- **消费组(Consumer Group)**:支持消费组机制
- **负载均衡**:消费组内部实现负载均衡
- 同一个消费组内的消费者可以并行消费不同分区的消息

### 5.3 架构层面的扩展性

**在架构数据上来说:**

- **扩展性**:得从数据分片、节点动态加入来设计
- **动态扩容**:支持节点的动态加入和移除
- **设计好**:不然的话上线就炸

## 六、消息队列的核心价值

消息队列不只是一个技术实现,它得解决很多业务痛点:

- **解耦**:生产者和消费者解耦,降低系统耦合度
- **削峰**:在流量高峰期削峰,保护下游系统
- **异步**:异步处理,提升系统响应速度
- **填谷**:在流量低谷期填谷,充分利用系统资源

**设计的时候还要考虑:**

- **吞吐量**:系统能处理多少消息
- **延迟**:消息处理的延迟是多少
- **可靠性**:消息会不会丢失
- **一致性**:分布式场景下的一致性保证

## 七、架构设计原则

### 7.1 保守设计

让架构师们保守设计,到处都是权衡。

**核心原则:**

1. **没有银弹**:技术方案由业务场景决定
2. **权衡取舍**:在性能、可靠性、一致性之间做权衡
3. **渐进式优化**:先保证核心功能,再逐步优化
4. **可观测性**:设计时就要考虑监控和运维

### 7.2 设计检查清单

在设计消息队列时,需要回答以下问题:

- [ ] 消息如何存储?持久化策略是什么?
- [ ] 采用推模式还是拉模式?还是混合模式?
- [ ] 如何保证消息不丢失?
- [ ] 如何防止重复消费?
- [ ] 分布式场景下如何保证高可用?
- [ ] 如何支持水平扩展?
- [ ] 如何实现负载均衡?
- [ ] 如何监控和运维?

## 八、总结

设计一个消息队列,需要从以下几个维度考虑:

1. **存储设计**:内存 vs 磁盘 vs 混合存储
2. **消费模式**:推 vs 拉 vs 混合模式
3. **可靠性**:ACK机制、主从复制、幂等性
4. **扩展性**:分区、消费组、负载均衡
5. **业务价值**:解耦、削峰、异步、填谷

**记住:技术本身没有银弹,技术方案由业务场景决定。** 在设计时,要结合具体的业务场景,在性能、可靠性、一致性之间做出合理的权衡。

---

2、40万年薪面试之MQ的消息重发问题是怎么处理的_哔哩哔哩_bilibili

# MQ消息重放问题处理方案:幂等性设计实战

## 引言

在消息队列的使用过程中,**消息重放问题**是一个常见且重要的问题。如果处理不当,可能会导致业务数据重复、资源浪费等严重问题。

在分析这个问题之前,我们需要先了解一下MQ消息生产和消费的原理。

## 一、MQ消息生产和消费原理

### 1.1 消息生产流程

1. **生产者发送消息**:生产者向MQ服务端发送消息
2. **服务端持久化**:MQ服务端收到消息后,会把消息落地到本地**磁盘**
3. **ACK确认**:服务端会回复一个**ACK确认消息**给生产者,告诉生产者消息已经成功**接收**到了

### 1.2 消息消费流程

1. **消费者拉取消息**:消费者从服务端中消费消息
2. **消费处理**:消费者成功消费消息后,会发送一个**ACK确认消息**给服务端
3. **消息标记**:服务端收到ACK后,会把这个消息标注为**已消费**或者删除掉

### 1.3 消息可靠性机制

在这个**架构**下,MQ为了保证消息必须要**触达**,就使用了以下机制:

- **消息超时**:消息如果长时间未消费,会触发超时机制
- **重传机制**:消息未收到ACK确认时,会重新发送
- **确认机制**:通过ACK确认保证消息的可靠传输

## 二、消息重复发送的原因

### 2.1 网络不稳定导致的问题

如果有消息可能会被重复发送,假如在网络不稳定的情况下,那么:

- **生产者端**:生产者和消费者便不能收到服务端返回的ACK确认消息
- **生产者重试**:生产者会认为消息没有发送成功,会重新发送消息
- **服务端重传**:服务端会认为消息没有被成功消费,会重新投递消息

**结果**:都会导致重复发送消息的情况产生。

### 2.2 业务场景示例

如果有消息的重复发送,容易导致一些严重的问题发生,比如:

**场景:订单优惠券发放**

- 在业务系统中,我们需要给下单成功的用户发送一张优惠券
- 如果网络出现问题,就会导致:
  - **订单服务**多次发送消息给服务端
  - **优惠券服务**收到重复发券的消息
- **后果**:用户可能收到多张优惠券,造成业务损失

## 三、解决方案:幂等性设计

### 3.1 核心思路

想要避免这种问题,我们需要针对每一条消息去申请一个**唯一的ID**,并且保存在Redis里面或者其他**存储**服务器里面。

### 3.2 实现步骤

#### 步骤1:消息生产时携带唯一ID

当**订单服务**发送消息到服务端的时候,就需要将这个唯一ID带过去。

```java
// 伪代码示例
String messageId = UUID.randomUUID().toString();
Message message = new Message();
message.setId(messageId);
message.setContent(orderInfo);
producer.send(message);
```

#### 步骤2:消息消费时检查唯一ID

**优惠券服务**需要消费这条消息的时候,先判断Redis中是否存在**唯一ID**:

- **如果不存在**:
  - 则正常消费
  - 并把唯一的ID存到Redis里面
  
- **如果存在**:
  - 便表示此消息已经被消费了
  - 不需要重复**处理**

#### 步骤3:使用Redis SETNX指令

这里我们可以使用Redis的**SETNX**指令去完成,也就是不需要去做读取和写入的**非原子**操作。

**SETNX的优势:**

- **原子性操作**:SETNX是原子性的,可以保证并发安全
- **避免竞态条件**:不需要先读取再写入,避免了非原子操作的问题
- **简单高效**:一条指令完成检查和设置

**实现示例:**

```java
// 伪代码示例
public boolean consumeMessage(String messageId, Message message) {
    // 使用SETNX,如果key不存在则设置,返回true;如果存在则返回false
    boolean isNew = redis.setnx("msg:" + messageId, "1");
    
    if (isNew) {
        // 消息未被消费过,正常处理
        processMessage(message);
        // 设置过期时间,避免Redis内存无限增长
        redis.expire("msg:" + messageId, 24 * 3600); // 24小时过期
        return true;
    } else {
        // 消息已被消费过,跳过处理
        log.warn("Duplicate message detected: " + messageId);
        return false;
    }
}
```

### 3.3 完整流程图

```
生产者发送消息(携带唯一ID)
    ↓
服务端持久化消息
    ↓
消费者拉取消息
    ↓
检查Redis中是否存在唯一ID
    ↓
    ├─ 不存在 → 处理消息 → 存储唯一ID到Redis
    └─ 存在 → 跳过处理(幂等性保证)
```

## 四、方案权衡

### 4.1 适用场景

在项目中,大部分的消息**重复**问题都可以通过这种方式来解决。

**适合使用的场景:**

- **金融支付**:支付消息不能重复处理
- **订单处理**:订单消息不能重复处理
- **优惠券发放**:优惠券不能重复发放
- **库存扣减**:库存不能重复扣减

### 4.2 方案代价

但是这样也会提高系统的复杂性:

- **存储成本**:需要额外的存储(Redis)来保存消息ID
- **性能开销**:每次消费都需要访问Redis
- **维护成本**:需要管理Redis的过期策略,避免内存无限增长

### 4.3 不需要处理的场景

所以在一些非必要的场景下,我们可以不对其进行处理。

**不需要处理的场景:**

- **通知提醒**:给用户发送提醒通知,发送一次或者多次影响不大
- **日志记录**:日志消息重复记录影响不大
- **统计上报**:统计数据可以容忍少量重复

**判断标准:**

- 重复处理是否会导致业务问题?
- 重复处理的代价是否可接受?
- 实现幂等性的成本是否值得?

### 4.4 权衡原则

所以我们在选择方案的时候,需要**权衡**去**付出的**代价,判断要不要采用此方法。

**权衡考虑:**

1. **业务影响**:重复处理对业务的影响程度
2. **实现成本**:实现幂等性的开发成本和维护成本
3. **性能影响**:对系统性能的影响
4. **存储成本**:额外的存储资源消耗

## 五、其他幂等性方案

除了使用唯一ID + Redis的方案,还有其他实现幂等性的方式:

### 5.1 数据库唯一约束

- 在数据库中为业务字段添加唯一约束
- 利用数据库的唯一性保证幂等性
- 适合有数据库存储的场景

### 5.2 业务状态机

- 使用状态机管理业务状态
- 只有特定状态才能执行操作
- 适合有明确状态流转的业务

### 5.3 分布式锁

- 使用分布式锁保证同一消息只被处理一次
- 适合需要强一致性的场景
- 但可能影响性能

## 六、最佳实践

### 6.1 消息ID设计

- **全局唯一**:确保消息ID在全局范围内唯一
- **可追溯**:消息ID应该包含时间戳、业务标识等信息
- **简洁高效**:避免过长的ID影响性能

### 6.2 Redis过期策略

- **设置过期时间**:避免Redis内存无限增长
- **过期时间选择**:根据业务特点选择合理的过期时间
- **定期清理**:对于重要业务,可以考虑定期清理机制

### 6.3 监控和告警

- **监控重复率**:监控消息重复消费的比例
- **告警机制**:当重复率异常时及时告警
- **日志记录**:记录重复消息的详细信息,便于排查

## 七、总结

处理MQ消息重放问题的核心是**实现幂等性**:

1. **理解原理**:了解MQ消息生产和消费的流程,理解重复消息产生的原因
2. **设计方案**:使用唯一ID + Redis(SETNX)实现幂等性
3. **权衡取舍**:根据业务场景判断是否需要实现幂等性
4. **持续优化**:监控重复率,优化过期策略,提升系统性能

**记住**:技术方案的选择需要权衡业务影响和实现成本,不是所有场景都需要实现幂等性。

---

*本文从MQ消息生产和消费原理出发,深入分析了消息重放问题的原因和解决方案,旨在帮助开发者系统性地理解和解决消息重复消费的问题。*

3、RocketMQ Broker中的消息被消费后会立即删除吗【Java面试】_哔哩哔哩_bilibili

# RocketMQ消息删除机制详解:消费后是否立即删除?

## 引言

在**大厂**面试中,如果你说你自己在项目中使用过MQ等技术,面试官大概率就会这样引导经典的面试题:

> **RocketMQ中的消息被消费后会不会立即被删除?**

这是一个非常经典的面试题,考察的是对RocketMQ消息生命周期和存储机制的理解。

## 一、消息可以被删除的条件

对这个问题,首先我们得知道这个消息什么时候能够去删除。

### 1.1 消费者已消费且Broker完成确认

**第一,消费者已经消费了这个消息,并且Broker也完成了确认,标记为已消费的消息。**

- 消费者成功消费消息后,会发送ACK确认给Broker
- Broker收到ACK后,会将消息标记为已消费状态
- 只有标记为已消费的消息才可能被删除

### 1.2 非事务消息

**第二,非事务消息。因为事务消息在事务完成之前是不能进行删除的。**

- **事务消息**:在事务提交或回滚之前,消息不能被删除
- **普通消息**:消费确认后就可以被删除
- 这是为了保证事务的完整性

### 1.3 非持久化消息(可选)

**第三,非持久化消息。对于持久化的消息,RocketMQ提供了完善的文件清理机制来进行数据清理到一定大小。**

- **持久化消息**:消息会被持久化到磁盘,有完善的文件清理机制
- **非持久化消息**:只存在内存中,可以立即删除
- 持久化消息的清理需要遵循文件清理策略

## 二、立即删除 vs 延迟删除

### 2.1 立即删除策略

**如果需要立即删除,对内存是比较友好的,但是没有办法保证消息的可靠性。**

**优点:**
- **内存友好**:消息消费后立即释放内存空间
- **存储效率高**:不会占用额外的存储空间
- **性能好**:减少磁盘IO操作

**缺点:**
- **可靠性低**:如果消费者处理失败,消息已经删除,无法重新消费
- **容错性差**:无法应对消费者崩溃等异常情况

### 2.2 延迟删除策略

**如果不立即删除,能提高消息的可靠性,但是对内存又不友好。**

**优点:**
- **可靠性高**:消息保留一段时间,可以重新消费
- **容错性强**:消费者崩溃后可以重新拉取消息
- **可追溯性**:可以查看历史消息,便于排查问题

**缺点:**
- **内存占用**:消息会占用更多的内存和磁盘空间
- **存储成本**:需要更多的存储资源
- **性能影响**:可能影响消息写入性能

### 2.3 权衡原则

**所以它其实也是一个根据不同场景的需求来做一个权衡的问题。**

RocketMQ官方一般会把决定权交给用户,让用户根据业务场景来选择:

- **对可靠性要求高的场景**:选择延迟删除
- **对内存要求高的场景**:选择立即删除
- **一般业务场景**:选择适中的延迟时间

## 三、配置参数说明

### 3.1 延迟删除配置

**用户可以通过这样一个参数配置来决定延迟删除的一个时间。**

**配置参数:`fileReservedTime`**

- **配置为0**:代表采用的是立即删除策略
- **配置大于0**:代表延迟删除的时间(单位:小时)

### 3.2 配置示例

```properties
# broker.conf 配置文件

# 文件保留时间,单位:小时
# 0 表示立即删除
# 48 表示保留48小时后删除
fileReservedTime=48

# 或者通过环境变量配置
# export ROCKETMQ_HOME=/path/to/rocketmq
```

### 3.3 其他相关配置

RocketMQ还提供了其他相关的配置参数:

```properties
# 删除文件的时间点(默认凌晨4点)
deleteWhen=04

# 磁盘使用率阈值(超过此值会触发文件删除)
diskMaxUsedSpaceRatio=75

# 单个文件的最大大小(超过此值会触发文件删除)
maxMessageSize=4194304
```

## 四、消息删除的完整流程

### 4.1 消息生命周期

```
消息发送
    ↓
消息持久化到磁盘
    ↓
消费者拉取消息
    ↓
消费者处理消息
    ↓
消费者发送ACK确认
    ↓
Broker标记消息为已消费
    ↓
根据配置决定删除时机
    ├─ fileReservedTime=0 → 立即删除
    └─ fileReservedTime>0 → 延迟删除
```

### 4.2 文件清理机制

对于持久化消息,RocketMQ的文件清理机制:

1. **时间触发**:根据`fileReservedTime`配置,定期清理过期文件
2. **空间触发**:当磁盘使用率超过阈值时,清理最旧的文件
3. **大小触发**:当单个文件超过最大大小时,触发文件滚动和清理

## 五、不同场景的选择建议

### 5.1 立即删除场景(fileReservedTime=0)

**适用场景:**
- 对消息可靠性要求不高的场景
- 日志类消息、统计类消息
- 内存资源紧张的场景
- 消息量巨大,需要快速释放存储空间

**示例:**
- 用户行为日志
- 系统监控指标
- 非关键业务通知

### 5.2 延迟删除场景(fileReservedTime>0)

**适用场景:**
- 对消息可靠性要求高的场景
- 金融支付、订单处理等关键业务
- 需要消息重试的场景
- 需要消息追溯的场景

**推荐配置:**
- **一般业务**:24-48小时
- **关键业务**:72-168小时(3-7天)
- **特殊业务**:根据业务需求自定义

**示例:**
- 订单支付消息
- 库存扣减消息
- 优惠券发放消息

## 六、最佳实践

### 6.1 配置建议

1. **根据业务重要性选择**:
   - 关键业务使用延迟删除
   - 非关键业务可以使用立即删除

2. **平衡存储和可靠性**:
   - 不要设置过长的保留时间,避免存储压力
   - 也不要设置过短,影响消息可靠性

3. **监控和调整**:
   - 监控消息删除情况
   - 根据实际业务情况调整配置

### 6.2 注意事项

1. **事务消息**:事务消息在事务完成前不能删除,需要特别注意

2. **消息重试**:如果使用延迟删除,需要确保重试机制正常工作

3. **存储监控**:定期监控磁盘使用率,避免存储空间不足

4. **性能影响**:延迟删除会增加存储压力,需要评估对性能的影响

## 七、面试回答要点

对于这个面试题,可以从以下几个方面回答:

1. **消息删除的条件**:
   - 消费者已消费且Broker完成确认
   - 非事务消息
   - 根据持久化策略

2. **删除策略的权衡**:
   - 立即删除:内存友好但可靠性低
   - 延迟删除:可靠性高但内存占用大

3. **配置方式**:
   - 通过`fileReservedTime`配置
   - 0表示立即删除,大于0表示延迟删除时间

4. **场景选择**:
   - 根据业务需求选择合适策略
   - 关键业务使用延迟删除,非关键业务可以使用立即删除

## 八、总结

对于RocketMQ消息删除机制,我们需要理解:

1. **删除条件**:消息必须被消费确认,且非事务消息,根据持久化策略决定
2. **删除策略**:立即删除和延迟删除各有优缺点,需要权衡
3. **配置方式**:通过`fileReservedTime`参数控制删除时机
4. **场景选择**:根据业务需求选择合适策略,平衡可靠性和存储成本

**核心思想**:RocketMQ把决定权交给用户,让用户根据业务场景来选择最适合的删除策略。这是一个典型的"没有银弹"的设计,需要根据实际业务需求来权衡。

你好:我的2025

上一篇:finalshell

下一篇:B站视频相关-5-mysql