🏗️ 基础设施架构手册

从单机到分布式:MySQL主从复制 · Redis集群 · 分库分表 · 负载均衡 · 多机部署

MySQL主从读写分离MySQL MHA Redis主从Redis哨兵Redis Cluster ShardingSphere分库分表 Nginx负载均衡Docker Compose 分布式Session配置中心
学习路径1. 理解架构图解+原理2. 看配置实战配置+代码3. 踩坑与面试高频考点+FAQ
每个主题:架构图 → 配置实战 → 面试速记

① MySQL 主从复制 — 一主多从的数据同步

为什么需要?生产环境单台MySQL扛不住高峰期QPS(读写都打到一台机器),而且一旦宕机数据就全丢了。主从复制解决两个问题:读性能扩展(读请求分散到从库)和数据冗余备份(从库是主库的实时副本)。

🏗️ 架构图:MySQL 主从复制

MySQL 主从复制架构
应用服务
▼ 写操作(INSERT/UPDATE/DELETE)
Master 主库 (10.0.0.1)
binlog(二进制日志)记录所有变更
order_info: INSERT id=101
product: UPDATE price=18
▼ binlog dump(推送binlog)
Slave 从库1 (10.0.0.2)
IO线程 → 接收binlog → 写relay log
SQL线程 → 执行relay log → 数据同步
Slave 从库2 (10.0.0.3)
IO线程 → 接收binlog → 写relay log
SQL线程 → 执行relay log → 数据同步
▲ 读操作(SELECT)可分散到从库
报表/查询服务
核心机制:Master 把所有数据变更写入 binlog(二进制日志文件),Slave 有两个线程协作:IO线程负责从Master拉取binlog写入本地的 relay log(中继日志),SQL线程负责读取relay log并执行SQL来同步数据。两者是异步的,所以主从之间存在延迟。

📋 主从复制的工作流程(3个线程)

Master binlog dump 线程 Slave IO线程 relay log Slave SQL线程 从库数据
第一步:Master上的操作写入 binlog
第二步:Slave 的 IO线程 连接 Master,请求 binlog
第三步:Master 的 binlog dump线程 读取 binlog 发给 Slave
第四步:Slave 的 IO线程 把收到的 binlog 写入 relay log(中继日志)
第五步:Slave 的 SQL线程 读取 relay log 并执行 → 数据同步完成

💻 具体配置

# ===== Master 配置 (my.cnf) ===== [mysqld] server-id = 1 # 服务器唯一ID log-bin = mysql-bin # 开启binlog binlog-format = ROW # binlog格式(ROW/STATEMENT/MIXED) expire-logs-days = 7 # binlog保留7天 gtid-mode = ON # 开启GTID(全局事务ID) enforce-gtid-consistency = ON # 创建复制账号 mysql> CREATE USER 'repl'@'10.0.0.%' IDENTIFIED BY 'Repl@123'; mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'10.0.0.%'; mysql> FLUSH PRIVILEGES;
# ===== Slave 配置 (my.cnf) ===== [mysqld] server-id = 2 # 不能和Master相同! relay-log = relay-bin # 中继日志 read-only = ON # 从库只读 gtid-mode = ON enforce-gtid-consistency = ON # 在从库上配置主库信息并启动复制 mysql> CHANGE MASTER TO MASTER_HOST='10.0.0.1', MASTER_USER='repl', MASTER_PASSWORD='Repl@123', MASTER_AUTO_POSITION=1; # 使用GTID自动定位 mysql> START SLAVE; # 检查复制状态 mysql> SHOW SLAVE STATUS\G Slave_IO_Running: Yes # ✅ IO线程在跑 Slave_SQL_Running: Yes # ✅ SQL线程在跑 Seconds_Behind_Master: 0 # ✅ 延迟0秒 Last_IO_Error: # ✅ 没有错误

📊 binlog 三种格式对比

