一、什么是消息队列?—— 用生活场景秒懂
1.1 一个简单的比喻
想象你去银行办业务。如果没有叫号机,所有人挤在窗口前,场面非常混乱。但有了叫号机,你只需要取个号,安心等待,工作人员按顺序处理。
消息队列(Message Queue,简称 MQ) 就是计算机世界里的"叫号系统":
| 角色 | 生活类比 | 技术含义 |
|---|---|---|
| 📦 生产者(Producer) | 快递员/寄信人 | 发送消息的一方 |
| 🙋 消费者(Consumer) | 取件人/收信人 | 接收、处理消息的一方 |
| 🏢 Broker(服务器) | 快递柜/邮局 | 暂存消息的中间人 |
| 📬 消息(Message) | 包裹/信件 | 需要传递的数据 |
| 📋 队列(Queue) | 排队队伍 | 先进先出(FIFO)的容器 |
一句话总结:消息队列让不同的服务、进程、线程之间实现 解耦通信 ——发送方不用等待接收方处理完才继续干活。
1.2 为什么需要消息队列?—— 五大使用场景
如果服务 A 直接调用服务 B,一旦 B 挂了或处理不过来,A 也跟着出问题。加入消息队列后,A 和 B 就 解耦 了——即使 B 暂时不工作,消息也不会丢失,等 B 恢复后继续处理。


| 场景 | 生活类比 | 技术说明 |
|---|---|---|
| 异步处理 | 网购下单后不用站在门口等快递 | 用户注册后,发短信、邮件等后台异步完成,用户不用等 |
| 流量削峰 | 高速公路收费站限流防拥堵 | 秒杀场景:MQ 挡住洪水般请求,后端按能力慢慢处理 |
| 服务解耦 | 快递员不需要认识收件人,只管放快递柜 | 订单系统发消息,库存/物流系统各自取消息处理 |
| 发布订阅 | 微信公众号发文,所有粉丝都能看到 | 游戏跨服广播:"全服还剩3把屠龙刀" |
| 高并发缓冲 | 水库蓄水,下游匀速放水 | 日志采集、监控数据上报,Kafka 缓冲高并发数据 |
异步 vs 同步处理对比


结论:异步处理让原本 200ms 的操作缩短到约 50ms,大幅提升系统响应速度!
1.3 两种消息模型


| 特性 | 点对点模型 | 发布/订阅模型 |
|---|---|---|
| 消息接收者 | 只有一个消费者能收到 | 所有订阅者都能收到 |
| 类似于 | 线程池分工 | 课堂通知/广播 |
1.4 三大保障机制
| 机制 | 作用 |
|---|---|
| ACK 确认机制 | 消费者处理完消息后回复"收到",队列才删除消息。若消费者宕机未回复,消息会重发给其他消费者 |
| 消息持久化 | 消息写入磁盘,即使服务器宕机重启,消息不丢失 |
| 顺序性保证 | FIFO 特性确保消息按发送顺序被处理 |
二、Kafka 是什么?—— 消息队列界的"高速公路"
2.1 一句话定义
Kafka 是 Apache 开源的 分布式消息队列系统,由 LinkedIn 开发,专为 高吞吐量、分布式、可持久化 的场景设计。每秒可处理 10万级 消息,特别适合大数据场景。
2.2 主流消息队列对比
| 特性 | RabbitMQ | RocketMQ | Kafka | ZeroMQ |
|---|---|---|---|---|
| 吞吐量 | 万级 | 10万级 | 10万级 | 百万级 |
| 延迟 | 微秒级(最低) | 毫秒级 | 毫秒级以内 | 微秒/毫秒级 |
| 可用性 | 高(主从架构) | 非常高(分布式) | 非常高(分布式+多副本) | 嵌入式,非独立服务 |
| 消息可靠性 | 基本不丢 | 可做到零丢失 | 可做到零丢失 | — |
| 适用场景 | 对延迟要求极高 | MQ功能全面,扩展性好 | 大数据实时计算、日志采集 | 嵌入进程内 |
| 开发语言 | Erlang | Java | Scala/Java | C |
总结:如果你的场景是 大数据实时处理、日志采集、流计算,Kafka 是首选。
三、Kafka 架构详解 —— 理解核心术语
3.1 整体架构图

