目录
一、NoSQL 是什么?为什么我们需要 NoSQL?
• NoSQL 的本质 :非关系型数据库的核心理念 • 关系型数据库的局限性 :高并发、灵活 schema、海量数据存储的挑战 • NoSQL 的优劣对比:高性能 vs 弱一致性、灵活扩展 vs 事务支持
二、Redis 入门与核心机制
• Redis 的核心数据结构 :String/Hash/List/Set/ZSet • Java 整合实战 :Spring Boot + Lettuce/Redisson 客户端配置 • 第一个 Redis 项目:实现分布式 Session 管理
三、Redis 进阶:企业级应用场景
• 缓存穿透/击穿/雪崩解决方案 :布隆过滤器、空值缓存、熔断机制 • 分布式锁实现 :Redisson 的 RLock 与看门狗机制 • 热 Key 处理:本地缓存 + 随机过期时间
四、Redis 高可用与生产实战
• 持久化机制 :RDB 快照 vs AOF 日志的选型策略 • 集群方案 :Redis Cluster 分片与数据迁移 • 生产问题排查 :内存分析(memory usage)、慢查询日志
五、MongoDB 入门与文档模型
• 文档数据库核心概念 :BSON 格式、集合与文档 • Java 整合实战 :Spring Data MongoDB + @Document 注解 • 第一个 MongoDB 项目:电商商品详情存储
六、MongoDB 进阶:查询与聚合
• 复杂查询 :嵌套文档查询、数组操作($elemMatch) • 聚合管道 :$match/$group/$project 实战 • 索引优化:覆盖索引、TTL 索引自动清理
七、MongoDB 高可用与分片策略
• 副本集原理 :选举机制与数据同步 • 分片集群配置 :哈希分片 vs 范围分片 • 生产调优:连接池配置、写入关注(Write Concern)
八、Elasticsearch 入门与搜索原理
• 倒排索引机制 :全文检索的核心原理 • Java 整合实战 :Spring Data Elasticsearch + @Query 注解 • 第一个搜索项目:商品多条件筛选实现
九、Elasticsearch 进阶:分析与优化
• 中文分词 :IK 分词器配置与自定义词典 • 聚合分析 :指标聚合(avg/max)与分桶聚合(terms) • 性能调优 :分片策略、_source 字段控制
十、NoSQL 与关系型数据库协同架构
• 混合架构设计 :MySQL + Redis 缓存加速 • 数据同步方案 :Canal 监听 binlog 同步至 Elasticsearch • 一致性保障:本地消息表 + 最大努力通知
十一、NoSQL 面试题精选
• Redis • 如何用 Redis 实现分布式锁?有哪些注意事项? • Redis 集群数据分片原理是什么? • MongoDB • 分片键的选择标准是什么? • 如何设计嵌套文档避免数据冗余? • Elasticsearch • 倒排索引和正排索引的区别? • 深分页问题如何解决?(Search After vs Scroll API)
附录:工具与资源推荐
• 开发工具 • Redis 可视化:RedisInsight • Elasticsearch 调试:Kibana Dev Tools • 学习资源 • 书籍:《Redis 设计与实现》 • 课程:慕课网《Elasticsearch 核心技术与实战》 • 云服务方案 • 阿里云 Redis(Tair 持久内存版) • 腾讯云 MongoDB 分片集群
一、NoSQL 是什么?为什么我们需要 NoSQL?
NoSQL 的本质
定义 :NoSQL(Not Only SQL)是非关系型数据库 的统核心目标是解决关系型数据库(如 MySQL)在高并发、海量数据、灵活数据结构 场景下的局限性。 核心理念:
- 
去 schema 化:无需预先定义表结构(如 MongoDB 的文档动态扩展)。 
- 
分布式架构:天然支持水平扩展(如 Redis Cluster 自动分片)。 
- 
场景驱动设计:针对特定场景优化(如 Redis 内存优先、Elasticsearch 全文检索)。 
Java 开发者视角: • 用 Redis 缓存热点数据(如商品详情),减少 MySQL 压力。 • 用 MongoDB 存储动态 JSON 数据(如用户行为日志),避免频繁修改表结构。
关系型数据库的局限性
1. 高并发下的性能瓶颈
• 案例 :电商秒杀场景中,MySQL 的行级锁 和事务开销 导致吞吐量骤降。 • 解决方案 : • 用 Redis 预减库存(内存操作,TPS 可达 10万+/秒)。
// Spring Boot + Redis 实现秒杀扣库存
public boolean seckill(Long productId) {
    String key = "stock:" + productId;
    Long stock = redisTemplate.opsForValue().decrement(key);
    return stock != null && stock >= 0;
}2. 海量数据存储与扩展难题
• 问题 :单表数据过亿时,MySQL 查询性能指数级下降 ,分库分表复杂且易出错。 • 对比方案 : • MongoDB 分片集群 :自动拆分数据到多个节点,写入可线性扩展。 • Elasticsearch 分布式索引:TB 级日志数据实时检索。
3. 动态 Schema 的维护成本
• 场景 :用户画像系统需频繁增减字段(如新增"兴趣爱好"标签)。 • 关系型痛点 :ALTER TABLE 修改表结构导致锁表、服务停机。 • NoSQL 优势:MongoDB 直接插入新字段文档,无需停机。
NoSQL 的优劣对比
| 维度 | 优势 | 劣势 | 适用场景 | 
|---|---|---|---|
| 性能 | 内存操作(Redis)或分布式架构(MongoDB)实现高吞吐 | 弱一致性(如 MongoDB 默认最终一致性) | 缓存、计数器、实时统计 | 
| 扩展性 | 水平扩展简单(加节点即可扩容) | 跨节点事务支持弱(需依赖外部方案) | 大数据存储(用户日志) | 
| 数据结构灵活性 | 支持文档、图、键值等多样化数据模型 | 复杂关联查询能力弱(如 MongoDB 无 JOIN) | 动态 Schema(商品属性) | 
| 事务支持 | 部分支持(如 Redis 6.0 的 MULTI-EXEC) | 无法完全替代关系型数据库的 ACID 事务 | 金融核心系统仍需 MySQL | 
为什么 Java 后端必须掌握 NoSQL?
- 
企业架构标配 : • 互联网大厂架构 = MySQL + Redis + Elasticsearch(如订单系统用 MySQL 存储,Redis 抗并发,ES 做搜索)。 
- 
面试高频考点 : • Redis 的分布式锁 、缓存穿透/击穿/雪崩 解决方案(90% 的面试会问)。 • MongoDB 的副本集选举机制 、Elasticsearch 的倒排索引原理。 
- 
性能优化刚需 : • 单靠 MySQL 无法支撑万级 QPS,必须通过 Redis 缓存 、MongoDB 分片分流。 
二、Redis 入门与核心机制
1. Redis 的核心数据结构与 Java 操作
Redis 的战斗力源于其 5 大核心数据结构,Java 开发者需掌握其特性与 API 用法:
| 数据结构 | 特性与场景 | Java 操作示例(Spring Boot) | 
|---|---|---|
| String | 文本、计数器(阅读量/库存) | redisTemplate.opsForValue().set("key", 100) | 
| Hash | 对象存储(用户信息、购物车) | redisTemplate.opsForHash().put("user:1", "name", "Jack") | 
| List | 消息队列(LPUSH+BRPOP 实现阻塞队列) | redisTemplate.opsForList().leftPush("queue", task) | 
| Set | 去重集合(共同关注/抽奖黑名单) | redisTemplate.opsForSet().add("blacklist", "userA") | 
| ZSet | 排行榜(分数排序) | redisTemplate.opsForZSet().add("rank", "player1", 95) | 
面试考点 : • ZSet 底层结构 :跳跃表(SkipList) + 哈希表,实现 O(logN复杂度插入与查询。 • List 做消息队列的缺陷:无 ACK 机制,需自行实现消息可靠性(推荐改用 RabbitMQ/Kafka)。
2. Spring Boot 整合 Redis 实战
步骤 1:依赖与配置
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId> <!-- 默认使用 Lettuce 客户端 -->
</dependency># application.yml
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    lettuce:
      pool:
        max-active: 8 # 连接池最大连接数(根据 CPU 核数调整)步骤 2:注入 RedisTemplate
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 设置序列化方式(默认 JDK 序列化可读性差,建议改为 JSON)
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);
    template.setKeySerializer(RedisSerializer.string());
    template.setValueSerializer(RedisSerializer.json());
    return template;
}步骤 3:分布式 Session 实战
// 启用 Redis 存储 Session
@Configuration
@EnableRedisHttpSession 
public class RedisSessionConfig {
}
// 登录时存储用户信息到 Session
@PostMapping("/login")
public String login(HttpSession session,) {
    session.setAttribute("currentUser", user);
    return "Login Success!";
}
// 其他服务读取 Session(微服务架构共享 Session)
@GetMapping("/profile")
public User profile(HttpSession session) {
    return (User) session.getAttribute("currentUser");
}生产经验 : • Session 过期时间 :通过 maxInactiveIntervalInSeconds 配置(默认 30 分钟)。 • 安全建议:敏感信息(如密码)避免存入 Session,改用 Token 机制。
3. Redis 核心机制解析
3.1 单线程模型
• 为何单线程还能高性能?
- 
纯内存操作(纳秒级响应)。 
- 
非阻塞 I/O 多路复用(epoll 机制)。 
- 
无锁竞争(避免线程切换开销)。 
• 单线程的副作用 : • 长命令阻塞 :如 KEYS * 扫描全库,需用 SCAN 替代。 • CPU 瓶颈:多核服务器建议部署多个 Redis 实例。
3.2 持久化机制
| 机制 | 原理 | 优点 | 缺点 | 适用场景 | 
|---|---|---|---|---|
| RDB | 定时生成内存快照(二进制文件) | 文件小,恢复速度快 | 可能丢失最后一次快照后的数据 | 灾备恢复、主从复制 | 
| AOF | 记录所有写操作命令(追加日志) | 数据丢失少(可配置同步策略) | 文件大,恢复慢 | 高数据安全要求场景 | 
配置建议(redis.conf):
# 开启混合持久化(Redis 4.0+)
aof-use-rdb-preamble yes  
# RDB 每 5 分钟至少 1 次修改触发
save 300 1  
# AOF 每秒同步
appendfsync everysec  4. 生产级避坑指南
• 内存优化 : • 避免存储大 Value(如 10MB 的 String),拆分多个小 Key。 • 使用 HASH 替代多个独立 String(减少 Key 数量)。 • 连接池配置 : • Lettuce 连接数公式:max-active = 最大并发数 / 平均命令耗时。 • 监控指标:redisTemplate.getRequiredConnectionFactory().getMetrics()。
三、Redis 进阶:企业级应用场景
1. 缓存穿透/击穿/雪崩解决方案
定义与区别:
| 问题类型 | 触发场景 | 核心解决思路 | Java 实现方案 | 
|---|---|---|---|
| 缓存穿透 | 大量请求查询不存在的数据(如非法ID) | 拦截无效请求 | 布隆过滤器(Redisson RBloomFilter) | 
| 缓存击穿 | 热点 Key 过期后瞬间高并发请求 | 防止并发重建缓存 | 分布式锁(Redisson RLock) + 互斥重建 | 
| 缓存雪崩 | 大量 Key 同时过期或 Redis 宕机 | 分散过期时间 + 降级策略 | 随机过期时间 + 熔断机制(Hystrix/Sentinel) | 
实战代码示例:
// 布隆过滤器拦截非法请求  
public boolean checkBloomFilter(String key) {  
    RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("productFilter");  
    bloomFilter.tryInit(100000L, 0.01);  // 预期数据量 10万,误判率 1%  
    return bloomFilter.contains(key);  
}  
// 分布式锁互斥重建缓存  
public String getData(String key) {  
    String value = redisTemplate.opsForValue().get(key);  
    if (value == null) {  
        RLock lock = redissonClient.getLock(key + ":lock");  
        try {  
            if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {  
                value = db.query(key);  // 查询数据库  
                redisTemplate.opsForValue().set(key, value, 300 + (int)(Math.random() * 60), TimeUnit.SECONDS);  
            }  
        } finally {  
            lock.unlock();  
        }  
    }  
    return value;  
}  避坑指南 : • 布隆过滤器误判率 :需根据数据量调整参数,避免误判导致正常请求被拦截。 • 锁超时时间:锁自动释放时间需大于业务执行时间,防止并发漏洞。
2. 分布式锁实现(Redisson 核心机制)
Redisson 分布式锁特性 : • 自动续期 :看门狗线程每隔 10 秒检查锁状态并续期(默认锁过期时间 30 秒)。 • 可重入性 :同一线程可重复获取锁,避免死锁。 • 高可用:支持 Redis 单机、哨兵、集群模式。
Java 实现秒杀扣库存:
public boolean seckill(Long productId) {  
    String lockKey = "seckill:lock:" + productId;  
    RLock lock = redissonClient.getLock(lockKey);  
    try {  
        // 尝试加锁,最多等待 100ms,锁自动释放时间 30s  
        if (lock.tryLock(100, 30000, TimeUnit.MILLISECONDS)) {  
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock:" + productId));  
            if (stock > 0) {  
                redisTemplate.opsForValue().decrement("stock:" + productId);  
                return true;  
            }  
        }  
    } finally {  
        lock.unlock();  
    }  
 false;  
}  生产问题排查 : • 看门狗线程阻塞 :避免在锁代码块内执行耗时操作(如复杂计算或)。 • 网络分区风险:Redis 集群脑裂时可能导致锁失效,需配合 Redlock 算法(争议较大,谨慎使用)。
3. 热 Key 处理方案
热 Key 检测手段 : • Redis 内置命令 :redis-cli --hotkeys(需开启 maxmemory-policy 为 LFU)。 • 监控工具:阿里云 Redis 控制台、自研监控脚本(统计 Key 访问频率)。
解决方案:
- 
本地缓存:使用 Caffeine 缓存热点数据,降低 Redis 压力。 // Spring Boot 整合 Caffeine @Bean public Cache<String, Object> localCache() { return Caffeine.newBuilder() .expireAfterWrite(5, TimeUnit.SECONDS) // 短暂过期,避免脏数据 .maximumSize(1000) .build(); } // 查询逻辑 public Object getData(String key) { Object value = localCache.getIfPresent(key); if (value == null) { value = redisTemplate.opsForValue().get(key); localCache.put(key, value); } return value; }
- 
随机过期时间:分散 Key 过期时间,避免集中失效。 redisTemplate.opsForValue().set(key, value, 30 + (int)(Math.random() * 10), TimeUnit.SECONDS);
- 
读写分离 :Redis Cluster 中从节点分担读压力(需开启 readFrom=REPLICA)。
面试考点 : • 本地缓存一致性 :如何保证本地缓存与 Redis 数据一致?(答案:设置短过期时间 + 监听 Redis 更新事件) • 热 Key 限流:结合 Sentinel 或网关对热 Key 请求限流。
4. 企业级方案总结
| 场景 | 技术选型 | 注意事项 | 
|---|---|---|
| 缓存穿透 | 布隆过滤器 + 空值缓存 | 空值需设置短暂过期时间(如 60 秒) | 
| 高并发锁竞争 | Redisson 可重入锁 | 锁粒度控制(避免锁整个大对象) | 
| 热 Key 性能瓶颈 | 本地缓存 + 集群分片 | 本地缓存需考虑 JVM 内存压力 | 
三、Redis 进阶:企业级应用场景
1. 缓存穿透/击穿/雪崩解决方案
定义与区别
| 问题类型 | 触发场景 | 核心解决思路 | Java 实现方案 | 
|---|---|---|---|
| 缓存穿透 | 大量请求查询不存在的数据(如非法ID) | 拦截无效请求 | 布隆过滤器(Redisson RBloomFilter) | 
| 缓存击穿 | 热点 Key 过期后瞬间高并发请求 | 防止并发重建缓存 | 分布式锁(Redisson RLock) + 互斥重建 | 
| 缓存雪崩 | 大量 Key 同时过期或 Redis 宕机 | 分散过期时间 + 降级策略 | 随机过期时间 + 熔断机制(Hystrix/Sentinel) | 
实战代码示例
// 布隆过滤器拦截非法请求  
public boolean checkBloomFilter(String key) {  
    RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("productFilter");  
    bloomFilter.tryInit(100000L, 0.01);  // 预期数据量 10万,误判率 1%  
    return bloomFilter.contains(key);  
}  
// 分布式锁互斥重建缓存  
public String getData(String key) {  
    String value = redisTemplate.opsForValue().get(key);  
    if (value == null) {  
        RLock lock = redissonClient.getLock(key + ":  
        try {  
            if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {  
                value = db.query(key);  // 查询数据库  
                redisTemplate.opsForValue().set(key, value, 300 + (int)(Math.random() * 60), TimeUnit.SECONDS);  
            }  
        } finally {  
            lock.unlock();  
        }  
    }  
    return value;  
}  避坑指南
• 布隆过滤器误判率 :需根据数据量调整参数,避免误判导致正常请求被拦截。 • 锁超时时间:锁自动释放时间需大于业务执行时间,防止并发漏洞。
2. 分布式锁实现(Redisson 核心机制)
Redisson 分布式锁特性
• 自动续期 :看门狗线程每隔 10 秒检查锁状态并续期(默认锁过期时间 30 秒)。 • 可重入性 :同一线程可重复获取锁,避免死锁。 • 高可用:支持 Redis 单机、哨兵、集群模式。
Java 实现秒杀扣库存
public boolean seckill(Long productId) {  
    String lockKey = "seckill:lock:" + productId;  
    RLock lock = redissonClient.getLock(lockKey);  
    try {  
        // 尝试加锁,最多等待 100ms,锁自动释放时间 30s  
        if (lock.tryLock(100, 30000, TimeUnit.MILLISECONDS)) {  
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock:" + productId));  
            if (stock > 0) {  
                redisTemplate.opsForValue().decrement("stock:" + productId);  
                return true;  
            }  
        }  
    } finally {  
        lock.unlock();  
    }  
    return false;  
}  生产问题排查
• 看门狗线程阻塞 :避免在锁代码块内执行耗时操作(如复杂计算或同步 IO)。 • 网络分区风险:Redis 集群脑裂时可能导致锁失效,需配合 Redlock 算法(争议较大,谨慎使用)。
3. 热 Key 处理方案
热 Key 检测手段
• Redis 内置命令 :redis-clikeys(需开启 maxmemory-policy 为 LFU)。 • 监控工具:阿里云 Redis 控制台、自研监控脚本(统计 Key 访问频率)。
解决方案
- 
本地缓存:使用 Caffeine 缓存热点数据,降低 Redis 压力。 // Spring Boot 整合 Caffeine @Bean public Cache<String, Object> localCache() { return Caffeine.newBuilder() .expireAfterWrite(5, TimeUnit.SECONDS) // 短暂过期,避免脏数据 .maximumSize(1000) .build(); } // 查询逻辑 public Object getData(String key) { Object value = localCache.getIfPresent(key); if (value == null) { value = redisTemplate.opsForValue().get(key); localCache.put(key, value); } return value; }
- 
随机过期时间:分散 Key 过期时间,避免集中失效。 redisTemplate.opsForValue().set(key, value, 30 + (int)(Math.random() * 10), TimeUnit.SECONDS);
- 
读写分离 :Redis Cluster 中从节点分担读压力(需开启 readFrom=REPLICA)。
面试考点
• 本地缓存一致性 :如何保证本地缓存与 Redis 数据一致?(答案:设置短过期时间 + 监听 Redis 更新事件) • 热 Key 限流:结合 Sentinel 或网关对热 Key 请求限流。
4. 企业级方案总结
| 场景 | 技术选型 | 注意事项 | 
|---|---|---|
| 缓存穿透 | 布隆过滤器 + 空值缓存 | 空值需设置短暂过期时间(如 60 秒) | 
| 高并发锁竞争 | Redisson 可重入锁 | 锁粒度控制(避免锁整个大对象) | 
| 热 Key 性能瓶颈 | 本地缓存 + 集群分片 | 本地缓存需考虑 JVM 内存压力 | 
四、Redis 高可用与生产实战
1. 持久化机制:RDB vs AOF
核心机制对比
| 维度 | RDB | AOF | 
|---|---|---|
| 原理 | 定时生成内存快照(二进制文件) | 记录所有写操作命令(追加日志) | 
| 优点 | 文件小、恢复速度快 | 数据丢失少(可配置同步策略) | 
| 缺点 | 可能丢失最后一次快照后的数据 | 文件大、恢复慢 | 
| 配置触发条件 | save 900 1(15分钟至少1次修改) | appendfsync everysec(每秒同步) | 
| 生产选型 | 灾备恢复、主从复制 | 金融级数据安全要求场景 | 
Java 开发者配置建议(redis.conf):
# 开启混合持久化(Redis 4.0+ 推荐)  
aof-use-rdb-preamble yes  
# 每小时生成 RDB 快照  
save 3600 1  
# AOF 文件重写阈值(避免文件过大)  
auto-aof-rewrite-percentage 100  
auto-aof-rewrite-min-size 64mb  避坑指南 : • 禁用 save "" :若不需要 RDB,需明确禁用而非留空配置。 • AOF 重写阻塞 :主进程重写期间可能阻塞写入,建议在从节点执行 BGREWRITEAOF。
2. 集群方案:Redis Cluster 分片与迁移
核心原理
• 数据分片 : • 16384 个哈希槽(Hash Slot)分配到多个节点。 • Key 通过 CRC16(key) % 16384 计算所属槽位。 • 高可用: • 每个分片由主节点 + 至少1个从节点组成。 • 主节点宕机时,从节点自动升级为主节点(需至少半数主节点存活)。
Java 客户端配置(Spring Boot + Lettuce):
spring:  
  redis:  
    cluster:  
      nodes: 192.168.1.101:7001,192.168.1.102:7002  
      max-redirects: 3  # 最大重定向次数  
    lettuce:  
      pool:  
        max-active: 16  数据迁移实战:
# 将槽位 1000 从节点 A 迁移到节点 B  
redis-cli --cluster reshard 192.168.1.101:7001  
# 输入目标节点 ID、迁移槽位数、源节点 ID(all 表示所有节点分摊)  生产经验 : • 节点数量 :至少3主3从,避免集群脑裂(分区容忍性)。 • 槽位分配 :避免单节点负载过高,可通过 redis-cli --cluster rebalance 调整。
3. 生产问题排查:内存与性能分析
3.1 内存分析
关键命令:
# 查看 Key 内存占用  
MEMORY USAGE user:1001  
# 统计大 Key(单值 > 10KB)  
redis-cli --bigkeys  
# 分析内存碎片率  
INFO memory | grep mem_fragmentation_ratio  内存优化方案 : • 大 Key 拆分 :Hash 拆分为多个子 Key,List 分页存储。 • 过期策略:主动清理 + 随机过期时间(避免集中淘汰)。
3.2 慢查询日志
配置与查看:
# 设置慢查询阈值(单位:微秒)  
CONFIG SET slowlog-log-slower-than 10000  
# 查看最近10条慢查询  
SLOWLOG GET 10  日志字段解析:
1) 1) (integer) 12               # 日志ID  
   2) (integer) 1690000000       # 时间戳  
   3) (integer) 12000            # 执行耗时(微秒)  
   4) 1) "KEYS"                 # 命令  
      2) "*"  典型问题处理 : • KEYS/FLUSHALL :禁用高危命令,使用 SCAN 渐进式遍历。 • 复杂 Lua 脚本:避免执行超过 1 秒的脚本(阻塞主线程)。
