MySQL + Redis + RabbitMQ/RocketMQ + Elasticsearch——这四件套是Java后端面试的"必考题",也是企业项目的"标配"

1. MySQL — 企业级数据库实践

MySQL是Java后端最核心的持久化存储,面试中围绕InnoDB引擎、索引优化、SQL调优的考察密度极高。

1.1 企业中MySQL的真实用法

1.1.1 部署架构选型

架构模式适用场景优点缺点
单机部署开发/测试环境、小型应用简单、成本低单点故障、性能瓶颈
主从复制 (Master-Slave)读写分离、备份恢复读扩展、数据备份主节点仍是单点
主主复制 (Master-Master)双活写入、高可用任一节点可写冲突处理复杂
MGR (MySQL Group Replication)强一致性要求自动选主、多节点写入版本要求高、网络敏感
PXC (Percona XtraDB Cluster)金融级数据一致性同步复制、多节点强一致写入性能下降

1.1.2 读写分离架构

          +-----------+
          |  应用层    |
          +-----+-----+
                |
          +-----+-----+
          |  读写分离   |
          |  中间件     |  (ShardingSphere/MyCat/ProxySQL)
          +--+------+--+
             |      |
     +-------+--+  ++--------+
     |  Master  |  |  Slave   |
     | (写)     |  | (读)     |
     +----------+  +----------+
                    |  Slave   |
                    | (读)     |
                    +----------+

企业实践:一主多从架构下,一般配置2~3个Slave节点。写请求路由到Master,读请求分发到Slave。从节点可以承担数据报表、ES数据同步等非核心业务查询。

1.1.3 分库分表实战

分库策略:

-- 按用户ID取模分库: user_db_0, user_db_1, user_db_2
-- 分库键: user_id % 3

-- 按时间分表: order_202601, order_202602
-- 分表键: create_time

-- 按地域分库: db_asia, db_europe, db_america
-- 分库键: region_code

ShardingSphere YAML配置示例:

# sharding-sphere-config.yaml
dataSources:
  ds_0:
    url: jdbc:mysql://localhost:3306/db_0?useSSL=false
    username: root
    password: ${MYSQL_PASSWORD}
  ds_1:
    url: jdbc:mysql://localhost:3306/db_1?useSSL=false
    username: root
    password: ${MYSQL_PASSWORD}

rules:
  - !SHARDING
    tables:
      t_order:
        actualDataNodes: ds_${0..1}.t_order_${0..1}
        databaseStrategy:
          standard:
            shardingColumn: user_id
            shardingAlgorithmName: db_inline_mod
        tableStrategy:
          standard:
            shardingColumn: order_id
            shardingAlgorithmName: table_inline_mod
    shardingAlgorithms:
      db_inline_mod:
        type: INLINE
        props:
          algorithm-expression: ds_${user_id % 2}
      table_inline_mod:
        type: INLINE
        props:
          algorithm-expression: t_order_${order_id % 2}

1.2 InnoDB核心原理——面试必问深度

1.2.1 B+Tree索引结构

对比维度B+TreeB-TreeHash
非叶子节点只存索引键存索引键+数据-
叶子节点存数据+双向链表存数据-
范围查询高效(链表遍历)低效(回溯)不支持
等值查询O(logN)O(logN)O(1)
磁盘I/O次数树高度决定(一般3~4层)树高度决定1次

聚簇索引 vs 辅助索引:

聚簇索引 (Primary Key)
  [10|...] → [20|...] → [30|...] → ...
    ↓           ↓           ↓
  数据行      数据行      数据行

辅助索引 (Secondary Index)
  [name='Alice'|pk=10] → [name='Bob'|pk=20] → ...
          ↓                       ↓
      回表查询聚簇索引          回表查询聚簇索引

1.2.2 MVCC (多版本并发控制)

核心机制:MVCC通过"快照读"实现非阻塞读,写操作不影响读操作,大幅提升并发性能。

三个关键隐藏字段:

字段含义作用
DB_TRX_ID事务ID标识最后修改该行的事务
DB_ROLL_PTR回滚指针指向Undo Log中的上一个版本
DB_ROW_ID行ID未定义主键时的隐藏主键

Undo Log版本链:

事务100: INSERT
  → 事务200: UPDATE name='Bob'
    → 事务300: UPDATE name='Charlie'
      → (最新版本)

ReadView可见性判断:

ReadView结构:
  - m_ids:   活跃事务ID列表(启动时未提交的)
  - min_trx_id: 最小活跃事务ID
  - max_trx_id: 预分配的下一个事务ID
  - creator_trx_id: 创建该ReadView的事务ID

判断规则:
  1. DB_TRX_ID == creator_trx_id → 可见(自己修改的)
  2. DB_TRX_ID < min_trx_id  → 可见(已提交的)
  3. DB_TRX_ID >= max_trx_id → 不可见(未来事务)
  4. DB_TRX_ID ∈ m_ids      → 不可见(未提交)

1.2.3 锁机制

锁类型粒度说明加锁时机
表锁整张表MyISAM使用,InnoDB不使用DDL语句
行锁单行记录InnoDB最细粒度锁UPDATE/DELETE/FOR UPDATE
间隙锁 (Gap Lock)间隙范围防止幻读,RR级别默认范围条件查询
Next-Key Lock行+间隙行锁与间隙锁组合RR级别索引条件
意向锁表级别快速判断行锁存在加行锁前自动加

