
我做 Elasticsearch 相關(guān)項(xiàng)目也有七八年了從 1.x 一路用到 8.x。這幾年被問得最多的問題幾乎都是同一個網(wǎng)上找的Elasticsearch 基本操作教程照著敲PUT /index/type/id怎么在 8.x 里直接報錯答案很簡單——Elasticsearch 8.x 這一代把 RESTful API 里很多沿用多年的習(xí)慣改了。這篇文章我就基于 Elasticsearch 8.x把最核心的 RESTful API 操作從頭到尾捋一遍索引怎么建、文檔怎么讀寫、查詢怎么寫、聚合怎么做、集群怎么查全部用實(shí)際請求和返回說話。適合剛上手 8.x 的開發(fā)者也適合從舊版本遷移過來、想確認(rèn)哪些命令已經(jīng)失效的老人。1. 為什么 8.x 的基本操作和你想的不一樣1.1 安全認(rèn)證默認(rèn)開啟第一次訪問就被攔住了8.x 和之前版本最大的體感差異不是 API 變多而是安全認(rèn)證默認(rèn)開啟了。7.x 及更早版本裝完直接curl localhost:9200就能看到那個經(jīng)典的 You Know, for Search 歡迎信息。8.x 裝完你再這么干返回的是 401 未認(rèn)證錯誤。這意味著你正式學(xué)習(xí)第一組 RESTful API 之前得先解決兩個東西用戶名密碼以及證書信任。安裝 8.x 后首次啟動Elasticsearch 會在控制臺打印一段高亮信息里面有elastic超級用戶的初始密碼還有一個 CA 證書指紋。很多人看到一長串隨機(jī)密碼直接無視等第二天重啟就傻眼了——初始密碼是一次性的丟了就得重置。我的建議是啟動后先把這段輸出保存到本地筆記本或者立刻用elasticsearch-setup-passwords工具手動改一套自己記得住的強(qiáng)密碼。測試環(huán)境更省事的做法是在elasticsearch.yml里加一行xpack.security.enabled: false然后重啟相當(dāng)于回到 7.x 的無認(rèn)證模式。不過這只是本地實(shí)驗(yàn)的臨時方案生產(chǎn)環(huán)境千萬別這么干。1.2 類型被移除URL 路徑的變化比安全認(rèn)證更隱蔽的變化是types類型徹底沒了。ES 6.x 開始就已經(jīng)官宣每個索引只能有一個類型7.x 開始建議用_doc占位8.x 直接移除。所以你在老教程里看到的這種寫法PUT /product/electronics/1在 8.x 里會直接報404之類的錯誤。正確寫法是PUT /product/_doc/1product是索引名_doc是固定端點(diǎn)1是文檔 ID。別小看這個變化很多從 6.x 遷過來的人其他操作都沒問題偏偏在這類老路徑上浪費(fèi)一晚上。記住一條鐵律8.x 里索引名后面直接跟操作端點(diǎn)_doc、_search、_update、_delete不會再出現(xiàn)類型名。1.3 環(huán)境準(zhǔn)備拿到密碼和證書再動手在你敲第一條命令之前把環(huán)境準(zhǔn)備好。我用的是 8.14 版本Linux / macOS 都適用。解壓之后配置好JAVA_HOME8.x 自帶 JDK所以這一步通??珊雎匀缓髥觔in/elasticsearch看到日志里的status: green或者至少yellow就說明服務(wù)起來了。驗(yàn)證最基本的 RESTful APIcurl -k https://localhost:9200 -u elastic:你的密碼-k是跳過證書校驗(yàn)本地測試圖省事可以這么干。正式一點(diǎn)的寫法是用自帶的 CA 證書curl --cacert config/certs/http_ca.crt https://localhost:9200 -u elastic:你的密碼響應(yīng)里會出現(xiàn)cluster_name、cluster_uuid和version.number能看到8.14.0就說明連通成功。后面所有示例我都用 HTTP 方法和請求體來表示你在 Kibana 的 Dev Tools 里面可以直接粘貼執(zhí)行如果要轉(zhuǎn)成 curl在請求體外面包一層-d即可。Kibana Dev Tools 是我強(qiáng)烈推薦的學(xué)習(xí)工具自帶語法高亮和格式化對路徑參數(shù)也會提示哪些地方是非法的。2. 索引操作創(chuàng)建、查看、修改與刪除2.1 創(chuàng)建索引從空索引到帶映射Elasticsearch 的索引和 MySQL 的 database 加 table 差不多是一個混合體。創(chuàng)建索引最簡單的方式是PUT /product返回{ acknowledged: true, shards_acknowledged: true, index: product }但實(shí)際工作中基本不會造這種空索引。因?yàn)樗饕暮诵氖?mapping——字段類型定義。沒有 mapping 的索引寫入數(shù)據(jù)時會觸發(fā)動態(tài)映射雖然能用但經(jīng)常給出出乎意料的類型后期很難改。更標(biāo)準(zhǔn)的做法是創(chuàng)建索引時就帶上 mappingPUT /product { mappings: { properties: { title: { type: text, fields: { keyword: { type: keyword, ignore_above: 256 } } }, brand: { type: keyword }, price: { type: double }, stock: { type: integer }, created_at: { type: date } } } }這里有個經(jīng)典設(shè)計title被定義成text加keyword子字段。text負(fù)責(zé)全文檢索也就是搜索時可以把智能手機(jī)拆成智能和手機(jī)來匹配keyword負(fù)責(zé)精確匹配、聚合、排序。如果你只定義text而沒有加.keyword后續(xù)想做按標(biāo)題排序或聚合會非常痛苦。2.2 查詢索引_cat系列命令索引建完怎么看它存不存在、狀態(tài)好不好用_cat接口。GET /_cat/indices?v輸出類似health status index uuid pri rep docs.count yellow open product xxxx 1 1 0health列是重點(diǎn)。green表示主分片和副本都正常yellow表示主分片正常但副本未分配red表示主分片出問題數(shù)據(jù)可能不可用。單節(jié)點(diǎn)集群里新建的索引默認(rèn)是yellow因?yàn)楦北疽峙涞搅硪粋€節(jié)點(diǎn)但只有一個節(jié)點(diǎn)副本放不出去。這不是故障后續(xù)要消除它要么增加節(jié)點(diǎn)要么把副本數(shù)改成 0。想查看某個索引的 mapping 和 settingsGET /product返回索引的mappings、settings和aliases。排查字段類型問題基本都靠這個接口。2.3 修改與刪除索引一些限制索引有些參數(shù)可以動態(tài)調(diào)整比如副本數(shù)和刷新間隔PUT /product/_settings { number_of_replicas: 0, refresh_interval: 30s }但有幾個東西創(chuàng)建后就不能改主分片數(shù)量、已有字段的類型。拿主分片數(shù)量來說5 個分片的索引想改成 10 個分片做不到只能重建索引。這也是為什么生產(chǎn)環(huán)境對分片規(guī)劃要提前想清楚。刪除索引的命令是DELETE /product返回acknowledged: true之后索引及內(nèi)部數(shù)據(jù)就徹底沒了。這條命令沒有回收站除非你有快照備份否則誤刪就是事故。我以前見過有人把DELETE請求的索引名寫錯直接刪掉了線上一個月的數(shù)據(jù)最后靠_reindex從備份里撈回來的。所以生產(chǎn)環(huán)境執(zhí)行 DELETE強(qiáng)烈建議先GET /_cat/indices?v確認(rèn)索引名。3. 文檔的增刪改查CUD 的完整鏈路3.1 寫入文檔三種方式的選擇邏輯索引有了接下來就是往里寫文檔。文檔在 Elasticsearch 里就是個 JSON 對象。指定 ID 寫入PUT /product/_doc/1 { title: 智能手機(jī), brand: 華為, price: 5999.00, stock: 100, created_at: 2024-06-01T10:00:00Z }返回結(jié)果里有幾個關(guān)鍵信息{ _index: product, _id: 1, _version: 1, result: created }指定 ID 的寫入是冪等的同一個 ID 再次寫入result會變成updated版本號遞增文檔被整體覆蓋。自動生成 IDPOST /product/_doc { title: 藍(lán)牙耳機(jī), brand: 索尼, price: 1200.00, stock: 50, created_at: 2024-06-02T10:00:00Z }區(qū)別在于用POST加/結(jié)尾ES 自動生成一個 20 字符的 ID。自動 ID 的好處是寫入壓力分散不需要做 UUID 生成壞處是你沒法通過 ID 直接定位這個文檔只能查出來之后再拿 ID。日志類數(shù)據(jù)我通常用自動 ID業(yè)務(wù)數(shù)據(jù)一般都用我們自己生成的業(yè)務(wù) ID。強(qiáng)制新建PUT /product/_create/2 { title: 筆記本電腦, brand: 聯(lián)想, price: 6999.00, stock: 30, created_at: 2024-06-03T10:00:00Z }如果 ID 已存在返回 409 Conflict文檔不會被覆蓋。_create適合做冪等寫入場景比如消息隊列消費(fèi)者重復(fù)投遞時第二次會直接失敗省得你寫判斷邏輯。3.2 更新與刪除底層機(jī)制和并發(fā)控制更新文檔有兩類常見寫法。第一類是局部更新POST /product/_update/1 { doc: { price: 5199.00 } }只更新price字段其他字段不動。第二類是腳本更新適合需要基于當(dāng)前值計算的場景比如庫存減一POST /product/_update/1 { script: { source: ctx._source.stock - 1 } }理解底層機(jī)制比記住命令更重要Elasticsearch 的文檔是不可變的。你執(zhí)行 update底層其實(shí)是把舊文檔標(biāo)記為刪除再寫入一份新文檔。這也是為什么每次更新后_version都會加一。所以頻繁 update 的文檔會帶來額外的段合并開銷寫入密集場景要盡量避免無謂的小更新。刪除很簡單DELETE /product/_doc/1返回result: deleted。這里要注意的是被刪除的文檔并不是物理上立刻消失它和更新邏輯一樣先標(biāo)記刪除后續(xù)由后臺 merge 真正清理。剛刪除完立刻做精確 ID 查詢得到的是找不到的響應(yīng)但做全文搜索時短期內(nèi)可能仍能搜到剛刪掉的文檔這是正?,F(xiàn)象。多實(shí)例并發(fā)修改同一文檔時ES 提供了樂觀并發(fā)控制。更新時帶上上次返回的_seq_no和_primary_termPUT /product/_doc/1?if_seq_no27if_primary_term1 { ...完整文檔... }如果期間文檔被改過_seq_no對不上服務(wù)端返回 409請求失敗。這比最后寫入覆蓋前一次的方式安全得多。3.3 批量操作_bulk的正確姿勢單條寫入在數(shù)據(jù)量小的時候沒問題一旦有大量文檔要灌進(jìn)來一條條發(fā) HTTP 請求就是災(zāi)難。_bulk接口是批量寫入的標(biāo)配。_bulk的格式非常特殊它不是標(biāo)準(zhǔn) JSON而是NDJSON——每兩行一組第一行是操作類型和文檔元數(shù)據(jù)第二行是文檔內(nèi)容POST /product/_bulk {index: {_id: 3}} {title: 平板電腦, brand: 蘋果, price: 3999.00, stock: 20, created_at: 2024-06-04T10:00:00Z} {update: {_id: 2}} {doc: {price: 999.00}}第一行可以用index、create、update、delete分別對應(yīng)寫入、強(qiáng)制新建、局部更新、刪除。第三行及以后可以繼續(xù)成對追加一次幾百對完全沒問題。用 curl 時_bulk請求體會很大-d參數(shù)容易遇到轉(zhuǎn)義問題我用的是--data-binarycurl -k -X POST https://localhost:9200/product/_bulk \ -u elastic:你的密碼 \ -H Content-Type: application/x-ndjson \ --data-binary bulk.json批量返回結(jié)果里會有errors: true/false這個字段。即使總體是 true也不代表全部失敗可能是其中幾條失敗。真正需要逐條檢查的是items數(shù)組里每條記錄的status比如 409 說明版本沖突400 說明單條數(shù)據(jù)格式有問題。我在實(shí)際項(xiàng)目里批量導(dǎo)數(shù)據(jù)批次大小一般控制在 5000 到 10000 條之間太大反而會因?yàn)閱未握埱篌w過大觸發(fā)內(nèi)存問題。4. 查詢與檢索從 match 到 bool 的一整套 DSL4.1 搜索請求的基本結(jié)構(gòu)搜索通過_search端點(diǎn)完成POST /product/_search請求體是一個 JSON最核心的是query對象。但搜索的返回結(jié)構(gòu)也要搞清楚{ hits: { total: { value: 2, relation: eq }, hits: [ ...文檔數(shù)組... ] } }total.value是命中的文檔總數(shù)。8.x 里默認(rèn)情況下這個數(shù)字是準(zhǔn)確計數(shù)但如果你對超大索引設(shè)置了rest_total_hits_as_int或者用了track_total_hits參數(shù)它可能變成閾值估計值這點(diǎn)在對比返回條數(shù)和實(shí)際預(yù)期時要注意。_source默認(rèn)會把完整文檔返回。如果不想看到某個大字段或者只想要個別字段可以在請求體里過濾POST /product/_search { _source: [title, price], query: { match_all: {} } }match_all就是返回所有文檔的意思適合先看數(shù)據(jù)概貌。實(shí)際查詢中很少用它更多是配合空query來統(tǒng)計文檔數(shù)。4.2 常用查詢match、term、range、boolmatch 查詢?nèi)臋z索作用于text字段。POST /product/_search { query: { match: { title: 智能手機(jī) } } }ES 會對智能手機(jī)進(jìn)行分詞然后匹配包含任一詞或全部詞的文檔并計算相關(guān)度分?jǐn)?shù)。適合搜標(biāo)題、描述這類自然語言文本。term 查詢精確匹配作用于keyword、數(shù)值、日期字段。POST /product/_search { query: { term: { brand: 華為 } } }term不會分詞搜華為就是華為所以不能拿它對text字段做精確匹配——text字段已經(jīng)被分詞器切成一個個詞了term 查不到完整字符串。range 查詢范圍過濾。POST /product/_search { query: { range: { price: { gte: 2000, lte: 6000 } } } }gt大于、gte大于等于、lt小于、lte小于等于靈活組合就行。bool 查詢組合查詢包含must、should、must_not、filter四種子句。它是我實(shí)際項(xiàng)目里用得最多的查詢類型。POST /product/_search { query: { bool: { must: [ { match: { title: 手機(jī) } } ], filter: [ { term: { brand: 華為 } }, { range: { price: { gte: 3000, lte: 8000 } } } ] } } }這里的重點(diǎn)是must和filter的區(qū)別must參與相關(guān)度評分會影響_scorefilter只做過濾不參與評分而且因?yàn)榻Y(jié)果可以緩存性能通常更優(yōu)。所以純過濾條件盡量放進(jìn)filter別全堆在must里。4.3 分頁與排序深分頁怎么避坑分頁最直觀的方式是from加sizePOST /product/_search { query: { match_all: {} }, sort: [ { price: { order: desc } } ], from: 0, size: 10 }from表示跳過多少條size表示返回多少條。配合sort按價格降序、升序都行。但這里坑很大from跳過的文檔不是真跳過ES 要把每個分片上的from size條都收集到協(xié)調(diào)節(jié)點(diǎn)再統(tǒng)一排序、截斷。from跑到 100000 以后性能和內(nèi)存都會明顯惡化。所以 8.x 里默認(rèn)from size的最大值是 10000。深分頁的正確姿勢是search_after先按某個唯一排序值定位再取它后面的數(shù)據(jù)。第一次請求同樣帶排序POST /product/_search { query: { match_all: {} }, sort: [ { price: desc }, { _id: asc } ], size: 10 }返回的 hits 里每個文檔的sort數(shù)組就是游標(biāo)。下一頁請求POST /product/_search { query: { match_all: {} }, sort: [ { price: desc }, { _id: asc } ], size: 10, search_after: [5999.0, 1] }注意排序中加一個唯一字段比如_id否則價格相同的文檔之間順序不穩(wěn)定翻頁會重或漏。search_after不能往前翻頁只適合順序遍歷的業(yè)務(wù)場景比如后臺導(dǎo)出或者瀑布流加載。5. 字段類型與映射規(guī)劃5.1 核心字段類型怎么選字段類型的選擇決定了后期能否高效查詢和聚合。我整理了一張常用對照表類型適用場景典型示例注意事項(xiàng)text全文搜索、分詞匹配標(biāo)題、描述、正文不能被聚合/排序除非加.keyword子字段keyword精確匹配、聚合、排序品牌、狀態(tài)、標(biāo)簽、ID不會分詞整體作為一整個詞匹配integer/long整數(shù)數(shù)值庫存、數(shù)量取值范圍不同超范圍會報錯double/float小數(shù)價格、評分精度敏感場景用scaled_float更可控date日期時間創(chuàng)建時間、更新時間默認(rèn)支持多種格式但混用會出問題boolean布爾值上架/下架聚合、條件過濾都很直接objectJSON 子對象用戶信息{ name: ... }默認(rèn)扁平化存儲子字段按user.name訪問nested數(shù)組對象訂單明細(xì)列表每個對象獨(dú)立索引查詢不會串?dāng)?shù)據(jù)geo_point經(jīng)緯度門店位置支持距離排序、范圍過濾選型的核心原則就一句話給查詢和聚合用的字段選對類型只是存儲展示的字段別過度設(shè)計。5.2 動態(tài)映射和顯式映射8.x 寫入新字段時如果索引里沒有對應(yīng) mapping會自動動態(tài)映射。比如寫入price: 99.9ES 自動設(shè)為float寫入name: 張三自動設(shè)為text加keyword子字段。動態(tài)映射確實(shí)方便但坑也在這里。舉個例子某個字段一開始全是數(shù)字ES 自動設(shè)為long。后來有人往里面寫了N/AES 會拒絕這條文檔嗎默認(rèn)會報錯除非你在 mapping 里配置ignore_malformed: true。再比如某個字段一開始是字符串被映射成text后面你想對它做 range 查詢直接報錯。生產(chǎn)環(huán)境的建議非常明確核心索引都用顯式映射創(chuàng)建動態(tài)映射留給開發(fā)環(huán)境。尤其是對接業(yè)務(wù)方數(shù)據(jù)時字段類型不是拍腦袋定的要跟查詢場景走。往已有的索引里加新字段沒問題PUT /product/_mapping { properties: { category: { type: keyword } } }這個操作只增不改對已有數(shù)據(jù)不會產(chǎn)生重建。但如果要改已有字段類型比如把price從long改成doubleES 是禁止的只能走 reindex。5.3 修改映射reindex 的完整流程改字段類型最穩(wěn)的思路就是新建索引、同步數(shù)據(jù)、切換訪問。流程分四步。第一步新建一個字段類型正確的新索引product_v2mapping 按正確方式定義。第二步用_reindex把舊索引數(shù)據(jù)拷貝過來POST _reindex { source: { index: product }, dest: { index: product_v2 } }_reindex會保留_id、_source內(nèi)容但已經(jīng)存在的分詞結(jié)果、倒排索引不會帶過來新索引會重新處理數(shù)據(jù)。這也是為什么 reindex 后搜索行為可能和原來略有差異。第三步確認(rèn)新索引數(shù)據(jù)量一致GET /_cat/indices/product_v2?v看看docs.count和舊索引是否對得上。第四步切換訪問。最簡單的方式是 alias別名POST /_aliases { actions: [ { remove: { index: product, alias: product_read } }, { add: { index: product_v2, alias: product_read } } ] }切換之后應(yīng)用層繼續(xù)訪問product_read底層已經(jīng)指向新索引。舊索引先保留幾天確認(rèn)無異常再 DELETE。reindex 在高并發(fā)場景下會占用不少資源建議控制requests_per_second參數(shù)POST _reindex { source: { index: product }, dest: { index: product_v2 }, conflicts: proceed }conflicts: proceed表示更新過程中如果遇到版本沖突跳過而不是中斷整個任務(wù)。數(shù)據(jù)從幾萬到百萬級別都能這么跑幾千萬以上建議用 split 成多次 slice 并發(fā)執(zhí)行以后再單獨(dú)聊。6. 聚合分析從統(tǒng)計視角看數(shù)據(jù)6.1 聚合的語法結(jié)構(gòu)Elasticsearch 不只會查文檔還自帶統(tǒng)計分析能力用aggs節(jié)點(diǎn)表達(dá)?;窘Y(jié)構(gòu)長這樣POST /product/_search { size: 0, aggs: { 自定義聚合名: { 聚合類型: { ...參數(shù)... } } } }size: 0是聚合查詢的常用套路意思是不看命中明細(xì)只看聚合結(jié)果。如果你不寫size: 0返回里會帶上大量文檔明細(xì)既浪費(fèi)帶寬又拖慢響應(yīng)。聚合分兩大類。桶聚合bucket把數(shù)據(jù)按規(guī)則分到不同的組里相當(dāng)于 SQL 的GROUP BY。指標(biāo)聚合metric對一組數(shù)據(jù)做統(tǒng)計相當(dāng)于AVG、SUM、MIN、MAX。兩個經(jīng)常組合使用先分桶再對桶內(nèi)做指標(biāo)統(tǒng)計。6.2 常用聚合示例按品牌統(tǒng)計商品數(shù)量POST /product/_search { size: 0, aggs: { brand_count: { terms: { field: brand } } } }brand是keyword類型才能聚合。如果你用的是動態(tài)映射出來的text字段這里就該寫brand.keyword。輸出里buckets數(shù)組會列出每個品牌的key和doc_count。統(tǒng)計價格平均值POST /product/_search { size: 0, aggs: { avg_price: { avg: { field: price } } } }一次拿到多種統(tǒng)計指標(biāo)可以用statsPOST /product/_search { size: 0, aggs: { price_stats: { stats: { field: price } } } }返回count、min、max、avg、sum五個值省得寫五個聚合。聚合和查詢可以同時存在。典型場景是先限定某個價格區(qū)間再按品牌分桶同時統(tǒng)計每個品牌的平均價格POST /product/_search { size: 0, query: { range: { price: { gte: 1000 } } }, aggs: { brand_count: { terms: { field: brand }, aggs: { avg_price: { avg: { field: price } } } } } }先跑query過濾再跑aggs這是聚合查詢性能優(yōu)化的基本姿勢。不管數(shù)據(jù)有多少條先縮小范圍再聚合能省下大量計算資源。7. 集群健康與節(jié)點(diǎn)管理7.1 集群健康狀態(tài)單節(jié)點(diǎn)玩得差不多了就該看看集群層面的東西。最常用的健康檢查命令GET /_cluster/health返回{ cluster_name: elasticsearch, status: yellow, number_of_nodes: 1, active_shards: 5, active_shards_percentage_as_number: 50.0 }status是你最需要盯的指標(biāo)green所有主分片和副本都正常。yellow所有主分片正常但有副本未分配。單節(jié)點(diǎn)集群最常見因?yàn)楦北緵]地方放。red有主分片未分配直接意味著部分?jǐn)?shù)據(jù)不可讀寫。出現(xiàn) red 時優(yōu)先檢查是哪個索引、哪個分片用GET /_cat/shards?v往下查。我這里多說一句很多人一看到 yellow 就慌其實(shí)在單節(jié)點(diǎn)測試環(huán)境里 yellow 是常態(tài)。生產(chǎn)環(huán)境出現(xiàn) yellow 才需要重視原因通常是節(jié)點(diǎn)宕機(jī)、磁盤滿、副本配置錯誤。7.2 節(jié)點(diǎn)、分片和索引狀態(tài)排查查看節(jié)點(diǎn)狀態(tài)GET /_cat/nodes?v輸出包含 ip、heap.percent、ram.percent、cpu、load_1m 等指標(biāo)。排查性能問題時第一個動作就是看這里。比如heap.percent長期超過 85%就要考慮要不要加節(jié)點(diǎn)、調(diào) JVM 堆大小。查看分片分布GET /_cat/shards?v可以看到每個索引的分片都落在哪個節(jié)點(diǎn)上、是主分片還是副本、占用磁盤多大。索引變紅時_cat/shards會顯示哪些分片沒有分配到節(jié)點(diǎn)或者顯示UNASSIGNED狀態(tài)。查看索引層面的狀態(tài)GET /_cat/indices?v這個命令前面提過但它也是排查的起點(diǎn)。pri.store.size可以看索引占用空間docs.count可以看文檔數(shù)。當(dāng)磁盤告警時先用它找出占用最大的索引再決定做刪除、歸檔還是 reindex。8. 生產(chǎn)環(huán)境要避開的坑8.1 安全配置別裸奔8.x 默認(rèn)安全是開著的但總有人為了圖省事把xpack.security.enabled設(shè)成 false。如果是公司公網(wǎng)環(huán)境這是非常危險的操作。我見過不止一次集群裸奔在公網(wǎng)上被掃描到之后直接被刪庫里面被塞了一個勒索提醒。至少要做到幾件事保存好elastic初始密碼或者用密碼重置工具改成自己的強(qiáng)密碼。給應(yīng)用申請專用賬號或 API key不要所有調(diào)用都拿超級用戶。開啟 TLS/HTTPS應(yīng)用連接時配置證書。訪問連接配置里用 API key 比用戶名密碼更適合應(yīng)用側(cè)因?yàn)?API key 可以設(shè)置過期時間、只授權(quán)特定索引的權(quán)限泄露后也方便吊銷。創(chuàng)建 API key 的接口POST /_security/api_key { name: my_app_key, role_descriptors: { product_only: { indices: [ { names: [product*], privileges: [read, write] } ] } } }返回的id和api_key組合成Authorization: ApiKey base64(id:api_key)頭發(fā)送請求即可。8.2 分片與寫入性能調(diào)優(yōu)分片多少合適這個問題沒有絕對標(biāo)準(zhǔn)但有一條原則分片不是越多越好。每個分片都有對應(yīng)的 Lucene 段文件、文件句柄、內(nèi)存開銷分片過多會導(dǎo)致小請求被放大到很多分片執(zhí)行集群協(xié)調(diào)壓力增大。經(jīng)驗(yàn)上單個分片的數(shù)據(jù)量控制在 20GB 到 50GB 之間具體取決于你的查詢模式。數(shù)據(jù)量小的話單節(jié)點(diǎn)索引設(shè)置 1 個主分片副本看可用性需求設(shè) 1 或 0完全夠用。大批量寫入時還有兩個常見調(diào)優(yōu)項(xiàng)副本先設(shè)為 0。副本寫入和數(shù)據(jù)寫入是并行的副本越多寫入越慢。導(dǎo)完數(shù)據(jù)再把副本改回來。refresh_interval調(diào)大。默認(rèn) 1 秒刷新一次意味著每秒都有一次 refresh會產(chǎn)生段文件。導(dǎo)數(shù)據(jù)時可以把刷新間隔調(diào)到 30 秒甚至 -1關(guān)閉讓段更少、寫入更快。PUT /product/_settings { number_of_replicas: 0, refresh_interval: 30s }數(shù)據(jù)導(dǎo)入完成后再調(diào)回來PUT /product/_settings { number_of_replicas: 1, refresh_interval: 1s }我第一次做性能壓測時這兩條參數(shù)改動讓寫入吞吐提升了將近五倍。別小看 settings 的作用。8.3 常見報錯的排查思路最后分享幾個高頻報錯和排查思路。集群變紅先GET /_cat/shards?v找UNASSIGNED的分片再用GET /_cluster/allocation/explain看具體原因通常是磁盤水位滿、節(jié)點(diǎn)不可達(dá)等。磁盤水位滿時ES 會主動停止分配分片。清理磁盤空間后狀態(tài)會自動恢復(fù)。索引只讀磁盤水位過高時ES 會把索引設(shè)置成只讀防止進(jìn)一步擴(kuò)大磁盤占用。此時寫請求會報錯。解決方式先釋放磁盤空間然后手動解除只讀PUT /product/_settings { index.blocks.read_only_allow_delete: false }搜索超時有的查詢在開發(fā)環(huán)境很快到生產(chǎn)就超時。先看_cat/nodes的 CPU 和 load再排查具體索引的分片數(shù)是否太少或太多最后看查詢里有沒有should過多、深分頁這類問題。調(diào)大 timeout 只是治標(biāo)真正要優(yōu)化的是查詢邏輯。內(nèi)存熔斷報錯信息里出現(xiàn)CircuitBreakingException說明內(nèi)存使用超過限額。常見原因是聚合的size設(shè)得太大或者一次性加載了太多大字段。把聚合桶數(shù)量調(diào)小或者加大節(jié)點(diǎn)內(nèi)存都可以緩解。做完這些基本操作你會發(fā)現(xiàn)自己已經(jīng)在拿 RESTful API 干很多正經(jīng)事了。我個人在實(shí)際項(xiàng)目里的體會是8.x 的核心操作并不復(fù)雜真正拉開差距的是對 mapping 和分片規(guī)劃的理解。寧可花半小時把字段類型想清楚也別在數(shù)據(jù)量上來之后折騰 reindex。先把上面這些請求多敲幾遍ES 的基本功就穩(wěn)了。