4. 高可用架构选型总结
| 方案 | 适用场景 | 优缺点 | 国内云服务参考 | 
|---|---|---|---|
| 主从复制 | 读写分离、数据备份 | 简单易用,无自动故障转移 | 阿里云 Redis 基础版 | 
| 哨兵模式 | 自动故障转移(高可用) | 额外资源消耗,配置复杂 | 腾讯云 Redis 哨兵版 | 
| Redis Cluster | 海量数据、水平扩展 | 原生分布式,运维成本高 | 华为云 GeminiDB Redis 接口 | 
5. 面试高频考点
- 
Redis 持久化如何选择? • 综合场景:混合持久化(RDB+AOF)。 
- 
集群脑裂问题如何解决 ? • 配置 min-slaves-to-write 1(主节点至少需1个从节点同步)。
- 
线上 Redis 内存突然飙升,如何排查 ? • 步骤: INFO memory→--bigkeys→ 分析业务代码(是否存在循环写入)。
五、MongoDB 入门与文档模型
1. 文档数据库核心概念
1.1 BSON 格式
• 定义 :BSON(Binary JSON)是 MongoDB 的存储格式,在 JSON 基础上扩展支持更多数据类型(如日期、二进制数据)。 • 对比 JSON:
  // JSON  
  { "price": 99.9, "created_at": "2023-07-20" }  
  // BSON  
  { "price": NumberDecimal("99.9"), "created_at": ISODate("2023-07-20T00:00:00Z") }  • Java 映射 :通过 org.bson.Document 类或 Spring Data 的 @Document 注解实现 POJO 转换。
