从零吃透 Elasticsearch + 分布式基础:倒排索引、DSL、Seata 分布式事务、Redis 分布式锁全记录

📅 2026/8/11 8:36:15
从零吃透 Elasticsearch + 分布式基础:倒排索引、DSL、Seata 分布式事务、Redis 分布式锁全记录
这周是我学习 Java 后端的一个分水岭。前六周学完了 Spring Boot、MySQL、Redis、微服务、消息队列感觉自己已经能写业务代码了。但这一周学完 ES 和分布式基础之后我才意识到真正的后端开发远不止 CRUD 那么简单。一、Elasticsearch为什么 MySQL LIKE 不够用1.1 从模糊查询说起之前做项目有个搜索框要查书名。我想当然地写了SELECT * FROM book WHERE title LIKE %Java%。数据量小的时候跑得挺欢等数据上了十万接口直接超时——全表扫描索引用不上。后来才知道MySQL 的 B 树索引是为精确查找和范围扫描设计的。你想查内容里包含哪些词B 树根本帮不上忙。这时候就该 Elasticsearch 出场了。1.2 倒排索引ES 的核心武器ES 用的是倒排索引。打个比方你有一本书传统做法是第 1 页写了什么、第 2 页写了什么正排倒排索引反过来是Java 这个词出现在第 1、5、10 页虚拟机出现在第 3、7 页词 → 页码列表。查询的时候把搜索词分词“Java 虚拟机分成Java和虚拟机”直接查倒排索引表拿到包含这些词的文档 ID根本不用扫全表。这就是为什么 ES 能在毫秒级返回结果。1.3 text 和 keyword两种字段类型ES 里字段类型分两种text写入时分词存的是一个个词条用于全文搜索用 match 查它keyword不分词整个值作为一个整体用于精确匹配、排序、聚合用 term 查它新手第一大坑用 term 去查 text 字段。text 字段写入时词条会被转成小写“Java存成了java”而 term 不分词拿大写Java原样去查当然查不到。正确做法是精确匹配走 keyword 子字段{term: {title.keyword: 完整书名}}。二、DSL 查询ES 里的 SQLES 的查询语言叫 DSLDomain Specific Language本质就是一段 JSON。查询的统一入口是GET /索引名/_searchbody 里放查询条件。2.1 match 和 term全文搜索 vs 精确匹配match查询词会被分词任意词条命中即匹配并按命中程度计算 _score相关性得分用于全文搜索term查询词不分词整体精确匹配用于状态、ID、枚举等精确过滤口诀搜内容用 match筛条件用 term。2.2 bool 查询四个子句各管一摊真实业务从来不只有一个条件。“价格 30-80、书名含 Java、排除某分类、最好还讲并发”——这种组合条件用 bool 查询子句含义参与算分吗must必须满足相当于 AND✅ 参与filter必须满足相当于 AND❌ 不参与should满足了加分不满足也行相当于 OR✅ 参与must_not必须不满足相当于 NOT❌ 不参与must 和 filter 的区别是面试高频中的高频两者都要求条件必须满足但 must 会计算 _scorefilter 不算分。不算分为什么反而是优点因为过滤结果可以被 ES 缓存而且省掉了算分开销——所以所有不需要按相关度排序的条件状态、时间范围、价格区间、分类一律放 filter性能更好。2.3 深分页问题用 from size 分页时ES 是分布式的数据散在多个分片上。你要第 100 页from990, size10ES 得让每个分片都把前 1000 条发给协调节点合并排序再从中挑出你要的 10 条——越往后翻白搬运的数据越多。所以生产上一般限制翻页深度更深的需求用search_after。三、Spring Boot 集成 ES三步走3.1 依赖和配置引入spring-boot-starter-data-elasticsearchyml 里配置 ES 地址spring:elasticsearch:uris:http://localhost:92003.2 实体类映射用Document注解把 Java 类映射到 ES 索引Field定义字段类型和分词器Document(indexNamebooks)publicclassBookDocument{IdprivateStringid;Field(typeFieldType.Text,analyzerik_max_word)privateStringtitle;Field(typeFieldType.Keyword)privateStringcategory;// ...}3.3 NativeQuery 三步流程复杂查询bool 组合、排序、高亮用ElasticsearchOperationsNativeQuery// 第一步构建 QueryNativeQueryqueryNativeQuery.builder().withQuery(q-q.bool(b-b.must(m-m.match(mq-mq.field(title).query(keyword))).filter(f-f.range(r-r.field(price).gte(JsonData.of(30)).lte(JsonData.of(80)))))).withPageable(PageRequest.of(page-1,size)).withSort(s-s.field(f-f.field(price).order(SortOrder.Asc))).withHighlightQuery(newHighlightQuery(newHighlight(List.of(newHighlightField(title))),BookDocument.class)).build();// 第二步执行查询SearchHitsBookDocumenthitsesOperations.search(query,BookDocument.class);// 第三步处理结果含高亮for(SearchHitBookDocumenthit:hits.getSearchHits()){BookDocumentbookhit.getContent();MapString,ListStringhighlightFieldshit.getHighlightFields();if(highlightFields.containsKey(title)){book.setTitle(String.join(,highlightFields.get(title)));}}记住这个流程NativeQuery.builder() 链式拼条件 → search 执行 → 遍历 SearchHit 处理高亮。3.4 数据同步MySQL 和 ES 怎么保持一致MySQL 是主存储增删改、事务都靠它ES 是搜索副本。两份数据怎么同步同步双写业务代码写完 MySQL 顺手写 ES简单但有失败不一致的风险监听 binlog用 Canal 等工具监听 MySQL 变更日志异步同步到 ES业务解耦、一致性更好大厂常用四、分布式事务CAP、BASE 和四种方案4.1 问题从哪来单体应用里一个Transactional就能保证多个操作要么全成功、要么全回滚。但微服务里订单在 order-service订单库库存在 inventory-service库存库Transactional只能管住自己这一个数据库——订单插进去了远程调用扣库存失败了订单怎么办跨服务记账怎么保证不出错就是分布式事务要解决的问题。4.2 CAP 定理三者最多满足两个CAP 定理说一个分布式系统一致性C、可用性A、分区容错性P三者最多只能同时满足两个。关键推论P 是没得选的。分布式系统里机器之间靠网络通信而网络故障是必然会发生的你不可能不允许网络分区。所以实际的选择是在 C 和 A 之间二选一CP保一致宁可拒绝服务也不能给错数据。ZooKeeper选主期间短暂不可用、银行转账AP保可用先给你响应数据之后再对齐。Eureka、Nacos、电商商品详情页选型原则资金、库存这类给错数据会出大事的场景保 C搜索、推荐、详情页这类给旧数据也无所谓的场景保 A。4.3 BASE 理论AP 的具体落地选了 AP等于承认数据可以暂时不一致。BASE 理论给出了答案Basically Available基本可用出问题时系统不整体挂掉但允许降级Soft State软状态允许数据存在中间状态不要求实时同步Eventually Consistent最终一致性不要求实时一致但最终所有数据会对齐一句话总结BASE 牺牲强一致性换取系统的高可用。4.4 四种分布式事务方案方案一致性性能业务侵入适用场景2PC两阶段提交强一致差同步阻塞低传统数据库 XA现在少用TCC强一致较高高要写三个接口资金、核心短链路Seata AT最终一致接近强中低一个注解一般业务最常用MQ 最终一致最终一致高中允许延迟的异步场景Seata AT 模式是我最想说的。它的卖点是对业务几乎零侵入——加一个注解搞定。工作原理分两阶段一阶段每个分支事务直接执行本地提交同时 Seata 自动拦截 SQL把修改前后的数据快照记到undo_log表里beforeImage 和 afterImage二阶段如果所有分支都成功 → 全局提交undo_log 异步删掉如果任何分支失败 → 全局回滚按 undo_log 里的 beforeImage 生成反向 SQL 补偿回去为什么一阶段直接提交还能保证一致因为 Seata 有全局锁被全局事务占用的行记录其他全局事务不能改防止了脏写。代码里就一个注解GlobalTransactional(rollbackForException.class)publicvoidcreateOrder(LonguserId,LongproductId,Integercount){orderMapper.insert(newOrder(userId,productId,count));inventoryFeign.deduct(productId,count);// 远程扣库存accountFeign.debit(userId,count*100);// 远程扣余额}任何一步抛异常TC 会驱动所有分支按 undo_log 回滚。五、分布式锁Redis 的三个坑和 Redisson5.1 为什么需要分布式锁单机场景synchronized锁住一个 JVM 内的共享资源就够了。但微服务部署了多个实例3 个 Pod 各自扣库存synchronized只能锁住自己这个 JVM3 个实例之间互相看不到——库存只剩 1 件3 个 Pod 同时判断还有货各自扣一次结果超卖了。你需要一把跨仓库的公共钥匙——谁拿到这把公共钥匙谁才能进仓库操作这就是分布式锁。5.2 Redis 实现分布式锁的三个坑基础实现SET key value NX EX secondsNX 表示不存在才设置保证互斥EX 表示过期时间防止死锁。必须原子操作不能用 SETNX EXPIRE 两步。坑 1不可重入。同一个线程再次获取锁会失败——自己把自己锁死了。解决用 Hash 结构记录持有者和重入次数。坑 2过期时间不好设。设短了业务没执行完锁就过期别人拿到锁设长了持有者宕机别人要等很久。解决看门狗自动续期——默认给锁 30 秒过期每 10 秒检查一次持有者还活着就续期到 30 秒。坑 3主从切换锁丢失。Redis 主节点刚加锁、还没同步到从节点就挂了从节点升级为主——别人可以拿到锁互斥性被破坏。解决RedLock 算法向 N 个独立 Redis 节点都加锁过半成功才算成功但争议大生产环境强一致场景用 ZooKeeper 更可靠。5.3 Redisson把三个坑全填了Redisson 是 Redis 官方推荐的 Java 客户端封装了分布式锁的所有坑RLocklockredisson.getLock(lock:stock:1);try{lock.lock();// 默认30s过期看门狗自动续期// 业务逻辑}finally{lock.unlock();}生产环境直接用 Redisson不用自己手写。六、分布式 ID雪花算法6.1 方案对比UUID128 位无序太长不适合做主键B 树插入性能差数据库自增简单但有瓶颈单点、性能差Snowflake 雪花算法趋势递增、不依赖中心、高性能6.2 雪花算法 64 位分配1 位符号位永远 0 41 位时间戳毫秒级可用 69 年 10 位机器 ID最多 1024 台 12 位序列号每毫秒 4096 个趋势递增时间戳在高位整体递增B 树插入友好不依赖中心每台机器根据 workerId 独立生成不需要协调高性能本地计算不依赖网络时钟回拨问题服务器时间被 NTP 同步回退导致生成的 ID 重复。解决方案等待时钟追上、或报错拒绝、或用美团 Leaf 等开源方案处理。七、项目实战设备管理平台这周学的东西在设备管理平台里都用到了ES 搜索设备搜索接口用 ESbool 查询组合关键词全文搜 状态过滤 时间范围过滤 高亮飘红数据同步设备主数据存 MySQL通过 Canal 监听 binlog 异步同步到 ES分布式锁设备绑定/解绑时用 Redisson 锁住 deviceId防止并发重复绑定分布式事务设备绑定要同时更新用户服务和设备服务用 Seata AT 保证强一致分布式 ID设备数据上报量巨大用雪花算法生成趋势递增的 report_id面试讲项目时可以说“强一致的场景我们用 Seata AT允许延迟的场景用 MQ 最终一致性选型依据是业务对一致性的容忍度”——这一句话就能体现你不是背八股而是真的做过取舍。八、本周感悟这周学完我最大的感触是后端开发远不止 CRUD。之前觉得会写 Spring Boot、会连数据库、会调接口就够了。但这周学完 ES 和分布式基础之后我才意识到真正的后端开发要考虑性能ES 倒排索引、要考虑一致性分布式事务、要考虑并发分布式锁、要考虑扩展性分布式 ID。每一个技术选型背后都是对业务场景的权衡。CAP 定理告诉你为什么做不到完美BASE 理论告诉你实践中怎么妥协四种分布式事务方案告诉你不同场景怎么选。这周的内容面试必问尤其是 CAP、Seata AT、Redis 分布式锁的三个坑、雪花算法。我把它们整理成博客也算给自己做个总结。下一周要学 Kafka 和 Flink搜广推方向的核心技术。继续加油。参考资料Elasticsearch 官方文档Spring Data Elasticsearch 参考文档Seata 官方文档Redisson 官方文档作者王俊翰原文链接一周从零吃透 Elasticsearch 分布式基础转载请注明出处