3.2 核心术语速查表
| 术语 | 通俗解释 | 生活类比 |
|---|---|---|
| Broker | Kafka 的一台服务器,多台 Broker 组成集群 | 邮局的分支网点 |
| Cluster | 多个 Broker 组成的集群 | 整个邮政系统 |
| Topic(主题) | 消息的"分类标签",生产者和消费者面向同一个 Topic 通信 | 信件分类(家书/公文/广告) |
| Partition(分区) | 一个 Topic 被切成多个分区,分散在不同 Broker 上,提高并发能力 | 一个信箱有多个格子,并行处理 |
| Offset(偏移量) | 消费者读到哪条消息的"书签",重启后从上次位置继续读 | 书签,记录读到了第几页 |
| Replication(副本) | 每个分区的数据可以复制多份,防止数据丢失 | 重要文件多复印几份放不同地方 |
| Leader | 每个分区多副本中的"老大",负责所有读写操作 | 原件 |
| Follower | Leader 的"小弟",只负责同步数据。Leader 挂了,Follower 自动上位 | 复印件 |
| Consumer Group(消费者组) | 一组消费者共同消费一个 Topic,组内每人负责不同分区 | 一个部门的多位同事共同处理邮件 |
| ZooKeeper | Kafka 的"管家",负责管理集群状态、Leader 选举等 | 邮政系统的总调度中心 |
3.3 消息的三级结构
Topic(主题)
├── Partition 0 → [消息0, 消息1, 消息2, ...] ← 每条消息有 Offset
├── Partition 1 → [消息0, 消息1, ...]
└── Partition 2 → [消息0, 消息1, 消息2, 消息3, ...]
💡 关键点:
同一条消息只保存在某一个分区中,不会跨分区重复存储
不同分区的 Offset 是各自独立的
每个 Partition 是一个有序的日志文件,消息只追加写入末尾
3.4 消息从产生到消费的完整旅程

四、生产者(Producer)详解 —— 消息是怎么发出去的?
4.1 为什么需要分区?
一句话:水平扩展 + 负载均衡。
就像一个超市只有一个收银台时处理能力有限,开多个收银台(分区)就能同时服务更多顾客。不同分区可以放在不同机器上,当性能不够时只需加机器即可。
4.2 消息进入哪个分区?—— 分区策略

| 策略 | 规则 | 适用场景 |
|---|---|---|
| 指定分区 | 直接指定 Partition 编号 | 有特殊业务需求时 |
| 按 Key 哈希 | 相同 Key 的消息 一定进同一个分区,保证顺序 | 需要按用户 ID 等维度保序时 |
| 轮询(默认) | 消息依次分配到各分区,负载最均衡 | 无特殊需求时,大部分场景适用 |
📌 简单记忆:指定了分区用分区,有 Key 用 Key 的哈希,啥都没有就轮询。
4.3 数据可靠性保证 —— ACK 应答机制
Producer 发消息后,需要等 Broker 确认收到。Kafka 提供三种确认级别:
| ACK 值 | 含义 | 可靠性 | 性能 | 适用场景 |
|---|---|---|---|---|
| 0 | 不等确认,发完就走 | ❌ 最低(可能丢数据) | ⚡ 延迟最低 | 日志等可容忍丢失的数据 |
| 1 | Leader 写入磁盘就确认 | ⚠️ 中等(Leader 挂了可能丢) | 🔄 折中 | 一般业务场景 |
| -1 (all) | Leader + 所有 Follower 都写入才确认 | ✅ 最高(但可能产生重复消息) | 🐢 延迟最高 | 核心业务数据,不可丢失 |
4.4 ISR 机制 —— 谁有资格当"接班人"?
ISR(In-Sync Replica Set) = 和 Leader 保持同步的 Follower 集合。