1.2 集合与文档
| 概念 | 关系型对应 | MongoDB 特性 | 
|---|---|---|
| 文档 | 行记录 | 动态 Schema(不同文档结构可不同) | 
| 集合 | 表 | 无需预定义结构,文档自动归属集合 | 
| 数据库 | 数据库 | 多个集合的容器 | 
适用场景: • 电商商品详情(不同类目商品属性差异大)。 • 用户行为日志(动态增减埋点字段)。
2. Spring Boot 整合 MongoDB 实战
2.1 依赖与配置
<!-- pom.xml -->  
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-data-mongodb</artifactId>  
</dependency>  # application.yml  
spring:  
  data:  
    mongodb:  
      uri: mongodb://user:password@127.0.0.1:27017/ecommerce  
      # 国内云服务示例(阿里云)  
      # uri: mongodb://user:password@docdb-xxx.mongodb.rds.aliyuncs.com:3717/admin?replicaSet=mgset-xxx  2.2 实体类与 Repository
// 商品详情实体类  
@Document(collection = "products")  
public class Product {  
    @Id  
    private String id;  
    private String name;  
    private Map<String, Object> specs;  // 动态规格参数(如颜色、尺寸)  
    // Getter/Setter  
}  
// Repository 接口  
public interface ProductRepository extends MongoRepository<Product, String> {  
    List<Product> findByName(String name);  // 自动生成查询方法  
}  2.3 基础 CRUD 操作
@Autowired  
private ProductRepository productRepository;  
// 插入文档  
public void saveProduct(Product product) {  
    productRepository.save(product);  
}  
// 查询文档  
public Product getProduct(String id) {  
    return productRepository.findById(id).orElse(null);  
}  
// 动态查询  
public List<Product> searchProducts(String keyword) {  
    Query query = new Query();  
    query.addCriteria(Criteria.where("name").regex(keyword, "i"));  
    return mongoTemplate.find(query, Product.class);  
}  3. 电商商品详情存储实战
3.1 文档设计示例
// 手机商品  
{  
  "name": "iPhone 14",  
  "specs": {  
    "color": "黑色",  
    "storage": "256GB",  
    "network": ["5G", "4G"]  
  }  
}  
// 服装商品  
{  
  "name": "男士T恤",  
  "specs": {  
    "size": "XL",  
    "material": "纯棉"  
  }  
}  3.2 动态查询与索引优化
查询示例:检索所有支持 5G 的手机
// 使用 MongoDB 的 $elemMatch 查询数组  
Query query = new Query();  
query.addCriteria(Criteria.where("specs.network").elemMatch(Criteria.where("$eq").is("5G")));  
List<Product> products = mongoTemplate.find(query, Product.class);  索引优化:
// 为 specs.network 字段创建索引  
@CompoundIndex(def = "{'specs.network': 1}")  
public class Product { /* ... */ }  
// 通过注解自动生成索引(需在启动类添加 @EnableMongoAuditing)  4. 生产经验与面试考点
4.1 生产避坑指南
• 连接池配置:
spring:  
  data:  
    mongodb:  
      uri: mongodb://host:port/db?maxPoolSize=50&minPoolSize=10  • 慢查询分析:
