一.消息队列
消息队列(Message Queue,简称 MQ)是一种进程间通信(IPC)机制,用于在分布式系统或多线程/多进程程序中传递消息。通过提供 消息传递 和 消息排队 模型,它可以在 分布式环境 下提供 应用解耦、弹性伸缩、冗余存储、流量削峰、异步通信、数据同步 等等功能。常见的消息队列有 Kafka、RabbitMQ、RocketMQ、ActiveMQ 等。
1. 解耦: 模块之间不直接通信,减少耦合度。
2. 异步处理: 可以先把请求丢进队列里,慢慢处理,提高系统响应速度。
3. 削峰填谷: 高并发时排队处理,避免系统被压垮。
4. 失败恢复: 消息未消费前可以保留,实现容错。
二.防止消息丢失
以kafka为例:
在 Kafka 中有一个重要的概念叫偏移量(Offset),它是消息在分区中的编号,用来唯一标识该分区里的一条消息的位置。 Kafka 中的消息是按“主题(Topic) → 分区(Partition)”来组织的,每条消息在分区中都有一个递增的数字编号,这就是偏移量。 消费者读取分区里的消息时,就是从某个 offset 开始往后读,然后手动或自动告诉 Kafka 读到哪里的数据了。
1.消费者丢失数据
在kafka中, offset 是默认提交的,但是如果消费者自动提交了offset,让 Kafka 以为已经消费好了这个消息,但其实才刚准备处理这个消息,客户端就挂了,此时这条消息就丢失了,我们只需要在消费者端改成在处理完业务后手动提交 offset 即可。
但此时我们又会产生重复消费数据的可能,下面说。
2.Kafka 丢失数据
首先,我们要知道,Kafka 为每个分区(Partition)支持多副本(replica)机制:每个分区有一个 Leader 和若干个 Follower。生产者写入数据时,只写入 Leader。Follower 会从 Leader 同步数据(副本)。
如果 Kafka 某个分区的leader宕机,然后重新选举 leader。如果此时其他的 follower 刚好还有些数据没有同步,然后选举某个 follower 成 leader 之后,就会丢失一些数据。
我们要可以设置以下参数:
1.replication.factor : 给 topic 设置该参数,这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。
2.min.insync.replicas : 在 Kafka 服务端设置该参数,这个只必须大于1至少几个副本必须同步成功,才算“写入成功”。
3.acks=all : 在生产者设置改参数,生产者等待“所有同步副本”都写成功,才能认为是写成功了。
4.retries=3 : 在生产者设置改参数,如果一旦写入失败,则重试。
3.生产者丢失数据
开启生产确认机制(acks),设置 acks=all,要求所有副本写入成功才返回,并开启重试机制。但这也可能引写重复的问题,下面说。
三.消息的幂等性
幂等性:相同的操作执行一次或多次,效果都一样。
在整个数据流转过程中,可能会发生以下情况:
1. 生产者层面的幂等性(防止写重复)
Kafka 生产者幂等性问题的本质原因在于:消息可能被“重复发送”到 Kafka Broker,而 Kafka 默认是允许重复写入的,如果没做“去重”或“确认机制”,在重试的时候就可能写了两条一模一样的消息。
Kafka 从 0.11 开始支持幂等生产者机制。
enable.idempotence=true
acks=all
retries > 0
Kafka 会为每个 producer 分配一个 producerId 和 sequence number;若发送同一条消息多次,Kafka 自动识别并去重;同一条消息在 broker 上 只会被写入一次。
注意:
1. 幂等性不是“去重”,而是防止“写重复”;
2. 幂等性保证的是同一个 producer 的“同一条消息”不会被 Kafka 多次写入;
3. 多个 producer(或重启时 ID 变了)就无效了。
2. 消费者重复消费
上面我们说,在消费端我们手动提交 offset,这样的话,在处理完数据之后,提交数据之前突然程序崩溃,那么我们实际已经消费了这条消息,但是kafka没有收到offset,所以在重启消费者后,会重复发生该数据,就会导致重复消费了。
解决方案:
1. 使用唯一消息 ID + 幂等判断
每条消息携带唯一业务 ID(比如订单号、消息流水号),消费时,先查数据库是否已处理过该 ID, 没处理就执行业务 + 记录已处理状态,如果已处理,直接跳过。如支付、扣款、积分发放等业务。
2. 采用唯一约束
为数据库加上唯一约束字段。或者增加一张消费记录表,表字段加上唯一约束条件(UNIQUE),消费完之后就往表里插入一条数据,防止重复插入。
3.消费写入使用数据库的 UPSERT 或 ON DUPLICATE KEY
例如 mysql 中使用 ON DUPLICATE KEY UPDATE
INSERT INTO user_bonus (user_id, bonus) VALUES ('u123', 100) ON DUPLICATE KEY UPDATE bonus = bonus;
如果发现有唯一约束冲突,则进行更新操作。
4.使用 redis 做幂等锁
幂等锁是一种防止同一请求/消息被重复处理的机制,是构建高并发下可靠幂等机制的关键手段之一,尤其在分布式场景下。
4.1 SETNX 实现幂等锁
SETNX key value
如果 key 不存在,就设置它,返回 true;如果已存在,就啥也不做,返回 false。
4.2 使用 Lua 脚本
如果需要执行多条redis指令,则可能发生线程安全的问题,这时可以采用Lua脚本。
Redis 会把整个 Lua 脚本当作一条命令执行,执行过程不会被中断、不会被其他命令插入,具备原子性。
四.Kafka保证消息有序性
Kafka 在每个分区内部保证消息是按照写入顺序被追加到日志文件中的。消费者按 offset 顺序读取,自然就能获取有序的消息。但是跨分区的情况下是不能保证有序的。
1. 生产者端:每条消息会带一个 key,比如订单号、用户 ID、会话 ID。Kafka 根据 key 的哈希值分配到特定 partition。每条具有相同的 key 的数据,会一直被发到同一个 partition,因此保持顺序。但要注意以单线程的方式写入。
2. Broker 端:消息会被顺序地写入相应的 partition, 每条消息都有一个递增的 offset。
3. 消费者端:按 offset 顺序读取消费。每个 partition 通常由一个消费者来处理,以保证顺序性。
跨分区的情况下,只能在业务层自己做排序了,如利用时间戳放在有序队列中,或者使用外部排序软件,如 Flink、Spark 等。
五.消息传输协议和队列
WebSocket、MQTT、Kafka、RabbitMQ、RocketMQ、ActiveMQ 虽然它们都与消息传递、实时通信或异步处理有关,但实际上它们处在不同的技术层面和使用场景。
1.WebSocket:一种通信协议,用于客户端和服务端之间的实时通信(比如网页聊天室)。
2.MQTT(Message Queuing Telemetry Transport): 是一种轻量级、发布/订阅(Pub/Sub)模式的消息协议,专为低带宽、不稳定网络环境设计,广泛用于物联网(IoT)、移动设备、智能家居等场景。
3.Kafka/RabbitMQ/RocketMQ/ActiveMQ:是消息中间件系统,用于分布式系统之间异步、可靠、高吞吐量的消息传递。
特性/工具 | WebSocket | MQTT | Kafka | RabbitMQ | RocketMQ | ActiveMQ |
---|---|---|---|---|---|---|
本质 | 通信协议 | 协议(轻量消息传输) | 分布式消息队列系统(日志系统) | 消息队列系统(面向可靠性) | 分布式消息队列系统(阿里) | 传统消息队列系统(JMS) |
使用场景 | 实时双向通信,低延迟 | 单向推送为主(客户端订阅) | 日志采集、指标流、流处理、高吞吐 | 任务调度、微服务解耦 | 分布式事务、金融、电商下单场景 | 企业应用集成、JMS兼容 |
消息模型 | 非消息队列 | 发布-订阅 | 发布-订阅 | 生产者-消费者(队列+主题) | 类似 Kafka,增强事务支持 | 点对点、发布订阅 |
传输协议 | TCP | TCP | 自定义协议(基于TCP) | AMQP(支持多协议) | 自定义协议 | OpenWire(也支持MQTT/AMQP等) |
持久化支持 | 不持久化 | 可选,依赖 QoS 配置 | 高效持久化(磁盘+零拷贝) | 可配置持久化 | 高性能持久化 | 可配置 |
实时性 | 极佳(毫秒级) | 非常好 | 延迟可接受(批量处理优先) | 较好 | 良好 | 一般 |
吞吐量 | 一般(每连接独立) | 中等偏低(低功耗、低延迟、小负载) | 超高(百万级 TPS) | 中等(万级) | 较高 | 较低 |
事务支持 | 不支持 | 不支持 | 弱事务(Exactly-once很难) | 支持事务 | 强事务支持(分布式事务) | 支持 |
部署复杂度 | 低 | 低(有QoS 2,但并不适用于复杂事务) | 高 | 低至中 | 中等 | 中高 |
适配网络 | 需长连接 | 非常好(2G/3G/卫星) | 要求网络质量高 | 中等 | 中等 | 中等 |
典型使用场景:
技术选型 | 场景 | 原因 |
---|---|---|
WebSocket | 网页聊天室 / 实时推送 / 游戏服务 |
WebSocket 保持双向连接、低延迟 |
MQTT | 智能家居 / 传感器网络 / 远程设备 |
网络差、设备资源少,用 MQTT 非常合适 |
Kafka | 大数据日志收集 / 链路追踪 / 流处理平台 |
高吞吐、分区并行、顺序性好 |
RabbitMQ | 微服务异步通信 / 任务队列 / 异步任务处理 / RPC解耦 / 分布式微服务 | 轻量级,易用性强,支持多种协议 |
RocketMQ | 高并发订单处理 / 分布式事务 / 金融场景 | 支持消息顺序、事务、延迟等复杂功能 |
ActiveMQ | 企业集成 / 兼容 JMS 老系统 |
JMS 支持好,适合传统业务 |