格式记录内容安全性适用场景
STATEMENT记录SQL语句原文低(某些函数如NOW()、UUID()在从库结果不同)日志体积小,适合简单SQL
ROW记录每行的变更前后值最高(精确到行级别)生产环境推荐,日志较大
MIXED默认STATEMENT,涉及不安全函数时自动切换ROW折中方案
GTID 是什么?
GTID = Global Transaction ID(全局事务ID),格式:server_uuid:transaction_id,比如 3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5
没有GTID时,从库崩溃重启后需要手动指定"从binlog的哪个文件、哪个位置继续复制"。有了GTID,每个事务有全局唯一编号,从库自动从最后一个已执行的事务之后继续,无需人工干预。

⚠️ 主从延迟怎么处理?

为什么会延迟?主从复制本质是异步的:Master 写完就返回了,不管从库有没有跟上。以下情况会加大延迟:

  • 主库并发写入量大,从库单线程回放来不及(MySQL 5.7之前)
  • 大事务(一个事务删了100万条数据,从库要逐条回放)
  • 从库机器性能差(磁盘慢/CPU弱)
  • 网络抖动导致binlog传输慢

生产环境解决方案:

  • 多线程复制(MySQL 5.7+ slave_parallel_workers,按库并行回放relay log)
  • 读写分离时强制走主库:对一致性要求高的查询(如"下单后立刻查订单详情")直接查主库,不查从库
  • 半同步复制:至少一个从库确认收到binlog后才返回成功(比异步安全,比全同步快)
  • 业务层延迟双读:写完主库后sleep一小段时间(如200ms)再读从库,给从库追平的时间

📊 异步 vs 半同步 vs 全同步

1️⃣ 异步复制(默认)
Master写入立即返回✅
Slave后台慢慢同步
快但不安全(Master挂了未同步的数据会丢)
2️⃣ 半同步复制
Master写入等1个Slave确认返回✅
折中方案(至少保证一个从库收到binlog)
3️⃣ 全同步复制
Master写入等所有Slave完成返回✅
最安全但最慢(实际很少用)

② 读写分离 — 写走主库、读走从库

为什么需要?主从搭好了,但应用代码里所有SQL都打到同一个数据源。读写分离的核心思路:INSERT/UPDATE/DELETE 路由到Master,SELECT 路由到Slave,从而把读压力分散到多台从库上。

🏗️ 架构图:读写分离

应用代码
▼ INSERT/UPDATE/DELETE
Master 主库
写操作
应用代码
▼ SELECT
Slave 1
读请求
Slave 2
读请求
读写分离的核心矛盾:写完主库后立刻去从库读,从库可能还没同步完 → 读到旧数据。典型场景:用户下单后刷新订单列表,发现订单没出现。解决方案:对一致性要求高的查询强行走主库

💻 实现方式对比

方式原理优缺点
代码层面AOP自定义注解 + AOP切面,根据方法名(get*/list*走从库,insert*/update*走主库)动态切换数据源灵活但侵入代码,需要维护ThreadLocal
ShardingSphere-JDBC代理JAR包嵌入应用,自动识别SQL类型路由(读SQL→Slave,写SQL→Master)对代码无侵入,配置即生效,新项目推荐
Spring AbstractRoutingDataSourceSpring原生API,动态切换底层数据源常见方案,需配合ThreadLocal传递路由key

💻 Spring + AOP 代码示例

