ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ES 8.14.0与SpringBoot整合:新版客户端配置与查询实战

ES 8.14.0与SpringBoot整合:新版客户端配置与查询实战 1. 开始之前8.x带来的几个新变化先理清楚再动手这几年在项目里和Elasticsearch打交道不少从6.x一路用到8.14.0发现很多朋友的习惯还停留在老一套尤其是拿旧教程硬套SpringBoot上来就各种报错。实际上Elasticsearch 8.14.0和SpringBoot整合这件事最大的变化不在SpringBoot这边而在于ES 8.x自己把安全机制的默认值、客户端SDK还有整体连接方式都改了。把这件事先捋清楚后面再做项目就顺很多。先说ES 8.14.0这边。从8.0开始Elasticsearch默认开启安全认证节点之间走TLS加密对外暴露的9200端口默认是HTTPS而不是HTTP。这个改动让很多人第一次启动ES就懵了——访问不了、密码不知道、证书报错满屏红色异常。旧版里那种“下载解压、直接curl localhost:9200就能看到一堆JSON”的日子已经没了。这是一个主动的安全加固把最低安全水位抬高了所以整合SpringBoot时第一步就不是“引入依赖写代码”而是先把连接用的账号密码、证书指纹、API Key这几个东西准备好。再来看客户端SDK。早期我们用TransportClient后来SpringBoot官方文档和大量博客教人用RestHighLevelClient这货在ES 8.x里也被标记为废弃官方推荐的正式替代品是elasticsearch-java这个新客户端它基于Java API Client底层还是走REST但通过代码生成的方式把ES的请求结构完整映射成了Java类型。RestHighLevelClient在7.17做了最后一次大版本维护到8.x只是兼容可用但不再增加新接口。所以新项目直接选新客户端别再用一个注定废弃的库。还有一点经常被忽略SpringBoot生态里有一个spring-boot-starter-data-elasticsearch它走的是Spring Data Elasticsearch这条路。很多教程拿它来讲CRUD但实际到了稍微复杂一点的搜索、聚合、高亮场景Spring Data那套抽象表达能力捉襟见肘最后还是得写原生查询DSL。我的做法很简单直接用elasticsearch-java官方客户端不走Spring Data封装SpringBoot只负责管理依赖和注入Bean。这样既能享受SpringBoot的便捷又不牺牲ES的原生查询能力。后面整个项目就是这么搭的。这套组合适合谁适合那种业务数据已经有MySQL兜底但需要在ES里做全文检索、聚合统计、日志分析的Java项目。本文所有内容都围绕实际开发中会碰到的场景来写不是翻译官方文档而是把搭建过程、核心操作、踩过的坑串一遍争取你看完就能在自己项目里跑起来。2. 环境准备与工程骨架版本对应关系是第一道坎2.1 先把ES 8.14.0跑起来Docker和tar包两种方式开发阶段我是优先推荐用Docker起ES的干净、版本切换方便、不需要深入配置系统参数。但要注意ES 8.14.0的Docker启动和7.x也不太一样主要是安全配置那几个参数。先看一眼最简单的方式单机模式Docker启动docker run -d \ --name es814 \ -p 9200:9200 -p 9300:9300 \ -e discovery.typesingle-node \ -e xpack.security.enabledtrue \ -e ELASTIC_PASSWORDyourpassword \ docker.elastic.co/elasticsearch/elasticsearch:8.14.0启动后打开终端执行curl --cacert certs/http_ca.crt -u elastic:yourpassword https://localhost:9200这里的http_ca.crt在容器里的/usr/share/elasticsearch/config/certs/目录下需要先copy出来才能让宿主机访问。如果你嫌证书麻烦可以在docker run时挂载目录-v /Users/you/es-certs:/usr/share/elasticsearch/config/certs如果你是直接下载tar包本地跑那么启动流程是解压、修改config/elasticsearch.yml里的discovery.type: single-node、然后执行bin/elasticsearch。第一次启动会在控制台打印一个临时密码默认用户是elastic千万别关终端关太快密码过一会儿就没了。如果没记下来重置密码的命令是bin/elasticsearch-reset-password -u elastic -i这个操作我建议在安装完就做别等到SpringBoot项目报401了才回来找密码。2.2 版本对应关系SpringBoot、Java、ES客户端一个都不能错ES的Java客户端对JDK版本有要求。8.14.0的elasticsearch-java要求Java 8以上但建议直接用Java 17因为SpringBoot 3.x本身也要求Java 17这样两边都省心。如果你项目还在Java 8那就选SpringBoot 2.7.x配合ES 8.14的客户端理论上能跑但我不推荐在生产环境这么干新客户端里有些代码用了java.time和varJava 8兼容性有坑。SpringBoot这边我用的是SpringBoot 3.2.x搭配ES官方客户端dependency groupIdco.elastic.clients/groupId artifactIdelasticsearch-java/artifactId version8.14.0/version /dependency dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.15.0/version /dependency dependency groupIdjakarta.json/groupId artifactIdjakarta.json-api/artifactId version2.1.3/version /dependency第三个依赖jakarta.json-api是很多人漏掉的。新客户端内部序列化JSON的对象模型标准基于Jakarta JSON Processing不引入它的话启动阶段大概率会报ClassNotFoundException: jakarta/json/Json。这个坑我踩过一次排查了半天最后发现是少了一个看似无关的依赖。如果你的项目用的是Maven记得检查最终打包的dependency tree里这3个jar都在。Gradle的话直接把三者放进implementation即可。2.3 工程骨架一个最小的SpringBoot整合ES目录结构我习惯的工程结构不是那种大而全的模块化而是按业务功能分包ES相关统一放在一个包下面方便以后换版本或者调整连接配置com.example.search ├── SearchApplication.java ├── config │ └── ElasticsearchConfig.java ├── entity │ └── Product.java ├── repository │ └── ProductSearchRepository.java ├── service │ └── ProductSearchService.java └── controller └── ProductSearchController.javarepository层我直接封装新客户端的操作不引入Spring Data那一套。这样做的好处是换ES大版本时只要改repository里的实现类对上面service层透明。3. 配置连接从TransportClient到现在的新Java API Client3.1 application.yml中怎么写连接配置ES客户端的配置并不复杂关键是把证书、账号、指纹这几样东西剥离开别硬编码到代码里。我用了Spring的配置绑定。elasticsearch: uris: https://localhost:9200 username: elastic password: ${ES_PASSWORD:yourpassword} ssl: enabled: true ca-fingerprint: 24:CE:... # 证书指纹从ES启动日志中获取指纹怎么拿ES 8.x首次启动的控制台日志里会打印一行Elasticsearch certificate fingerprint: 24:CE:...或者在容器里执行openssl x509 -fingerprint -sha256 -in config/certs/http_ca.crt把输出的指纹值复制出来配置进去就行。这里我说一个容易被忽略的点指纹配了之后如果后面ES密码重置了指纹不会变不用改但如果证书重新生成过指纹就会变别死记一个旧值。3.2 配置类创建RestClient和ElasticsearchClientSpringBoot的配置类写法如下package com.example.search.config; import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.json.jackson.JacksonJsonpMapper; import co.elastic.clients.transport.ElasticsearchTransport; import co.elastic.clients.transport.rest_client.RestClientTransport; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.ssl.SSLContexts; import org.elasticsearch.client.RestClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.net.ssl.SSLContext; Configuration public class ElasticsearchConfig { Value(${elasticsearch.uris}) private String uri; Value(${elasticsearch.username}) private String username; Value(${elasticsearch.password}) private String password; Value(${elasticsearch.ssl.ca-fingerprint}) private String caFingerprint; Bean public ElasticsearchClient elasticsearchClient() throws Exception { SSLContext sslContext SSLContexts.custom() .loadTrustMaterial(null, (chain, authType) - { // 开发环境可暂时信任证书生产环境务必校验 return true; }) .build(); HttpHost host HttpHost.create(uri); CredentialsProvider credentialsProvider new BasicCredentialsProvider(); credentialsProvider.setCredentials( AuthScope.ANY, new UsernamePasswordCredentials(username, password) ); RestClient restClient RestClient.builder(host) .setHttpClientConfigCallback(httpClientBuilder - httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider) .setSSLContext(sslContext)) .build(); ElasticsearchTransport transport new RestClientTransport(restClient, new JacksonJsonpMapper()); return new ElasticsearchClient(transport); } }有几点要说明JacksonJsonpMapper会使用Jackson来序列化和反序列化文档对象所以前面加jackson-databind依赖是有必要的它决定了你后面Java对象和JSON文档之间互相转换的体验。我上面为了演示简单在loadTrustMaterial里直接信任了所有证书。这个做法只适合本地开发。生产环境一定要用CA指纹来校验否则等于把人家的HTTPS安全机制卸了一半。指纹校验的写法要复杂一些要自定义SSLConnectionSocketFactory或TrustManager我建议直接参考官方配置别为了省事裸奔。如果想用API Key而不是用户名密码也是可以的。ES 8.x里可以通过Kibana或API创建API Key然后设置Authorization: ApiKey {...}请求头。在客户端里可以通过setDefaultHeaders来实现restClient RestClient.builder(host) .setDefaultHeaders(new Header[]{ new BasicHeader(Authorization, ApiKey apiKey) }) .build();用API Key的好处是可以在服务端按权限范围控制比如只给某个应用只读权限比共享管理员密码安全得多。如果是给多个后端服务用我建议每个服务建一个API Key后面要收回权限也不会误伤其他服务。3.3 验证连接写个测试类确认一切正常配置类和依赖都准备好之后不要急着写业务先写一个简单的测试确认连接没有报错。我直接在SpringBoot的test依赖里写SpringBootTest class SearchApplicationTests { Autowired private ElasticsearchClient client; Test void testConnectionAndPing() throws IOException { InfoResponse info client.info(); System.out.println(info.clusterName()); System.out.println(info.version().number()); } }如果能正常打印出集群名和版本号说明整条链路已经通了。这里顺便提一句如果你在Windows上跑HttpHost.create(uri)里的URI一定要以https://开头不要图省事写成localhost:9200不然会报UnsupportedSchemeException。4. 核心操作实战索引管理、文档CRUD、批量写入4.1 创建索引Mapping和Setting提前规划好ES不像关系型数据库它可以先写入后自动建索引但只要字段类型错了后面再重新映射是一件麻烦事。所以项目里我习惯把索引的mapping和settings显式定义好用代码保证索引结构可控。下面是创建一个商品索引的示例包含一个ik_smart分词器字段前提是集群里安装了IK分词插件String indexName product_idx; client.indices().create(c - c .index(indexName) .settings(s - s .numberOfShards(3) .numberOfReplicas(2) .analysis(a - a .analyzer(ik_analyzer, an - an .custom(ct - ct .tokenizer(ik_smart) ) ) ) ) .mappings(m - m .properties(id, p - p.integer(t - t)) .properties(title, p - p.text(t - t .analyzer(ik_analyzer) .searchAnalyzer(ik_smart) .fields(keyword, f - f.keyword(k - k))) ) .properties(price, p - p.double_(t - t)) .properties(createTime, p - p.date(d - d .format(yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis))) .properties(brand, p - p.keyword(k - k)) ) );几个值得说的点为什么title既设置text又挂了keyword子字段因为text类型虽然能分词搜索但不能精确匹配、不能排序、不能聚合。业务里经常要从“搜索标题”切换到“按标题精确过滤”所以同时保留keyword子字段是一个通用惯用法。brand直接设计成keyword因为品牌这种词通常不需要分词搜“Apple”就应该精确命中“Apple”而不是被切成“App”和“le”。keyword对这个场景最合适还方便聚合。日期字段建议把多种格式都写进去避免后面导入数据时因为格式不统一报错。重复执行创建索引的操作会报resource_already_exists_exception所以代码里最好先判断一下if (client.indices().exists(r - r.index(indexName)).value()) { // 已经存在跳过或走更新的逻辑 }4.2 文档写入单条和Bulk批量写入单条写入最简单直接以Java对象作为文档源Product product new Product(); product.setId(1); product.setTitle(Apple iPhone 15 Pro Max 256G); product.setPrice(9999.0); product.setBrand(Apple); product.setCreateTime(new Date()); IndexResponse response client.index(i - i .index(indexName) .id(String.valueOf(product.getId())) .document(product) );注意ES文档的ID是字符串即使你mapping里设了id为integer写入时的id参数本质上还是字符串。批量写入建议用Bulk性能远高于一条一条循环。官方推荐的批量大小是1MB到5MB左右或者总条数在1000到5000之间。千万不要无脑把10万条丢进一个bulk请求里会直接堆爆内存。BulkRequest.Builder br new BulkRequest.Builder(); for (Product p : products) { br.ops(o - o .index(idx - idx .index(indexName) .id(String.valueOf(p.getId())) .document(p) ) ); } BulkResponse result client.bulk(br.build()); if (result.errors()) { for (BulkResponseItem item : result.items()) { if (item.error() ! null) { System.err.println(item.error().reason()); } } }写完后你可以用refresh让文档立即可搜client.indices().refresh(r - r.index(indexName));开发调试的时候很有用但生产环境不建议每写一批就刷一次refresh会带来较大的写入性能损失。正常ES的默认refresh间隔是1秒稍微等等就能搜到这个特性对大部分业务是够用的。4.3 文档更新和删除的代码写法更新文档时如果使用update接口本质是合并JSON字段而不是整个覆盖。比如只更新价格client.update(u - u .index(indexName) .id(1) .doc(Map.of(price, 8999.0)), Product.class );注意这个doc()方法既可以传Map也可以传对象内部做部分字段合并。如果你想全量覆盖用index方法再写一遍即可ES会用新文档替换旧文档。删除文档更简单client.delete(d - d.index(indexName).id(1));4.4 核心CRUD之外判断文档是否存在ES里判断一个文档是否存在可以用exists接口它的开销比搜索一次小很多boolean exists client.exists(e - e .index(indexName) .id(1) ).value();这个接口我在幂等场景里用得多比如先判断后插入避免重复写入。不过要提醒一点这个判断只是帮你在应用层做了过滤最保险的去重还是应该靠ES文档ID的唯一性同ID重复写入会覆盖业务层面可以通过生成稳定ID来控制。5. 查询与检索从简单查找到组合查询5.1 基础查询match、term、range的不同语义ES查询的Java API非常直观几乎和JSON DSL一一对应。我下面把几个高频查询的真实用法都列出来这些场景在商品搜索里很典型。match查询适合全文检索会对你输入的关键词做分词然后去匹配text字段。比如用户搜“苹果手机”SearchResponseProduct response client.search(s - s .index(indexName) .query(q - q .match(m - m .field(title) .query(苹果手机) ) ), Product.class );term查询适合精确匹配不做分词keyword类型字段最常用client.search(s - s .index(indexName) .query(q - q .term(t - t.field(brand).value(Apple)) ), Product.class );range查询用于范围过滤比如价格区间client.search(s - s .index(indexName) .query(q - q .range(r - r .field(price) .gte(5000.0) .lte(10000.0) ) ), Product.class );这三个是最基础的单条件查询。实际业务里很少只用其中一个更多是用bool把它们嵌套组合。5.2 组合查询bool/must/should/filter的完整示例bool查询是ES里最核心的复合查询没有之一。理解bool关键是区分四个子句must必须满足且参与相关性评分filter必须满足但不参与评分should应该满足满足则加分不满足也不排除must_not必须不满足不参与评分一个典型场景用户搜索标题包含“iPhone”品牌是Apple价格在5000到10000且不要已下架的商品SearchResponseProduct response client.search(s - s .index(indexName) .query(q - q .bool(b - b .must(m - m .match(mt - mt.field(title).query(iPhone)) ) .filter(f - f .term(t - t.field(brand).value(Apple)) ) .filter(f - f .range(r - r.field(price).gte(5000.0).lte(10000.0)) ) .mustNot(mn - mn .term(t - t.field(status).value(OFF_SHELF)) ) ) ), Product.class );注意bool内部可以直接链式调用多个filter多个filter之间是AND关系。把不需要评分的条件放进filter除了语义清晰之外ES 8.x还会自动缓存filter结果长尾搜索场景性能提升明显。should的用法要小心。如果在bool下只有一个should子句没有must也没有filter那么你的查询语义会变成“至少要匹配一个should条件”。而如果bool下还有must或filtershould只起加分作用。这个区别在写搜索时经常踩坑排查半天发现自己以为的“或”逻辑变成了“且”逻辑。5.3 分页与排序from-size的限制和search_after方案常规分页直接用from和sizeclient.search(s - s .index(indexName) .query(q - q.matchAll(m - m)) .from(0) .size(20) .sort(so - so.field(f - f.field(createTime).order(SortOrder.Desc))), Product.class );但这种分页有个硬性限制from size默认不能超过10,000超过会报错因为ES需要先把分页范围内的数据全部构建出来深度分页的成本极高。这个场景下如果你只是做后台管理列表可以把索引的index.max_result_window调大但这不是治本方案。真正的深度分页方案是search_after它利用排序字段的游标来滚动数据。比如第一页查出来后取最后一条文档的排序字段值然后作为下一页的起点SearchResponseProduct firstPage client.search(s - s .index(indexName) .sort(so - so.field(f - f.field(price).order(SortOrder.Asc))) .size(10), Product.class ); ListString sortValues firstPage.hits().hits().get(9).sort().stream() .map(FieldSort::value) .toList(); SearchResponseProduct secondPage client.search(s - s .index(indexName) .sort(so - so.field(f - f.field(price).order(SortOrder.Asc))) .searchAfter(sortValues) .size(10), Product.class );要注意search_after依赖排序字段有唯一值否则翻页时可能出现数据抖动。通常做法是在排序字段之外再追加一个_id或timestamp做tie-breaker保证顺序稳定。5.4 高亮与聚合让搜索结果更好看、更有分析价值高亮是搜索引擎标配。用Java API实现高亮很简单client.search(s - s .index(indexName) .query(q - q.match(m - m.field(title).query(iPhone))) .highlight(h - h .fields(title, f - f.preTags(em).postTags(/em)) ), Product.class ); for (HitProduct hit : response.hits().hits()) { ListString highlights hit.highlight().get(title); // 把原始title替换成高亮后的片段展示 }理解高亮碎片生成的机制ES将匹配字段的内容切成多个片段默认每个片段长度是100个字符超出就截断。所以高亮展示出来的内容可能只是原文的一小段别当成完整字段用。聚合方面最常见的场景是统计某个字段的分布比如按品牌分组统计商品数SearchResponseProduct response client.search(s - s .index(indexName) .size(0) .aggs(group_by_brand, a - a .terms(t - t.field(brand).size(20)) ), Product.class ); Aggregation agg response.aggregations().get(group_by_brand); // 这里是一堆terms bucket可以遍历拿到key和docCountsize(0)表示不返回文档列表只要聚合结果可以少传输很多数据。这个做法在BI报表、后台看板里非常常见。6. 与MySQL数据同步真实项目绕不开的实操问题6.1 三种常见同步方案怎么选ES通常不作为数据的唯一存储MySQL才是业务主库。那么MySQL的数据怎么进ES我在不同项目里用过三类方案方案一应用层同步。在Service里增删改MySQL成功之后再调用ES的增删改方法。这个方案最简单适合数据量小、操作不频繁的项目。缺点是代码侵入性强如果MySQL事务提交之后ES写入失败两边数据就出现了不一致而且这个问题不好补偿。方案二订阅MySQL binlog。通过Canal或Flink CDC监听binlog变更事件实时把数据同步到ES。这个方案对业务代码零侵入数据一致性也好适合持续高并发写入的场景。缺点是需要额外部署Canal/Flink组件运维成本上升。方案三定时任务全量同步。比如每5分钟查一次MySQL里更新过的时间戳大于上次同步时间的记录批量写入ES。这个方案实现简单适合非实时性要求不高的搜索场景比如商品搜索、资讯搜索用户能接受延迟几分钟。我的个人倾向是中小项目用方案三起步等并发上来了再逐步切换到方案二最不建议一上来就搞Canal那一套复杂度会在初期拖慢你的迭代速度。6.2 增量同步时候注意的坑用方案三做增量同步最需要关注的是“删除”事件。如果你的业务里存在物理删除那定时任务只按时间戳捞MySQL记录会漏掉被删掉的数据ES里就会残留脏数据。解决思路有两个一是把物理删除改成逻辑删除加一个deleted字段同步任务过滤掉deleted1的数据并在ES里删除对应文档二是额外维护一张删除流水表同步任务同时读取流水表把ID传给ES做删除。这两个思路里逻辑删除最简单。因为很多业务系统本来就有逻辑删除的惯例只是在使用中发现没有把这一层纳入搜索架构。等到ES搜索结果出现已删除商品还挂在页面上的时候再来补就晚了。7. 避坑8.14 版本那些容易翻车的细节7.1 版本不匹配客户端版本和服务器版本最好保持一致ES有一个原则客户端版本不应该比服务器版本新太多也不应该老太多。官方策略是支持服务器版本往上和往下兼容若干个小版本但最好不要挑战这个边界。我见过一个项目服务器是7.17客户端却是8.14结果启动时连接正常但一旦执行某些新签名的请求服务端直接返回invalid_index_name_exception或者干脆反序列化失败。所以我的建议很简单客户端版本号直接用服务器版本号比如服务器8.14.0客户端就加8.14.0的依赖不要凭空升级或降级。尤其是大版本跨度RestHighLevelClient和elasticsearch-java之间更是不能混用。如果你维护多个老项目升版本的时候一定要同时检查所有用到ES客户端的模块。7.2 连接池与线程安全ElasticsearchClient不是每次new的很多新手会在Service里用new ElasticsearchClient(...)的方式来获取客户端这个做法非常糟糕。因为每次创建ElasticsearchClient都会新建一个RestClient连接池频繁创建会耗尽底层连接资源最终抛ConnectionPoolTimeoutException。正确姿势是把客户端定义为Spring的单例Bean全局共用。你可以在配置类里像前面那样直接声明Bean这样整个应用就一个客户端实例。它的内部会维护连接池按需复用连接线程安全也有保障可以放心地注入到各个Service里。7.3 常见异常从401到410基本都能提前预防在ES 8.14对接SpringBoot过程中我遇到频率最高的异常基本就是下面这几种异常信息出现原因解决方案401 Unauthorized密码错误/用户权限不足确认elastic用户密码或检查API Key权限SSLHandshakeException证书校验失败、指纹不匹配检查CA证书路径和指纹是否和ES一致UnsupportedSchemeExceptionURI没写https或写作http确认uris以https://开头ClassNotFoundException: jakarta/json/Json缺少jakarta.json-api依赖把jakarta.json-api加入pom/gradleorg.elasticsearch.ElasticsearchException: type... security_exception用户权限或角色不对在Kibana里给对应用户分配索引权限connect_timeout_exception网络不通/端口被防火墙拦截用telnet测试9200端口连通性这些坑几乎都是环境或配置问题不是代码逻辑问题。如果你遇到其中某一个不要改代码先回到配置层面排查往往几分钟就解决了。7.4 数据模型设计Java对象字段命名的避坑点最后分享一个容易被忽视的细节ES的mapping字段名是区分大小写的而且默认不支持字段名里带大写字母。如果Java对象里使用了camelCase命名比如createTime那么在写入文档的时候会原样存成createTime这个字段名。这里不是不能这么用但查询时所有Java API都要用createTime来写字段路径mapping里也要保持一致。如果你在ES里用了默认动态映射然后又在Java对象里突然改成下划线命名就会导致字段对不上查出来全是null。所以我在项目里习惯统一用camelCase作为Java对象字段名同时ES mapping里同样定义为camelCase两边保持一致省去一堆转换麻烦。如果你一定要用snake_case并且和Java对象做映射需要在JacksonJsonpMapper里配置字段命名策略或者在Java对象字段上加JsonProperty不然序列化结果始终是Java风格存到ES里依然camelCase。这个细节我印象很深有次从某个老服务接手一个ES索引里面的字段全是snake_case而新代码里全是camelCase对象写入后没有报错但查询结果全部为空排查了整整一天才意识到是字段名转换问题。写在最后的实操体会和Elasticsearch 8.14.0搭配SpringBoot这件事说穿了就是用对版本、配好连接、把核心查询逻辑跑通剩下更多的其实是日常打磨。我在实际项目里最有感触的一点是ES的报错信息其实挺直白的多数人卡住不是因为不懂API而是因为对版本和安全机制的变化没有概念拿旧经验应对新版本自然会走弯路。建议你在动手前先把ES的Docker环境跑起来用命令行把常用DSL都熟练一遍再回到SpringBoot里套Java API这样两边互相印证上手速度会快很多。如果项目已经上线别忘了关注监控ES客户端连接池的使用率、搜索平均响应时间、bulk失败的条数这几个指标能帮你提前发现很多隐患。最后一个小建议在代码仓库里把ES的版本号、连接方式、证书指纹的获取命令写进README换人接手的时候会感谢你。
返回列表