欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 新车 > NoSQL数据库

NoSQL数据库

2025/4/23 13:23:19 来源:https://blog.csdn.net/2301_79902294/article/details/147402332  浏览:    关键词: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.1port: 6379password: 123456lettuce: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

核心机制对比

维度RDBAOF
原理定时生成内存快照(二进制文件)记录所有写操作命令(追加日志)
优点文件小、恢复速度快数据丢失少(可配置同步策略)
缺点可能丢失最后一次快照后的数据文件大、恢复慢
配置触发条件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: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 面试高频问题

  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 锁实现),回答时优先提供 落地解决方案 而非纯理论,展现工程化思维。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词