public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { // 从ThreadLocal获取当前应该用主库还是从库 return DataSourceContextHolder.get(); // "master" 或 "slave" } } // AOP 切面:读方法走从库,写方法走主库 @Aspect @Component public class DataSourceAspect { @Before("execution(* com.example.service.*.select*(..)) || " + "execution(* com.example.service.*.get*(..)) || " + "execution(* com.example.service.*.list*(..))") public void setSlave() { DataSourceContextHolder.set("slave"); // 读 → 从库 } @Before("execution(* com.example.service.*.insert*(..)) || " + "execution(* com.example.service.*.update*(..)) || " + "execution(* com.example.service.*.delete*(..))") public void setMaster() { DataSourceContextHolder.set("master"); // 写 → 主库 } }

③ MySQL MHA 高可用 — 主库挂了自动切换

为什么需要?凌晨3点主库宕机,如果没有自动切换机制,所有写操作全部失败,业务停摆。MHA(Master High Availability)是一个监控+自动切换方案:主库一挂,它自动把数据最新的从库提升为新主库,整个过程10-30秒。

🏗️ 架构图:MHA 故障切换

😱 故障前:Master正常运行
✅ Master
Slave1
Slave2
MHA Manager
持续监控Master状态
💥 故障!Master宕机了
❌ Master 挂了
Slave1
Slave2
MHA Manager
检测到故障!
✅ MHA自动切换:Slave1提升为新Master
✅ 新Master
(原Slave1)
Slave2
→ 指向新Master
⬜ 旧Master
等待修复
✅ MHA
切换完成(10-30秒)
MHA 工作原理:
1. 持续监控:Manager节点每隔几秒ping Master,连续失败则判定故障
2. 数据补齐:从旧Master的binlog中挽救还没同步到从库的数据(如果旧Master磁盘还活着,SSH过去捞binlog)
3. 选举新主:从所有Slave中选一个数据最新的提升为新Master
4. 重建拓扑:其他Slave改为复制新Master
5. VIP漂移:应用连接的VIP自动漂移到新Master,应用无需改配置
# /etc/mha/app1.cnf — MHA 应用配置
[server default]
manager_workdir=/var/log/mha/app1
manager_log=/var/log/mha/app1/manager.log
user=mha_monitor
password=mha_pass
repl_user=repl
repl_password=repl_pass
ssh_user=root

[server1]
hostname=10.0.0.1
port=3306
candidate_master=1   # 可提升为主库

[server2]
hostname=10.0.0.2
port=3306
candidate_master=1

[server3]
hostname=10.0.0.3
port=3306
no_master=1          # 永远不提升为主库(纯从库)

# 启动 MHA Manager
# masterha_manager --conf=/etc/mha/app1.cnf &
# 检查状态
# masterha_check_status --conf=/etc/mha/app1.cnf
生产建议:MHA是MySQL生态最成熟的高可用方案之一,但现在更多团队转向 Orchestrator(GitHub出品,支持自动拓扑发现)或云厂商自带的RDS高可用。不管用什么工具,核心都是:监控 → 选举 → 切换 → 通知

④ Redis 部署模式演进 — 从单机到集群

为什么需要演进?单机Redis挂了缓存全丢 → 引入主从做备份;主挂了要手动切 → 引入哨兵自动切换;单机内存放不下/扛不住高并发 → 引入Cluster分片水平扩展。

📊 四种部署模式对比

Redis 部署模式演进路线
① 单机模式 ② 主从模式 ③ 哨兵模式 ④ Cluster模式
① 单机模式
一台Redis搞定
适合:开发/测试
❌ 单点故障
② 主从模式
一主多从,读写分离
适合:读多写少
⚠️ 主挂了要手动切
③ 哨兵模式
自动监控+故障切换
适合:要高可用
✅ 主挂了自动切
④ Cluster模式
分片+高可用
适合:大数据量/高并发
✅ 支持水平扩展
模式解决了什么问题遗留问题典型场景
单机单点故障,挂了全丢开发/测试环境
主从数据备份 + 读扩展Master挂了需人工干预读多写少的小型项目
哨兵自动故障转移单Master写性能瓶颈,无法水平扩展需要高可用但数据量不大的场景
Cluster数据分片存储 + 水平扩展 + 高可用运维复杂度高,跨槽操作受限大数据量/高并发的生产环境

⑤ Redis 哨兵模式(Sentinel)— 自动故障转移

🏗️ 架构图:Redis Sentinel

Redis Sentinel 哨兵架构
应用服务
▼ 先问哨兵:"谁是Master?"
Sentinel 1
监控+投票
Sentinel 2
监控+投票
Sentinel 3
监控+投票
▲ 3个哨兵互相监控,少数服从多数
✅ Redis Master
读写
Redis Slave1
只读
Redis Slave2
只读

📋 哨兵的工作流程

1. 监控 2. 发现故障 3. 投票 4. 故障转移 5. 通知

第一步:监控 — 每个Sentinel每秒向Master/Slave/其他Sentinel发PING命令

第二步:主观下线(SDOWN) — 如果Master超过 down-after-milliseconds 没回应,单个Sentinel认为它挂了

第三步:客观下线(ODOWN) — 超过半数Sentinel都认为Master挂了,投票确认故障

第四步:故障转移 — 选举一个Slave提升为新Master,其他Slave改为复制新Master

第五步:通知 — 告诉应用新Master的地址

为什么需要多个Sentinel?网络抖动可能导致误判(比如Master只是暂时忙到没响应PING)。多个Sentinel投票可以避免"一个哨兵误判就切换"。生产环境至少部署3个Sentinel(奇数,保证投票有结果)。

💻 配置示例

# ===== sentinel.conf(3个哨兵都配置) ===== port 26379 sentinel monitor mymaster 10.0.0.1 6379 2 # 监控的Master,quorum=2(2票同意才故障转移) sentinel down-after-milliseconds mymaster 5000 # 5秒无响应判定下线 sentinel failover-timeout mymaster 10000 # 故障转移超时10秒 sentinel parallel-syncs mymaster 1 # 同时同步的Slave数量 # ===== Spring Boot 配置 ===== # application.yml spring: redis: sentinel: master: mymaster nodes: - 10.0.0.1:26379 - 10.0.0.2:26379 - 10.0.0.3:26379

⑥ Redis Cluster(集群模式)— 分片存储海量数据

为什么需要?单机Redis内存有限(一般几十GB),如果缓存数据量达到上百万个key、占用上百GB内存,单台Redis放不下。Redis Cluster 把数据分散到多台机器上存储,同时保持每个分片的高可用。

🏗️ 架构图:Redis Cluster

Redis Cluster 分片架构(16384个槽)
Node A (Master)
槽 0 ~ 5460
+ Slave A'
Node B (Master)
槽 5461 ~ 10922
+ Slave B'
Node C (Master)
槽 10923 ~ 16383
+ Slave C'
写入流程:
① 客户端发 SET product:101 "{...}"
② 计算 key 的hash → slot = CRC16("product:101") % 16384 = 8190
③ 8190 落在 Node B 的范围 → 转发到 Node B
④ Node B 存储这个key

读取流程:
① 客户端发 GET product:101
② 同样计算hash → slot 8190 → 去 Node B 取

📋 分片原理详解

Redis Cluster 把所有key空间分成 16384个槽(slot),每个Master节点负责一部分槽。一个key属于哪个槽由哈希函数决定:

slot = CRC16(key) mod 16384

客户端连接任意节点时,如果key不在该节点的槽范围内,节点会返回 MOVED 重定向,告诉客户端去正确的节点。客户端可以缓存slot→node的映射,减少重定向。

每个Master节点配一个Slave节点做高可用:Master挂了,Slave自动提升。但Slave不提供读服务(默认配置下),主要做数据冗余。

💻 配置和创建集群

# 每个节点的 redis.conf port 7001 cluster-enabled yes # 开启集群模式 cluster-config-file nodes-7001.conf # 集群配置文件 cluster-node-timeout 5000 # 节点超时5秒 appendonly yes # 开启AOF持久化 # 创建集群(6个节点:3主3从) [root@redis ~]# redis-cli --cluster create \ 10.0.0.1:7001 10.0.0.1:7002 \ 10.0.0.2:7001 10.0.0.2:7002 \ 10.0.0.3:7001 10.0.0.3:7002 \ --cluster-replicas 1 # 每个Master配1个Slave >>> Performing hash slots allocation on 6 nodes... Master[0] -> Slots 0 - 5460 Master[1] -> Slots 5461 - 10922 Master[2] -> Slots 10923 - 16383 Adding replica 10.0.0.2:7002 to 10.0.0.1:7001 Adding replica 10.0.0.3:7002 to 10.0.0.2:7001 Adding replica 10.0.0.1:7002 to 10.0.0.3:7001 [OK] All 16384 slots covered. # ✅ 所有槽都已分配

📊 Redis Cluster vs 哨兵 对比

对比项Sentinel 哨兵Cluster 集群
数据量单机数据量(受内存限制)分布式存储(多机容量叠加)
扩展性不能水平扩展可以加节点扩展
高可用✅ 自动故障转移✅ 自动故障转移
复杂度中等(需要额外Sentinel进程)较高(需要管理槽分配)
适用场景数据量不大但要高可用数据量大/高并发/需要扩展

⑦ 分库分表 — 一张表放不下了怎么办?

为什么需要?订单表已经有5000万条数据,查询越来越慢。加索引也不行了,因为单表数据量太大,B+树变4层,每次查询多一次磁盘IO。
为什么5000万就不行了?MySQL默认使用InnoDB引擎,数据组织为B+树索引。3层B+树大约存2000万行(这是为什么"2000万"是个经验阈值)。超过后B+树从3层变4层,叶子节点更多,查询路径变长,性能急剧下降。

🏗️ 分库分表策略

分库分表的4种方式
1️⃣ 垂直分库
按业务拆分
库1:order_info, order_detail
库2:product, category
库3:user, address
不同业务模块放不同数据库
2️⃣ 垂直分表
按字段拆分
order_info:id, user_id, amount
order_ext:id, remark, extra
把不常用的大字段拆到扩展表
3️⃣ 水平分库
按规则拆到不同库
库1:order (user_id % 2 = 0)
库2:order (user_id % 2 = 1)
同一张表的数据分散到多个库
4️⃣ 水平分表
按规则拆成多张表
order_0:id % 3 = 0
order_1:id % 3 = 1
order_2:id % 3 = 2
同一个库里把数据拆成多张表

🏗️ MyCat / ShardingSphere 架构图

分库分表中间件架构
应用代码 ShardingSphere-JDBC 自动路由
DB1 (ds0)
order_0 ~ order_4
DB2 (ds1)
order_5 ~ order_9
应用只看到一张 order_info 逻辑表,中间件根据分片规则自动路由到对应的物理库和物理表
ShardingSphere-JDBC vs MyCat:
MyCat 是独立部署的中间件(多一层网络转发),应用无需改代码,但多一跳网络开销,且中间件本身是单点
ShardingSphere-JDBC 是一个JAR包,直接嵌入应用中,无额外网络开销,性能更好。新项目推荐。

💻 ShardingSphere-JDBC 配置

# application.yml — ShardingSphere-JDBC 分片配置
spring:
  shardingsphere:
    datasource:
      names: ds0,ds1           # 两个数据源
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        jdbc-url: jdbc:mysql://10.0.0.1:3306/order_db_0
        username: root
        password: root
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        jdbc-url: jdbc:mysql://10.0.0.2:3306/order_db_1
        username: root
        password: root
    rules:
      sharding:
        tables:
          order_info:                          # 分片表
            actual-data-nodes: ds0.order_info_$->{0..4},ds1.order_info_$->{5..9}  # 10张表
            table-strategy:
              standard:
                sharding-column: id            # 按id分片
                sharding-algorithm-name: order-inline
            key-generate-strategy:
              column: id
              key-generator-name: snowflake    # 雪花算法生成ID
        sharding-algorithms:
          order-inline:
            type: INLINE
            props:
              algorithm-expression: order_info_$->{id % 10}  # 取模分片

⚠️ 分库分表后的问题

问题描述解决方案
跨库JOIN订单在库A,商品在库B,不能JOIN了应用层组装 / 冗余字段 / 全局表
分布式事务一个操作涉及多个库,事务不好保证XA事务 / Seata / 最终一致性
跨库排序/分页ORDER BY 需要所有库都查出来再合并中间件自动合并 / 限制查询范围
全局ID自增ID在多个库会冲突Snowflake雪花算法 / 号段模式
扩容困难数据已经分好了,要加节点需要迁移一致性哈希 / 提前规划好分片数

⑧ Nginx 负载均衡 — 把请求均匀分给多台服务器

为什么需要?一台应用服务器扛不住了(CPU 90%、内存80%)。加服务器后,需要一个组件在前面做流量分发:Nginx 就是这个"流量分配器"。

🏗️ 架构图:Nginx 负载均衡

Nginx 负载均衡架构
用户 / 浏览器
Nginx (10.0.0.100)
根据策略分发请求
应用服务器1
10.0.0.1:8080
order-service.jar
应用服务器2
10.0.0.2:8080
order-service.jar
应用服务器3
10.0.0.3:8080
order-service.jar
共享 Redis Session 共享 MySQL 数据库

📊 负载均衡策略

策略分配规则适用场景
轮询(Round Robin)1→2→3→1→2→3...(默认)服务器性能相同
加权轮询按权重分配(weight=5的服务器比weight=1的多5倍请求)服务器性能不同
IP Hash同一个客户端IP永远路由到同一台服务器需要会话保持(但不推荐,用Redis Session更好)
最少连接(least_conn)哪个服务器当前连接数少就分给谁请求处理时间差异大的场景

💻 Nginx 配置

# nginx.conf upstream order_backend { # 加权轮询 + 健康检查 server 10.0.0.1:8080 weight=5 max_fails=3 fail_timeout=30s; # 权重5 server 10.0.0.2:8080 weight=3 max_fails=3 fail_timeout=30s; # 权重3 server 10.0.0.3:8080 weight=2 max_fails=3 fail_timeout=30s; # 权重2 server 10.0.0.4:8080 backup; # 备用,其他全挂了才上 } server { listen 80; server_name api.example.com; location /api/ { proxy_pass http://order_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; } }

⑨ Docker Compose — 多服务编排

为什么需要?一个完整的应用需要 Nginx + 应用 + MySQL + Redis + RabbitMQ 等多个服务。手动一个个 docker run 太痛苦。Docker Compose 用一个 YAML 文件定义所有服务及其依赖关系,一条命令 docker-compose up 全部启动。

🏗️ 完整部署架构图

典型电商系统部署架构
用户 (小程序/H5)
↓ HTTPS
Nginx (反向代理 + 负载均衡 + 静态资源)
处理HTTPS证书、静态文件(CSS/JS/图片)、转发API请求到后端
↓ 轮询分发
App-1
order-service.jar
App-2
order-service.jar
MySQL Master
10.0.0.1:3306
Redis Cluster
3主3从
RabbitMQ
消息队列
MySQL Slave
10.0.0.2:3306
MinIO
文件存储
Prometheus+Grafana
监控

💻 Docker Compose 配置

# docker-compose.yml version: '3.8' services: nginx: image: nginx:latest ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./ssl:/etc/nginx/ssl depends_on: - app1 - app2 app1: image: order-service:latest environment: - SPRING_PROFILES_ACTIVE=prod - DB_HOST=mysql-master - REDIS_HOST=redis ports: - "8081:8080" app2: image: order-service:latest environment: - SPRING_PROFILES_ACTIVE=prod - DB_HOST=mysql-master - REDIS_HOST=redis ports: - "8082:8080" mysql-master: image: mysql:8.0 environment: - MYSQL_ROOT_PASSWORD=xxx volumes: - mysql-data:/var/lib/mysql - ./my-master.cnf:/etc/mysql/conf.d/my.cnf redis: image: redis:7 command: redis-server --requirepass xxx volumes: - redis-data:/data volumes: mysql-data: redis-data:

⑩ 分布式 Session — 多台服务器怎么共享登录状态

为什么需要?用户登录后,Session存在应用服务器A的内存中。下一次请求被Nginx分到了服务器B → 服务器B内存里没有这个Session → 用户被踢回登录页。多台服务器之间需要共享登录状态。

📊 4种Session共享方案

方案原理优缺点
Session复制每台服务器都存所有Session(Tomcat内置支持)简单但浪费内存,服务器多时同步开销大
Session粘滞(IP Hash)Nginx IP Hash,同一用户永远路由到同一台服务器简单但不均衡,服务器挂了Session也丢
Redis集中存储所有Session存Redis,每台服务器都从Redis读写✅ 推荐:统一管理,服务器挂了Session不丢
Token(JWT)不用Session,用户每次请求自己带Token无状态、天然分布式,但Token不好主动撤销

💻 Spring Session + Redis 配置

# pom.xml — 加一个依赖 <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> # application.yml spring: session: store-type: redis # Session存到Redis timeout: 30m # 30分钟过期 redis: host: 10.0.0.5 port: 6379 # 就这么简单!加了依赖+配置,Spring自动把HttpSession存到Redis # 多台应用服务器共享同一个Redis → Session共享了 # 代码中照常用 HttpSession session = request.getSession(); 不用改任何业务代码

⑪ 配置中心 — 多台机器的配置统一管理

为什么需要?5台应用服务器,每台都有自己的 application.yml。改一个数据库密码要SSH到5台机器改5次,改完还要一台一台重启。配置中心统一管理所有配置,改一次全部生效,还能动态刷新(不改代码不重启)。

📊 常见配置中心对比

特性NacosApolloSpring Cloud Config
开发方阿里巴巴携程Spring官方
实时推送✅ 支持✅ 支持❌ 需要手动刷新
配置回滚✅ 支持✅ 支持⚠️ 依赖Git
服务发现✅ 内置❌ 不支持❌ 需要Eureka
上手难度简单中等中等

💻 Nacos 配置示例

<!-- pom.xml -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

# bootstrap.yml
spring:
  application:
    name: order-service
  cloud:
    nacos:
      config:
        server-addr: 10.0.0.100:8848
        namespace: dev
        group: DEFAULT_GROUP
        file-extension: yaml
        shared-configs:        # 共享配置(多个服务公用)
          - data-id: common-db.yaml
            group: DEFAULT_GROUP
            refresh: true

// 动态刷新示例:配置变更时自动生效,无需重启
@RestController
@RefreshScope              // Nacos配置变更时自动刷新
public class ConfigController {

    @Value("${order.max-retry:3}")
    private int maxRetry;

    @Value("${order.timeout-seconds:30}")
    private int timeoutSeconds;

    @GetMapping("/config/info")
    public Map<String, Object> getConfig() {
        return Map.of(
            "maxRetry", maxRetry,
            "timeoutSeconds", timeoutSeconds
        );
    }
}

⑫ 全链路架构总览 — 一个请求的完整旅程

面试高频面试官问:"你能描述一下你们系统的整体架构吗?一个用户请求从发起到返回,经过了哪些组件?"这道题考的不是某个组件的细节,而是你对全局的理解。

🏗️ 完整链路图

用户发一个请求 —— 请求的完整旅程
① 用户点击"下单"
↓ DNS解析
② CDN / DNS 静态资源(CSS/JS/图片)从CDN取
↓ HTTPS
③ Nginx SSL终结、负载均衡、静态文件
↓ 负载均衡
④ Spring Boot 应用 Token验证、业务逻辑
⑤ Redis
查缓存/Token
⑥ MySQL
写订单(主库)
⑦ RabbitMQ
发消息通知
↓ 异步
⑧ 消费者服务 扣库存、发通知、记日志
↓ 返回
⑨ Nginx → 用户 JSON响应 → 页面显示"下单成功"

📊 每一步的技术职责

步骤技术组件职责
① 用户点击小程序/浏览器发起HTTPS请求
② CDN/DNSCDN边缘节点静态资源就近返回,减少后端压力
③ Nginx反向代理+负载均衡SSL终结、分发请求到多台应用服务器
④ Spring Boot业务应用Token验证、参数校验、业务逻辑处理
⑤ Redis缓存/Session存储查缓存命中则直接返回,不走DB
⑥ MySQL持久化存储写入订单数据(走主库)
⑦ RabbitMQ消息队列发送异步消息,解耦下游服务
⑧ 消费者异步处理服务扣库存、发推送通知、记录日志
⑨ 返回Nginx → 用户JSON响应返回前端

📊 面试总结:架构高频考点

考点关键词一句话回答
MySQL高可用主从、MHA、GTID"我们用MySQL主从+MHA,半同步复制,主库挂了30秒内自动切换"
读写分离AOP、ShardingSphere"写走主库读走从库,用ShardingSphere-JDBC自动路由,强一致性查询走主库"
Redis高可用哨兵、Cluster"缓存用Redis Cluster,3主3从,自动故障转移"
分库分表ShardingSphere"订单量大了做水平分表,按userId取模分片,用ShardingSphere-JDBC"
负载均衡Nginx、轮询、权重"Nginx做反向代理,加权轮询,后端健康检查"
Session共享Spring Session、JWT"用Spring Session+Redis集中存储Session"
配置管理Nacos"用Nacos做配置中心和服务发现,配置实时推送"
消息队列RabbitMQ/Kafka"用MQ做异步解耦,下单成功发消息,消费者异步扣库存"