面试高频题:MySQL RR级别如何解决幻读?答案是 Next-Key Lock。在SELECT ... FOR UPDATE时,不仅锁住命中行,还锁住行之间的间隙,防止新记录插入。


1.3 索引设计与优化

1.3.1 联合索引(最左前缀原则)

-- 创建联合索引
CREATE INDEX idx_org_status_time ON t_order (org_id, status, create_time);

-- 走索引的情况(最左前缀匹配)
WHERE org_id = 100;                                 -- 只使用org_id
WHERE org_id = 100 AND status = 1;                  -- 使用org_id + status
WHERE org_id = 100 AND status = 1 AND create_time > '2026-01-01'; -- 完全使用

-- 不走索引或部分走索引的情况
WHERE status = 1;                                   -- 无最左列,全表扫描
WHERE org_id = 100 AND create_time > '2026-01-01';  -- 只用到org_id,跳过status

核心要点:联合索引本质上是有序的多维数组,优先按第一列排序,同值下按第二列排序。跳过了中间列,后面的列无法使用索引。

1.3.2 覆盖索引(Covering Index)

-- 表结构
CREATE TABLE t_user (
  id BIGINT PRIMARY KEY,
  name VARCHAR(50),
  age INT,
  email VARCHAR(100),
  INDEX idx_name_age (name, age)
);

-- 覆盖索引查询:SQL所需字段完全在索引中,无需回表
EXPLAIN SELECT name, age FROM t_user WHERE name = 'Alice';
-- Extra: Using index  ← 表示使用了覆盖索引

-- 非覆盖索引查询:需要回表
EXPLAIN SELECT name, email FROM t_user WHERE name = 'Alice';
-- Extra: Using index condition  ← 需要回表查询email

1.3.3 索引下推 (Index Condition Pushdown, ICP)

-- MySQL 5.6+ 特性:将WHERE条件从Server层"下推"到存储引擎层过滤

-- 示例:联合索引 idx_age_name (age, name)
SELECT * FROM t_user WHERE age > 20 AND name LIKE '%Li%';

-- 无ICP时:
--   存储引擎扫描age>20的记录 → 回表 → Server层过滤name LIKE
-- 有ICP时:
--   存储引擎同时用age和name条件过滤 → 减少回表次数

1.3.4 EXPLAIN实战解读

列名关键值含义
typeconst → ref → range → index → ALL访问类型(从好到差)
key实际使用的索引名使用了哪个索引
rows扫描行数估算越少越好
ExtraUsing index覆盖索引,最佳
ExtraUsing filesort需要额外排序,需优化
ExtraUsing temporary使用了临时表,需优化
ExtraUsing index condition使用了ICP
-- 优化前
EXPLAIN SELECT * FROM t_order WHERE status = 0 ORDER BY create_time DESC;
-- type: ALL, Extra: Using filesort  ← 全表扫描+额外排序,危险!

-- 优化后:创建联合索引
CREATE INDEX idx_status_create_time ON t_order (status, create_time);
EXPLAIN SELECT * FROM t_order WHERE status = 0 ORDER BY create_time DESC;
-- type: ref, Extra: NULL  ← 索引定位+索引有序,高效!

1.4 SQL编写规范(企业级代码规范)

-- =============================================
-- 企业SQL编写规范示例
-- =============================================

-- 1. 禁止使用SELECT *,必须指定字段列表
-- 错误: SELECT * FROM t_user;
-- 正确:
SELECT id, name, age FROM t_user;

-- 2. 大表查询必须带LIMIT
SELECT id, name FROM t_order WHERE status = 1 LIMIT 100;

-- 3. JOIN不宜超过3张表,必须确保关联字段有索引
SELECT o.id, u.name
FROM t_order o
INNER JOIN t_user u ON o.user_id = u.id   -- user_id和id都有索引
WHERE o.create_time > '2026-01-01';

-- 4. WHERE条件中禁止对索引列做函数操作
-- 错误: WHERE DATE(create_time) = '2026-01-01'
-- 正确: WHERE create_time >= '2026-01-01' AND create_time < '2026-01-02'

-- 5. 分页查询深翻页优化
-- 错误(深翻页): SELECT * FROM t_order LIMIT 1000000, 20;
-- 正确(子查询优化):
SELECT * FROM t_order
WHERE id > (SELECT id FROM t_order ORDER BY id LIMIT 1000000, 1)
ORDER BY id LIMIT 20;

-- 6. UNION代替OR(OR导致索引失效)
-- 错误: SELECT * FROM t_user WHERE name = 'Alice' OR age = 25;
-- 正确:
SELECT * FROM t_user WHERE name = 'Alice'
UNION
SELECT * FROM t_user WHERE age = 25;

-- 7. 批量INSERT使用一条语句
INSERT INTO t_order (id, user_id, amount) VALUES
(1, 101, 99.9),
(2, 102, 199.9),
(3, 103, 299.9);

1.5 慢查询治理流程

企业标准流程:发现 → 分析 → 优化 → 验证,形成闭环。

┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│  发现    │ →  │  分析    │ →  │  优化    │ →  │  验证    │
│          │    │          │    │          │    │          │
│ slow_log  │    │ EXPLAIN  │    │ 加索引    │    │ EXPLAIN  │
│ 慢查询日志 │    │ profile  │    │ 改写SQL   │    │ 响应时间  │
│ 告警系统   │    │ 表结构   │    │ 分表     │    │ QPS监控   │
└──────────┘    └──────────┘    └──────────┘    └──────────┘

开启慢查询日志:

-- 查看慢查询配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

