Kafka、RabbitMQ、Nginx、Redis
目录
一、Kafka 详解二、RabbitMQ 详解三、Nginx 详解四、Redis 详解
一、Kafka 详解
1.1 什么是 Kafka?
Apache Kafka 是一个分布式流处理平台,最初由 LinkedIn 开发,现由 Apache 软件基金会维护。Kafka 主要用于构建实时数据管道和流式应用程序。
1.2 Kafka 核心概念
1.2.1 Topic(主题)
定义:消息的逻辑分类,类似于数据库中的表特点:
一个 Topic 可以有多个 Producer 和 ConsumerTopic 中的数据是有序的Topic 可以持久化存储消息
1.2.2 Partition(分区)
定义:Topic 的物理分割,每个 Partition 是一个有序的、不可变的消息序列特点:
每个 Partition 在物理上对应一个目录Partition 可以分布在不同的 Broker 上每个 Partition 可以有多个副本(Replica)Partition 内的消息是有序的,但 Topic 级别的消息不保证全局有序
1.2.3 Broker(代理)
定义:Kafka 集群中的服务器节点作用:
存储消息处理 Producer 和 Consumer 的请求管理 Partition 的副本
1.2.4 Producer(生产者)
定义:向 Topic 发送消息的客户端特点:
可以指定消息发送到哪个 Partition支持同步和异步发送支持消息压缩
1.2.5 Consumer(消费者)
定义:从 Topic 读取消息的客户端Consumer Group:
多个 Consumer 可以组成一个 Consumer Group同一个 Consumer Group 中的 Consumer 会负载均衡地消费消息不同 Consumer Group 可以独立消费同一个 Topic
1.2.6 Offset(偏移量)
定义:Consumer 在 Partition 中的位置特点:
Offset 是递增的Consumer 可以控制从哪个 Offset 开始消费Offset 可以存储在 Kafka 内部(__consumer_offsets)或外部系统
1.2.7 Replica(副本)
Leader Replica:负责处理读写请求的副本Follower Replica:从 Leader 同步数据的副本ISR(In-Sync Replicas):与 Leader 保持同步的副本集合
1.3 Kafka 架构设计
1.3.1 分布式架构
Producer → Kafka Cluster (Broker1, Broker2, Broker3) → Consumer
↓
Topic: user-events
Partition 0, 1, 2 (每个 Partition 有多个副本)
1.3.2 消息存储机制
日志结构:Kafka 使用追加日志(Append-only Log)存储消息分段存储:每个 Partition 被分成多个 Segment 文件索引文件:为快速定位消息,维护索引文件(.index 和 .timeindex)
1.3.3 高可用性
副本机制:每个 Partition 可以有多个副本Leader 选举:当 Leader 失效时,从 ISR 中选举新的 Leader数据持久化:消息写入磁盘,支持持久化存储
1.4 Kafka 工作流程
1.4.1 消息发送流程
Producer 创建消息根据 Key 或 Partition 策略选择目标 Partition发送到对应 Partition 的 LeaderLeader 将消息追加到日志文件Follower 从 Leader 同步消息返回确认信息给 Producer
1.4.2 消息消费流程
Consumer 订阅 TopicConsumer Group Coordinator 分配 PartitionConsumer 从分配的 Partition 读取消息更新 Offset处理消息
1.5 Kafka 配置参数
1.5.1 Producer 配置
:Kafka 集群地址
bootstrap.servers:消息确认机制(0, 1, all)
acks:重试次数
retries:批次大小
batch.size:等待时间
linger.ms:压缩类型(none, gzip, snappy, lz4)
compression.type
1.5.2 Consumer 配置
:Consumer Group ID
group.id:Offset 重置策略(earliest, latest, none)
auto.offset.reset:是否自动提交 Offset
enable.auto.commit:每次拉取的最大记录数
max.poll.records:会话超时时间
session.timeout.ms
1.5.3 Broker 配置
:Broker 唯一标识
broker.id:日志存储目录
log.dirs:网络线程数
num.network.threads:IO 线程数
num.io.threads:副本拉取最大字节数
replica.fetch.max.bytes
1.6 Kafka 面试题
面试题 1:Kafka 为什么性能高?
答案要点:
顺序写入:Kafka 使用追加日志,顺序写入磁盘,性能接近内存零拷贝:使用 sendfile 系统调用,减少数据拷贝次数批量处理:Producer 和 Consumer 都支持批量操作分区并行:多个 Partition 可以并行处理页缓存:利用操作系统页缓存,提高读写性能压缩:支持消息压缩,减少网络传输
面试题 2:Kafka 如何保证消息不丢失?
答案要点:
Producer 端:
设置 :等待所有 ISR 副本确认设置
acks=all:失败重试设置
retries:保证顺序
max.in.flight.requests.per.connection=1
Broker 端:
设置 :至少 2 个副本设置
replication.factor >= 2:至少 1 个 ISR 副本设置
min.insync.replicas >= 1:禁止非 ISR 副本成为 Leader
unclean.leader.election.enable=false
Consumer 端:
关闭自动提交 Offset:处理完消息后手动提交 Offset处理消息时使用幂等性保证
enable.auto.commit=false
面试题 3:Kafka 如何保证消息顺序性?
答案要点:
Partition 内有序:Kafka 保证单个 Partition 内消息有序单 Partition 单 Consumer:一个 Partition 只能被同一个 Consumer Group 中的一个 Consumer 消费Key 路由:使用相同的 Key 发送消息,确保消息发送到同一个 PartitionProducer 配置:设置 和
max.in.flight.requests.per.connection=1
retries > 0
示例:
// 使用相同的 Key 保证顺序
producer.send(new ProducerRecord<>("topic", "user-123", message));
面试题 4:Kafka 的 Consumer Group 机制是什么?
答案要点:
负载均衡:同一个 Consumer Group 中的 Consumer 会平均分配 PartitionRebalance:当 Consumer 加入或离开时,会触发 Rebalance 重新分配 Partition独立消费:不同 Consumer Group 可以独立消费同一个 TopicOffset 管理:每个 Consumer Group 维护自己的 Offset
Rebalance 触发条件:
Consumer 加入或离开Topic 的 Partition 数量变化Consumer 订阅的 Topic 变化
面试题 5:Kafka 的 ISR 机制是什么?
答案要点:
ISR(In-Sync Replicas):与 Leader 保持同步的副本集合同步条件:
Follower 与 Leader 的 Offset 差距在 内Follower 在
replica.lag.time.max.ms 时间内向 Leader 发送 fetch 请求 Leader 选举:优先从 ISR 中选择新的 Leader数据一致性:只有 ISR 中的副本才能参与 Leader 选举,保证数据一致性
replica.lag.time.max.ms
面试题 6:Kafka 如何实现高吞吐量?
答案要点:
批量发送:Producer 批量发送消息批量消费:Consumer 批量拉取消息分区并行:多个 Partition 并行处理零拷贝:使用 sendfile 减少数据拷贝压缩:消息压缩减少网络传输顺序写入:追加日志顺序写入磁盘
面试题 7:Kafka 的 Offset 存储在哪里?
答案要点:
内部 Topic:,Kafka 0.9+ 默认存储方式外部存储:ZooKeeper(旧版本)或外部数据库Offset 管理:
__consumer_offsets
自动提交:,定期提交手动提交:
enable.auto.commit=true,处理完消息后提交
enable.auto.commit=false
面试题 8:Kafka 如何处理消息重复消费?
答案要点:
幂等性 Producer:设置 Consumer 幂等性:在业务层面实现幂等性处理去重机制:使用唯一标识(如消息 ID)进行去重事务性 Producer:使用事务保证 Exactly-Once 语义
enable.idempotence=true
面试题 9:Kafka 的 Partition 数量如何选择?
答案要点:
吞吐量:Partition 数量影响并行度,但过多会导致文件句柄和内存消耗Consumer 数量:Partition 数量应该 >= Consumer Group 中的 Consumer 数量建议:
小规模:1-3 个 Partition中等规模:10-20 个 Partition大规模:根据吞吐量需求,但不超过 100 个 注意:Partition 数量只能增加,不能减少
面试题 10:Kafka 和 RabbitMQ 的区别?
答案要点:
| 特性 | Kafka | RabbitMQ |
|---|---|---|
| 设计目标 | 高吞吐量、日志流处理 | 消息队列、任务分发 |
| 消息模型 | 发布订阅、流式处理 | 点对点、发布订阅 |
| 消息存储 | 持久化到磁盘 | 内存+磁盘可选 |
| 吞吐量 | 非常高(百万级/秒) | 中等(万级/秒) |
| 消息顺序 | Partition 内有序 | 队列内有序 |
| 消息确认 | 批量确认 | 单条确认 |
| 适用场景 | 日志收集、流处理、大数据 | 任务队列、RPC、实时消息 |
二、RabbitMQ 详解
2.1 什么是 RabbitMQ?
RabbitMQ 是一个开源的消息代理软件(Message Broker),实现了高级消息队列协议(AMQP)。它主要用于应用程序之间的异步通信、解耦和负载均衡。
2.2 RabbitMQ 核心概念
2.2.1 Exchange(交换机)
定义:接收 Producer 发送的消息,并根据路由规则将消息路由到队列类型:
Direct Exchange:精确匹配 routing keyTopic Exchange:模糊匹配 routing key(支持通配符)Fanout Exchange:广播模式,忽略 routing keyHeaders Exchange:根据消息 headers 匹配
2.2.2 Queue(队列)
定义:存储消息的缓冲区特点:
队列是消息的最终目的地队列可以持久化队列可以设置优先级、TTL、最大长度等
2.2.3 Binding(绑定)
定义:Exchange 和 Queue 之间的连接Binding Key:用于路由消息的键
2.2.4 Routing Key(路由键)
定义:Producer 发送消息时指定的键,用于路由消息
2.2.5 Connection(连接)
定义:应用程序与 RabbitMQ 服务器之间的 TCP 连接
2.2.6 Channel(通道)
定义:Connection 中的虚拟连接特点:
一个 Connection 可以有多个 ChannelChannel 是轻量级的,减少 TCP 连接开销大部分操作都在 Channel 上进行
2.2.7 Virtual Host(虚拟主机)
定义:RabbitMQ 中的逻辑隔离单元作用:类似于命名空间,不同 Virtual Host 之间完全隔离
2.3 RabbitMQ 消息模型
2.3.1 工作队列模式(Work Queue)
Producer → Queue → Consumer1
→ Consumer2
特点:一个队列,多个消费者,消息轮询分发应用场景:任务分发、负载均衡
2.3.2 发布订阅模式(Publish/Subscribe)
Producer → Exchange (Fanout) → Queue1 → Consumer1
→ Queue2 → Consumer2
→ Queue3 → Consumer3
特点:一条消息被多个消费者消费应用场景:日志收集、事件通知
2.3.3 路由模式(Routing)
Producer → Exchange (Direct) → Queue1 (routing key: error)
→ Queue2 (routing key: info)
→ Queue3 (routing key: warning)
特点:根据 routing key 精确匹配应用场景:日志级别分类
2.3.4 主题模式(Topics)
Producer → Exchange (Topic) → Queue1 (routing key: *.orange.*)
→ Queue2 (routing key: *.*.rabbit)
→ Queue3 (routing key: lazy.#)
特点:支持通配符匹配通配符:
:匹配一个单词
*:匹配零个或多个单词
#
2.3.5 RPC 模式(Remote Procedure Call)
Client → Request Queue → Server
← Reply Queue ←
特点:请求-响应模式应用场景:远程过程调用
2.4 RabbitMQ 消息确认机制
2.4.1 生产者确认(Publisher Confirms)
事务模式:使用事务保证消息发送确认模式:异步确认消息是否到达 Broker
2.4.2 消费者确认(Consumer Acknowledgments)
自动确认:,消息发送后立即确认手动确认:
auto_ack=true,处理完消息后手动确认拒绝消息:
auto_ack=false
:拒绝单条消息,可以重新入队
basic.reject:拒绝多条消息,可以批量拒绝
basic.nack:消息重新入队
requeue=true:消息被丢弃或进入死信队列
requeue=false
2.5 RabbitMQ 高级特性
2.5.1 消息持久化
队列持久化:,队列在 Broker 重启后仍然存在消息持久化:设置
durable=true,消息持久化到磁盘Exchange 持久化:
delivery_mode=2,Exchange 持久化
durable=true
2.5.2 死信队列(Dead Letter Queue)
定义:当消息无法被正常消费时,会被路由到死信队列触发条件:
消息被拒绝且 消息 TTL 过期队列达到最大长度 应用场景:消息重试、异常处理、消息审计
requeue=false
2.5.3 消息 TTL(Time To Live)
队列 TTL:队列中所有消息的过期时间消息 TTL:单条消息的过期时间过期处理:过期消息会被路由到死信队列或丢弃
2.5.4 优先级队列
定义:支持消息优先级,高优先级消息优先被消费配置:设置队列 参数限制:优先级范围 0-255
x-max-priority
2.5.5 延迟队列
实现方式:
使用 TTL + 死信队列实现延迟使用 RabbitMQ 延迟消息插件(rabbitmq-delayed-message-exchange) 应用场景:订单超时、定时任务、延迟通知
2.6 RabbitMQ 集群与高可用
2.6.1 集群模式
普通集群:多个节点共享元数据,但队列只在一个节点镜像队列:队列在多个节点复制,实现高可用节点类型:
磁盘节点:存储元数据到磁盘内存节点:元数据存储在内存
2.6.2 镜像队列配置
ha-mode:
:镜像到所有节点
all:镜像到指定数量的节点
exactly:镜像到指定节点 ha-sync-mode:
nodes
:手动同步
manual:自动同步
automatic
2.7 RabbitMQ 面试题
面试题 1:RabbitMQ 如何保证消息不丢失?
答案要点:
Producer 端:
使用事务或 Publisher Confirms 确认消息到达 Broker设置消息持久化:使用持久化 Exchange 和 Queue
delivery_mode=2
Broker 端:
队列持久化:消息持久化:
durable=true使用镜像队列保证高可用
delivery_mode=2
Consumer 端:
关闭自动确认:处理完消息后手动确认使用死信队列处理异常消息
auto_ack=false
面试题 2:RabbitMQ 如何保证消息顺序性?
答案要点:
单队列单消费者:一个队列只有一个消费者,保证顺序消息分组:使用相同的 routing key 将相关消息路由到同一队列消费者串行处理:消费者串行处理消息,不使用并发注意:RabbitMQ 不保证全局消息顺序,只保证队列内顺序
面试题 3:RabbitMQ 的 Exchange 类型有哪些?
答案要点:
Direct Exchange:精确匹配 routing key,用于点对点消息Topic Exchange:模糊匹配 routing key,支持通配符,用于路由模式Fanout Exchange:广播模式,忽略 routing key,用于发布订阅Headers Exchange:根据消息 headers 匹配,不依赖 routing key
面试题 4:RabbitMQ 如何处理消息堆积?
答案要点:
增加消费者:增加 Consumer 数量提高消费速度批量确认:使用批量确认减少确认开销消息预取:设置 限制未确认消息数队列最大长度:设置队列最大长度,防止无限堆积死信队列:将无法处理的消息路由到死信队列监控告警:监控队列长度,及时告警
prefetch_count
面试题 5:RabbitMQ 的死信队列是什么?
答案要点:
定义:当消息无法被正常消费时,会被路由到死信队列触发条件:
消息被拒绝且 消息 TTL 过期队列达到最大长度 配置:设置队列
requeue=false 和
x-dead-letter-exchange应用场景:消息重试、异常处理、消息审计
x-dead-letter-routing-key
面试题 6:RabbitMQ 如何实现延迟队列?
答案要点:
TTL + 死信队列:
创建延迟队列,设置 TTL消息过期后路由到死信队列(实际消费队列) 延迟消息插件:
使用 插件支持更精确的延迟控制 应用场景:订单超时、定时任务、延迟通知
rabbitmq-delayed-message-exchange
面试题 7:RabbitMQ 的 Channel 和 Connection 的区别?
答案要点:
Connection:
TCP 连接,应用程序与 RabbitMQ 服务器的连接创建和销毁开销大一个应用程序通常只有一个 Connection Channel:
Connection 中的虚拟连接轻量级,创建和销毁开销小一个 Connection 可以有多个 Channel大部分操作都在 Channel 上进行 使用建议:每个线程使用独立的 Channel,共享 Connection
面试题 8:RabbitMQ 如何实现集群高可用?
答案要点:
普通集群:
多个节点共享元数据队列只在一个节点,其他节点只有元数据节点故障时,队列不可用 镜像队列:
队列在多个节点复制实现高可用,节点故障时自动切换配置 和
ha-mode 负载均衡:使用负载均衡器(如 HAProxy)分发请求
ha-sync-mode
面试题 9:RabbitMQ 和 Kafka 的区别?
答案要点:
| 特性 | RabbitMQ | Kafka |
|---|---|---|
| 设计目标 | 消息队列、任务分发 | 高吞吐量、日志流处理 |
| 消息模型 | 点对点、发布订阅 | 发布订阅、流式处理 |
| 消息存储 | 内存+磁盘可选 | 持久化到磁盘 |
| 吞吐量 | 中等(万级/秒) | 非常高(百万级/秒) |
| 消息顺序 | 队列内有序 | Partition 内有序 |
| 消息确认 | 单条确认 | 批量确认 |
| 适用场景 | 任务队列、RPC、实时消息 | 日志收集、流处理、大数据 |
面试题 10:RabbitMQ 的预取机制是什么?
答案要点:
定义: 限制 Consumer 未确认消息的最大数量作用:
prefetch_count
实现负载均衡,防止某个 Consumer 处理过多消息提高消息分发效率 配置:工作模式:
channel.basic_qos(prefetch_count=1)
:公平分发
prefetch_count=1:批量分发
prefetch_count>1
三、Nginx 详解
3.1 什么是 Nginx?
Nginx 是一个高性能的 Web 服务器和反向代理服务器,也可以作为负载均衡器、HTTP 缓存和邮件代理服务器。Nginx 采用事件驱动的异步非阻塞架构,能够处理大量并发连接。
3.2 Nginx 核心概念
3.2.1 主进程和工作进程
主进程(Master Process):
读取配置文件管理工作进程平滑重启和升级 工作进程(Worker Process):
处理客户端请求多个工作进程并行处理请求
3.2.2 事件驱动模型
epoll(Linux):高效的事件通知机制kqueue(FreeBSD):BSD 系统的事件通知机制异步非阻塞:一个工作进程可以处理多个连接
3.2.3 配置文件结构
main # 全局配置
├── events # 事件模块配置
├── http # HTTP 模块配置
│ ├── server # 虚拟主机配置
│ │ ├── location # 位置配置
│ │ └── location
│ └── server
└── mail # 邮件模块配置
3.3 Nginx 常用配置
3.3.1 基本配置
# 工作进程数,通常设置为 CPU 核心数
worker_processes auto;
# 每个工作进程的最大连接数
events {
worker_connections 1024;
use epoll; # 使用 epoll 事件模型
}
# HTTP 配置
http {
# MIME 类型
include mime.types;
default_type application/octet-stream;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;
# 服务器配置
server {
listen 80;
server_name example.com;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
}
3.3.2 反向代理配置
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
upstream backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
3.3.3 负载均衡配置
upstream backend {
# 负载均衡算法
# ip_hash; # IP 哈希
# least_conn; # 最少连接
# fair; # 响应时间(需要第三方模块)
server 192.168.1.10:8080 weight=3; # 权重
server 192.168.1.11:8080 weight=2;
server 192.168.1.12:8080 weight=1;
server 192.168.1.13:8080 backup; # 备份服务器
server 192.168.1.14:8080 down; # 下线服务器
}
3.3.4 静态文件服务
server {
listen 80;
server_name example.com;
location /static/ {
alias /var/www/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location ~* .(jpg|jpeg|png|gif|ico|css|js)$ {
expires 7d;
add_header Cache-Control "public";
}
}
3.3.5 SSL/TLS 配置
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://backend;
}
}
3.4 Nginx 高级特性
3.4.1 限流配置
# 限制请求速率
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://backend;
}
}
# 限制连接数
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
location / {
limit_conn conn_limit 10;
proxy_pass http://backend;
}
}
3.4.2 缓存配置
# 代理缓存
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g
inactive=60m use_temp_path=off;
server {
location / {
proxy_cache my_cache;
proxy_cache_valid 200 60m;
proxy_cache_valid 404 1m;
proxy_cache_key $scheme$proxy_host$request_uri;
proxy_pass http://backend;
}
}
3.4.3 重写规则
server {
# URL 重写
rewrite ^/old/(.*)$ /new/$1 permanent;
# 条件重写
if ($request_method = POST) {
return 405;
}
location / {
# 内部重定向
try_files $uri $uri/ /index.html;
}
}
3.4.4 访问控制
server {
# IP 白名单
location /admin/ {
allow 192.168.1.0/24;
allow 10.0.0.0/8;
deny all;
proxy_pass http://backend;
}
# 基本认证
location /secure/ {
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://backend;
}
}
3.5 Nginx 性能优化
3.5.1 工作进程优化
# 工作进程数,通常设置为 CPU 核心数
worker_processes auto;
# 绑定工作进程到 CPU 核心
worker_cpu_affinity auto;
# 工作进程优先级
worker_priority -5;
3.5.2 连接优化
events {
# 使用 epoll 事件模型
use epoll;
# 每个工作进程的最大连接数
worker_connections 10240;
# 允许一个工作进程同时接受多个连接
multi_accept on;
}
3.5.3 缓冲优化
http {
# 客户端请求体缓冲区大小
client_body_buffer_size 128k;
# 客户端请求头缓冲区大小
client_header_buffer_size 1k;
# 大请求头缓冲区
large_client_header_buffers 4 4k;
# 代理缓冲区
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
}
3.5.4 压缩优化
http {
# 启用 gzip 压缩
gzip on;
gzip_vary on;
gzip_min_length 1000;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_comp_level 6;
}
3.6 Nginx 面试题
面试题 1:Nginx 为什么性能高?
答案要点:
事件驱动模型:使用 epoll/kqueue 异步非阻塞 I/O多进程架构:主进程 + 多个工作进程,充分利用多核 CPU内存池:减少内存分配和释放开销零拷贝:使用 sendfile 系统调用减少数据拷贝高效的数据结构:使用红黑树等高效数据结构
面试题 2:Nginx 如何处理高并发?
答案要点:
事件驱动:一个工作进程可以处理多个连接,非阻塞 I/O多进程:多个工作进程并行处理请求连接复用:支持 HTTP/1.1 Keep-Alive 和 HTTP/2负载均衡:将请求分发到多个后端服务器缓存:缓存静态资源和代理结果,减少后端压力
面试题 3:Nginx 的负载均衡算法有哪些?
答案要点:
轮询(Round Robin):默认算法,依次分配请求加权轮询(Weighted Round Robin):根据权重分配请求IP 哈希(IP Hash):根据客户端 IP 哈希分配,保证同一 IP 访问同一服务器最少连接(Least Connections):分配给连接数最少的服务器公平(Fair):根据响应时间分配(需要第三方模块)
面试题 4:Nginx 如何实现反向代理?
答案要点:
proxy_pass:配置后端服务器地址proxy_set_header:设置代理请求头upstream:定义后端服务器组负载均衡:在 upstream 中配置多个服务器健康检查:使用第三方模块或主动健康检查
面试题 5:Nginx 如何实现限流?
答案要点:
limit_req_zone:限制请求速率
基于 IP 或变量定义限流区域设置限流速率(如 10r/s) limit_req:应用限流规则
:允许突发请求数
burst:不延迟处理突发请求 limit_conn_zone:限制连接数应用场景:API 限流、防止 DDoS 攻击
nodelay
面试题 6:Nginx 的 location 匹配规则是什么?
答案要点:
精确匹配:,优先级最高前缀匹配:
location = /path
:优先匹配,停止正则匹配
location ^~ /path:普通前缀匹配 正则匹配:
location /path
:区分大小写
location ~ /path:不区分大小写 通用匹配:
location ~* /path,优先级最低匹配顺序:精确匹配 > 优先前缀匹配 > 正则匹配 > 普通前缀匹配 > 通用匹配
location /
面试题 7:Nginx 如何实现动静分离?
答案要点:
静态资源:直接由 Nginx 提供服务
location /static/ {
alias /var/www/static/;
expires 30d;
}
动态请求:反向代理到后端应用服务器
location /api/ {
proxy_pass http://backend;
}
优势:减轻后端服务器压力,提高静态资源访问速度
面试题 8:Nginx 如何实现 HTTPS?
答案要点:
SSL 证书配置:
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
SSL 协议和加密套件:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
HTTP/2:SSL 优化:
listen 443 ssl http2;、
ssl_session_cache
ssl_session_timeout
面试题 9:Nginx 如何实现缓存?
答案要点:
代理缓存:
:定义缓存路径和参数
proxy_cache_path:启用缓存
proxy_cache:设置缓存有效期 缓存键:
proxy_cache_valid 定义缓存键缓存控制:
proxy_cache_key
:绕过缓存
proxy_cache_bypass:不缓存 应用场景:减少后端压力,提高响应速度
proxy_no_cache
面试题 10:Nginx 和 Apache 的区别?
答案要点:
| 特性 | Nginx | Apache |
|---|---|---|
| 架构 | 事件驱动、异步非阻塞 | 多进程/多线程 |
| 并发处理 | 高并发性能好 | 并发性能相对较低 |
| 内存占用 | 内存占用小 | 内存占用较大 |
| 配置 | 配置简洁 | 配置复杂 |
| 模块 | 模块化设计 | 丰富的模块生态 |
| 适用场景 | 高并发、反向代理、负载均衡 | 传统 Web 应用、动态内容 |
四、Redis 详解
4.1 什么是 Redis?
Redis(Remote Dictionary Server)是一个开源的内存数据结构存储系统,可以用作数据库、缓存和消息中间件。Redis 支持多种数据结构,如字符串、哈希、列表、集合、有序集合等。
4.2 Redis 核心概念
4.2.1 数据结构
String(字符串):最基本的数据类型,可以存储字符串、整数、浮点数Hash(哈希):键值对集合,适合存储对象List(列表):有序的字符串列表,支持双向操作Set(集合):无序的字符串集合,支持集合运算Sorted Set(有序集合):有序的字符串集合,每个元素关联一个分数
4.2.2 持久化机制
RDB(Redis Database):
快照方式,定期将数据保存到磁盘文件小,恢复快可能丢失最后一次快照后的数据 AOF(Append Only File):
追加日志方式,记录每个写操作数据更安全,文件较大恢复速度相对较慢
4.2.3 内存管理
过期策略:
定时删除:设置过期时间时创建定时器惰性删除:访问时检查是否过期定期删除:定期扫描过期键 淘汰策略:
:不淘汰,返回错误
noeviction:从所有键中淘汰最近最少使用
allkeys-lru:从过期键中淘汰最近最少使用
volatile-lru:从所有键中随机淘汰
allkeys-random:从过期键中随机淘汰
volatile-random:从过期键中淘汰最早过期
volatile-ttl
4.3 Redis 常用命令
4.3.1 String 操作
# 设置和获取
SET key value
GET key
# 批量操作
MSET key1 value1 key2 value2
MGET key1 key2
# 数值操作
INCR key
DECR key
INCRBY key increment
DECRBY key decrement
# 过期时间
EXPIRE key seconds
TTL key
4.3.2 Hash 操作
# 设置和获取
HSET key field value
HGET key field
# 批量操作
HMSET key field1 value1 field2 value2
HMGET key field1 field2
# 获取所有
HGETALL key
# 删除字段
HDEL key field
4.3.3 List 操作
# 添加元素
LPUSH key value
RPUSH key value
# 获取元素
LPOP key
RPOP key
# 获取范围
LRANGE key start stop
# 获取长度
LLEN key
4.3.4 Set 操作
# 添加元素
SADD key member
# 获取所有成员
SMEMBERS key
# 集合运算
SINTER key1 key2 # 交集
SUNION key1 key2 # 并集
SDIFF key1 key2 # 差集
# 删除成员
SREM key member
4.3.5 Sorted Set 操作
# 添加元素
ZADD key score member
# 获取排名
ZRANK key member
ZREVRANK key member
# 获取范围
ZRANGE key start stop
ZREVRANGE key start stop
# 获取分数
ZSCORE key member
4.4 Redis 高级特性
4.4.1 发布订阅(Pub/Sub)
# 发布消息
PUBLISH channel message
# 订阅频道
SUBSCRIBE channel1 channel2
# 取消订阅
UNSUBSCRIBE channel
# 模式订阅
PSUBSCRIBE pattern
4.4.2 事务
# 开始事务
MULTI
# 执行命令
SET key1 value1
SET key2 value2
# 提交事务
EXEC
# 取消事务
DISCARD
注意:Redis 事务不支持回滚,命令执行失败不会影响其他命令。
4.4.3 Lua 脚本
# 执行 Lua 脚本
EVAL "return redis.call('get', KEYS[1])" 1 key
# 加载脚本
SCRIPT LOAD "return redis.call('get', KEYS[1])"
# 执行已加载的脚本
EVALSHA sha1 1 key
4.4.4 管道(Pipeline)
# 批量执行命令,减少网络往返
PIPELINE
SET key1 value1
SET key2 value2
GET key1
EXEC
4.5 Redis 集群与高可用
4.5.1 主从复制
主节点(Master):处理写操作,复制数据到从节点从节点(Slave):复制主节点数据,处理读操作复制流程:
从节点连接主节点,发送 SYNC 命令主节点执行 BGSAVE 生成 RDB 文件主节点发送 RDB 文件给从节点主节点发送缓冲区中的写命令给从节点从节点执行命令,完成同步
4.5.2 哨兵模式(Sentinel)
作用:监控主从节点,自动故障转移功能:
监控:检查主从节点是否正常运行通知:当节点故障时通知管理员自动故障转移:主节点故障时选举新的主节点配置提供者:客户端连接时提供主节点地址
4.5.3 集群模式(Cluster)
分片:将数据分散到多个节点哈希槽:16384 个哈希槽,分配到各个节点节点通信:使用 Gossip 协议进行节点间通信故障转移:主节点故障时,从节点自动提升为主节点
4.6 Redis 性能优化
4.6.1 内存优化
使用合适的数据结构:根据场景选择最优数据结构设置过期时间:及时清理不需要的数据使用压缩:对于大 value,考虑压缩存储避免大 key:单个 key 的 value 不要过大
4.6.2 命令优化
批量操作:使用 MGET、MSET、Pipeline 等批量操作避免阻塞命令:避免使用 KEYS、SMEMBERS 等阻塞命令使用 SCAN:使用 SCAN 替代 KEYS 遍历键合理使用事务:避免事务中包含过多命令
4.6.3 网络优化
Pipeline:批量执行命令,减少网络往返连接池:使用连接池复用连接避免频繁连接:保持长连接,避免频繁创建连接
4.7 Redis 面试题
面试题 1:Redis 为什么快?
答案要点:
内存存储:数据存储在内存中,读写速度快单线程模型:避免上下文切换和锁竞争高效的数据结构:使用哈希表、跳表等高效数据结构I/O 多路复用:使用 epoll/kqueue 实现高效的网络 I/O优化的编码:针对不同数据采用不同的编码方式
面试题 2:Redis 的持久化机制有哪些?
答案要点:
RDB(快照):
定期将数据保存到磁盘文件小,恢复快可能丢失数据 AOF(追加日志):
记录每个写操作数据更安全文件较大,恢复较慢 混合持久化:RDB + AOF,结合两者优点
面试题 3:Redis 如何保证高可用?
答案要点:
主从复制:
主节点处理写操作从节点复制主节点数据,处理读操作实现读写分离 哨兵模式:
监控主从节点自动故障转移提供高可用 集群模式:
数据分片自动故障转移水平扩展
面试题 4:Redis 的内存淘汰策略有哪些?
答案要点:
noeviction:不淘汰,内存满时返回错误allkeys-lru:从所有键中淘汰最近最少使用volatile-lru:从过期键中淘汰最近最少使用allkeys-random:从所有键中随机淘汰volatile-random:从过期键中随机淘汰volatile-ttl:从过期键中淘汰最早过期
面试题 5:Redis 的过期策略是什么?
答案要点:
定时删除:设置过期时间时创建定时器(不常用,消耗 CPU)惰性删除:访问时检查是否过期,过期则删除定期删除:定期扫描过期键,删除过期键实际使用:Redis 采用惰性删除 + 定期删除的组合策略
面试题 6:Redis 事务的特点是什么?
答案要点:
原子性:事务中的命令要么全部执行,要么全部不执行不支持回滚:命令执行失败不会回滚,其他命令继续执行隔离性:事务执行期间不会被其他客户端打断一致性:事务执行前后数据保持一致持久性:取决于持久化配置
面试题 7:Redis 的缓存穿透、缓存击穿、缓存雪崩是什么?
答案要点:
缓存穿透:
定义:查询不存在的数据,绕过缓存直接查询数据库解决方案:
布隆过滤器过滤不存在的数据缓存空值,设置较短的过期时间
缓存击穿:
定义:热点 key 过期,大量请求直接访问数据库解决方案:
设置热点 key 永不过期使用互斥锁,只允许一个线程查询数据库
缓存雪崩:
定义:大量 key 同时过期,导致大量请求访问数据库解决方案:
设置不同的过期时间,避免同时过期使用多级缓存限流降级
面试题 8:Redis 的主从复制原理是什么?
答案要点:
全量复制:
从节点连接主节点,发送 SYNC 命令主节点执行 BGSAVE 生成 RDB 文件主节点发送 RDB 文件给从节点从节点加载 RDB 文件 增量复制:
主节点维护复制缓冲区主节点发送缓冲区中的写命令给从节点从节点执行命令,保持数据同步 部分重同步:从节点断线重连后,只同步断线期间的数据
面试题 9:Redis 集群如何实现数据分片?
答案要点:
哈希槽:16384 个哈希槽,分配到各个节点分片算法: 计算 key 所属的哈希槽节点管理:每个节点负责一部分哈希槽数据迁移:支持在线迁移哈希槽客户端路由:客户端根据 key 计算哈希槽,直接访问对应节点
CRC16(key) % 16384
面试题 10:Redis 和 Memcached 的区别?
答案要点:
| 特性 | Redis | Memcached |
|---|---|---|
| 数据类型 | 丰富(String、Hash、List、Set、Sorted Set) | 简单(只有 String) |
| 持久化 | 支持 RDB 和 AOF | 不支持 |
| 复制 | 支持主从复制 | 不支持 |
| 集群 | 支持集群模式 | 不支持 |
| 事务 | 支持事务 | 不支持 |
| 发布订阅 | 支持 | 不支持 |
| 性能 | 单线程,性能好 | 多线程,性能好 |
| 内存管理 | 支持多种淘汰策略 | LRU 淘汰 |
| 适用场景 | 缓存、消息队列、计数器等 | 纯缓存场景 |
五、4大核心中间件终极拆解(大白话 + 场景落地)
5.1 总纲:4个中间件的”分工边界”
很多新手觉得这4个中间件很乱,核心是没抓准「每个中间件的唯一核心定位」和「什么时候该用谁」。下面用「核心定位→解决痛点→实战场景→和其他中间件的区别」的逻辑,把每个中间件拆透,看完就能精准对应业务场景。
用一句话先把每个中间件的”专属职责”定死,避免混淆:
| 中间件 | 唯一核心定位 | 一句话记住 |
|---|---|---|
| Redis | 内存级高速数据操作 | 所有”要快、要临时存、要防并发冲突”的场景都找它 |
| Kafka | 海量消息的高吞吐传输 | 所有”消息多、要批量、不追求实时确认”的场景都找它 |
| Nginx | 请求入口的调度与安全 | 所有”用户请求进来的第一道关卡”的事都找它 |
| RabbitMQ | 精准消息的可靠投递 | 所有”消息要确认、要按规则发、怕丢/怕重”的业务场景都找它 |
简单记忆口诀:
要快、要缓存、要防并发 → Redis要海量、要批量、要削峰 → Kafka要入口、要分流、要安全 → Nginx要精准、要确认、要路由 → RabbitMQ
5.2 一、Redis:“速度狂魔”(核心是”快”和”原子性”)
5.2.1 核心定位再强化
Redis 是什么?
Redis 是「基于内存的键值存储中间件」本质是”把慢数据(硬盘)搬到快空间(内存)”同时提供原子操作,解决”慢”和”并发冲突”两大问题它不负责”消息传递”,只负责”数据存取”
打个比方:Redis 就像你桌上的”便签本”,常用的电话号码、重要提醒都写在上面,随手就能拿到,不用每次都去翻厚厚的通讯录(数据库)。
5.2.2 解决的核心痛点(没有 Redis 会怎样?)
痛点 1:数据库被压垮(慢)
场景:1000 个人同时查同一个商品详情,都去怼 MySQL结果:数据库直接卡爆,响应时间从 0.1 秒变成 10 秒Redis 解决:第一次查 MySQL 后存 Redis,后续 999 个人直接读 Redis,0.01 秒返回
痛点 2:并发冲突导致超卖(并发冲突)
场景:秒杀活动,1000 人抢 10 个商品,多个人同时抢到同一个结果:库存显示 -5,超卖了 5 个商品Redis 解决:用分布式锁(SETNX),只有 1 个人能抢到锁,其他人排队
痛点 3:计数不准(原子性)
场景:统计文章阅读量,1000 人同时点”阅读”结果:最后数出来少了 200 次(并发导致计数丢失)Redis 解决:用 INCR 命令(原子操作),绝对不会数错
5.2.3 实战场景(只做 Redis 能做的事)
场景 1:缓存高频数据(最核心)
做法:
第一次请求:用户查商品 → MySQL 查询(1秒)→ 存 Redis(设置1小时过期)
后续请求:用户查商品 → Redis 查询(0.01秒)→ 直接返回
核心价值:
MySQL 的 QPS 从 1000 降到 1,压力大减用户查商品从”1 秒”变成”0.01 秒”,体验提升 100 倍
代码示例:
# 伪代码示例
def get_product(product_id):
# 先查 Redis
product = redis.get(f"product:{product_id}")
if product:
return product # 缓存命中,直接返回
# Redis 没有,查数据库
product = db.query(f"SELECT * FROM products WHERE id={product_id}")
# 存到 Redis,1小时过期
redis.setex(f"product:{product_id}", 3600, product)
return product
场景 2:分布式锁(防并发冲突)
做法:
秒杀流程:
1. 用户要下单,先去 Redis 抢"锁"(SETNX lock:product_123 user_456)
2. 只有 1 个人能抢到锁(返回 1),其他人抢不到(返回 0)
3. 抢到锁的人才能扣库存、下单
4. 下单完成后释放锁(DEL lock:product_123)
核心价值:
彻底避免”多人抢同一商品”,解决超卖问题这是 Kafka/RabbitMQ/Nginx 都做不了的事(它们不管数据操作)
代码示例:
# 伪代码示例
def seckill(product_id, user_id):
lock_key = f"lock:product:{product_id}"
# 尝试抢锁,设置 10 秒过期(防止死锁)
if redis.set(lock_key, user_id, nx=True, ex=10):
try:
# 抢到锁,检查库存
stock = redis.get(f"stock:{product_id}")
if stock > 0:
# 扣库存
redis.decr(f"stock:{product_id}")
# 创建订单
create_order(product_id, user_id)
return "秒杀成功"
else:
return "库存不足"
finally:
# 释放锁
redis.delete(lock_key)
else:
return "抢购失败,请重试"
场景 3:计数器/限流(原子操作)
做法:
计数场景:
- 每有人看文章,Redis 执行 INCR article:123(原子操作,不会数错)
- 限制用户 1 分钟只能查 10 次,Redis 记 INCR user:456,超过 10 就拒绝
核心价值:
计数绝对准确(原子操作保证)限流简单高效(不需要复杂的算法)Kafka/RabbitMQ 是传消息的,没法做”实时计数”
代码示例:
# 伪代码示例:文章阅读量统计
def view_article(article_id):
# 原子操作,计数 +1
views = redis.incr(f"article:views:{article_id}")
return views
# 限流:1 分钟最多 10 次
def check_rate_limit(user_id):
key = f"rate_limit:{user_id}"
count = redis.incr(key)
if count == 1:
redis.expire(key, 60) # 设置 60 秒过期
if count > 10:
return False # 超过限制
return True
5.2.4 绝对不要用 Redis 做的事
❌ 不要用 Redis 存海量数据
原因:内存贵,存 100G 数据成本比 MySQL 高 10 倍正确做法:只存热点数据,冷数据放 MySQL
❌ 不要用 Redis 做消息传递
原因:虽然支持发布订阅,但没有确认机制,丢了都不知道正确做法:用 Kafka 或 RabbitMQ
❌ 不要用 Redis 做请求分流
原因:它管数据,不管请求正确做法:用 Nginx
5.3 二、Kafka:“海量搬运工”(核心是”吞吐”和”持久化”)
5.3.1 核心定位再强化
Kafka 是什么?
Kafka 是「分布式日志型消息队列」本质是”用硬盘的顺序写特性,实现海量消息的低成本存储和传输”它不负责”数据操作”,只负责”消息传递”优先保证”能装、能快传”,不优先保证”精准确认”
打个比方:Kafka 就像”快递分拣中心”,每天处理百万件包裹,先把包裹都存起来(持久化),然后慢慢分拣发送。它不关心每个包裹是否精确送达,只关心”能装下、能快速处理”。
5.3.2 解决的核心痛点(没有 Kafka 会怎样?)
痛点 1:流量峰值压垮系统(削峰填谷)
场景:秒杀活动 1 分钟产生 10 万条下单请求,直接发给支付系统结果:支付系统瞬间崩了,处理不过来Kafka 解决:10 万条请求先扔进 Kafka,支付系统按自己的能力(每秒 1000 条)慢慢取
痛点 2:海量数据归集困难(日志归集)
场景:100 台服务器产生的日志,要统一分析结果:挨个服务器查日志要半天,效率极低Kafka 解决:所有服务器把日志实时发给 Kafka,分析系统统一从 Kafka 取
痛点 3:系统耦合导致连锁故障(解耦)
场景:下单后要同时通知支付、物流、积分系统,只要其中一个系统卡了,下单就失败结果:系统之间强耦合,一个崩了全部崩Kafka 解决:下单系统只发消息到 Kafka,其他系统各自从 Kafka 取,互不影响
5.3.3 实战场景(只做 Kafka 能做的事)
场景 1:削峰填谷(扛流量峰值)
做法:
流量高峰:
用户下单 → Kafka(10万条消息存起来)→ 支付系统(每秒处理1000条,慢慢取)
流量低谷:
Kafka 里的消息慢慢被处理完,系统恢复正常
核心价值:
支付系统不会被峰值压垮Kafka 像”蓄水池”,先把高峰的水存起来,再慢慢放这是 RabbitMQ 做不到的(RabbitMQ 只有万级 QPS,Kafka 有百万级 QPS)
图解:
高峰时段:████████████████ (10万请求/分钟)
↓ 存入 Kafka
Kafka: [████████████████] (消息堆积)
↓ 慢慢消费
支付系统:███ (1000请求/秒,稳定处理)
场景 2:日志归集(海量数据传输)
做法:
100台服务器 → 实时发送日志到 Kafka 的"日志主题" → 日志分析系统从 Kafka 取日志分析
核心价值:
服务器只负责发日志,不用管分析就算分析系统崩了,日志也存在 Kafka 里,不会丢支持多个分析系统同时消费(比如实时分析和离线分析)
代码示例:
# 伪代码示例:服务器发送日志
def send_log(log_message):
kafka_producer.send("server-logs", log_message)
# 日志分析系统消费日志
def analyze_logs():
for message in kafka_consumer.consume("server-logs"):
analyze(message)
场景 3:大数据传输(批量处理)
做法:
用户行为数据(点击、浏览、下单)→ 实时发 Kafka → 大数据平台批量从 Kafka 取数据做分析
核心价值:
百万级 QPS 的消息传输,Kafka 能轻松扛住这是 RabbitMQ 做不到的(RabbitMQ 只有万级 QPS)支持批量消费,提高处理效率
5.3.4 绝对不要用 Kafka 做的事
❌ 不要用 Kafka 做”需要消息确认”的业务
原因:Kafka 没有 ACK 确认机制,没法确认消息是否被成功处理例子:下单发优惠券,Kafka 没法确认优惠券是否发成功正确做法:用 RabbitMQ(有 ACK 确认)
❌ 不要用 Kafka 做”按规则精准路由”的消息
原因:Kafka 的路由能力弱,只能按 Topic 和 Partition 路由例子:只给北京用户发通知,Kafka 做不到精准路由正确做法:用 RabbitMQ(支持复杂的路由规则)
❌ 不要用 Kafka 做”实时计数/缓存”
原因:它是传消息的,不是存数据的正确做法:用 Redis
5.4 三、Nginx:“门卫大哥”(核心是”入口调度”和”安全”)
5.4.1 核心定位再强化
Nginx 是什么?
Nginx 是「反向代理/负载均衡中间件」本质是”所有用户请求的唯一入口”它不负责”数据操作”,也不负责”消息传递”只负责”请求的调度、安全、静态资源返回”
打个比方:Nginx 就像”公司前台”,所有访客(用户请求)都要先经过前台,前台决定:
这个访客是不是坏人(安全防护)该找哪个部门(负载均衡)要的文件直接给(静态资源)要办业务再转给后台(反向代理)
5.4.2 解决的核心痛点(没有 Nginx 会怎样?)
痛点 1:用户不知道访问哪个服务器(反向代理)
场景:用户访问 www.xxx.com,不知道该找后端哪台服务器结果:直接输 IP 又记不住,用户体验差Nginx 解决:用户只访问 Nginx 的域名,Nginx 把请求转发给后端服务器
痛点 2:单点故障导致服务不可用(负载均衡)
场景:所有请求都怼到一台服务器结果:服务器崩了网站就挂了Nginx 解决:Nginx 把请求分给多台服务器,一台崩了其他还能用
痛点 3:恶意攻击打垮服务器(限流/防护)
场景:有人恶意刷请求,每秒发 1 万次结果:直接打垮后端服务器Nginx 解决:Nginx 限制单个 IP 每秒最多 10 次请求,把攻击拦在门外
痛点 4:静态资源访问慢(静态资源托管)
场景:访问一张图片,也要走后端服务器结果:速度慢,后端压力大Nginx 解决:图片、CSS、JS 文件存在 Nginx 上,直接返回,不用走后端
5.4.3 实战场景(只做 Nginx 能做的事)
场景 1:反向代理(隐藏后端)
做法:
用户访问:www.xxx.com → Nginx(用户只看到域名)
Nginx 转发:→ 后端服务器 A(192.168.1.10)
→ 后端服务器 B(192.168.1.11)
→ 后端服务器 C(192.168.1.12)
用户完全不知道后端有哪些服务器,更安全
核心价值:
后端服务器地址不暴露,更安全用户不用记 IP,只记域名后端服务器可以随时更换,用户无感知
配置示例:
server {
listen 80;
server_name www.xxx.com;
location / {
proxy_pass http://backend; # 转发到后端
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
upstream backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
场景 2:负载均衡(请求分流)
做法:
Nginx 按"权重"把请求分给 A/B/C 服务器:
- A 服务器权重 3(性能好)
- B 服务器权重 2(性能中等)
- C 服务器权重 1(性能差)
6 个请求分配:
- A 接 3 个请求
- B 接 2 个请求
- C 接 1 个请求
核心价值:
单台服务器崩了,Nginx 会自动把请求分给其他服务器网站不会挂,高可用可以根据服务器性能分配请求
配置示例:
upstream backend {
server 192.168.1.10:8080 weight=3; # 权重 3
server 192.168.1.11:8080 weight=2; # 权重 2
server 192.168.1.12:8080 weight=1; # 权重 1
server 192.168.1.13:8080 backup; # 备份服务器
}
场景 3:静态资源托管(提速)
做法:
网站的图片、CSS、JS 文件存在 Nginx 服务器上
用户访问这些文件时,Nginx 直接返回,不用麻烦后端
核心价值:
图片加载速度从 1 秒变成 0.1 秒后端服务器不用处理无关请求,压力大减可以设置缓存,进一步提高速度
配置示例:
server {
location /static/ {
alias /var/www/static/; # 静态文件目录
expires 30d; # 缓存 30 天
add_header Cache-Control "public, immutable";
}
}
场景 4:安全防护(防攻击)
做法:
Nginx 限制单个 IP 每秒最多 10 次请求
拦截恶意刷请求的 IP
配置 HTTPS 证书,所有请求加密
核心价值:
后端服务器不用管防护,Nginx 把大部分攻击拦在门外限流、防 DDoS、HTTPS 加密,一站式解决
配置示例:
# 限流配置
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api_limit burst=20 nodelay; # 限流
proxy_pass http://backend;
}
}
5.4.4 绝对不要用 Nginx 做的事
❌ 不要用 Nginx 做”数据缓存/计数”
原因:它管请求,不管数据正确做法:用 Redis
❌ 不要用 Nginx 做”消息传递”
原因:它不是消息队列正确做法:用 Kafka 或 RabbitMQ
❌ 不要用 Nginx 做”业务逻辑处理”
原因:比如计算订单金额,Nginx 做不了正确做法:用后端应用服务器
5.5 四、RabbitMQ:“精准快递员”(核心是”可靠投递”和”精准路由”)
5.5.1 核心定位再强化
RabbitMQ 是什么?
RabbitMQ 是「AMQP 协议的消息队列」本质是”面向业务的精准消息传递”它不负责”数据操作”,也不负责”请求调度”只负责”消息的精准、可靠传递”
打个比方:RabbitMQ 就像”顺丰快递”,每个包裹(消息)都要:
确认签收(ACK 确认)按地址精准投递(路由规则)投递失败要重试(死信队列)不能丢件(消息持久化)
5.5.2 解决的核心痛点(没有 RabbitMQ 会怎样?)
痛点 1:消息丢失导致业务失败(消息确认)
场景:下单后发优惠券,优惠券系统崩了结果:优惠券就漏发了,用户投诉RabbitMQ 解决:优惠券系统取消息后发 ACK,RabbitMQ 才删消息;如果系统崩了,消息会重新发给其他实例
痛点 2:消息路由效率低(精准路由)
场景:要给”北京的 VIP 用户”发通知,只能挨个系统发结果:效率低,资源浪费RabbitMQ 解决:设置主题路由,只有”北京 + VIP”的用户消息,才会发到对应队列
痛点 3:失败消息无法处理(死信队列)
场景:消息处理失败,不知道为啥失败,也没法重试结果:问题无法排查,业务受影响RabbitMQ 解决:失败消息进死信队列,可以查看、重试、手动处理
5.5.3 实战场景(只做 RabbitMQ 能做的事)
场景 1:业务消息的可靠投递(确认机制)
做法:
下单系统 → 发送"发优惠券"消息到 RabbitMQ → 优惠券系统取消息
→ 处理完成后发 ACK 确认 → RabbitMQ 才删消息
→ 如果优惠券系统崩了,RabbitMQ 会把消息重新发给其他实例
核心价值:
优惠券必发,不会漏发/重发这是 Kafka 做不到的(Kafka 没有 ACK 确认机制)保证消息至少被消费一次(At-Least-Once)
代码示例:
# 伪代码示例:发送消息
def send_coupon_message(user_id, order_id):
message = {
"user_id": user_id,
"order_id": order_id,
"coupon_type": "new_user"
}
rabbitmq.publish("coupon_queue", message)
# 消费消息
def consume_coupon_message():
def callback(ch, method, properties, body):
try:
# 处理消息
send_coupon(body)
# 处理成功,发送 ACK
ch.basic_ack(delivery_tag=method.delivery_tag)
except Exception as e:
# 处理失败,拒绝消息(可以重新入队)
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
rabbitmq.consume("coupon_queue", callback, auto_ack=False)
场景 2:消息的精准路由(按规则发)
做法:
给 RabbitMQ 的交换机设置"主题路由":
- 只有"北京 + VIP"的用户消息 → 发到"北京 VIP 用户队列"
- 只有"上海 + 普通"的用户消息 → 发到"上海普通用户队列"
- 所有"VIP"用户消息 → 发到"VIP 用户队列"
核心价值:
不用给所有用户发通知,只给目标用户发节省资源,提高效率这是 Kafka 做不到的(Kafka 路由能力弱)
配置示例:
# 伪代码示例:主题路由
# 交换机类型:Topic Exchange
# 路由键规则:
# - beijing.vip.* → 北京 VIP 用户队列
# - shanghai.normal.* → 上海普通用户队列
# - *.vip.* → 所有 VIP 用户队列
def send_notification(user_location, user_level, message):
routing_key = f"{user_location}.{user_level}.notification"
rabbitmq.publish("notification_exchange", routing_key, message)
场景 3:死信队列(问题消息处理)
做法:
优惠券消息处理失败(比如用户不存在)
→ 消息被移到"死信队列"
→ 运维人员查看死信队列,手动处理
→ 可以重试、修复、或者丢弃
核心价值:
不会丢消息,还能排查问题这是 Kafka 没有的功能(Kafka 没有死信队列)提高系统的可观测性和可维护性
配置示例:
# 伪代码示例:死信队列配置
# 队列配置
queue_config = {
"queue_name": "coupon_queue",
"dead_letter_exchange": "dlx_exchange", # 死信交换机
"dead_letter_routing_key": "coupon_dlq" # 死信队列
}
# 处理死信消息
def handle_dead_letter_message():
for message in rabbitmq.consume("coupon_dlq"):
# 查看失败原因
print(f"失败消息: {message}")
# 手动处理或重试
retry_or_fix(message)
5.5.4 绝对不要用 RabbitMQ 做的事
❌ 不要用 RabbitMQ 做”海量消息传输”
原因:RabbitMQ 只有万级 QPS,处理不了百万级消息例子:百万级 QPS 的日志,RabbitMQ 会卡死正确做法:用 Kafka(百万级 QPS)
❌ 不要用 RabbitMQ 做”数据缓存/计数”
原因:它是传消息的,不是存数据的正确做法:用 Redis
❌ 不要用 RabbitMQ 做”请求调度/负载均衡”
原因:它不管用户请求正确做法:用 Nginx
5.6 五、4个中间件的协同场景(秒杀案例,彻底理清)
用”电商秒杀 100 台手机”的完整流程,看 4 个中间件怎么配合:
5.6.1 完整流程图
用户发起请求
↓
【Nginx - 门卫大哥】
├─ 拦截恶意 IP(限流:每个 IP 每秒 1 次)
├─ 负载均衡(按权重分给 3 台下单服务器)
└─ 静态资源直接返回(秒杀页面图片)
↓
下单服务器处理请求
↓
【Redis - 速度狂魔】
├─ 查库存(100 台 → 99 台),库存为 0 直接拒绝
├─ 抢分布式锁(SETNX),抢到才能继续
└─ 记录用户秒杀次数(超过 1 次拒绝)
↓
库存扣减成功
↓
【Kafka - 海量搬运工】
└─ 把"用户下单"消息发给 Kafka 的"秒杀订单主题"
(削峰填谷:10 万条消息先存起来,支付系统慢慢处理)
↓
支付系统处理
↓
支付完成后
↓
【RabbitMQ - 精准快递员】
├─ 支付系统把"支付成功"消息发给 RabbitMQ
├─ RabbitMQ 按规则路由:
│ ├─ "优惠券队列" → 优惠券系统(发 ACK 确认,确保必发)
│ └─ "物流队列" → 物流系统(发 ACK 确认,确保必发)
└─ 失败消息进死信队列(可以查看、重试)
5.6.2 各中间件职责详解
1. Nginx 的职责(请求入口)
✅ 限流:防止恶意刷请求✅ 负载均衡:把请求分给 3 台服务器✅ 静态资源:直接返回图片、CSS、JS❌ 不管:数据操作、消息传递
2. Redis 的职责(数据操作)
✅ 缓存库存:快速查询,减少数据库压力✅ 分布式锁:防止超卖✅ 计数器:限制用户秒杀次数❌ 不管:消息传递、请求调度
3. Kafka 的职责(海量消息)
✅ 削峰填谷:10 万条下单消息先存起来✅ 解耦:下单系统和支付系统解耦✅ 持久化:消息存在 Kafka,不会丢❌ 不管:消息确认、精准路由
4. RabbitMQ 的职责(精准消息)
✅ 可靠投递:支付成功后发优惠券,必须确认✅ 精准路由:按规则发给优惠券队列和物流队列✅ 死信队列:失败消息可以查看、重试❌ 不管:海量消息传输、数据操作
5.6.3 为什么这样设计?
为什么用 Nginx 不用 Redis 做限流?
Nginx 是请求入口,在这里限流最合适Redis 是数据存储,不适合做请求拦截
为什么用 Redis 不用 Kafka 做分布式锁?
Redis 支持原子操作(SETNX),适合做锁Kafka 是消息队列,不支持数据操作
为什么用 Kafka 不用 RabbitMQ 做削峰?
Kafka 支持百万级 QPS,能扛住流量峰值RabbitMQ 只有万级 QPS,会被压垮
为什么用 RabbitMQ 不用 Kafka 发优惠券?
RabbitMQ 有 ACK 确认,能保证优惠券必发Kafka 没有确认机制,可能漏发
5.7 最终总结(新手必背)
5.7.1 记核心定位
| 要解决的问题 | 用哪个中间件 | 核心原因 |
|---|---|---|
| 要快、要缓存、要防并发 | Redis | 内存存储,原子操作 |
| 要海量、要批量、要削峰 | Kafka | 高吞吐,持久化 |
| 要入口、要分流、要安全 | Nginx | 请求调度,安全防护 |
| 要精准、要确认、要路由 | RabbitMQ | ACK 确认,精准路由 |
5.7.2 记边界职责
数据 vs 消息 vs 请求:
Redis 管”数据”(存/取/计数)Kafka/RabbitMQ 管”消息”(传)Nginx 管”请求”(进/分/防)
简单判断方法:
如果是”存数据、取数据、计数” → Redis如果是”传消息、发通知” → Kafka 或 RabbitMQ如果是”用户请求、访问网站” → Nginx
5.7.3 记搭配使用
标准搭配流程:
所有用户请求 → 先过 Nginx(限流、负载均衡、静态资源)
↓
高频数据操作 → 找 Redis(缓存、锁、计数)
↓
海量消息传输 → 用 Kafka(削峰、日志、大数据)
↓
业务消息确认 → 用 RabbitMQ(可靠投递、精准路由)
5.7.4 快速决策树
遇到问题,按这个流程判断:
问题是什么?
├─ 用户请求相关(访问网站、API 调用)
│ └─ → Nginx(反向代理、负载均衡、限流)
│
├─ 数据操作相关(存数据、取数据、计数、锁)
│ └─ → Redis(缓存、分布式锁、计数器)
│
└─ 消息传递相关(发消息、通知、事件)
├─ 海量消息(百万级 QPS、日志、大数据)
│ └─ → Kafka(高吞吐、持久化)
│
└─ 业务消息(需要确认、精准路由、可靠投递)
└─ → RabbitMQ(ACK 确认、路由规则、死信队列)
5.7.5 常见误区避免
❌ 误区 1:用 Redis 做消息队列
原因:Redis 的发布订阅没有确认机制,消息可能丢失正确:用 Kafka 或 RabbitMQ
❌ 误区 2:用 Kafka 做业务消息确认
原因:Kafka 没有 ACK 确认机制正确:用 RabbitMQ
❌ 误区 3:用 Nginx 做数据缓存
原因:Nginx 不管数据,只管请求正确:用 Redis
❌ 误区 4:用 RabbitMQ 做海量日志传输
原因:RabbitMQ 只有万级 QPS,会被压垮正确:用 Kafka
这样拆分后,每个中间件的”专属地盘”就清晰了,再也不会乱。核心是先判断”要解决的问题是数据、消息、还是请求”,再对应找中间件。
总结
本文档详细介绍了 Kafka、RabbitMQ、Nginx 和 Redis 四个常用中间件的核心概念、配置、高级特性和常见面试题。这些中间件在现代分布式系统中发挥着重要作用:
Kafka:高吞吐量的消息流处理平台,适合日志收集、流处理等场景RabbitMQ:功能丰富的消息队列,适合任务分发、RPC 等场景Nginx:高性能的 Web 服务器和反向代理,适合负载均衡、静态资源服务等场景Redis:高性能的内存数据库,适合缓存、计数器、消息队列等场景
掌握这些中间件的原理和使用方法,对于构建高性能、高可用的分布式系统至关重要。
