后端和运维同学聊到 es(Elasticsearch)时,经常发现同一批人在各个维度上“鸡同鸭讲”:有人关心它搜得快不快,有人关心它数据丢不丢,有人关心它装没装、怎么在 Windows 上验证,还有人一上来就问 Java 异步写入怎么写。其实这些都属于 es 的不同维度,而很多人以为 es 只是一个“搜索框架”,这才是最大的误解。我最早接触 es,是给一个电商后台做日志检索,后来做订单宽表查询,越用越觉得要把它当作一个分布式系统来理解。这篇内容就围绕大家最常搜的几个点——es 本地如何安装、Windows 怎么看自己有没有安装 es、es routing、es 异步写入 Java——把 es 的多个维度串起来聊一遍。文章里会用我自己的踩坑经历做提示,希望能帮你少走一些弯路。
1. es 能做什么:先别急着装环境,搞清楚它的三个身份
1.1 搜索引擎身份:Lucene 和倒排索引
es 的底层是 Lucene,一个 Java 写的全文检索库。搜索快的原因不是排序多聪明,而是用了倒排索引:把文档里的词条拆出来,建立“词条 -> 文档 id 列表”的映射。查询一个关键词时,直接定位到词条,再拿文档 id,而不是像数据库那种 B+ 树从开头扫到结尾。你可以把倒排索引理解为书的末尾索引,你想找“分片”这个词出现在哪一页,直接翻索引页比从头翻整本书快得多。这个身份决定了 es 最擅长的是全文检索、模糊匹配、词频统计。
但这个身份也带来限制。es 的搜索针对“分词后的词条”,不是天然支持“子串匹配”。比如你搜“ab”,默认不一定能匹配“abc”里的“ab”。很多人刚开始用 es,以为它像 MySQL 的 like 一样,结果发现搜不到,这就是没理解倒排索引的边界。理解了这点,后面遇到搜索精度问题,就不会怪 es 玄学,而是查分词器和映射类型。
1.2 数据库身份:文档、mapping 和近实时性
es 里存的数据叫“文档”(document),格式是 JSON,每个文档有一个_id,字段类型不是随意推断的,而是由 mapping 定义。mapping 有点像 MySQL 的 schema,但更灵活:支持嵌套对象、数组、多字段,还能为同一个字段同时保留 keyword 和 text 两种类型,排序用 keyword,搜索用 text。很多团队拿它当数据库用,因为它的写入和查询接口非常像数据库:你可以通过 REST 接口对索引做增删改查,还可以做聚合、分组、范围查询。
要注意 es 不是强实时数据库。写入一个文档后,默认要经过 refresh 间隔(通常 1 秒左右)才能在搜索结果里出现。这就是它经常被称为“近实时”(NRT)的原因。换句话说,你不能期望 es 像 Redis 那样写一次立刻读到自己。设计系统时,如果你需要强一致、严格事务,es 不是适合的存储;但如果你需要海量日志、商品搜索、宽表聚合,它就是很顺手的工具。
1.3 分布式身份:节点、分片和副本
单机 es 其实只是 Lucene 的简单封装,真正让 es 流行的,是它的分布式能力。一个集群由一个或多个节点组成,索引的数据会被切分成主分片(primary shard),每个主分片又可以有副本分片(replica shard)。写入时先写主分片,再同步到副本;查询时主副本都可以服务。这个设计很像常见的分库分表,只是 es 把分片和副本的创建、分配、恢复都自动化了。
分片数量在索引创建时就固定了,后面不能随意调整。这是很多人忽略的关键点。如果你一开始把主分片数定得太少,数据增长后想扩容,只能重建索引,不能直接改分片数。所以我一直建议,创建索引前先认真估算数据量,不要直接照搬默认值。默认主分片数是 1,在数据量小的开发环境没问题,但如果未来数据量可能是几十上百 GB,一开始可以多拆几个分片,省得后面 reindex 折腾。
1.4 从热搜词反推大家关心的问题
搜索热词不会说谎。“es 本地如何安装”“windows 怎么看自己有没有安装 es”说明很多人在本地环境卡在第一步;“es routing”说明数据路由是很多人用 es 时绕不开的难点;“es 异步写入 java”说明大家都在考虑用 Java 写入海量数据时,怎么提升吞吐量。这些关键词表面上是操作问题,背后其实是同一件事:想用 es 干活,但又不确定它内部怎么存、怎么分布、怎么写最稳。
接下来的内容就按这个顺序展开:先讲本地环境怎么装、怎么验证,再讲数据是怎么路由到分片的,然后讲 Java 客户端异步写怎么落地,最后聊聊日常运维和常见误区。整个过程不是一个“从零到精通”的教程,而是一张帮你把 es 散装知识点串起来的地图。
2. 本地安装与基础验证:es 本地如何安装,Windows 怎么看自己有没有安装 es
2.1 动手前先选版本,别让版本坑你两天
安装 es 第一件事是选版本。目前生产环境最常见的还是 7.x 和 8.x。7.x 时代很多资料用的是 High Level REST Client,配置相对简单;8.x 默认开启安全认证,API 也换成了新的 Elasticsearch Java Client,很多老教程的代码不一定能直接跑。如果你自己学习,我建议直接装 8.x,别在 7.x 和 8.x 之间纠结太久。8.x 自带 JDK,不需要额外安装 Java,只要机器上有 JDK 17 或兼容版本即可,没有也能用自带的 JDK 启动。
另一个坑是发行版。es 官方提供 zip/tar.gz、deb/rpm、Docker 等。Windows 上最简单的就是下载 zip 包解压,不需要安装器。注意不要用系统自带的“服务”注册工具乱注册,先跑一次 bin\elasticsearch.bat 确认环境没问题,再考虑要不要做成服务。下载地址去官网对应版本页找,文件名一般是 elasticsearch-8.x.x-windows-x86_64.zip。选版本时还要注意你后续用的 Java 客户端、Docker 镜像、Kibana 版本都要尽量与 es 主版本保持一致,避免版本不匹配导致连接失败或 API 差异。
2.2 Windows 本地安装 es 的实操步骤
我按 8.x 的 zip 包方式整理一下步骤。第一步,解压到一个不含中文和空格的目录,比如 D:\elasticsearch-8.x.x,这点很关键,Windows 下中文路径偶尔会导致脚本解析问题,纯属给自己找麻烦。第二步,进入 config\elasticsearch.yml,如果你是单机开发,至少设置这几项:cluster.name: my-local-cluster,node.name: node-1,network.host: 127.0.0.1,http.port: 9200,discovery.type: single-node。discovery.type 设成 single-node 很重要,不然默认的发现机制会尝试找其他节点,单机启动容易报错。
第三步,进入 bin 目录,双击或命令行运行elasticsearch.bat。首次启动如果开启了安全认证,控制台会输出很多初始化信息,包括生成的 elastic 用户密码、Kibana 配置 token 等。为了本地学习方便,可以在 yml 里加xpack.security.enabled: false关闭安全认证,这样访问 http://localhost:9200 直接用浏览器就能看 JSON 响应。但注意,这只适合本地测试,生产环境千万别关。最后,验证方式很简单:浏览器或 curl 打开http://localhost:9200,能看到类似"You Know, for Search"或者cluster_name的响应,就说明 es 已经起来了。
2.3 Windows 上怎么看自己有没有安装 es,别只盯着“服务”
很多人在 Windows 上不知道 es 装没装,其实可以从五个地方交叉验证。第一,看端口:运行netstat -ano | findstr :9200,如果有监听,说明 es 或者占用 9200 的程序在跑。第二,看进程:运行tasklist | findstr java,如果能看到一个 java.exe,再结合端口监听的 PID 去任务管理器里看命令行,确认是不是 elasticsearch。第三,看目录:检查常见安装路径,比如 C:\elasticsearch、D:\elasticsearch、Program Files 下有没有 elasticsearch 文件夹,以及里面有没有 bin\elasticsearch.bat。第四,看 HTTP 端口:curl http://localhost:9200,如果返回 JSON,说明不仅装了而且在运行。第五,看服务:如果安装成 Windows 服务,用sc query elasticsearch或Get-Service *elastic*可以查到服务状态。
这里我要多说一句,很多朋友把“安装”和“运行”混为一谈。你下载了解压,但没启动进程,curl 不通,这不算安装完成;你启动了elasticsearch.bat,但窗口一关进程就没了,这是前台运行,不是服务安装。所以“怎么看自己有没有安装”和“怎么看有没有运行”是两个问题,建议都检查一遍。最可靠的方式就是 curl 9200 端口:只要有响应,es 就一定在运行;没有响应,再按端口、进程、目录、服务顺序排查。
2.4 启动失败的高频原因排查
es 启动失败,Windows 上常见的大概就这几类。第一类是 JDK 环境变量问题。如果你手动设置了 JAVA_HOME,但指向的是 JDK 8,启动会直接报错;8.x 要求 JDK 17 以上,解决办法是卸载或调整 JAVA_HOME,或者直接不设置 JAVA_HOME,让 es 使用自带的 JDK。第二类是端口被占用,9200 或 9300 被别的程序占用,日志会明显提示 bind 失败,改成别的端口即可。第三类是堆内存配置问题,默认堆内存通常是物理内存的一半,但如果机器只有 4G 内存,默认就可能起不来;修改config\jvm.options里的-Xms4g和-Xmx4g为更小值。第四类是目录权限或路径问题,data 目录没有写权限、路径带中文、磁盘空间不足,也会导致启动失败。
排查时最忌讳只看“启动失败”四个字。es 的日志文件在 logs 目录下,启动信息输出在控制台,同时写入logs\elasticsearch.log。遇到失败,先看最后几十行日志,通常错误信息已经把原因写得很清楚。如果日志里出现BindException,就是端口问题;出现DataTooLargeException或 heap 相关提示,就是内存问题;出现AccessDeniedException,就是权限问题。按照这个顺序排查,大部分安装问题能在十分钟内解决。下面这个表可以作为速查:
| 现象 | 常见原因 | 处理建议 |
|---|---|---|
| bind 失败 | 9200/9300 端口被占用 | 换端口或杀进程 |
| JDK 版本错误 | JAVA_HOME 指向 JDK 8 | 调整到 JDK 17 或使用自带 JDK |
| 内存不足 | 堆内存设置过大 | 调小 -Xms/-Xmx |
| 目录权限错误 | data 目录不可写 | 检查权限或调整路径 |
3. 数据路由的底层逻辑:es routing 决定了你的数据去哪
3.1 默认情况下,es 怎么决定一条文档落到哪个分片
es 写入一条文档时,并不是随便放到某个节点上,而是通过一个简单的哈希规则决定它属于哪个主分片。默认的 routing 值是文档的_id,计算公式是shard = hash(_id) % number_of_primary_shards。也就是说,文档写入哪个主分片,完全由_id和一个固定数量的模运算决定。这样做的优点是数据分布均匀,代价是查询一条文档时,es 不知道它在哪个分片,只能向所有分片广播查询请求,然后各分片执行查询,最终由协调节点合并结果。
这个“广播查询”的机制对单条文档查询影响不大,但如果你经常要按某个业务维度批量查询,广播的浪费就会很明显。比如你有一个订单索引,里面有 10 个分片,每次查某个用户“最近的 100 条订单”,es 都要让 10 个分片各自查一遍,再汇总排序。数据量小的时候无所谓,数据量大了以后,你会明显感觉到查询变慢,因为永远只能打到机器最慢的那个分片,热点和长尾问题也会被放大。
3.2 自定义 routing:把同一类数据锁进同一个分片
es 允许你在写入、查询、更新、删除时都指定一个routing参数,用来替代默认的_id参与哈希计算。比如你写订单文档时指定routing=user_123,那么user_123的所有订单都会被分配到同一个主分片。之后查询这个用户的订单时,也带上routing=user_123,es 就能直接只访问那个分片,不用广播给所有分片。效果类似于按用户维度做的分库分表,把一次查询的扫描范围从整个集群缩小到了一个分片。
在实际项目中,这个参数极其常用。典型的场景包括:多租户系统,一个租户的数据集中在一个分片,隔离性和查询效率都好;订单和用户关联查询,用 userId 作为 routing;日志系统按实例 ID 或业务线路由,方便按业务维度清理旧数据。Java 客户端的IndexRequest、SearchRequest、BulkRequest里都有setRouting或.routing()方法,设置起来很简单,难的是你得想清楚用什么字段作为 routing 才能既均匀又高效。
3.3 使用 routing 的收益和代价,必须同时考虑
自定义 routing 不是银弹。收益是查询范围收敛、单分片内数据局部性好、更新和删除可以直接定位;代价至少有三个。第一,如果 routing 字段选得不好,比如某个热点用户数据量超大,其他用户都很少,那这些热点数据都会压在同一个分片上,造成热分片,反而比广播更慢。第二,所有查询都必须带 routing 才能享受收益,如果有些查询不带 routing,es 照样广播,等于双倍开销。第三,更新或删除文档时如果忘了带 routing,es 会找不到文档,因为默认路由是_id,和写入时用的 routing 不一致,文档可能在另一个分片上。
所以我的建议是:在决定是否用 routing 之前,先想清楚业务查询的主路径是什么。如果查询绝大多数时候都是“按某个固定字段展开”,那用 routing 是合理的;如果查询条件是五花八门,今天按用户、明天按订单号,那就别强制做 routing,否则你为了收敛一个场景,反而拖累了其他场景。选 routing 字段的黄金法则是:基数足够大、分布足够均匀、查询频率最高。下面这个表整理了几个核心问题:
| 考虑点 | 适合用 routing | 不适合用 routing |
|---|---|---|
| 查询主路径 | 固定按 user_id / tenant_id 查询 | 查询条件变化多,无法保持统一 |
| 数据分布 | routing 字段基数大且均匀 | 少数值数据量巨大,容易热分片 |
| 维护成本 | 写入和查询都强制带 routing | 团队容易忘记带,反而广播 |
3.4 分片数量为什么不能随意改,和 routing 有什么关系
前面提到主分片数创建后不可变,这跟 routing 的哈希计算有直接关系。因为shard = hash(routing) % number_of_primary_shards,一旦分片数量变了,原来在分片 0 的文档可能就被算到分片 5,等于所有数据的位置都变了,es 又没有能力自动重新分配主分片的数据位置,所以只能禁止修改。这个设计倒逼你在建索引时就要规划好分片数。
实际经验是,开发环境主分片数设 1 就够了,反正数据不多;生产环境要先估算单分片容量,每分片建议控制在 30GB 到 50GB 以内,总数据量除以单分片预期容量,再向上取整得到主分片数。宁可在前期多设一点,也不要设少了再 reindex。reindex 虽然能解决分片数不够的问题,但要额外占用资源,还会受 mapping、字段类型、别名等影响,能避免就避免。
4. Java 异步写入 es:从同步 REST 到异步回调的实践
4.1 先回答选型:为什么别用 TransportClient,优先用官方 REST 客户端
如果你在 2024 年还看到有人推荐TransportClient,请直接跳过。TransportClient 是 es 内部传输协议的客户端,曾用于 Java 应用直连节点,但它在 7.0 开始被标记废弃,8.0 已经移除。现在官方统一走 HTTP REST 协议,客户端类型经历了两代:旧的是 High Level REST Client,在 8.x 里也逐步淡出;新版是 Elasticsearch Java Client,配套新的 transport 封装。
Java 客户端选型的核心逻辑很简单:跟主版本匹配,跟官方 API 文档同步。如果你用 7.x es,用 High Level REST Client 问题不大,资料最全;如果你用 8.x,建议直接学新的 Java Client,因为老的 client 已经属于兼容过渡产物。无论选哪种,都要记住一点:es Java 客户端底层基于 HTTP 长连接,不是直连 JVM 内部,所以它不可能像 TransportClient 那样绕过序列化,性能优势来自异步和批量,不是协议本身。
4.2 同步与异步的核心区别:不是“更快”而是“不阻塞”
很多人误以为异步写入就是“让 es 写得更快”,其实不是。同步写入时,调用client.index会一直阻塞当前线程,直到 es 返回结果或超时;异步写入时,调用client.indexAsync立即返回一个CompletableFuture或注册一个 Listener,当前线程可以去干别的事,等 es 响应后再触发回调。所以异步提升的是客户端所在线程的利用率,而不是 es 的服务端性能。如果你的应用本来就没什么并发,异步反而让代码更复杂,看不出收益。
异步的经典使用场景是日志采集、指标上报这类高吞吐写操作。应用线程只需要把请求发给 es,不用等结果,回调里再处理失败,这样同样一个线程池可以支撑远高于同步模式的写入 QPS。但异步绝不等于无限快写,es 客户端和 Bulk API 都有队列和线程池,如果请求发送速度超过 es 处理能力,最终会在客户端或者服务端出现拒写,常见错误是EsRejectedExecutionException或 HTTP 429。这时候不是简单改成异步就能解决的,而是要做批量合并和限流。
4.3 一个基于新版 Java Client 的异步写入示例
我以新版 Elasticsearch Java Client 的异步接口为例子,只演示核心链路,不粘完整项目。首先是构造客户端:
HttpHost host = new HttpHost("localhost", 9200, "http"); RestClient restClient = RestClient.builder(host).build(); ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); ElasticsearchAsyncClient asyncClient = new ElasticsearchAsyncClient(transport);注意如果你在 yml 里关闭了安全认证,这里就不需要传用户名密码;如果生产环境开启了安全认证,需要在RestClientBuilder上配置DefaultCredentialsProvider。然后构造一个带 routing 的文档请求并异步写入:
Map<String, Object> doc = new HashMap<>(); doc.put("userId", "user_123"); doc.put("orderId", "order_10001"); doc.put("amount", 99.9); IndexRequest<Object> request = IndexRequest.of(r -> r .index("orders") .id("order_10001") .routing("user_123") .document(doc)); CompletableFuture<IndexResponse> future = asyncClient.index(request); future.whenComplete((resp, error) -> { if (error != null) { // 这里千万不要只打印日志,要根据错误类型决定是否重试 error.printStackTrace(); } else { // resp.result() 可以是 CREATED 或 UPDATED System.out.println("indexed, result = " + resp.result()); } });这段代码里有几个细节要强调。routing("user_123")就是前面讲的自定义 routing,必须和查询时保持一致。id("order_10001")是业务幂等键,如果异步回调失败后你决定重试,带上同一个 id 不会产生重复文档;如果你不指定 id,es 会生成随机 UUID,重试一次就会得到两条重复文档。另外,whenComplete里不要做耗时阻塞操作,比如不要再同步调数据库或 es,否则回调线程池会被拖垮,最好把后续处理丢到业务线程池。
4.4 大批量写入的更好姿势:BulkRequest + 异步回调
异步单条写入只是入门,真正生产环境不会一条条异步写,更常见的是攒一批再发。BulkRequest可以一次提交多条文档,配合异步bulkAsync,吞吐量会明显好于单条并发。原因很简单,一次网络请求的开销被多份文档分摊了。类似拼车,单条同步请求像每趟只拉一个人,异步 Bulk 像一趟大巴装几十人,效率完全不是一个量级。
下面是批量异步写的一个简化示例,假设你有一个List<Map>,每次封装 500 条。这里我用的是很多项目还在用的 High Level REST Client 写法,方便理解批量思想:
RestHighLevelClient client = new RestHighLevelClient( RestClient.builder(new HttpHost("localhost", 9200, "http"))); BulkRequest bulkRequest = new BulkRequest(); for (Map<String, Object> doc : docs) { IndexRequest request = new IndexRequest("orders") .id(String.valueOf(doc.get("orderId"))) .routing(String.valueOf(doc.get("userId"))) .source(doc, XContentType.JSON); bulkRequest.add(request); } client.bulkAsync(bulkRequest, RequestOptions.DEFAULT, new ActionListener<BulkResponse>() { @Override public void onResponse(BulkResponse response) { if (response.hasFailures()) { // 检查每个 BulkItemResponse 的失败原因 System.out.println("partial failure"); } else { System.out.println("bulk success, items=" + response.getItems().length); } } @Override public void onFailure(Exception e) { // 网络或连接层失败,需要根据项目情况重试 } });这里我用的是 High Level REST Client 写法,因为 7.x 和 8.x 早期版本里这套 API 最普遍。新版 Java Client 也有 BulkRequest 构建器,逻辑类似,只是构建方式从 setter 变成了函数式BulkRequest.of(b -> b.operations(...))。重点不是 API 长什么样,而是你心里要有一条清晰的批量链路:上游攒批、客户端批量发送、失败重试、下游监控。
4.5 异步写入常见坑:回调泄漏、背压和线程池击穿
异步写最容易踩的第一个坑是“回调地狱”。如果每个回调里都嵌套新的 es 调用,链路会越来越深,最后根本没有超时控制,线程一卡住,问题特别难查。我的经验是,回调里只做轻量收尾工作,比如更新计数、记录失败批次、投递到重试队列,绝对不要在回调里再发起新的同步写。第二个坑是背压判断错误。当你看到大量 429 或 reject 错误时,不要只加大客户端的并发数,应该先看 es 服务端的 CPU、堆内存、IO 指标。加大并发只会让服务端更忙,正确做法是退避重试、降低批量大小,或者调大服务端写线程池队列,但后者要谨慎,不能无限调。
第三个坑是线程池资源没有复用。很多新手每写一条就 new 一个 RestClient,这是非常糟糕的用法,客户端的连接池和线程池会被反复销毁重建。正确做法是把 RestClient 和 transport 做成单例,应用启动时创建,关闭时统一transport.close()。最后一个坑是日志没打全。异步回调里的异常如果只打堆栈不打请求内容,回头排查时根本不知道失败的是哪批数据。我习惯在回调里把 index、id、routing 和错误摘要一起打出来,方便对账。
5. 从运维视角看 es:本地验证只是开始,线上还要盯着这些指标
5.1 节点健康:CPU、堆内存和 GC,先学会看这三样
es 作为一个 Java 进程,最需要关注的首先是 JVM 堆内存和垃圾回收。堆内存默认上限最好别超过物理内存的一半,也不要超过 30GB 左右;很多人只知道调-Xmx,却不注意把-Xms和-Xmx设为相同值,避免运行期堆扩容带来的卡顿。日志里如果频繁出现长时间 Full GC,通常说明堆内存压力大或查询过于重,需要先减少不必要的查询聚合,再考虑加节点。
CPU 和磁盘同样重要。如果某个节点 CPU 长期打满,可能是主分片分配不均衡、查询聚合太重、或没走对 routing 导致全分片广播。磁盘使用率超过 85% 后,es 默认会触发分片分配限制,只读时更是会直接拒绝写入。我见过不少事故是因为日志没清理、磁盘写满后 es 集群变红。本地安装阶段可能感受不到这些,但只要上生产,健康检查就要从“9200 通不通”升级成“集群状态、节点水位、分片分布、慢日志”这些维度。
5.2 索引级指标:文档数、段合并和 refresh 间隔
索引级有很多容易被忽略的指标。refresh_interval决定数据多久可见,默认 1 秒,频繁刷新会带来段合并压力;如果写入量大、对可见性要求不高,可以调大到 30 秒甚至关闭 refresh,等批量写入结束后再手动 refresh。段合并(merge)是 es 后台自动做的,把小的 Lucene 段合并成大段,合并时 CPU 和 IO 开销很高,高峰期容易拖垮写入性能。通过_cat/indices可以看到每个索引的 docs 数量、存储大小和段数,如果段数异常多,需要考虑优化写入节奏。
另一个容易被忽视的是“已删除文档”和物理存储膨胀。es 更新和删除文档不是立即物理删除,只是标记删除,后台 merge 时才会真正清理。所以有时候你删了很多数据,磁盘空间并没有减少,这不是 bug,而是 es 的底层机制决定的。遇到这种情况,可以执行 force merge,但要清楚这会带来很大的 IO 开销,最好放在业务低峰期,并且只对不再写入或很少写入的索引执行。
5.3 查询慢怎么排:先看慢日志,再看分片分布,最后看 routing
生产环境中,查询慢的排查顺序很重要。第一步打开慢查询日志,在 index setting 里配置index.search.slowlog.threshold.query.warn等参数,超过阈值就会记录查询语句和时间。第二步看查询是不是广播到了全部分片。用 es 的_search接口,response 里每个 shard 都会返回耗时,你可以看到是某个分片慢,还是所有分片都慢。某个分片慢通常是数据偏斜、该分片在磁盘繁忙的节点上;所有分片都慢通常是查询本身太重,比如深度分页、超大聚合、不带筛选条件的 match_all。
第三步才是检查 routing。如果一个查询本来可以带routing=user_123却没带,那它就是在全分片扫,这个我们从_cat/shards和执行计划很难直接看出来,只能通过代码 review 和客户端日志发现。所以我的经验是:在封装查询入口时,就强制要求传入 routing 字段,和写入时保持一致,避免业务同学后续忘带。这个工作在项目初期做几乎零成本,后期再改就是伤筋动骨。
5.4 Windows 本机的状态怎么观察
如果你只在 Windows 本地测试,想看 es 状态不用装复杂监控。http://localhost:9200/_cat/health?v看集群颜色:绿色表示主分片和副本都正常,黄色表示有副本未分配,红色表示有主分片丢失。http://localhost:9200/_cat/nodes?v看节点 CPU、堆内存、磁盘。http://localhost:9200/_cat/indices?v看索引列表。用这三个接口,足够覆盖本地开发和排障。生产环境再上 Kibana 监控或 Prometheus 采集,那是另一个话题。
6. 关于 es 的常见误区与我的实操心得
6.1 几个高频误区,一次性说清楚
误区一:es 装了解压就算装好了。实际上,只有启动进程、9200 端口有响应才算真正运行,安装包只是解压文件。误区二:分片数越多查询越快。分片多了广播开销也大,而且每个分片都是独立 Lucene 索引,查询要合并的结果更多,分片数一般跟随数据量走,不是越大越好。误区三:异步写入可以不关注失败。异步请求发出去不代表写成功,回调里的 error 和 BulkResponse 里的 failure 必须处理,否则丢数据了你都发现不了。误区四:routing 可以让所有查询更快。只有带 routing 的查询才会命中单分片,不带 routing 的查询反而要广播,使用不当会雪上加霜。
6.2 我踩过的一些坑,分享给你
个人经验,最大的一次教训是建索引时主分片数定了 1,半年后数据量上来,查询明显变慢,想扩分片只能 reindex。reindex 期间新数据还在写入,最后用了别名切换才搞定,整个晚上都在加班。所以现在建索引前,我会先做数据量估算,再留一些余量。还有一次是异步批量写入到 es,回调里只打了堆栈,没有记录失败请求的 id,第二天对账发现少了一批数据,查了很久才通过客户端日志拼回去。如果你做类似的数据管道,一定要把请求上下文和错误一起打出来,宁可日志多,不能丢线索。
6.3 给新手的实践路径建议
如果你想系统认识 es 的多个维度,我建议按这个顺序动手:先在 Windows 本地装好 8.x,用 curl 和 Kibana 跑通增删改查;然后建一个带 routing 的测试索引,写入几百条文档,观察_cat/shards确认数据落在哪个分片;接着写一个 Java 异步写入的小 demo,熟悉同步、异步、Bulk 三种方式的区别;最后打开慢日志和_cat/indices,试着从运维视角观察索引状态。这一套走下来,你的 es 认知就不再是零散知识点,而是一张互相连接的网。