-- 配置(my.cnf)
slow_query_log = ON
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1        -- 超过1秒的记录
log_queries_not_using_indexes = ON

pt-query-digest分析:

# 分析慢查询日志
pt-query-digest /var/log/mysql/slow.log > slow_analysis.txt

# 关键指标:Query_time、Lock_time、Rows_examined、Rows_sent

1.6 连接池配置(Druid/HikariCP最佳实践)

HikariCP(Spring Boot 2.x+ 默认):

# application.yml
spring:
  datasource:
    hikari:
      maximum-pool-size: 20          # 最大连接数(核心配置)
      minimum-idle: 10               # 最小空闲连接
      idle-timeout: 300000           # 空闲超时(5分钟)
      connection-timeout: 30000      # 连接超时(30秒)
      max-lifetime: 1800000          # 最大存活时间(30分钟)
      pool-name: HikariPool-Main     # 连接池名称

Druid(阿里巴巴,监控能力强):

spring:
  datasource:
    druid:
      initial-size: 5               # 初始化连接数
      min-idle: 5                   # 最小空闲连接
      max-active: 20                # 最大活跃连接
      max-wait: 60000               # 获取连接等待超时(毫秒)
      time-between-eviction-runs-millis: 60000   # 检查间隔
      validation-query: SELECT 1    # 心跳SQL
      test-while-idle: true         # 空闲时验证
      test-on-borrow: false         # 获取时验证(影响性能)
      test-on-return: false         # 归还时验证
      filter:
        stat:
          enabled: true
          log-slow-sql: true
          slow-sql-millis: 1000
对比维度HikariCPDruidDBCP2
性能最高(字节码优化)高一般
监控基础(JMX)丰富(Web页面/SQL分析)基础
功能简洁防火墙/慢SQL/日志基础
社区Spring Boot默认阿里生态Apache
选择建议追求性能的场景需要SQL监控的场景遗留项目

2. Redis — 企业级缓存实践

Redis是Java后端缓存的事实标准,面试核心关注:数据结构、缓存策略、分布式锁、集群方案。

2.1 企业中Redis的6种典型使用场景

场景技术方案数据结构示例
缓存Cache-Aside缓存数据库String/Hash用户信息缓存、配置缓存
分布式锁Redisson/SetNX+LuaString订单幂等、定时任务互斥
计数器INCR/DECR原子操作StringPV/UV统计、限流计数
排行榜ZSet有序集合ZSet积分排名、热门文章
消息队列List/StreamList/Stream异步任务、延迟队列
Session共享Spring Session + RedisHash分布式Session

2.2 Redis数据类型在企业中的真实用法

2.2.1 String(字符串)—— 最基础,但最常用

// Spring Data Redis 示例
@Service
public class UserCacheService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 缓存穿透保护:缓存空值
    public User getUserById(Long id) {
        String key = "user:" + id;

        // 1. 查缓存
        User user = (User) redisTemplate.opsForValue().get(key);
        if (user != null) {
            return user;
        }

        // 2. 缓存空值保护(缓存穿透)
        String nullFlag = (String) redisTemplate.opsForValue().get(key + ":null");
        if (nullFlag != null) {
            return null;  // 防止穿透到DB
        }

        // 3. 查数据库
        user = userMapper.selectById(id);
        if (user == null) {
            // 设置空值缓存,TTL 60秒
            redisTemplate.opsForValue().set(key + ":null", "1", 60, TimeUnit.SECONDS);
        } else {
            // 正常缓存,TTL 30分钟
            redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
        }
        return user;
    }
}

2.2.2 Hash(哈希)—— 对象存储优选

// Hash适合存储结构化对象,比String + JSON更节省内存,支持字段单独操作
public void updateUserField(Long userId, String field, Object value) {
    String key = "user:" + userId;
    redisTemplate.opsForHash().put(key, field, value);
}
对比项String + JSONHash
内存占用大(整个JSON序列化)小(独立字段存储)
修改单个字段需要全量读写直接操作字段
序列化开销高低
适用场景整体读写多的场景频繁修改部分字段的场景

2.2.3 List(列表)—— 简单消息队列

// 生产者
public void sendTask(Long taskId) {
    redisTemplate.opsForList().rightPush("task:queue", taskId.toString());
}

// 消费者(阻塞读取)
public void consumeTask() {
    while (true) {
        String taskId = (String) redisTemplate.opsForList()
            .leftPop("task:queue", 5, TimeUnit.SECONDS);
        if (taskId != null) {
            processTask(Long.parseLong(taskId));
        }
    }
}

2.2.4 ZSet(有序集合)—— 排行榜

// 积分排行榜
public void addScore(Long userId, double score) {
    redisTemplate.opsForZSet().add("ranking:score", userId.toString(), score);
}

// 获取TOP 10
public Set<String> getTop10() {
    return redisTemplate.opsForZSet()
        .reverseRange("ranking:score", 0, 9);
}

// 获取用户排名
public Long getUserRank(Long userId) {
    return redisTemplate.opsForZSet()
        .reverseRank("ranking:score", userId.toString());
}

2.2.5 Stream(流)—— Redis 5.0+ 消息队列

// Stream相比List的优势:支持消费者组、消息持久化、ACK机制
// 适合需要可靠消费的场景
redisTemplate.opsForStream()
    .add(StreamRecords.newRecord()
        .ofObject(message)
        .withStreamKey("stream:orders"));

2.3 缓存策略模式对比