# 开启慢查询日志(执行时间超过 100ms)  
db.setProfilingLevel(1, 100)  
# 查看慢查询记录  
db.system.profile.find().sort({ ts: -1 }).limit(10)  4.2 面试高频问题
- 
MongoDB 与 MySQL 如何选型? • 答:动态 Schema 用 MongoDB,强事务用 MySQL。 
- 
文档嵌套层级过深有什么问题? • 答:查询性能下降,建议嵌套不超过 3 层,复杂关系拆分为引用。 
六、MongoDB 进阶:查询与聚合
1. 复杂查询:嵌套文档与数组操作
1.1 嵌套文档查询
场景 :电商订单中嵌套用户地址信息,需根据地址查询订单。 文档结构:
{  
  "orderId": "O1001",  
  "user": {  
    "name": "张三",  
    "address": {  
      "city": "北京",  
      "street": "朝阳区"  
    }  
  }  
}  Java 查询代码:
// 查询北京地区的所有订单  
Query query = new Query();  
query.addCriteria(Criteria.where("user.address.city").is("北京"));  
List<Order> orders = mongoTemplate.find(query, Order.class);  
// 使用字段投影(只返回用户地址)  
query.fields().include("user.address");  1.2 数组操作($elemMatch)
场景 :商品评论中包含标签数组,需筛选同时包含"质量好"和"物流快"的评论。 文档结构:
{  
  "productId": "P100",  
  "comments": [  
    { "userId": "U1", "tags": ["质量好", "物流快"] },  
    { "userId": "U2", "tags": ["质量好"] }  
  ]  
}  Java 查询代码:
// 使用 $elemMatch 匹配数组元素  
Query query = new Query();  
query.addCriteria(Criteria.where("comments").elemMatch(  
    Criteria.where("tags").all("质量好", "物流快")  
));  
List<Product> products = mongoTemplate.find(query, Product.class);  避坑指南 : • $all vs $and :$all 表示同时包含,$and 需满足多个条件(可能跨不同数组元素)。 • 性能优化 :为数组字段添加索引(如 db.products.createIndex({"comments.tags": 1}))。
2. 聚合管道实战
2.1 聚合阶段解析
| 阶段 | 作用 | 示例 | 
|---|---|---|
| $match | 过滤数据(类似 SQL WHERE) | { $match: { status: "PAID" } } | 
| $group | 分组统计(类似 SQL GROUP BY) | { $group: { _id: "$category", total: { $sum: "$price" } } } | 
| $project | 字段重塑(类似 SQL SELECT) | { $project: { productName: 1, price: 1 } } | 
| $sort | 排序结果 | { $sort: { total: -1 } } | 
2.2 电商用户行为分析案例
需求 :统计每个用户的订单总金额和平均订单价。 聚合管道:
Aggregation aggregation = Aggregation.newAggregation(  
    Aggregation.match(Criteria.where("status").is("PAID")),  // 筛选已支付订单  
    Aggregation.group("userId")  
        .sum("totalPrice").as("totalAmount")  
        .avg("totalPrice").as("avgAmount"),  
    Aggregation.sort(Sort.Direction.DESC, "totalAmount")  
);  
AggregationResults<UserOrderStats> results =  
    mongoTemplate.aggregate(aggregation, "orders", UserOrderStats.class);  输出结果:
[  
  { "_id": "U1001", "totalAmount": 1500, "avgAmount": 500 },  
  { "_id": "U1002", "totalAmount": 800, "avgAmount": 400 }  
]  3. 索引优化策略
3.1 覆盖索引(Covered Index)
原理:索引包含查询所需的所有字段,无需回表查询文档。 **:
// 创建覆盖索引  
db.orders.createIndex({ userId: 1, status: 1 });  
// 查询使用索引  
Query query = new Query();  
query.addCriteria(Criteria.where("userId").is("U1001").and("status").is("PAID"));  
query.fields().include("userId").include("status");  // 只返回索引字段  
List<Order> orders = mongoTemplate.find(query, Order.class);  性能对比 : • 无覆盖索引 :需扫描文档(COLLSCAN)。 • 有覆盖索引:仅扫描索引(IXSCAN)。
3.2 TTL 索引(自动清理过期数据)
场景 :自动删除 30 天前的用户登录日志。 Java 实现:
@Document(collection = "login_logs")  
public class LoginLog {  
    @Id  
    private String id;  
    @Indexed(expireAfterSeconds = 2592000)  // 30天过期  
    private Date loginTime;  
}  生产经验 : • 误差范围 :TTL 清理任务每分钟运行一次,数据可能延迟删除。 • 复合 TTL 索引:仅支持单字段,若需多字段组合需在代码逻辑处理。
4. 面试高频考点
- 
聚合管道执行顺序对性能的影响 ? • 答:优先使用 $match和$project减少数据处理量。
- 
如何选择索引类型? • 答:高频查询用复合索引,排序用有序索引,数组用多键索引。 
- 
$group的_id字段为null的作用? • 答:将所有文档视为一个分组(类似 SQL 的全局聚合)。
七、MongoDB 高可用与分片策略
1. 副本集(Replica Set)核心原理
1.1 副本集架构与选举机制
• 节点角色:
| 角色 | 作用 | 
|---|---|
| Primary | 处理所有写操作和读请求(默认) | 
| Secondary | 异步复制主节点数据,可处理读请求 | 
| Arbiter | 不存储数据,仅参与选举投票 | 
• 选举触发条件 : • 主节点宕机(心跳超时)。 • 超过半数节点无法通信(网络分区)。 • 强制重新选举(rs.stepDown())。
Java 客户端配置:
spring:  
  data:  
    mongodb:  
      uri: mongodb://主节点IP:27017,从节点IP:27017/dbname?replicaSet=rs0&readPreference=secondaryPreferred  • readPreference 参数 : • primary:默认,只从主节点读。 • secondaryPreferred:优先从从节点读,主节点不可用时切回。
2. 分片集群(Sharding)实战
2.1 分片集群组成
| 组件 | 作用 | 
|---|---|
| Shard | 存储实际数据的分片节点(每个分片可以是副本集) | 
| Config Server | 存储集群元数据(分片键、路由信息) | 
| Mongos | 路由节点,对外提供统一访问入口 | 
2.2 分片策略选择
| 分片类型 | 适用场景 | 优点 | 缺点 | 
|---|---|---|---|
| 哈希分片 | 数据均匀分布(如用户ID) | 负载均衡,写入扩展性强 | 范围查询效率低 | 
| 范围分片 | 范围查询频繁(如时间序列数据) | 查询性能高,易于冷热归档 | 可能导致数据不均 | 
分片键选择原则 : • 基数大 (如用户ID而非性别)。 • 写操作分布均匀 (避免热点分片)。 • 匹配查询模式(常用条件字段如时间、地域)。
2.3 分片集群搭建步骤
- 
启动 Config Server(3节点副本集): mongod --configsvr --replSet configRs --port 27019
- 
启动 Shard 节点(每个分片为副本集): mongod --shardsvr --replSet shard1Rs --port 27018
- 
启动 Mongos: mongos --configdb configRs/配置服务器IP:27019
- 
添加分片并启用分片: # 连接到 Mongos mongo --port 27017 # 添加分片 sh.addShard("shard1Rs/分片节点IP:27018") # 启用数据库分片 sh.enableSharding("ecommerce # 选择分片键 sh.shardCollection("ecommerce.orders", { "userId": "hashed" })
3. 生产调优与避坑指南
3.1 连接池配置
# Spring Boot 连接池配置(阿里云推荐)  
spring:  
  data:  
    mongodb:  
      uri: mongodb://mongos1:27017,mongos2:27017/dbname?  
           maxPoolSize=50&minPoolSize=10&maxIdleTimeMS=30000  参数建议 : • maxPoolSize:按 (总QPS / 单个连接QPS) * 1.2 计算。 • maxIdleTimeMS:30秒~5分钟,避免长空闲连接超时。
3.2 写入关注(Write Concern)
// Java 代码设置写入关注级别  
MongoTemplate mongoTemplate = new MongoTemplate(...);  
mongoTemplate.setWriteConcern(WriteConcern.MAJORITY); // 确保数据写入多数节点  | 级别 | 描述 | 数据安全性 | 性能影响 | 
|---|---|---|---|
| ACKNOWLEDGED | 默认,确认写入主节点 | 低 | 低 | 
| MAJORITY | 确认写入多数副本集节点 | 高 | 中 | 
| JOURNALED | 确认写入磁盘日志 | 最高 | 高 | 
4. 国内大厂解决方案
| 云服务商 | MongoDB 服务 | 核心特性 | 
|---|---|---|
| 阿里云 | 云数据库 MongoDB 版 | 全托管分片集群、跨可用区容灾 | 
| 腾讯云 | 云数据库 MongoDB | 一键扩缩容、审计日志与慢查询分析 | 
| 华为云 | 文档数据库 DDS | 兼容 MongoDB API、备份恢复自动化 | 
5. 面试高频考点
- 
如何解决分片集群数据分布不均的问题 ? • 答:调整分片键(增加散列性)、手动迁移块( sh.moveChunk())。
- 
副本集选举过程中如何避免脑裂 ? • 答:配置 多数节点在同一机房或通过priority调整节点权重。
- 
分片键选择错误如何补救? • 答:唯一方案是创建新集合并重新分片,旧数据迁移(无在线修改分片键功能)。 
八、Elasticsearch 入门与搜索原理
1. Elasticsearch 核心概念
1.1 倒排索引机制
• 原理 : • 正排索引 :文档ID → 文档内容(类似数据库行记录)。 • 倒排索引 :关键词 → 文档ID列表(快速定位包含关键词的文档)。 • 示例:
  文档1: "Java工程师"  
  文档2: "Python工程师"  
  倒排索引:  
  "Java" → [文档1]  
  "工程师" → [文档1, 文档2]  
  "Python" → [文档2]  Java 开发者视角 : • 优势:全文检索速度远超 MySQL 的 LIKE 查询。 • 场景:商品搜索、日志分析、实时数据分析。
2. Spring Boot 整合 Elasticsearch
2.1 依赖与配置
<!-- pom.xml -->  
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>  
</dependency>  # application.yml  
spring:  
  elasticsearch:  
    uris: http://localhost:9200  
    # 阿里云服务配置示例  
    # uris: https://es-cn-xxx.elasticsearch.aliyuncs.com:9200  
    # username: elastic  
    # password: your_password  2.2 实体类与 Repository
@Document(indexName = "products")  
public class Product {  
    @Id  
    private String id;  
    @Field(type = FieldType.Text, analyzer = "ik_max_word")  
    private String name;  
    @Field(type = FieldType.Double)  
    private Double price;  
    // Getter/Setter  
}  
public interface ProductRepository extends ElasticsearchRepository<Product, String> {  
    List<Product> findByName(String name);  
}  2.3 基础 CRUD 操作
@Autowired  
private ProductRepository productRepository;  
// 插入文档  
public void saveProduct(Product product) {  
    productRepository.save(product);  
}  
// 搜索商品  
public List<Product> search(String keyword) {  
    NativeSearchQuery query = new NativeSearchQueryBuilder()  
        .withQuery(QueryBuilders.matchQuery("name", keyword))  
        .build();  
    return elasticsearchOperations.search(query, Product.class)  
                                   .getSearchHits()  
                                   .stream()  
                                   .map(SearchHit::getContent)  
                                   .collect(Collectors.toList());  
}  3. 搜索与聚合实战
3.1 中文分词与 IK 分词器
配置 IK 分词器:
- 
下载 IK 插件并放入 Elasticsearch 的 plugins目录。
- 
重启 Elasticsearch,实体类字段指定分词器: @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart") private String description;
搜索示例:
// 使用 multi_match 跨字段搜索  
NativeSearchQuery query = new NativeSearchQueryBuilder()  
    .withQuery(QueryBuilders.multiMatchQuery(keyword, "name", "description"))  
    .build();  3.2 聚合分析
统计每个类目的商品数量与平均价格:
TermsAggregationBuilder aggregation = AggregationBuilders  
    .terms("category_agg").field("category.keyword")  
    .subAggregation(AggregationBuilders.avg("avg_price").field("price"));  
NativeSearchQuery query = new NativeSearchQueryBuilder()  
    .addAggregation(aggregation)  
    .build();  
SearchHits<Product> hits = elasticsearchOperations.search(query, Product.class);  输出结果:
"aggregations": {  
  "category_agg": {  
    "buckets": [  
      { "key": "手机", "doc_count": 100, "avg_price": 2999 },  
      { "key": "笔记本", "doc_count": 50, "avg_price": 6999 }  
    ]  
  }  
}  4. 集群与高可用架构
4.1 分片与副本机制
• 分片(Shard) :数据水平拆分的单元(默认每个索引5个主分片)。 • 副本(Replica):每个分片的拷贝(默认1个副本,提升容错与读性能)。
Java 客户端配置:
spring:  
  elasticsearch:  
    uris: http://node1:9200,http://node2:9200,http://node3:9200  4.2 集群状态监控
# 查看集群健康状态  
GET /_cluster/health  
# 查看节点分片分布  
GET /_cat/shards  
---
## **5. 生产调优与面试考点**  
### **5.1 性能优化**  
• **分片策略**:  
  • 单个分建议 10GB~50GB(阿里云推荐)。  
  • 避免过度分片(每个分片消耗资源)。  
• **内存配置**:  
  • JVM 堆内存不超过 32GB(避免指针压缩失效)。  
  • 预留 50% 内存给操作系统文件缓存。  
### **5.2 高频面试题1. **倒排索引为什么比 B+ 树快**?  
   • 答:倒排索引直接定位关键词,B+ 树需从根节点逐层查找。  
2. **如何解决深分页性能问题**?  
   • 答:改用 `search_after` 分页(游标分页),避免 `from + size` 深分页。  
3. **集群脑裂如何预防**?  
   • 答:配置 `discovery.zen.minimum_master_nodes = (节点数/2 + 1)`。  九、Elasticsearch 进阶:分析与优化