-
只有 ISR 中的 Follower 才有资格在 Leader 挂掉时被选为新 Leader
-
如果某个 Follower 长时间没同步数据,会被踢出 ISR
-
超时时间由参数
replica.lag.time.max.ms控制
4.5 副本机制的关键规则
| 规则 | 说明 |
|---|---|
| Leader 和 Follower 必须在不同机器上 | 避免单点故障,确保容灾 |
| Follower 不对外提供读服务 | 所有读写都通过 Leader(不像 MongoDB 可以从备份读) |
| Leader 挂掉后自动切换 | ZooKeeper 感知并从 ISR 中选出新 Leader |
| 3 个副本是生产环境推荐值 | 既保证可靠性,又不过度浪费资源 |
| 副本数量 不能大于 Broker 数量 | 否则创建 Topic 会失败 |
五、消费者(Consumer)详解 —— 消息是怎么被消费的?
5.1 Pull(拉取)模式
Kafka 的 Consumer 采用 Pull 模式 从 Broker 主动拉取数据:
| 特点 | 说明 |
|---|---|
| ✅ 优点 | 消费者按自己的能力消费,不会被撑爆 |
| ⚠️ 缺点 | 如果没数据,会空转浪费资源 |
| 🔧 解决方案 | Kafka 提供 timeout 参数——没数据就等一会儿再返回 |
5.2 Consumer Group 的精妙设计
Kafka 用 Consumer Group(消费者组) 这一个机制,同时实现了两种消息模型:


核心规则:
-
✅ 所有消费者在同一个 Group → 实现「消息队列」模型(一条消息只被一个消费者处理)
-
✅ 每个消费者在不同的 Group → 实现「发布/订阅」模型(一条消息被所有消费者处理)
-
🔒 一个分区只能被同一 Group 内的一个消费者消费(避免重复消费)
-
🔓 不同消费者组之间互不影响(各组各读各的)
5.3 分区分配策略
当 Consumer Group 内有多个消费者时,如何决定谁消费哪个分区?
Range 策略(默认)
按主题分配。分区数 ÷ 消费者数,除不尽则前面的消费者多分一个。
| 场景 | 分区分配结果 |
|---|---|
| 10 分区,3 消费者 | C1→[0,1,2,3] C2→[4,5,6] C3→[7,8,9] |
| 11 分区,3 消费者 | C1→[0,1,2,3] C2→[4,5,6,7] C3→[8,9,10] |
⚠️ 弊端:当订阅多个 Topic 时,前面的消费者总是多消费,分配不均。
RoundRobin 策略(轮询)
将所有 Topic 的所有分区打散排序后,轮流分配给消费者。分配最大差别仅为 1 个分区,更均衡。
⚠️ 前提:所有消费者必须订阅相同的 Topic 集合,否则可能分配混乱。
5.4 消费可靠性 —— Offset 管理
| 提交方式 | 说明 | 风险 |
|---|---|---|
enable.auto.commit = true | 自动提交 Offset(默认) | 可能丢消息或重复消费 |
| 手动提交 | 消费完成后主动提交 | 更安全,高可靠场景推荐 |
📌 核心原则:宁愿重复消费,也不丢消息。
六、开发环境搭建 —— 手把手教你装 Kafka
6.1 安装流程总览