模式读操作写操作优点缺点
Cache-Aside先查缓存→未命中查DB→回写缓存先写DB→删除缓存实现简单、命中率高存在短暂不一致
Read-Through缓存层自动加载DB数据同Cache-Aside对应用透明实现复杂
Write-Through同Read-Through先写缓存→缓存写DB一致性高写入延迟增加
Write-Behind同Read-Through先写缓存→异步写DB写入性能高宕机可能丢数据

企业推荐:Cache-Aside 是最广泛采用的模式。更新策略使用"先写DB,后删缓存"而不是"更新缓存",因为后者在并发写入时会出现脏数据。


2.4 缓存穿透/击穿/雪崩的企业级解决方案

问题现象解决方案
缓存穿透查询不存在的数据,每次穿透到DB1. 缓存空值;2. 布隆过滤器;3. 参数校验
缓存击穿热点Key过期,高并发打到DB1. 互斥锁;2. 逻辑过期(异步刷新);3. 永不过期+后台更新
缓存雪崩大量Key同时过期,DB被打垮1. TTL加随机值;2. 多级缓存(本地+Redis);3. 限流降级
// 布隆过滤器防止缓存穿透
@Component
public class BloomFilterService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String BLOOM_KEY = "bloom:users";
    private static final int EXPECTED_SIZE = 100_0000;
    private static final double FPP = 0.01;

    private BloomFilter<Long> bloomFilter;

    @PostConstruct
    public void init() {
        bloomFilter = BloomFilter.create(
            Funnels.longFunnel(), EXPECTED_SIZE, FPP);
        // 从DB加载已有用户ID到布隆过滤器
        List<Long> allUserIds = userMapper.selectAllIds();
        allUserIds.forEach(bloomFilter::put);
    }

    public boolean mightContain(Long userId) {
        return bloomFilter.mightContain(userId);
    }
}

// 互斥锁防止缓存击穿
public User queryWithMutex(Long id) {
    String key = "user:" + id;
    // 1. 查缓存
    User user = (User) redisTemplate.opsForValue().get(key);
    if (user != null) return user;

    // 2. 获取互斥锁
    String lockKey = "lock:user:" + id;
    Boolean locked = redisTemplate.opsForValue()
        .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);

    if (Boolean.TRUE.equals(locked)) {
        try {
            // 3. 查DB
            user = userMapper.selectById(id);
            redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
            return user;
        } finally {
            redisTemplate.delete(lockKey);
        }
    } else {
        // 4. 没拿到锁,休眠后重试
        Thread.sleep(100);
        return queryWithMutex(id);
    }
}

// TTL加随机值防止雪崩
int baseTTL = 30 * 60;  // 30分钟
int randomTTL = baseTTL + new Random().nextInt(10 * 60);  // 增加0~10分钟随机值
redisTemplate.opsForValue().set(key, user, randomTTL, TimeUnit.SECONDS);

2.5 Redis集群方案对比

对比维度主从复制哨兵模式Redis Cluster
数据分片无无有(16384个槽)
高可用需手动切换自动故障转移自动故障转移
写能力单点单点多节点写入
读能力从节点扩展从节点扩展多节点扩展
节点数1主N从1主N从+3哨兵至少3主3从
一致性异步复制,弱一致异步复制,弱一致异步复制,弱一致
客户端直连直连+感知切换Smart Client
适用场景读多写少、数据量不大高可用要求海量数据、高并发

选型建议:数据量 < 10G 选哨兵模式,简单易维护;数据量 > 10G 或需要水平扩展选 Cluster。注意 Cluster 不支持 multi-key 跨槽操作。


2.6 Redisson企业级分布式锁

@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
            .setAddress("redis://localhost:6379")
            .setConnectionPoolSize(10);
        return Redisson.create(config);
    }
}

@Service
public class OrderService {
    @Autowired
    private RedissonClient redissonClient;

    // 标准分布式锁
    public void createOrder(Long orderId) {
        RLock lock = redissonClient.getLock("order:" + orderId);
        try {
            // 尝试加锁,最多等3秒,锁有效30秒
            if (lock.tryLock(3, 30, TimeUnit.SECONDS)) {
                // 执行业务逻辑
                processOrder(orderId);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();
        }
    }

    // 看门狗机制(自动续期)
    public void processWithWatchdog(Long orderId) {
        RLock lock = redissonClient.getLock("order:" + orderId);
        lock.lock(); // 默认30秒,看门狗每10秒续期一次
        try {
            // 执行业务逻辑(即使超过30秒也不用担心锁超时)
            longProcess(orderId);
        } finally {
            lock.unlock();
        }
    }

    // 可重入锁
    public void reentrantExample(Long userId) {
        RLock lock = redissonClient.getLock("user:" + userId);
        lock.lock();
        try {
            // 第一层
            innerMethod(userId);  // 内部再次加同一把锁,可重入
        } finally {
            lock.unlock();
        }
    }

    private void innerMethod(Long userId) {
        RLock lock = redissonClient.getLock("user:" + userId);
        lock.lock();  // 可重入:同一个线程可多次加锁
        try {
            // 内部逻辑
        } finally {
            lock.unlock();
        }
    }

    // 红锁(多节点分布式锁,RedLock算法)
    public void redLockExample(Long resourceId) {
        RLock lock1 = redissonClient.getLock("node1:resource:" + resourceId);
        RLock lock2 = redissonClient.getLock("node2:resource:" + resourceId);
        RLock lock3 = redissonClient.getLock("node3:resource:" + resourceId);
        RLock redLock = new RedissonRedLock(lock1, lock2, lock3);

        try {
            if (redLock.tryLock(5, 30, TimeUnit.SECONDS)) {
                // 获得多数节点锁,执行业务
                processResource(resourceId);
            }
        } finally {
            redLock.unlock();
        }
    }
}

看门狗机制:Redisson的自动续期机制。锁默认30秒过期,看门狗每10秒检查一次,如果业务还在执行则自动续期到30秒。业务执行完毕主动释放,避免死锁。

2.7 大Key/热Key问题排查与解决方案

问题定义影响解决方案
大KeyString > 10KB 或 集合元素 > 5000阻塞其他命令、内存不均、网络延迟拆分、压缩、不用的及时清理
热Key单个Key QPS > 100000单节点CPU打满、请求超时本地缓存、读写分离、散列

大Key排查:

# 使用redis-cli --bigkeys 扫描
redis-cli --bigkeys -h 127.0.0.1 -p 6379

# 手动查看Key大小
MEMORY USAGE user:123456
STRLEN big_string_key
HLEN big_hash_key

热Key解决方案:

// 1. 本地缓存 + Redis两级缓存(Caffeine + Redis)
@Configuration
public class TwoLevelCacheConfig {
    @Bean
    public Cache<String, Object> caffeineCache() {
        return Caffeine.newBuilder()
            .maximumSize(10000)
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .build();
    }
}

@Service
public class HotKeyService {
    @Autowired
    private Cache<String, Object> caffeineCache;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public Object getHotData(String key) {
        // 1. 查本地缓存
        Object value = caffeineCache.getIfPresent(key);
        if (value != null) return value;

        // 2. 查Redis
        value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            caffeineCache.put(key, value);
        }
        return value;
    }
}