1. 索引设计与 Mapping 优化
1.1 索引生命周期管理(ILM)
场景 :电商日志按天滚动存储,自动删除30天前数据。 Java 配置索引模板:
// 定义 ILM 策略(Rollover + Delete)  
String policyJson = """  
{  
  "policy": {  
    "phases": {  
      "hot": {  
        "actions": {  
          "rollover": { "max_size": "50GB", "max_age": "30d" }  
        }  
      },  
      "delete": {  
        "min_age": "30d",  
        "actions": { "delete": {} }  
      }  
    }  
  }  
}  
""";  
// 创建索引模板关联 ILM  
IndexTemplateRequest request = new IndexTemplateRequest("logs-template");  
request.patterns(List.of("logs-*"));  
request.settings(Settings.builder().put("index.lifecycle.name", "logs-policy"));  
client.indices().putTemplate(request, RequestOptions.DEFAULT);  1.2 字段类型优化
| 字段类型 | 适用场景 | 优化技巧 | 
|---|---|---|
| Keyword | 精确匹配(如状态码、分类ID) | 避免对长文本使用(占用内存) | 
| Text | 全文检索(如商品描述) | 结合 fields实现多分词策略 | 
| Date | 时间范围查询 | 指定 format避免解析歧义 | 
| Nested | 对象数组独立查询 | 控制嵌套层级(不超过3层) | 
Java 实体类优化示例:
@Document(indexName = "products")  
public class Product {  
    @Field(type = FieldType.Text, analyzer = "ik_max_word", fields = {  
        @Field(type = FieldType.Keyword, name = "raw")  // 精确匹配子字段  
    })  
    private String name;  
    @Field(type = FieldType.Nested)  // 嵌套类型  
    private List<Spec> specs;  
}  2. 查询性能优化
2.1 DSL 查询优化技巧
• 避免通配符查询:
  // 错误示例:通配符导致全索引扫描  
  QueryBuilders.wildcardQuery("name", "*手机*");  
  // 正确方案:改用分词后的全文检索  
  QueryBuilders.matchQuery("name", "智能手机");  • 过滤器上下文(Filter Context):
// 使用 filter 不计算相关性分数,提升性能  
BoolQueryBuilder query = QueryBuilders.boolQuery()  
    .filter(QueryBuilders.termQuery("status", "ON_SALE"))  
    .must(QueryBuilders.matchQuery("name", "手机"));  2.2 聚合查询优化
Terms 聚合 Cardinality 控制:
// 限制返回桶数量,避免内存溢出  
TermsAggregationBuilder agg = AggregationBuilders.terms("category_agg")  
    .field("category.keyword")  
    .size(100);  // 默认10,按需调整  
// 使用 show_term_doc_count_error 跳过低频词条  
agg.showTermDocCountError(true);  Pipeline 聚合优化:
// 使用 moving_avg 减少计算开销  
AggregationBuilder movingAvg = PipelineAggregatorBuilders  
    .movingAvg("price_trend", "price");  3. 分片与集群管理
3.1 分片策略最佳实践
• 分片数量公式:
总分片数 = 数据总量 / 单个分片推荐大小(30GB)  • 动态扩容 : • 增加节点后,Elasticsearch 自动平衡分片。 • 手动迁移:POST _cluster/reroute 调整分片分布。
3.2 集群监控与告警
关键指标监控 : • 节点健康 :GET _cluster/health • 资源瓶颈:CPU、内存、磁盘IO(通过 Prometheus + Grafana 可视化)。
Java 集成告警:
// 使用 Elasticsearch Java Client 查询集群状态  
ClusterHealthRequest request = new ClusterHealthRequest();  
ClusterHealthResponse response = client.cluster().health(request, RequestOptions.DEFAULT);  
if (response.getStatus() == ClusterHealthStatus.RED) {  
    // 触发告警通知  
}  4. 生产问题排查
4.1 慢查询日志分析
启用慢日志:
PUT /products/_settings  
{  
  "index.search.slowlog.threshold.query.warn": "10s",  
  "index.search.slowlog.threshold.fetch.debug": "500ms"  
}  日志解读:
[2023-07-20T10:00:00] took[15s], took_millis[15000], types[], stats[],  
search_type[QUERY_THEN_FETCH], total_shards[100], extra[]  4.2 内存与 GC 调优
JVM 参数建议:
# jvm.options  
-Xms16g  
-Xmx16g  
-XX:+UseG1GC  
-XX:MaxGCPauseMillis=200  堆内存分配原则: • 不超过物理内存的 50%,且不超过 32GB(压缩指针优势)。 • 预留 50% 内存给 Lucene 文件缓存。
5. 面试高频考点
- 
如何设计一个支持千万级商品搜索的系统 ? • 分片策略:按类目哈希分片 + 时间范围别名。 • 查询优化:禁用 _source字段 + 路由分片查询。
- 
Elasticsearch 如何保证数据一致性 ? • 写入:主分片同步复制( consistency=quorum)。 • 读取:preference=_primary强制读主分片。
- 
Terms 聚合的精度问题如何解决 ? • 方案: execution_hint=map提升速度,或使用composite分页聚合。
十、NoSQL 与关系型数据库协同架构
1. 混合架构设计:MySQL + Redis 缓存加速
1.1 典型场景:电商首页性能优化
• 架构设计 : • Redis :缓存商品详情、秒杀库存、用户会话。 • MySQL :存储订单、用户账户等强一致性数据。 • Elasticsearch:商品搜索与聚合分析。
• Java 实现缓存逻辑:
  @Cacheable(value = "product", key = "#productId")  
  public Product getProduct(Long productId) {  
      // 缓存未命中时查询数据库  
      return productRepository.findById(productId).orElse(null);  
  }  
  @CacheEvict(value = "product", key = "#productId")  
  public void updateProduct(Product product) {  
      productRepository.save(product);  
      // 可选:延迟双删(防缓存不一致)  
      redisTemplate.delete("product:" + product.getId());  
  }  生产经验 : • 缓存击穿防护 :热点 Key 永不过期 + 互斥锁重建。 • 数据一致性:监听 MySQL binlog(如 Canal)同步删除缓存。