6.2 安装 Java 环境
# 解压 JDK
tar -zxvf jdk-8u291-linux-x64.tar.gz
# 移动到标准目录
sudo mkdir /usr/lib/jdk
sudo mv jdk1.8.0_291 /usr/lib/jdk/
# 配置环境变量(编辑 /etc/profile,末尾添加)
export JAVA_HOME=/usr/lib/jdk/jdk1.8.0_291
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
# 生效并验证
source /etc/profile
java -version
6.3 安装并启动 Kafka
# 下载并解压
wget https://archive.apache.org/dist/kafka/2.0.0/kafka_2.11-2.0.0.tgz
tar -zxvf kafka_2.11-2.0.0.tgz
cd kafka_2.11-2.0.0
# 启动 ZooKeeper(默认端口 2181)
sh bin/zookeeper-server-start.sh -daemon config/zookeeper.properties
# 修改 Kafka 配置(config/server.properties)确保:
# zookeeper.connect=localhost:2181
# 启动 Kafka(默认端口 9092)
sh bin/kafka-server-start.sh -daemon config/server.properties
6.4 Topic 操作命令速查
# 创建 Topic(1个分区,1个副本)
sh kafka-topics.sh --create --zookeeper localhost:2181 \
--replication-factor 1 --partitions 1 --topic test
# 查看所有 Topic
sh kafka-topics.sh --list --zookeeper localhost:2181
# 查看 Topic 详细信息
sh kafka-topics.sh --describe --zookeeper localhost:2181 --topic test
# 增加分区数(只能增不能减!)
sh kafka-topics.sh --zookeeper localhost:2181 --topic test \
--alter --partitions 3
# 删除 Topic(需要先设置 delete.topic.enable=true)
sh kafka-topics.sh --zookeeper localhost:2181 --delete --topic test
6.5 收发消息测试
# 终端1:启动生产者(输入消息后回车发送)
sh kafka-console-producer.sh --broker-list 127.0.0.1:9092 --topic test
> Hello Kafka!
> 你好世界!
# 终端2:启动消费者(从头开始消费)
sh kafka-console-consumer.sh --bootstrap-server 127.0.0.1:9092 \
--topic test --from-beginning
-
副本数不能大于 Broker 数量,否则创建 Topic 会失败
-
分区数只能增加不能减少,如需减少只能删除 Topic 重建
-
删除 Topic 需要在
server.properties中设置delete.topic.enable=true
七、C++ 操作 Kafka 实战 —— 使用 librdkafka
7.1 librdkafka 库安装
librdkafka 是 Kafka 的高性能 C/C++ 客户端库。
git clone https://github.com/edenhill/librdkafka.git
cd librdkafka
git checkout v1.7.0
./configure
make
sudo make install
sudo ldconfig
7.2 核心类关系图

7.3 回调类速查
| 类名 | 作用 |
|---|---|
RdKafka::DeliveryReportCb | 投递报告回调——消息发送成功/失败时触发 |
RdKafka::EventCb | 事件回调——错误、统计、日志等 |
RdKafka::RebalanceCb | 消费者组再平衡回调——分区重新分配时触发 |
RdKafka::PartitionerCb | 自定义分区器回调——决定消息进入哪个分区 |
7.4 Producer 开发流程

完整 Producer 示例代码:
#include "rdkafkacpp.h"
#include <iostream>
#include <string>
// 1. 投递报告回调:消息发送成功/失败时调用
class ProducerDeliveryReportCb : public RdKafka::DeliveryReportCb {
public:
void dr_cb(RdKafka::Message &message) {
if (message.err())
std::cerr << "❌ 发送失败: " << message.errstr() << std::endl;
else
std::cerr << "✅ 发送成功 topic=" << message.topic_name()
<< " partition=" << message.partition()
<< " offset=" << message.offset() << std::endl;
}
};
// 2. 自定义 Hash 分区器(可选)
class HashPartitionerCb : public RdKafka::PartitionerCb {
public:
int32_t partitioner_cb(const RdKafka::Topic *topic, const std::string *key,
int32_t partition_cnt, void *msg_opaque) {
return generate_hash(key->c_str(), key->size()) % partition_cnt;
}
private:
static unsigned int generate_hash(const char *str, size_t len) {
unsigned int hash = 5381;
for (size_t i = 0; i < len; i++)
hash = ((hash << 5) + hash) + str[i];
return hash;
}
};
int main() {
std::string errstr;
// 创建全局配置
RdKafka::Conf *conf = RdKafka::Conf::create(RdKafka::Conf::CONF_GLOBAL);
conf->set("bootstrap.servers", "192.168.0.105:9092", errstr);
// 注册投递报告回调
ProducerDeliveryReportCb dr_cb;
conf->set("dr_cb", &dr_cb, errstr);
// 创建 Topic 配置 & 注册自定义分区回调
RdKafka::Conf *tconf = RdKafka::Conf::create(RdKafka::Conf::CONF_TOPIC);
HashPartitionerCb partitioner_cb;
tconf->set("partitioner_cb", &partitioner_cb, errstr);
// 创建 Producer 和 Topic
RdKafka::Producer *producer = RdKafka::Producer::create(conf, errstr);
RdKafka::Topic *topic = RdKafka::Topic::create(producer, "test", tconf, errstr);
// 发送消息
std::string msg = "Hello Kafka";
std::string key = "mykey";
producer->produce(topic, RdKafka::Topic::PARTITION_UA,
RdKafka::Producer::RK_MSG_COPY,
(void*)msg.data(), msg.size(), &key, NULL);
producer->poll(0);
producer->flush(5000); // 等待所有消息发送完成
// 清理资源
delete topic;
delete producer;
delete tconf;
delete conf;
RdKafka::wait_destroyed(5000);
return 0;
}
7.5 Consumer 开发流程