// 2. 热Key散列:将单个Key拆分为多个
// 原Key: hot_product:12345
// 拆分后: hot_product:12345:0, hot_product:12345:1, ... hot_product:12345:9
// 读写时随机选择一个子Key

3. 消息队列 — RabbitMQ vs RocketMQ vs Kafka

MQ是Java后端解耦、异步、削峰的核心武器。面试关注:选型对比、消息可靠性、业务场景。

3.1 三者企业级对比大表(12维度)

对比维度RabbitMQRocketMQKafka
语言ErlangJavaScala + Java
协议AMQP 0-9-1自研协议自研协议
吞吐量万级/s十万级/s百万级/s
延迟微秒级毫秒级毫秒级
消息可靠性高(Confirm+持久化)高(同步刷盘)中(异步刷盘)
顺序消息有限支持(单队列)支持(分区有序)支持(分区有序)
事务消息不支持(需插件)✓ 原生支持不支持
延时消息通过TTL+死信队列✓ 原生18个级别不支持
死信队列✓ 原生支持✓ 原生支持通过重试Topic实现
消息回溯不支持支持(按时间)✓ 强项(按offset/时间)
社区生态最成熟阿里生态友好大数据生态标准
运维复杂度中等中高高(需ZooKeeper/KRaft)

选型建议:

中小企业、快速集成    → RabbitMQ(简单、生态好)
金融/订单/事务场景     → RocketMQ(事务消息、可靠)
大数据/日志/流处理     → Kafka(高吞吐、海量数据)
IoT/物联网            → RabbitMQ(MQTT协议支持)

3.2 RabbitMQ在企业中的用法

3.2.1 核心架构

          +-----------+
          |  Producer  |
          +-----+-----+
                |
          +-----+-----+
          |  Exchange  |
          +--+------+--+
             |      |
    +--------+-+  +-+--------+
    | Queue A  |  | Queue B  |
    +--------+-+  +-+--------+
             |      |
    +--------+-+  +-+--------+
    |Consumer A|  |Consumer B|
    +----------+  +----------+

Exchange类型:

类型路由规则典型场景
DirectRouting Key精确匹配点对点、RPC
TopicRouting Key通配符(* / #)多消费者订阅
Fanout广播到所有绑定队列全局通知、配置刷新
HeadersHeaders属性匹配复杂路由,不常用

3.2.2 Spring Boot整合RabbitMQ

# application.yml
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    # 生产者确认
    publisher-confirm-type: correlated   # 异步回调确认
    publisher-returns: true              # 消息不可达返回
    # 消费者
    listener:
      simple:
        acknowledge-mode: manual          # 手动ACK
        prefetch: 1                      # 每次拉取1条
        retry:
          enabled: true
          max-attempts: 3
@Configuration
public class RabbitMQConfig {
    // 延迟队列:使用死信队列实现
    @Bean
    public Queue delayQueue() {
        return QueueBuilder.durable("delay.queue")
            .ttl(30000)                        // 30秒过期
            .deadLetterExchange("direct.exchange")
            .deadLetterRoutingKey("process")
            .build();
    }

    @Bean
    public Queue processQueue() {
        return new Queue("process.queue");
    }
}

@Service
public class OrderMessageService {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 发送订单消息(带确认回调)
    public void sendOrderMessage(Order order) {
        CorrelationData correlationData = new CorrelationData(order.getOrderId());
        rabbitTemplate.convertAndSend(
            "order.exchange", "order.create", order, correlationData);

        // 异步确认回调
        rabbitTemplate.setConfirmCallback((correlation, ack, cause) -> {
            if (!ack) {
                log.error("消息发送失败: {}", cause);
                // 补偿:重发或记录到数据库
            }
        });
    }

    // 消费订单消息(手动ACK)
    @RabbitListener(queues = "process.queue")
    public void processOrder(Order order, Channel channel,
                            @Header(AmqpHeaders.DELIVERY_TAG) long tag) {
        try {
            // 业务处理
            orderService.process(order);
            // 手动ACK
            channel.basicAck(tag, false);
        } catch (Exception e) {
            // 拒绝消息,不重新入队(进入死信队列)
            channel.basicNack(tag, false, false);
        }
    }
}

3.2.3 延迟队列 + 死信队列

// 常见延迟场景:订单30分钟未支付自动取消
// 方案:RabbitMQ死信队列(TTL过期 → 死信队列 → 消费者)
//
// 生产流程:
// 1. 下单 → 发送消息到 delay.queue(TTL=30分钟)
// 2. 30分钟后消息过期 → 转发到 dead.exchange
// 3. dead.exchange 路由到 cancel.queue
// 4. 取消订单消费者处理

// 也可以使用 rabbitmq-delayed-message-exchange 插件
@Bean
public CustomExchange delayExchange() {
    Map<String, Object> args = new HashMap<>();
    args.put("x-delayed-type", "direct");
    return new CustomExchange("delay.exchange", "x-delayed-message",
        true, false, args);
}

3.3 RocketMQ的企业级特性

3.3.1 事务消息(金融级保证)

事务消息流程:

Producer                  Broker
    |                        |
    |-- 1. 发送半消息 -------→|  (Half Message,暂不可见)
    |←--- 2. OK -------------|
    |                        |
    |-- 3. 执行本地事务 ------|
    |                        |
    |-- 4. COMMIT/ROLLBACK --→|  (提交或回滚)
    |                        |
    |    5. 回查(最多15次)    |
    |←--- 回查接口 -----------|
    |-- 回查结果 ------------→|
    |                        |
    |         6. 消息可见     |
    |                        → Consumer
// RocketMQ事务消息示例
@Component
public class OrderTransactionListener implements TransactionListener {
    @Autowired
    private OrderService orderService;

    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        try {
            // 执行本地事务(创建订单)
            orderService.createOrder((Order) arg);
            return LocalTransactionState.COMMIT_MESSAGE;
        } catch (Exception e) {
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        // 回查:检查本地事务是否成功
        String orderId = msg.getKeys();
        boolean success = orderService.checkOrderExists(orderId);
        return success ?
            LocalTransactionState.COMMIT_MESSAGE :
            LocalTransactionState.ROLLBACK_MESSAGE;
    }
}

3.3.2 顺序消息

// 顺序消息:同一个订单的消息必须依次消费
// 原理:相同MessageQueue的消息串行消费
// 策略:使用订单ID作为Sharding Key

// 生产者
producer.send(message, (mqs, msg, arg) -> {
    String orderId = (String) arg;
    int index = Math.abs(orderId.hashCode()) % mqs.size();
    return mqs.get(index);  // 相同orderId进入同一个Queue
}, orderId);

3.3.3 延时消息

// RocketMQ原生支持18个级别的延时
// 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

Message msg = new Message("order_topic", "cancel", orderJson.getBytes());
msg.setDelayTimeLevel(5);  // 5 = 1分钟(取消订单延迟1分钟)
producer.send(msg);

3.4 Kafka在大数据场景的应用

# Kafka核心概念
# Topic → Partition (有序、可并行) → Replica (副本)
# Consumer Group (组内负载均衡消费)

# 日志收集架构
# App → Logback/KafkaAppender → Kafka → Logstash → ES → Kibana

# CDC (Change Data Capture)
# MySQL Binlog → Canal → Kafka → 下游服务/ES/数仓

Kafka与Spring Boot整合:

spring:
  kafka:
    bootstrap-servers: localhost:9092,localhost:9093
    consumer:
      group-id: order-group
      auto-offset-reset: earliest    # 从头消费
      enable-auto-commit: false      # 手动提交
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      acks: all                      # 最强可靠性
      retries: 3

3.5 消息可靠性保证

端到端可靠性:三端保障——Producer确认、Broker持久化、Consumer ACK。

┌─────────┐   Confirm    ┌─────────┐  持久化    ┌─────────┐
│Producer │ ───────────→ │ Broker  │ ────────→ │Consumer │
│         │              │         │           │         │
│ 重试机制 │              │ 同步刷盘 │           │ 手动ACK │
│ 事务消息 │              │ 主从复制 │           │ 幂等消费 │
│ 回调确认 │              │ OFFSET   │           │ 重试机制 │
└─────────┘              └─────────┘           └─────────┘
阶段风险解决方案
生产者 → Broker网络问题导致消息丢失Confirm回调 + 重试机制
Broker存储宕机丢消息同步刷盘 + 主从复制 / ISR
Broker → 消费者消费者崩溃手动ACK + 死信队列

3.6 消息堆积/重复消费/顺序消费的企业级解决方案

消息堆积

原因排查手段解决方案
消费者处理慢查看消费延迟(Consumer Lag)增加分区和消费者数量
消费者异常检查错误日志捕获异常 + 死信队列隔离坏消息
生产者流量暴增监控发送速率限流 + 扩容 + 临时调整消费者批量大小

RocketMQ查看堆积:

# RocketMQ控制台或CLI
mqadmin consumerProgress -n 127.0.0.1:9876 -g order_consumer_group

Kafka查看堆积:

# Kafka Consumer Group状态
kafka-consumer-groups --bootstrap-server localhost:9092 \
  --group order-group --describe
# 关注 LAG(未消费消息数)

重复消费

// 幂等消费:业务层使用唯一键去重
@Component
public class IdempotentConsumer {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public boolean consume(String messageId, Consumer<Message> consumer) {
        String key = "msg:idempotent:" + messageId;
        // SETNX原子操作,保证只执行一次
        Boolean success = redisTemplate.opsForValue()
            .setIfAbsent(key, "1", 1, TimeUnit.DAYS);

        if (Boolean.TRUE.equals(success)) {
            consumer.accept(message);
            return true;
        }
        log.info("消息已消费,跳过: {}", messageId);
        return false;
    }
}

顺序消费

// 方案一:单一队列/分区(性能受限)
// RabbitMQ:所有消息发到同一个Queue
// Kafka:所有消息使用同一Partition Key
// RocketMQ:相同订单ID使用同一MessageQueue

// 方案二:业务层排序(推荐)
// 将顺序要求拆解到业务层面:
// 1. 为每个消息携带 sequenceId
// 2. 消费者按sequenceId排序处理
// 3. 乱序的消息暂存到缓冲区

核心要点:消息队列面试三连问——"如何保证消息不丢失?"(端到端可靠性)、"如何保证消息不重复?"(幂等消费)、"如何保证消息顺序?"(分区有序)。


4. Elasticsearch — 企业级搜索实践

ES在Java后端项目中扮演"搜索基础设施"的角色,面试关注索引设计、数据同步、DSL查询。

4.1 ES在Java项目中的定位

应用场景说明典型数据量
全文搜索商品搜索、文章搜索、用户搜索百万 ~ 十亿级
日志分析ELK Stack日志收集与分析TB ~ PB级
APM应用性能监控(Elastic APM)GB ~ TB级
数据分析聚合统计、报表生成百万 ~ 亿级
企业搜索内部文档/知识库搜索万 ~ 百万级

4.2 索引设计最佳实践

4.2.1 Mapping设计

// Product索引Mapping示例
PUT /products
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "refresh_interval": "10s",
    "analysis": {
      "analyzer": {
        "ik_smart_analyzer": {
          "type": "custom",
          "tokenizer": "ik_smart"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "productId": {
        "type": "keyword"           // 精确匹配
      },
      "productName": {
        "type": "text",             // 全文检索
        "analyzer": "ik_smart_analyzer",
        "fields": {
          "keyword": {
            "type": "keyword"       // 子字段:精确匹配
          }
        }
      },
      "categoryId": {
        "type": "long"
      },
      "price": {
        "type": "double"
      },
      "stock": {
        "type": "integer"
      },
      "status": {
        "type": "byte"
      },
      "createTime": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss"
      },
      "tags": {
        "type": "keyword"           // 数组字段
      },
      "description": {
        "type": "text",
        "analyzer": "ik_smart_analyzer"
      }
    }
  }
}

