马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
目录
一、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
核心机制对比
维度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. 面试高频考点
- 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: " AID" } }$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 锁实现),答复时优先提供 落地办理方案 而非纯理论,显现工程化头脑。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |