NoSQL数据库

目录

一、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)在高并发、海量数据、灵活数据结构 场景下的局限性。 核心理念

  1. 去 schema 化:无需预先定义表结构(如 MongoDB 的文档动态扩展)。

  2. 分布式架构:天然支持水平扩展(如 Redis Cluster 自动分片)。

  3. 场景驱动设计:针对特定场景优化(如 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?

  1. 企业架构标配 : • 互联网大厂架构 = MySQL + Redis + Elasticsearch(如订单系统用 MySQL 存储,Redis 抗并发,ES 做搜索)。

  2. 面试高频考点 : • Redis 的分布式锁缓存穿透/击穿/雪崩 解决方案(90% 的面试会问)。 • MongoDB 的副本集选举机制 、Elasticsearch 的倒排索引原理

  3. 性能优化刚需 : • 单靠 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 单线程模型

为何单线程还能高性能

  1. 纯内存操作(纳秒级响应)。

  2. 非阻塞 I/O 多路复用(epoll 机制)。

  3. 无锁竞争(避免线程切换开销)。

单线程的副作用 : • 长命令阻塞 :如 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 访问频率)。

解决方案

  1. 本地缓存:使用 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;  
    }  
  2. 随机过期时间:分散 Key 过期时间,避免集中失效。

    复制代码
    redisTemplate.opsForValue().set(key, value, 30 + (int)(Math.random() * 10), TimeUnit.SECONDS);  
  3. 读写分离 :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 访问频率)。

解决方案

  1. 本地缓存:使用 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;  
    }  
  2. 随机过期时间:分散 Key 过期时间,避免集中失效。

    复制代码
    redisTemplate.opsForValue().set(key, value, 30 + (int)(Math.random() * 10), TimeUnit.SECONDS);  
  3. 读写分离 :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. 面试高频考点

  1. Redis 持久化如何选择? • 综合场景:混合持久化(RDB+AOF)。

  2. 集群脑裂问题如何解决 ? • 配置 min-slaves-to-write 1(主节点至少需1个从节点同步)。

  3. 线上 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:[email protected]:27017/ecommerce  
      # 国内云服务示例(阿里云)  
      # uri: mongodb://user:[email protected]: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 面试高频问题

  1. MongoDB 与 MySQL 如何选型? • 答:动态 Schema 用 MongoDB,强事务用 MySQL。

  2. 文档嵌套层级过深有什么问题? • 答:查询性能下降,建议嵌套不超过 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. 面试高频考点

  1. 聚合管道执行顺序对性能的影响 ? • 答:优先使用 $match$project 减少数据处理量。

  2. 如何选择索引类型? • 答:高频查询用复合索引,排序用有序索引,数组用多键索引。

  3. $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 分片集群搭建步骤

  1. 启动 Config Server(3节点副本集):

    复制代码
    mongod --configsvr --replSet configRs --port 27019  
  2. 启动 Shard 节点(每个分片为副本集):

    复制代码
    mongod --shardsvr --replSet shard1Rs --port 27018  
  3. 启动 Mongos

    复制代码
    mongos --configdb configRs/配置服务器IP:27019  
  4. 添加分片并启用分片

    复制代码
    # 连接到 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. 面试高频考点

  1. 如何解决分片集群数据分布不均的问题 ? • 答:调整分片键(增加散列性)、手动迁移块(sh.moveChunk())。

  2. 副本集选举过程中如何避免脑裂 ? • 答:配置 多数节点在同一机房 或通过 priority 调整节点权重。

  3. 分片键选择错误如何补救? • 答:唯一方案是创建新集合并重新分片,旧数据迁移(无在线修改分片键功能)。


八、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 分词器

  1. 下载 IK 插件并放入 Elasticsearch 的 plugins 目录。

  2. 重启 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. 面试高频考点

  1. 如何设计一个支持千万级商品搜索的系统 ? • 分片策略:按类目哈希分片 + 时间范围别名。 • 查询优化:禁用 _source 字段 + 路由分片查询。

  2. Elasticsearch 如何保证数据一致性 ? • 写入:主分片同步复制(consistency=quorum)。 • 读取:preference=_primary 强制读主分片。

  3. 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 工作原理

核心流程

  1. Canal 伪装为 MySQL 从库,接收 binlog。

  2. 解析 binlog 为结构化数据(JSON/ProtoBuf)。

  3. 推送变更事件到 Kafka/RocketMQ。

  4. 消费者(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:订单创建事件通知(积分发放、物流调度)。

数据流向

  1. 用户下单 → MySQL 写入订单 → Canal 同步到 ES。

  2. 支付成功 → Redis 清除库存缓存 → 本地消息表触发积分服务。

4.2 容灾与降级策略

缓存故障 :熔断策略(直接读 DB,记录日志后续补偿)。 • ES 同步延迟:本地缓存近期订单,提供降级查询接口。


5. 面试高频考点

  1. 如何保证缓存与数据库的一致性? • 答:延迟双删 + binlog 监听失效缓存。

  2. Canal 同步数据时,如何处理删除操作? • 答:解析 binlog 的 DELETE 事件,触发 ES 的 deleteByQuery。

  3. 最大努力通知可能造成消息重复消费,如何解决? • 答:消费者接口幂等设计(如唯一流水号 + 数据库去重表)。


附录:工具与资源推荐

数据同步工具 : • 阿里云 Canal(开源版) • Debezium(Kafka Connect 生态) • 学习资源: • 《凤凰架构》(本地消息表与 TCC 详解) • Elastic 官方文档《Cross-cluster replication》

十一、NoSQL 面试题精选


Redis

1. 如何用 Redis 实现分布式锁?有哪些注意事项?

答案

  1. 核心实现 : • 加锁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  
  2. 注意事项 : • 锁续期 :使用 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)。 • 迁移阻塞 :避免在迁移过程中执行 KEYSFLUSHDB 等高危命令。


MongoDB

1. 分片键的选择标准是什么?

答案

  1. 基数大:如用户ID(唯一性高)优于性别(基数低)。

  2. 写分布均匀选择单调递增字段(如时间戳),导致写入集中在最后一个分片。

  3. 匹配查询模式:高频查询条件字段(如地域、类目)。

分片策略对比

类型 优点 缺点
**哈希分 数据均匀分布 范围查询需跨分片
范围分片 高效范围查询 可能导致数据倾斜

案例 : • 电商订单表按 用户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 锁实现),回答时优先提供 落地解决方案 而非纯理论,展现工程化思维。

相关推荐
双叶83610 小时前
(51单片机)LCD显示温度(DS18B20教程)(LCD1602教程)(延时函数教程)(单总线教程)
c语言·开发语言·单片机·嵌入式硬件·mongodb·51单片机·nosql
双叶8361 天前
(51单片机)LCD显示数据存储(DS1302时钟模块教学)(LCD1602教程)(独立按键教程)(延时函数教程)(I2C总线认识)(AT24C02认识)
c语言·数据库·单片机·嵌入式硬件·mongodb·51单片机·nosql
镜舟科技5 天前
NoSQL 与 NewSQL 全面对比:如何选择适合你的数据库方案?
数据库·starrocks·nosql·newsql·技术架构·实时数据分析
lilye6610 天前
程序化广告行业(78/89):多因素交织下的行业剖析与展望
数据库·zookeeper·nosql
Microsoft Word16 天前
NoSQL数据库
数据库·nosql
爱的叹息19 天前
主流数据库的存储引擎/存储机制的详细对比分析,涵盖关系型数据库、NoSQL数据库和分布式数据库
数据库·分布式·nosql
这个懒人20 天前
深入解析Translog机制:Elasticsearch的数据守护者
数据库·elasticsearch·nosql·translog
这个懒人20 天前
MongoDB 核心机制解析
数据库·mongodb·nosql
dushky1 个月前
主流NoSQL数据库类型及选型分析
nosql数据库·nosql·数据库架构