4.2.2 索引优化原则

优化项建议
分片数单分片20~50GB;总分片数 = 节点数 × (1~2)
副本数生产环境至少1个副本;读写均衡场景2个
Refresh Interval搜索场景 1-10s(默认1s对写入压力大)
Translog异步持久化(写入性能提升5倍)
禁用Norms不需要评分的字段禁用,节省内存
禁用Doc Values不需要聚合排序的字段禁用
字段数量单个索引不超过1000个字段

4.3 与MySQL数据同步方案

方案对比

方案实时性一致性复杂度推荐场景
Canal → MQ → ES秒级最终一致中企业标准方案
Logstash JDBC分钟级(定时轮询)中低全量同步/小数据量
应用双写同步/准实时高高强一致性要求
Canal直接 → ES秒级最终一致低简单场景

Canal + MQ + ES架构

MySQL Binlog → Canal → MQ (Kafka/RocketMQ) → 消费程序 → ES
                                                     → 缓存 (可选)
                                                     → 其他下游

Canal配置:

# canal.properties
canal.serverMode = tcp
canal.destinations = example

# instance.properties
canal.instance.master.address = 127.0.0.1:3306
canal.instance.dbUsername = canal
canal.instance.dbPassword = canal
canal.instance.connectionCharset = UTF-8
canal.instance.filter.regex = mydb\\.t_product
canal.mq.topic = es_product_topic

ES消费程序:

@Component
public class ProductSyncConsumer {
    @Autowired
    private ElasticsearchRestTemplate esTemplate;

    @KafkaListener(topics = "es_product_topic", groupId = "es-sync-group")
    public void syncProduct(String message) {
        CanalMessage canalMsg = JSON.parseObject(message, CanalMessage.class);

        switch (canalMsg.getType()) {
            case "INSERT":
            case "UPDATE":
                Product product = JSON.parseObject(
                    canalMsg.getData().get(0).toString(), Product.class);
                esTemplate.save(product);
                break;
            case "DELETE":
                String productId = canalMsg.getData().get(0)
                    .getString("productId");
                esTemplate.delete(productId, Product.class);
                break;
        }
    }
}

4.4 复杂查询DSL示例

全文检索 + 过滤 + 聚合

GET /products/_search
{
  "size": 20,
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "productName": "手机 华为"      // 全文匹配
          }
        }
      ],
      "filter": [
        {
          "term": {
            "status": 1                    // 精确过滤
          }
        },
        {
          "range": {
            "price": {
              "gte": 1000,
              "lte": 8000                // 范围过滤
            }
          }
        }
      ]
    }
  },
  "sort": [
    { "price": { "order": "asc" } }      // 排序
  ],
  "highlight": {
    "fields": {
      "productName": {
        "pre_tags": ["<em>"],
        "post_tags": ["</em>"]
      }
    }
  },
  "aggs": {
    "by_category": {
      "terms": {
        "field": "categoryId",            // 按类目聚合
        "size": 10
      },
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "price"             // 每个类目的平均价格
          }
        }
      }
    }
  }
}