完整 Consumer 示例代码:
#include "rdkafkacpp.h"
#include <iostream>
#include <vector>
#include <string>
// 再平衡回调:分区重新分配时触发
class ConsumerRebalanceCb : public RdKafka::RebalanceCb {
public:
void rebalance_cb(RdKafka::KafkaConsumer *consumer,
RdKafka::ErrorCode err,
std::vector<RdKafka::TopicPartition*> &partitions) {
if (err == RdKafka::ERR__ASSIGN_PARTITIONS)
consumer->assign(partitions); // 分配分区
else
consumer->unassign(); // 撤销分配
}
};
// 消息处理函数
void msg_consume(RdKafka::Message *msg) {
switch (msg->err()) {
case RdKafka::ERR_NO_ERROR:
std::cout << "📬 收到消息: topic=" << msg->topic_name()
<< " partition=" << msg->partition()
<< " offset=" << msg->offset()
<< " 内容=" << (char*)msg->payload() << std::endl;
break;
case RdKafka::ERR__TIMED_OUT:
break; // 超时,正常情况
default:
std::cerr << "❌ 消费错误: " << msg->errstr() << std::endl;
}
}
int main() {
std::string errstr;
// 创建配置
RdKafka::Conf *conf = RdKafka::Conf::create(RdKafka::Conf::CONF_GLOBAL);
conf->set("bootstrap.servers", "192.168.0.105:9092", errstr);
conf->set("group.id", "testGroup", errstr); // 必须设置消费者组
// 注册再平衡回调
ConsumerRebalanceCb rebalance_cb;
conf->set("rebalance_cb", &rebalance_cb, errstr);
// Topic 配置:从最新消息开始消费
RdKafka::Conf *tconf = RdKafka::Conf::create(RdKafka::Conf::CONF_TOPIC);
tconf->set("auto.offset.reset", "latest", errstr);
conf->set("default_topic_conf", tconf, errstr);
// 创建消费者
RdKafka::KafkaConsumer *consumer = RdKafka::KafkaConsumer::create(conf, errstr);
// 订阅 Topic
std::vector<std::string> topics = {"test", "test2"};
consumer->subscribe(topics);
// 消费循环
while (true) {
RdKafka::Message *msg = consumer->consume(1000); // 等待1秒
msg_consume(msg);
delete msg; // 及时释放,避免内存泄漏
}
// 关闭并清理
consumer->close();
delete consumer;
delete tconf;
delete conf;
RdKafka::wait_destroyed(5000);
return 0;
}
7.6 CMake 构建配置
cmake_minimum_required(VERSION 2.8)
project(KafkaDemo)
set(CMAKE_CXX_STANDARD 11)
# Kafka 头文件和库路径
include_directories(/usr/local/include/librdkafka)
link_directories(/usr/local/lib)
aux_source_directory(. SOURCE)
add_executable(${PROJECT_NAME} ${SOURCE})
# 链接 librdkafka 的 C++ 库
target_link_libraries(${PROJECT_NAME} rdkafka++)
7.7 produce() 函数的 msgflags 参数
| 标志 | 含义 |
|---|---|
RK_MSG_COPY | 拷贝消息数据,produce 后原始数据可以安全释放 |
RK_MSG_FREE | produce 完成后由 librdkafka 负责释放 payload 内存 |
RK_MSG_BLOCK | 消息队列满时阻塞等待(⚠️ 注意死锁风险) |
⚠️
RK_MSG_FREE和RK_MSG_COPY互斥,只能选一个。
7.8 librdkafka 常用缩略语
| 缩写 | 全称 | 说明 |
|---|---|---|
| rd | Rapid Development | 库前缀 |
| rk | RdKafka | Kafka 相关结构 |
| rkb | RdKafka Broker | Broker 代理 |
| rko | RdKafka Operation | 操作对象 |
| rkm | RdKafka Message | 消息对象 |
| toppar | Topic Partition | 主题分区组合 |
八、常用配置参数速查
8.1 全局配置(重点参数)
| 参数 | 默认值 | 说明 |
|---|---|---|
bootstrap.servers | — | Broker 地址列表(必填) |
group.id | — | 消费者组 ID(Consumer 必填) |
acks / request.required.acks | 1 | ACK 应答级别:0 / 1 / -1 |
message.max.bytes | 1000000 | 最大消息大小(约1MB) |
enable.auto.commit | true | 是否自动提交 Offset |
auto.commit.interval.ms | 5000 | 自动提交 Offset 间隔(毫秒) |
session.timeout.ms | 30000 | 消费者组会话超时(30秒) |
heartbeat.interval.ms | 1000 | 心跳间隔(1秒) |
max.partition.fetch.bytes | 1MB | 每分区最大拉取大小 |
security.protocol | plaintext | 通信协议(可选 ssl / sasl) |
8.2 Topic 配置(重点参数)
| 参数 | 默认值 | 说明 |
|---|---|---|
auto.offset.reset | largest | 无初始 Offset 时的策略:earliest / latest |
compression.codec | inherit | 消息压缩算法:none / gzip / snappy / lz4 |
message.timeout.ms | 300000 | 消息发送超时(5分钟) |
auto.commit.enable | true | 是否自动提交偏移量 |
九、知识体系全景图