2. 数据同步方案:Canal → Elasticsearch
2.1 Canal 工作原理
• 核心流程:
- 
Canal 伪装为 MySQL 从库,接收 binlog。 
- 
解析 binlog 为结构化数据(JSON/ProtoBuf)。 
- 
推送变更事件到 Kafka/RocketMQ。 
- 
消费者(Java 服务)将数据写入 Elasticsearch。 
Java 监听示例:
@KafkaListener(topics = "canal.product")  
public void syncProductToES(String message) {  
    CanalMessage<Product> canalMessage = JSON.parseObject(message, new TypeReference<>() {});  
    if (canalMessage.getType() == CanalMessage.EventType.UPDATE) {  
        elasticsearchTemplate.save(canalMessage.getData());  
    }  
}  2.2 同步一致性保障
• 最终一致性方案 : • 本地消息表 :将同步任务写入数据库,确保事务原子性。 java @Transactional public void createOrder(Order order) { orderRepository.save(order); // 写入本地消息表 eventRepository.save(new Event("order_created", order.getId())); } • 最大努力通知 :定时任务重试失败事件(如每5分钟)。 java @Scheduled(fixedRate = 300000) public void retryFailedEvents() { List<Event> failedEvents = eventRepository.findByStatus(EventStatus.FAILED); failedEvents.forEach(event -> { if (retrySync(event)) { event.setStatus(EventStatus.SUCCESS); } }); }
3. 一致性保障:本地消息表 + 最大努力通知
3.1 分布式事务挑战
• 场景 :用户支付成功后,需更新订单状态(MySQL)并发送消息通知(Redis/ES)。 • 难点:跨数据库事务无法保证原子性。
3.2 解决方案对比
| 方案 | 原理 | 适用场景 | Java 实现复杂度 | 
|---|---|---|---|
| 本地消息表 | 数据库事务 + 异步重试 | 中低频业务(如订单通知) | 低 | 
| TCC 事务 | Try-Confirm-Cancel 阶段补偿 | 高频高一致性(如账户扣款) | 高 | 
| Seata AT 模式 | 全局锁 + 反向 SQL 回滚 | 简单事务,强依赖数据库支持 | 中 | 
本地消息表 Java 实现:
public void payOrder(Long orderId) {  
    // 1. 开启事务  
    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());  
    try {  
        // 2. 更新订单状态  
        orderRepository.updateStatus(orderId, PAID);  
        // 3. 写入本地消息表  
        eventRepository.save(new Event("order_paid", orderId));  
        // 4. 提交事务  
        transactionManager.commit(status);  
    } catch (Exception e) {  
        transactionManager.rollback(status);  
        throw e;  
    }  
}  4. 生产级架构设计案例
4.1 电商订单系统协同架构
• 组件分工 : • MySQL :订单表、用户表(ACID 事务)。 • Redis :库存扣减(原子操作)、订单状态缓存。 • Elasticsearch :订单历史搜索(按时间、商品名检索)。 • Kafka:订单创建事件通知(积分发放、物流调度)。
• 数据流向:
- 
用户下单 → MySQL 写入订单 → Canal 同步到 ES。 
- 
支付成功 → Redis 清除库存缓存 → 本地消息表触发积分服务。 
4.2 容灾与降级策略
• 缓存故障 :熔断策略(直接读 DB,记录日志后续补偿)。 • ES 同步延迟:本地缓存近期订单,提供降级查询接口。
5. 面试高频考点
- 
如何保证缓存与数据库的一致性? • 答:延迟双删 + binlog 监听失效缓存。 
- 
Canal 同步数据时,如何处理删除操作? • 答:解析 binlog 的 DELETE 事件,触发 ES 的 deleteByQuery。 
- 
最大努力通知可能造成消息重复消费,如何解决? • 答:消费者接口幂等设计(如唯一流水号 + 数据库去重表)。 
附录:工具与资源推荐
• 数据同步工具 : • 阿里云 Canal(开源版) • Debezium(Kafka Connect 生态) • 学习资源: • 《凤凰架构》(本地消息表与 TCC 详解) • Elastic 官方文档《Cross-cluster replication》
十一、NoSQL 面试题精选
Redis
1. 如何用 Redis 实现分布式锁?有哪些注意事项?
答案:
- 
核心实现 : • 加锁 : SET key unique_value NX PX 30000(NX 表示不存在时设置,PX 设置过期时间)。 • 解锁:Lua 脚本保证原子性(校验值 + 删除 Key)。if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end
- 
注意事项 : • 锁续期 :使用 Redisson 的看门狗机制(默认每 10 秒续期)。 • 网络分区风险 :集群脑裂可能导致锁失效,需结合业务重试机制。 • 锁粒度:避免锁住大对象(如锁整个用户表,改为锁用户ID)。 
大厂考点 : • RedLock 算法的争议 :需半数以上节点加锁成功,但网络分区时仍可能失效。 • GC 停顿影响:JVM 长时间 GC 可能导致锁过期后业务仍在执行。
2. Redis 集群数据分片原理是什么?
答案 : • 虚拟槽分区 : • 预分配 16384 个槽(Slot),每个 Key 通过 CRC16(key) % 16384 计算所属槽。 • 节点负责部分槽,集群扩容时通过 redis-cli --cluster reshard 迁移槽数据。 • 客户端路由: • 客户端缓存槽与节点的映射关系,直接定位目标节点(Moved/Ask 重定向优化)。
生产经验 : • 热点 Key :同一槽的 Key 可能集中在某节点,可通过 Hash Tag 强制分配(如 {user100}.order)。 • 迁移阻塞 :避免在迁移过程中执行 KEYS 或 FLUSHDB 等高危命令。
MongoDB
1. 分片键的选择标准是什么?
答案:
- 
基数大:如用户ID(唯一性高)优于性别(基数低)。 
- 
写分布均匀选择单调递增字段(如时间戳),导致写入集中在最后一个分片。 
- 
匹配查询模式:高频查询条件字段(如地域、类目)。 
分片策略对比:
| 类型 | 优点 | 缺点 | 
|---|---|---|
| **哈希分 | 数据均匀分布 | 范围查询需跨分片 | 
| 范围分片 | 高效范围查询 | 可能导致数据倾斜 | 
案例 : • 电商订单表按 用户ID 哈希分片,用户行为日志按 时间范围 分片。
2. 如何设计嵌套文档避免数据冗余?
答案 : • 嵌套层级 :不超过 3 层(如 用户 → 订单 → 商品)。 • 引用设计 :高频查询的字段内嵌,低频字段用 DBRef 或手动关联。
// 内嵌常用字段  
{  
  "orderId": "O1001",  
  "user": {  
    "name": "张三",  
    "userId": "U1001"  // 用于关联查询用户详情  
  }  
}  避坑指南 : • 数组膨胀:避免单个文档的数组无限增长(如评论列表),超过 16MB 文档限制时需分页存储。
Elasticsearch
1. 倒排索引和正排索引的区别?
答案:
| 索引类型 | 数据结构 | 应用场景 | 
|---|---|---|
| 倒排索引 | 词项 → 文档列表(快速定位文档) | 全文检索、关键词过滤 | 
| 正排索引 | 文档ID → 字段值(存储原始数据) | 排序、聚合、高亮显示 | 
技术细节 : • Doc Values :Elasticsearch 的正排索引实现,列式存储优化聚合性能。 • 联合查询:倒排索引筛选文档,正排索引计算排序/聚合。
2. 深分页问题如何解决?(Search After vs Scroll API)
答案 : • 问题根源 :from + size 深分页时,协调节点需汇总所有分片数据,内存消耗大。 • 解决方案:
| 方案 | 原理 | 适用场景 | 
|---|---|---|
| Search After | 基于上一页排序值定位 | 用户实时翻页(如下一页按钮) | 
| Scroll API | 创建快照,游标遍历(非实时) | 数据导出、离线分析 | 
Java 实现 Search After:
SearchRequest request = new SearchRequest("products");  
SearchSourceBuilder source = new SearchSourceBuilder()  
    .size(10)  
    .sort("price", SortOrder.ASC)  
    .sort("_id", SortOrder.ASC);  // 避免排序字段值重复  
// 第二页开始设置 Search After  
if (lastPagePrices != null) {  
    source.searchAfter(lastPagePrices);  
}  生产经验 : • Scroll API 限制:快照占用堆内存,不适合高并发场景。 性能对比**:Search After 比 Scroll 快 5 倍以上(无快照开销)。
总结 :NoSQL 面试需结合原理、生产避坑经验、框架源码(如 Redisson 锁实现),回答时优先提供 落地解决方案 而非纯理论,展现工程化思维。