Java RestClient代码

@Service
public class ProductSearchService {
    @Autowired
    private ElasticsearchRestTemplate esTemplate;

    public SearchResponse<Product> searchProducts(
            String keyword, Long categoryId, Double priceMin,
            Double priceMax, int page, int size) {

        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        // 全文检索
        if (StringUtils.hasText(keyword)) {
            boolQuery.must(QueryBuilders.matchQuery("productName", keyword));
        }

        // 类目过滤
        if (categoryId != null) {
            boolQuery.filter(QueryBuilders.termQuery("categoryId", categoryId));
        }

        // 价格范围过滤
        if (priceMin != null || priceMax != null) {
            RangeQueryBuilder range = QueryBuilders.rangeQuery("price");
            if (priceMin != null) range.gte(priceMin);
            if (priceMax != null) range.lte(priceMax);
            boolQuery.filter(range);
        }

        // 分页(from + size,避免深分页用search_after)
        queryBuilder.withQuery(boolQuery)
            .withPageable(PageRequest.of(page, size))
            .withSort(Sort.by("price").ascending());

        SearchHits<Product> hits = esTemplate.search(
            queryBuilder.build(), Product.class);
        return new SearchResponse<>(hits);
    }
}

深分页问题:

方案原理适用场景
from + size默认分页前10000条
search_after基于上一页最后一条的排序值深度翻页(无跳页)
scroll快照游标全量导出/批量处理
PIT (Point In Time)7.x+ 替代Scroll稳定性更好的全量处理

4.5 ES集群规划

# elasticsearch.yml 配置要点

# 节点角色(7.x+ 角色分离)
node.roles: [ data, master, ingest ]

# 生产环境推荐 3个节点集群:
# Node 1: master + data + ingest
# Node 2: master + data + ingest
# Node 3: master + data + ingest
# 至少 3 个 master-eligible 节点(防止脑裂)

# JVM内存配置
# ES_JAVA_HOME=/usr/share/elasticsearch/jdk
# ES_JAVA_OPTS="-Xms16g -Xmx16g"   # 不超过物理内存50%,不超过32G

性能调优:

// 写入优化
PUT /_template/log_template
{
  "index_patterns": ["logs-*"],
  "settings": {
    "refresh_interval": "30s",          // 降低刷新频率
    "translog.durability": "async",     // 异步持久化
    "translog.sync_interval": "5s",
    "number_of_replicas": 0,            // 写入时副本数设0,写入后改为1
    "routing.allocation.total_shards_per_node": 3
  }
}

// 查询优化
// - 使用filter context代替query context(利用缓存)
// - 减少返回字段(_source选择性返回)
// - 避免script查询
// - 合理设置字段的index_options

5. 其他中间件速览

这些中间件在企业项目中也有广泛应用,面试中作为"加分项"出现。

5.1 MongoDB(文档存储)

对比维度MongoDBMySQL
数据模型文档型(BSON/JSON)关系型(表结构)
Schema无Schema固定Schema
查询方式类JSON查询SQL
事务4.0+ 支持多文档事务强事务支持
扩展方式原生分片分库分表
应用场景评论系统、日志、配置、物联网核心业务数据、复杂关系

典型用例:

// 评论系统:文档结构天然适合
@Document(collection = "comments")
public class Comment {
    @Id
    private String id;
    private Long userId;
    private String content;
    private List<String> images;
    private Date createTime;
    private Map<String, Integer> likes;  // {userId: 1}
}

5.2 Nginx(反向代理/负载均衡)

# Nginx核心配置示例

upstream backend_servers {
    # 负载均衡策略
    # 默认: 轮询
    # ip_hash: 根据IP哈希(Session保持)
    # least_conn: 最小连接数
    # weight: 权重
    server 127.0.0.1:8080 weight=3;
    server 127.0.0.1:8081 weight=2;
    server 127.0.0.1:8082 weight=1 backup;  # 备用
}

server {
    listen 80;
    server_name api.example.com;

    # 动静分离
    location /api/ {
        proxy_pass http://backend_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /static/ {
        root /var/www/site;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # 限流
    location /api/order/ {
        limit_req zone=order_limit burst=20 nodelay;
        proxy_pass http://backend_servers;
    }
}

# 限流区域定义
limit_req_zone $binary_remote_addr zone=order_limit:10m rate=100r/s;

5.3 MinIO / OSS(对象存储)

对比MinIO阿里云OSSAWS S3
部署自建(Docker/物理机)云服务云服务
协议S3兼容S3兼容原生S3
适用场景内网/私有云公网、CDN分发AWS生态
# MinIO Docker Compose
services:
  minio:
    image: minio/minio:latest
    ports:
      - "9000:9000"    # API端口
      - "9001:9001"    # Web控制台端口
    environment:
      MINIO_ROOT_USER: admin
      MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD}
    command: server /data --console-address ":9001"
    volumes:
      - ./data:/data

核心要点:数据存储与中间件层是企业后端架构的核心。MySQL解决"怎么存得稳"(持久化),Redis解决"怎么取得快"(缓存),MQ解决"怎么连得通"(解耦),ES解决"怎么找得到"(搜索)。四者相辅相成,构成一套完整的数据基础设施。