十、核心结论与最佳实践
✅ 何时选择 Kafka?
-
日志采集与实时分析
-
大数据流处理(配合 Spark、Flink)
-
系统间异步解耦
-
高并发场景的流量削峰
✅ 生产环境六大建议
| 序号 | 建议 | 原因 |
|---|---|---|
| 1 | 副本数设为 3 | 既保证可靠性,又不过度浪费资源 |
| 2 | ACK 设为 -1(对于重要数据) | 确保所有副本都写入,数据不丢失 |
| 3 | 消费者手动提交 Offset | 避免自动提交导致的消息丢失 |
| 4 | Topic 分区数合理规划 | 分区数 ≈ 期望的消费者并发数 |
| 5 | 监控 ISR 集合变化 | ISR 缩小意味着有 Follower 掉队,需告警 |
| 6 | 合理设置消息大小限制 | 防止超大消息阻塞队列 |
✅ C++ 开发五大要点
| 序号 | 要点 |
|---|---|
| 1 | 使用 KafkaConsumer(高级 API)而非 Consumer(低级 API) |
| 2 | 生产者务必注册 DeliveryReportCb 回调以确认消息是否发送成功 |
| 3 | 消费者必须设置 group.id |
| 4 | 调用 flush() 确保程序退出前所有消息发送完毕 |
| 5 | 及时 delete 消费到的 Message 对象,避免内存泄漏 |
✅ 学习重点回顾清单
- Kafka 环境搭建(JDK + ZooKeeper + Kafka)
- Kafka 基本架构:生产者分区策略、消费者组消费策略
- C++ 操作 Kafka(librdkafka)核心流程
- Kafka 命令行工具(
./kafka-console-consumer.sh --help) - Kafka 存储逻辑:索引 index → log,如何查找消息,零拷贝技术
- 生产者:强一致性 vs 可用性的权衡(ACK 配置)
- 消费者:重复消费问题、自动提交 vs 手动提交
- Broker 内部:HW(High Watermark)与 LEO(Log End Offset)的关系
📚 参考资料

1733

被折叠的 条评论
为什么被折叠?



