ES高級篇
集群部署
集群的意思:就是將多個節點歸為一體罷了,這個整體就有一個指定的名字了
window中部署集群 - 了解
把下載好的window版的ES中的data文件夾、logs文件夾下的所有的文件刪掉,然后拷貝成三份,對文件重命名

1、修改node-1001節點的config/elasticsearch.yml配置文件。這個配置文件里面有原生的配置信息,感興趣的可以查看,因為現在要做的配置信息都在原生的配置信息里,只是被注釋掉了而已,當然:沒興趣的,直接全選刪掉,然后做如下配置
# ---------------------------------- Cluster -----------------------------------
#
# Use a descriptive name for your cluster:
# 集群名稱 注意:是把多個節點歸為一個整體,所以這個集群名字就是各節點歸為一體之后的名字
# 因此:各個節點中這個集群名字也就要一樣了
cluster.name: es-colony
#
# ------------------------------------ Node ------------------------------------
#
# Use a descriptive name for the node:
# 節點名稱 在一個集群中,這個名字要全局唯一
node.name: node-1001
# 是否有資格成為主機節點
node.master: true
# 是否是數據節點
node.data: true
#
# ---------------------------------- Network -----------------------------------
#
# Set the bind address to a specific IP (IPv4 or IPv6):
# 當前節點的ip地址
network.host: 127.0.0.1
#
# Set a custom port for HTTP:
# 當前節點的端口號
http.port: 1001
# 當前節點的通訊端口( 監聽端口 )
transport.tcp.port: 9301
# 跨域配置
http.cors.enabled: true
http.cors.allow-origin: "*"
2、修改node-1002節點的config/elasticsearch.yml配置文件
# ---------------------------------- Cluster -----------------------------------
#
# Use a descriptive name for your cluster:
# 集群名稱 注意:是把多個節點歸為一個整體,所以這個集群名字就是各節點歸為一體之后的名字
# 因此:各個節點中這個集群名字也就要一樣了
cluster.name: es-colony
#
# ------------------------------------ Node ------------------------------------
#
# Use a descriptive name for the node:
# 節點名稱 在一個集群中,這個名字要全局唯一
node.name: node-1002
# 是否是主機節點
node.master: true
# 是否是數據節點
node.data: true
#
# ---------------------------------- Network -----------------------------------
#
# Set the bind address to a specific IP (IPv4 or IPv6):
# 當前節點的ip地址
network.host: 127.0.0.1
#
# Set a custom port for HTTP:
# 當前節點的端口號
http.port: 1002
# 當前節點的通訊端口( 監聽端口 )
transport.tcp.port: 9302
# 當前節點不知道集群中另外節點是哪些,所以配置,讓當前節點能夠找到其他節點
discovery.seed_hosts: ["127.0.0.1:9301"]
# ping請求調用超時時間,但同時也是選主節點的delay time 延遲時間
discovery.zen.fd.ping_timeout: 1m
# 重試次數,防止GC[ 垃圾回收 ]節點不響應被剔除
discovery.zen.fd.ping_retries: 5
# 跨域配置
http.cors.enabled: true
http.cors.allow-origin: "*"
3、修改node-1003節點的config/elasticsearch.yml配置文件
# ---------------------------------- Cluster -----------------------------------
#
# Use a descriptive name for your cluster:
# 集群名稱 注意:是把多個節點歸為一個整體,所以這個集群名字就是各節點歸為一體之后的名字
# 因此:各個節點中這個集群名字也就要一樣了
cluster.name: es-colony
#
# ------------------------------------ Node ------------------------------------
#
# Use a descriptive name for the node:
# 節點名稱 在一個集群中,這個名字要全局唯一
node.name: node-1003
# 是否是主機節點
node.master: true
# 是否是數據節點
node.data: true
#
# ---------------------------------- Network -----------------------------------
#
# Set the bind address to a specific IP (IPv4 or IPv6):
# 當前節點的ip地址
network.host: 127.0.0.1
#
# Set a custom port for HTTP:
# 當前節點的端口號
http.port: 1003
# 當前節點的通訊端口( 監聽端口 )
transport.tcp.port: 9303
# 當前節點不知道集群中另外節點是哪些,所以配置,讓當前節點能夠找到其他節點
discovery.seed_hosts: ["127.0.0.1:9301","127.0.0.1:9302"]
# ping請求調用超時時間,但同時也是選主節點的delay time
discovery.zen.fd.ping_timeout: 1m
# 重試次數,防止GC[ 垃圾回收 ]節點不響應被剔除
discovery.zen.fd.ping_retries: 5
# 跨域配置
http.cors.enabled: true
http.cors.allow-origin: "*"
依次啟動1、2、3節點的bin/elasticsearch.bat即可啟動集群
用postman測試集群
GET http://localhost:1001/_cluster/health
// 響應內容
{
"cluster_name": "es-colony",
"status": "green", // 重點查看位置 狀態顏色
"timed_out": false,
"number_of_nodes": 3, // 重點查看位置 集群中的節點數量
"number_of_data_nodes": 3, // 重點查看位置 集群中的數據節點數量
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0,
"delayed_unassigned_shards": 0,
"number_of_pending_tasks": 0,
"number_of_in_flight_fetch": 0,
"task_max_waiting_in_queue_millis": 0,
"active_shards_percent_as_number": 100.0
}
status字段顏色表示:當前集群在總體上是否工作正常。它的三種顏色含義如下:
- green: 所有的主分片和副本分片都正常運行
- yellow: 所有的主分片都正常運行,但不是所有的副本分片都正常運行
- red: 有主分片沒能正常運行
附加內容:一些配置說明,下面的一些配置目前有些人可能並沒有遇到,但是在這里留個印象吧,知道個大概和怎么去找就行了
官網地址: https://www.elastic.co/guide/en/elasticsearch/reference/current/modules.html
主節點 [ host區域 ] 和 數據節點 [ stale區域 ]:
cluster.name: elastics # 定義集群名稱所有節點統一配置
node.name: es-0 # 節點名稱自定義
node.master: true # 主節點,數據節點設置為 false
node.data: false # 數據節點設置為true
path.data: /home/es/data # 存儲目錄,可配置多個磁盤
path.logs: /home/es/logs # 日志文件路徑
bootstrap.mlockall: true # 啟動時鎖定內存
network.publish_host: es-0 # 綁定網卡
network.bind_host: es-0 # 綁定網卡
http.port: 9200 # http端口
discovery.zen.ping.multicast.enabled: false # 禁用多播,跨網段不能用多播
discovery.zen.ping_timeout: 120s
discovery.zen.minimum_master_nodes: 2 # 至少要發現集群可做master的節點數,
client.transport.ping_timeout: 60s
discovery.zen.ping.unicast.hosts: ["es-0","es-1", "es-2","es-7","es-8","es-4","es-5","es-6"] # 集群自動發現
# fd 是 fault detection
# discovery.zen.ping_timeout 僅在加入或者選舉 master 主節點的時候才起作用;
# discovery.zen.fd.ping_timeout 在穩定運行的集群中,master檢測所有節點,以及節點檢測 master是否暢通時長期有用
discovery.zen.fd.ping_timeout: 120s # 超時時間(根據實際情況調整)
discovery.zen.fd.ping_retries: 6 # 重試次數,防止GC[垃圾回收]節點不響應被剔除
discovery.zen.fd.ping_interval: 30s # 運行間隔
# 控制磁盤使用的低水位。默認為85%,意味着如果節點磁盤使用超過85%,則ES不允許在分配新的分片。當配置具體的大小如100MB時,表示如果磁盤空間小於100MB不允許分配分片
cluster.routing.allocation.disk.watermark.low: 100GB # 磁盤限額
# 控制磁盤使用的高水位。默認為90%,意味着如果磁盤空間使用高於90%時,ES將嘗試分配分片到其他節點。上述兩個配置可以使用API動態更新,ES每隔30s獲取一次磁盤的使用信息,該值可以通過cluster.info.update.interval來設置
cluster.routing.allocation.disk.watermark.high: 50GB # 磁盤最低限額
node.zone: hot # 磁盤區域,分為hot和stale,做冷熱分離
script.inline: true # 支持腳本
script.indexed: true
cluster.routing.allocation.same_shard.host: true # 一台機器部署多個節點時防止一個分配到一台機器上,宕機導致丟失數據
# 以下6行為設置thread_pool
threadpool.bulk.type: fixed
threadpool.bulk.size: 32
threadpool.bulk.queue_size: 100
threadpool.search.type: fixed
threadpool.search.size: 49
threadpool.search.queue_size: 10000
script.engine.groovy.inline.aggs: on
# 以下為配置慢查詢和慢索引的時間
index.search.slowlog.threshold.query.warn: 20s
index.search.slowlog.threshold.query.info: 10s
index.search.slowlog.threshold.query.debug: 4s
index.search.slowlog.threshold.query.trace: 1s
index.search.slowlog.threshold.fetch.warn: 2s
index.search.slowlog.threshold.fetch.info: 1600ms
index.search.slowlog.threshold.fetch.debug: 500ms
index.search.slowlog.threshold.fetch.trace: 200ms
index.indexing.slowlog.threshold.index.warn: 20s
index.indexing.slowlog.threshold.index.info: 10s
index.indexing.slowlog.threshold.index.debug: 4s
index.indexing.slowlog.threshold.index.trace: 1s
# 索引庫設置
indices.fielddata.cache.size: 20% # 索引庫緩存時占用大小
indices.fielddata.cache.expire: "48h" # 索引庫緩存的有效期
indices.cache.filter.size: 10% # 索引庫緩存過濾占用大小
index.search.slowlog.level: WARN # 索引庫搜索慢日志級別
Linux中部署ES
部署單機ES
1、准備工作
- 下載linux版的ES,自行百度進行下載,老規矩,我的版本是:7.8.0
- 將下載好的linux版ES放到自己的服務器中去
2、解壓文件:
# 命令
tar -zxvf elasticsearch-7.8.0-linux-x86_64.tar.gz
3、對文件重命名:
# 命令
mv elasticsearch-7.8.0 es
4、創建用戶:因為安全問題, Elasticsearch 不允許 root 用戶直接運行,所以要創建新用戶,在 root 用戶中創建新用戶
useradd es # 新增 es 用戶
passwd es # 為 es 用戶設置密碼,輸入此命令后,輸入自己想設置的ES密碼即可
userdel -r es # 如果錯了,可以把用戶刪除了再重新加
chown -R es:es /opt/install/es # 文件授權 注意:/opt/install/es 改成自己的ES存放路徑即可
5、修改 config/elasticsearch.yml 配置文件
# 在elasticsearch.yml文件末尾加入如下配置
cluster.name: elasticsearch
node.name: node-1
network.host: 0.0.0.0
http.port: 9200
cluster.initial_master_nodes: ["node-1"]
6、修改 /etc/security/limits.conf 文件
# 命令
vim /etc/security/limits.conf
# 在文件末尾中增加下面內容 這個配置是:每個進程可以打開的文件數的限制
es soft nofile 65536
es hard nofile 65536
7、修改 /etc/security/limits.d/20-nproc.conf 文件
# 命令
vim /etc/security/limits.d/20-nproc.conf
# 在文件末尾中增加下面內容
# 每個進程可以打開的文件數的限制
es soft nofile 65536
es hard nofile 65536
# 操作系統級別對每個用戶創建的進程數的限制
* hard nproc 4096
# 注: * 表示 Linux 所有用戶名稱
8、修改 /etc/sysctl.conf 文件
# 在文件中增加下面內容
# 一個進程可以擁有的 VMA(虛擬內存區域)的數量,默認值為 65536
vm.max_map_count=655360
9、重新加載文件
# 命令
sysctl -p
10、啟動程序 : 准備進入坑中
cd /opt/install/es/
# 啟動
bin/elasticsearch
# 后台啟動
bin/elasticsearch -d

這個錯誤告知:不能用root用戶,所以:切換到剛剛創建的es用戶
# 命令
su es
然后再次啟動程序,進入下一個坑

這個錯誤是因為:啟動程序的時候會動態生成一些文件,這和ES沒得關系,所以:需要切回到root用戶,然后把文件權限再次刷新一下
# 切換到root用戶
su root
# 切換到root用戶之后,執行此命令即可
chown -R es:es /opt/install/es
# 再切換到es用戶
su es

11、再次啟動程序

吃雞,這樣linux中單機ES就部署成功了
不過啊,前面這種方式都是low的方式,有更簡單的方式,就是使用docker容器來進行配置,簡直不要太簡單,雖然:使用docker容器來啟動程序有弊端,如:MySQL就不建議放在docker容器中,因為:MySQL是不斷地進行io操作,放到docker容器中,就會讓io操作效率降低,而ES放到docker中也是同樣的道理,但是:可以玩,因為:有些公司其實並沒有在意docker的弊端,管他三七二十一扔到docker中
如果想要用docker容器進行ES配置,編寫如下的docker-compose.yml文件
version: "3.1"
services:
elasticsearch:
# 注:此網站版本不全,可以直接用官網 elasticsearch:7.8.0
image: daocloud.io/library/elasticsearch:7.9.0
restart: always
container_name: elasticsearch
ports:
- 9200:9200
environment:
- Java_OPTS=--Xms256m -Xmx1024m
然后啟動容器即可
注:使用docker安裝需要保證自己的linux中安裝了docker和docker-compose, 沒有安裝的話,教程鏈接:centos7安裝docker和docker-compose
注:有些人可能還會被防火牆整一下,貧道的防火牆是關了的
# 暫時關閉防火牆
systemctl stop firewalld
# 永久關閉防火牆
systemctl enable firewalld.service # 打開防火牆永久性生效,重啟后不會復原
systemctl disable firewalld.service # 關閉防火牆,永久性生效,重啟后不會復原
12、測試是否成功
// 在瀏覽器或postman中輸入以下指令均可
GET http://ip:9200/

瀏覽器訪問不了,看看自己服務器開放9200端口沒有,別搞這種扯犢子事啊
部署集群ES
可以選擇和windows版的集群搭建一樣
- 解壓ES、重命名
- 復制幾份ES文件夾
- 修改對應配置
更簡單的方式
- 解壓linux版的ES,重命名
- 分發節點
# 分發節點的步驟
xsync es-cluster # es-cluster為解壓之后的es名字
# 注:xsync需要單獨配置,配置過程如下:
# 1、安裝rsync
yum -y install rsync
# 配置hosts 節點服務配置 此配置是在/etc/hosts中進行的
ip name # 如:192.168.0.100 hadoop
# 2、編寫腳本 看自己的/usr/local/bin中是否有xsync文件 都看這里了,那就是第一次弄,肯定沒有,所以
# 在/usr/local/bin中新建一個xsync文件 touch xsync
# 編輯內容如下:
#!/bin/sh
# 獲取輸入參數個數,如果沒有參數,直接退出
pcount=$#
if((pcount==0)); then
echo no args...;
exit;
fi
# 獲取文件名稱
p1=$1
fname=`basename $p1`
echo fname=$fname
# 獲取上級目錄的絕對路徑
pdir=`cd -P $(dirname $p1); pwd`
echo pdir=$pdir
# 獲取當前用戶名稱
user=`whoami`
# 循環
for((host=1; host<=2; host++)); do
echo $pdir/$fname $user@slave$host:$pdir
echo ==================slave$host==================
rsync -rvl $pdir/$fname $user@slave$host:$pdir
done
# Note:這里的slave對應自己主機名,需要做相應修改。另外,for循環中的host的邊界值由自己的主機編號決定
# 3、給新建的xsync文件授權
chmod a+x xsync
# 這樣就配置成功了
1、同樣的,root用戶不能直接運行,所以創建用戶
useradd es # 新增 es 用戶
passwd es # 為 es 用戶設置密碼
userdel -r es # 如果錯了,可以刪除再加
chown -R es:es /opt/module/es # 給文件夾授權
2、編輯 ES文件夾的config/elasticsearch.yml文件,實現集群配置
# 加入如下配置,這些配置在網上都有,看不懂的網上比我更詳細
# 集群名稱
cluster.name: cluster-es
# 節點名稱, 每個節點的名稱不能重復
node.name: node-1
# ip 地址, 每個節點的地址不能重復
network.host: zixieqing-linux1 # 這個主機名字是在前面使用xsync異步分發時在hosts中配置的名字
# 是不是有資格主節點
node.master: true
node.data: true
http.port: 9200
# head 插件需要這打開這兩個配置
http.cors.allow-origin: "*"
http.cors.enabled: true
http.max_content_length: 200mb
# es7.x 之后新增的配置,初始化一個新的集群時需要此配置來選舉 master
cluster.initial_master_nodes: ["node-1"]
# es7.x 之后新增的配置,節點發現
discovery.seed_hosts: ["zixieqing-linux1:9300","zixieqing-linux2:9300","zixieqing-linux3:9300"]
gateway.recover_after_nodes: 2
network.tcp.keep_alive: true
network.tcp.no_delay: true
transport.tcp.compress: true
# 集群內同時啟動的數據任務個數,默認是 2 個
cluster.routing.allocation.cluster_concurrent_rebalance: 16
# 添加或刪除節點及負載均衡時並發恢復的線程個數,默認 4 個
cluster.routing.allocation.node_concurrent_recoveries: 16
# 初始化數據恢復時,並發恢復線程的個數,默認 4 個
cluster.routing.allocation.node_initial_primaries_recoveries: 16
3、修改 etc/security/limits.conf
# 在文件末尾中增加下面內容
es soft nofile 65536
es hard nofile 65536
4、修改 /etc/security/limits.d/20-nproc.conf
# 在文件末尾中增加下面內容
es soft nofile 65536
es hard nofile 65536
* hard nproc 4096
# 注: * 表示 Linux 所有用戶名稱
5、修改/etc/sysctl.conf
# 在文件中增加下面內容
vm.max_map_count=655360
6、加載文件
sysctl -p
7、啟動軟件
cd /opt/module/es-cluster
# 啟動
bin/elasticsearch
# 后台啟動
bin/elasticsearch -d
防火牆的問題前面已經提到了
8、集群驗證

分片、副本、分配
分片 shards - 重要
這玩意兒就類似於關系型中的分表
在關系型中如果一個表的數據太大了,查詢效率很低、響應很慢,所以就會采用大表拆小表,如:用戶表,不可能和用戶相關的啥子東西都放在一張表吧,這不是找事嗎?因此:需要分表
相應的在ES中,也需要像上面這么干,如:存儲100億文檔數據的索引,在單節點中沒辦法存儲這么多的文檔數據,所以需要進行切割,就是將這整個100億文檔數據切幾刀,然后每一刀切分出來的每份數據就是一個分片 ( 索引 ),然后在切開的每份數據單獨放在一個節點中,這樣切開的所有文檔數據合在一起就是一份完整的100億數據,因此:這個的作用也是為了提高效率
創建一個索引的時候,可以指定想要的分片的數量。每個分片本身也是一個功能完善並且獨立的“索引”,這個“索引”可以被放置到集群中的任何節點上
分片有兩方面的原因:
- 允許水平分割 / 擴展內容容量,水平擴充,負載均衡嘛
- 允許在分片之上進行分布式的、並行的操作,進而提高性能 / 吞吐量
注意: 當 Elasticsearch 在索引中搜索的時候, 它發送查詢到每一個屬於索引的分片,然后合並每個分片的結果到一個全局的結果集中
副本 Replicas - 重要
這不是游戲中的刷副本的那個副本啊。是指:分片的復制品
失敗是常有的事嘛,所以:在ES中也會失敗呀,可能因為網絡、也可能因此其他鬼原因就導致失敗了,此時不就需要一種故障轉移機制嗎,也就是 創建分片的一份或多份拷貝,這些拷貝就叫做復制分片( 副本 )
副本( 復制分片 )之所以重要,有兩個原因:
- 在分片 / 節點失敗的情況下,提供了高可用性。因為這個原因,復制分片不與原 / 主要( original / primary )分片置於同一節點上是非常重要的
- 擴展搜索量 / 吞吐量,因為搜索可以在所有的副本上並行運行
多說一嘴啊,分片和副本這兩個不就是配套了嗎,分片是切割數據,放在不同的節點中( 服務中 );副本是以防服務宕掉了,從而丟失數據,進而把分片拷貝了任意份。這個像什么?不就是主備嗎( 我說的是主備,不是主從啊 ,這兩個有區別的,主從是主機具有寫操作,從機具有讀操作;而主備是主機具有讀寫操作,而備機只有讀操作 ,不一樣的啊 )
有個細節需要注意,在ES中,分片和副本不是在同一台服務器中,是分開的,如:分片P1在節點1中,那么副本R1就不能在節點1中,而是其他服務中,不然服務宕掉了,那數據不就全丟了嗎
分配 Allocation
前面講到了分片和副本,對照Redis中的主備來看了,那么對照Redis的主從來看呢?主機宕掉了怎么重新選一個主機?Redis中是加了一個哨兵模式,從而達到的。那么在ES中哪個是主節點、哪個是從節點、分片怎么去分的?就是利用了分配
所謂的分配是指: 將分片分配給某個節點的過程,包括分配主分片或者副本。如果是副本,還包含從主分片復制數據的過程。注意:這個過程是由 master 節點完成的,和Redis還是有點不一樣的啊
既然都說了這么多,那就再來一個ES的系統架構吧

其中,P表示分片、R表示副本
默認情況下,分片和副本都是1,根據需要可以改變
單節點集群
這里為了方便就使用window版做演示,就不再linux中演示了
1、打開前面玩的window版集群的1節點

2、創建索引 把這個索引切成3份( 切片 )、每份拷貝1份副本
PUT http://127.0.0.1:1001/users
// 請求體內容
{
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}
}
3、開始安裝head插件,這就是一個可視化界面而已,后續還會用Kibana
還有一種es的集群監控的方式是使用cerebro,官網地址:https://github.com/lmenezes/cerebro 下載解壓,運行 bin/cerebro.bat 即可
自行到官網下載elasticsearch-head-master,這是用Vue寫的。啟動效果如下:

訪問上圖中的地址即可,但是:這個端口是9100,而我們的ES是9200端口,所以9100訪問9200是跨越的,因此:需要對ES設置跨越問題,而這個問題在第一次玩ES集群時就配置了的

head打開之后就是下圖中的樣子

head鏈接ES之后就是下圖的樣子

三種顏色再鞏固一下:
- green:所有的主分片和副本分片都正常運行
- yellow:所有的主分片都正常運行,但不是所有的副本分片都正常運行
- red:有主分片沒能正常運行
但是:上述的單節點集群有問題,就是將分片和副本都放在一個節點( node-1001 )中了,這樣會導致前面說的服務宕掉,數據就沒了,做的副本就是無用功。要解決就要引入接下來的內容了
故障轉移
所謂的故障轉移指的就是:
- 若新開節點,那么ES就會將原有數據重新分配到所有節點上
- 若是節點掛了,那么ES就會將掛了的節點的數據進行拷貝到另外好的節點中。要是掛的正好是master主節點,那么還有多一個選主過程,然后再分配數據————這種情況也可以稱之為“應對故障”
1、新開節點的情況: 啟動node-1002節點
可能由於玩windows版時的一些數據導致node-1002節點啟動不了,所以刪掉data文件夾和logs文件夾下的東西即可
刷新head可視化頁面:

恢復正常
水平擴容 / 負載均衡
1、啟動node-1003節點

刷新head頁面

對照前面單節點集群來看,數據就被很好的分開了,這樣性能不就提上來了嗎
但是:如果相應繼續擴容呢?即:超過6份數據( 6個節點,前面講到過索引切分之后,每一份又是單獨的索引、副本也算節點 ),那怎么辦?
- 首先知道一個點:主分片的數目在索引創建時就已經確定下來了的,這個我們沒法改變,這個數目定義了這個索引能夠存儲的最大數據量( 實際大小取決於你的數據、硬件和使用場景 )
- 但是,讀操作——搜索和返回數據——可以同時被主分片 或 副本分片所處理,所以當你擁有越多的副本分片時,也將擁有越高的吞吐量
- 因此:增加副本分片的數量即可
put http://127.0.0.1:1001/users/_settings
// 請求體內容
{
"number_of_replicas": 2
}
刷新head頁面

應對故障
應對的是什么故障?前面一直在說:服務宕掉了嘛
1、關掉node-1001節點( 主節點 )
2、刷新head頁面

但是注意啊:yellow雖然不正常,但是不影響操作啊,就像你看了yellow之后,影響你正常發揮嗎?只是可能有點虛脫而已,所以對於ES來說也是可以正常查詢數據的,只是:效率降低了而已嘛( 主節點和3個分片都在的嘛 )
3、解決這種問題: 開啟新節點(把node-1001節點啟動。此時它就不是主節點了 ,當成新節點了)

這就會報錯: unless existing master is discovered 找不到主節點( 對於啟動的集群來說,它現在是新節點],因此:需要做一下配置修改( node-1001的 config/ElasticSearch.yml )
discovery.seed_hosts: ["127.0.0.1:9302","127.0.0.1:9303"]
保存開啟node-1001節點即可
4、刷新head頁面

故障恢復了,所以:這也告知一個問題,配置集群時,最好在每個節點的配置文件中都加上上述的配置,從而節點宕掉之后,重啟節點即可( 不然每次改不得煩死 ),注意:ES版本不一樣,這個配置方法不一樣的,6.x的版本是用cluster.initial_master_nodes: 來進行配置的
路由計算和分片控制理論
路由計算
路由、路由,這個東西太熟悉了,在Vue中就見過路由router了( 用來轉發和重定向的嘛 )
那在ES中的路由計算又是怎么回事?這個主要針對的是ES集群中的存數據,試想:你知道你存的數據是在哪個節點 / 哪個主分片中嗎( 副本是拷貝的主分片,所以主分片才是核心 )?
- 當然知道啊,就是那幾個節點中的任意一個嘛。娘希匹~這樣的騷回答好嗎?其實這是由一個公式來決定的
shard = hash( routing ) % number_of_primary_shards
routing 是一個任意值,默認是文檔的_id,也可以自定義
number_of_primary_shards 表示主分片的數量,如前面切分為了3份
hash() 是一個hash函數
這就解釋了為什么我們要在創建索引的時候就確定好主分片的數量並且永遠不會改變這個數量:因為如果數量變化了,那么之前所有路由的值都會無效,文檔也再也找不到了
分片控制
既然有了存數據的問題,那當然就有取數據的問題了。
請問:在ES集群中,取數據時,ES怎么知道去哪個節點中取數據( 假如在3節點中,你去1節點中,可以取到嗎?),因此:來了分片控制
負載均衡,輪詢嘛。所以這里有個小知識點,就是:協調節點 coordinating node,我們可以發送請求到集群中的任一節點,每個節點都有能力處理任意請求,每個節點都知道集群中任一文檔位置,這就是分片控制,而我們發送請求的那個節點就是:協調節點,它會去幫我們找到我們要的數據在哪里
綜合前面的知識就可以得到:
-
所謂的分片就是:將索引切分成任意份嘛,然后得到的每一份數據都是一個單獨的索引
-
分片完成后,我們存數據時,存到哪個節點上,就是通過 shard = hash( routing ) % number_of_primary_shards 得到的
-
而我們查詢數據時,ES怎么知道我們要找的數據在哪個節點上,就是通過協調節點做到的,它會去找到和數據相關的“所有節點”,從而輪詢,然后進行數據整合,通過協調節點返回給客戶端。因此最后的結果可能是從主分片上得到的,也可能是從副本上得到的,就看最后輪詢到的是哪個節點罷了
集群下的數據寫流程
新建、刪除請求都是寫操作, 必須在主分片上面完成之后才能被復制到相關的副本分片
整個流程也很簡單
- 客戶端請求任意節點(協調節點)
- 通過路由計算,協調節點把請求轉向指定的節點
- 轉向的節點的主分片保存數據
- 主節點再將數據轉發給副本保存
- 副本給主節點反饋保存結果
- 主節點給客戶端反饋保存結果
- 客戶端收到反饋結果

但是:從圖中就可以看出來,這套流程完了,才可以做其他事( 如:才可以去查詢數據 ),那我為什么不可以異步呢?就是我只要保證到了哪一個步驟之后,就可以進行數據查詢,所以:這里有兩個小東西需要了解
在進行寫數據時,我們做個小小的配置,這就是接下來的兩個小節內容
一致性 consistency
這玩意就是為了和讀數據搭配起來嘛,寫入和讀取保證數據的一致性唄
這玩意兒可以設定的值如下:
- one :只要主分片狀態 ok 就允許執行讀操作,這種寫入速度快,但不能保證讀到最新的更改
- all:這是強一致性,必須要主分片和所有副本分片的狀態沒問題才允許執行寫操作
- quorum:這是ES的默認值。即大多數的分片副本狀態沒問題就允許執行寫操作。這是折中的方法,write的時候,W>N/2,即參與寫入操作的節點數W,必須超過副本節點數N的一半,在這個默認情況下,ES是怎么判定你的分片數量的,就一個公式:
int((primary + number_of_replicas) / 2) + 1
primary 指的是創建的索引數量
number_of_replicas 是指的在索引設置中設定的副本分片數
如果你的索引設置中指定了當前索引擁有3個副本分片
那規定數量的計算結果為:int(1 primary + 3 replicas) / 2) + 1 = 3,
如果此時你只啟動兩個節點,那么處於活躍狀態的分片副本數量就達不到規定數量,
也因此你將無法索引和刪除任何文檔
- realtime request:就是從translog里頭讀,可以保證是最新的。但是注意:get是最新的,但是檢索等其他方法不是( 如果需要搜索出來也是最新的,需要refresh,這個會刷新該shard但不是整個index,因此如果read請求分發到repliac shard,那么可能讀到的不是最新的數據,這個時候就需要指定preference=_primar y)
超時 timeout
如果沒有足夠的副本分片會發生什么?Elasticsearch 會等待,希望更多的分片出現。默認情況下,它最多等待 1 分鍾。 如果你需要,你可以使用timeout參數使它更早終止,單位是毫秒,如:100就是100毫秒
新索引默認有1個副本分片,這意味着為滿足規定數量應該需要兩個活動的分片副本。 但是,這些默認的設置會阻止我們在單一節點上做任何事情。為了避免這個問題,要求只有當number_of_replicas 大於1的時候,規定數量才會執行
上面的理論不理解、或者感覺枯燥也沒事兒,后面慢慢的就理解了,這里只是打個預防針、了解理論罷了
集群下的數據讀流程
有寫流程,那肯定也要說一下讀流程嘛,其實和寫流程很像,只是變了那么一丟丟而已
流程如下:
- 客戶端發送請求到任意節點( 協調節點 )
- 這里不同,此時協調節點會做兩件事:1、通過路由計算得到分片位置,2、還會把當前查詢的數據所在的另外節點也找到( 如:副本 )
- 為了負載均衡( 可能某個節點中的訪問量很大嘛,減少一下壓力咯 ),所以就會對查出來的所有節點做輪詢操作,從而找到想要的數據( 因此:你想要的數據在主節點中有、副本中也有,但是:給你的數據可能是主節點中的,也可能是副本中的 ———— 看輪詢到的是哪個節點中的 )
- 節點反饋結果
- 客戶端收到反饋結果

這里有個注意點: 在文檔( 數據 )被檢索時,已經被索引的文檔可能已經存在於主分片上但是還沒有復制到副本分片。 在這種情況下,副本分片可能會報文檔不存在,但是主分片可能成功返回文檔。 一旦索引請求成功返回給用戶,文檔在主分片和副本分片都是可用的
集群下的更新操作流程
更新操作流程

- 客戶端向node 1發送更新請求
- 它將請求轉發到主分片所在的node 3
- node 3從主分片檢索文檔,修改_source字段中的JSON,並且嘗試重新索引主分片的文檔。如果文檔已經被另一個進程修改,它會重試步驟3 ,超過retry_on_conflict次后放棄
- 如果 node 3成功地更新文檔,它將新版本的文檔並行轉發到node 1和 node 2上的副本分片,重新建立索引。一旦所有副本分片都返回成功,node 3向協調節點也返回成功,協調節點向客戶端返回成功
當然:上面有個漏洞,就是萬一在另一個進程修改之后,當前修改進程又去修改了,那要是把原有的數據修改了呢?這不就成關系型數據庫中的“不可重復讀”了嗎?
- 不會的。因為當主分片把更改轉發到副本分片時, 它不會轉發更新請求。 相反,它轉發完整文檔的新版本。注意點:這些更改將會“異步轉發”到副本分片,並且不能保證它們以相同的順序到達。 如果 ES 僅轉發更改請求,則可能以錯誤的順序應用更改,導致得到的是損壞的文檔
批量更新操作流程
這個其實更容易理解,單文檔更新懂了,那多文檔更新就懂了嘛,多文檔就請求拆分唄
所謂的多文檔更新就是:將整個多文檔請求分解成每個分片的文檔請求,並且將這些請求並行轉發到每個參與節點。協調節點一旦收到來自每個節點的應答,就將每個節點的響應收集整理成單個響應,返回給客戶端
原理圖的話:我就在網上偷一張了

其實mget 和 bulk API的模式就類似於單文檔模式。區別在於協調節點知道每個文檔存在於哪個分片中
用單個 mget 請求取回多個文檔所需的步驟順序:
- 客戶端向 Node 1 發送 mget 請求
- Node 1為每個分片構建多文檔獲取請求,然后並行轉發這些請求到托管在每個所需的主分片或者副本分片的節點上。一旦收到所有答復,Node 1 構建響應並將其返回給客戶端。可以對docs數組中每個文檔設置routing參數
- bulk API, 允許在單個批量請求中執行多個創建、索引、刪除和更新請求

bulk API 按如下步驟順序執行:
- 客戶端向Node 1 發送 bulk請求
- Node 1為每個節點創建一個批量請求,並將這些請求並行轉發到每個包含主分片的節點主機
- 主分片一個接一個按順序執行每個操作。當每個操作成功時,主分片並行轉發新文檔(或刪除)到副本分片,然后執行下一個操作。一旦所有的副本分片報告所有操作成功,該節點將向協調節點報告成功,協調節點將這些響應收集整理並返回給客戶端
文檔搜索
不可變的倒排索引
以前的全文檢索是將整個文檔集合弄成一個倒排索引,然后存入磁盤中,當要建立新的索引時,只要新的索引准備就緒之后,舊的索引就會被替換掉,這樣最近的文檔數據變化就可以被檢索到
而索引一旦被存入到磁盤就是不可變的( 永遠都可以修改 ),而這樣做有如下的好處:
- 只要索引被讀入到內存中了,由於其不變性,所以就會一直留在內存中( 只要空間足夠 ),從而當我們做“讀操作”時,請求就會進入內存中去,而不會去磁盤中,這樣就減小開銷,提高效率了
- 索引放到內存中之后,是可以進行壓縮的,這樣做之后,也就可以節約空間了
- 放到內存中后,是不需要鎖的,如果自己的索引是長期不用更新的,那么就不用怕多進程同時修改它的情況了
當然:這種不可變的倒排索引有好處,那就肯定有壞處了
- 不可變,不可修改嘛,這就是最大的壞處,當要重定一個索引能夠被檢索時,就需要重新把整個索引構建一下,這樣的話,就會導致索引的數據量很大( 數據量大小有限制了 ),同時要更新索引,那么這頻率就會降低了
- 這就好比是什么呢?關系型中的表,一張大表檢索數據、更新數據效率高不高?肯定不高,所以延伸出了:可變索引
可變的倒排索引
又想保留不可變性,又想能夠實現倒排索引的更新,咋辦?
- 就搞出了
補充索引,所謂的補充索引:有點類似於日志這個玩意兒,就是重建一個索引,然后用來記錄最近指定一段時間內的索引中文檔數據的更新。這樣更新的索引數據就記錄在補充索引中了,然后檢索數據時,直接找補充索引即可,這樣檢索時不再重寫整個倒排索引了,這有點類似於關系型中的拆表,大表拆小表嘛,但是啊:每一份補充索引都是一份單獨的索引啊,這又和分片很像,可是:查詢時是對這些補充索引進行輪詢,然后再對結果進行合並,從而得到最終的結果,這和前面說過的讀流程中說明的協調節點掛上鈎了
這里還需要了解一個配套的按段搜索,玩過 Lucene 的可能聽過。按段,每段也就可以理解為:補充索引,它的流程其實也很簡單:
- 新文檔被收集到內存索引緩存
- 不時地提交緩存
- 一個新的段,一個追加的倒排索引,被寫入磁盤
- 一個新的包含新段名字的提交點被寫入磁盤
- 磁盤進行同步,所有在文件系統緩存中等待的寫入都刷新到磁盤,以確保它們被寫入物理文件
- 內存緩存被清空,等待接收新的文檔
- 新的段被開啟,讓它包含的文檔可見,以被搜索
一樣的,段在查詢的時候,也是輪詢的啊,然后把查詢結果合並從而得到的最終結果
另外就是涉及到刪除的事情,段本身也是不可變的, 既不能把文檔從舊的段中移除,也不能修改舊的段來進行文檔的更新,而刪除是因為:是段在每個提交點時有一個.del文件,這個文件就是一個刪除的標志文件,要刪除哪些數據,就對該數據做了一個標記,從而下一次查詢的時候就過濾掉被標記的這些段,從而就無法查到了,這叫邏輯刪除( 當然:這就會導致倒排索引越積越多,再查詢時。輪詢來查數據也會影響效率 ),所以也有物理刪除,它是把段進行合並,這樣就舍棄掉被刪除標記的段了,從而最后刷新到磁盤中去的就是最新的數據( 就是去掉刪除之后的 ,別忘了前面整的段的流程啊,不是白寫的 )
近實時搜索、文檔刷新、文檔刷寫、文檔合並
ES的最大好處就是實時數據全文檢索
但是:ES這個玩意兒並不是真的實時的,而是近實時 / 准實時
原因就是:ES的數據搜索是分段搜索,最新的數據在最新的段中(每一個段又是一個倒排索引),只有最新的段刷新到磁盤中之后,ES才可以進行數據檢索,這樣的話,磁盤的IO性能就會極大的影響ES的查詢效率,而ES的目的就是為了:快速的、准確的獲取到我們想要的數據,因此:降低數據查詢處理的延遲就very 重要了,而ES對這方面做了什么操作?
- 就是搞的一主多副的方式(一個主分片,多個副本分片),這雖然就是一句話概括了,但是:里面的門道卻不是那么簡單的
首先來看一下主副操作

但是:這種去找尋節點的過程想都想得到會造成延時,而延時 = 主分片延時 + 主分片拷貝數據給副本的延時
而且並不是這樣就算完了,前面提到了N多次的分段、刷新到磁盤還沒上堂呢,所以接着看

但是:在flush到磁盤中的時候,萬一斷電了呢?或者其他原因導致出問題了,那最后數據不就沒有flush到磁盤嗎
因此:其實還有一步操作,把數據保存到另外一個文件中去

數據放到磁盤中之后,translog中的數據就會清空
同時更新到磁盤之后,用戶就可以進行搜索數據了
注意:這里要區分一下,數據庫中是先更新到log中,然后再更新到內存中,而ES是反着的,是先更新到Segment( 可以直接認為是內存,因它本身就在內存中 ),再更新到log中
可是啊,還是有問題,flush刷寫到磁盤是很耗性能的,假如:不斷進行更新呢?這樣不斷進行IO操作,性能好嗎?也不行,因此:繼續改造(沒有什么是加一層解決不了的,一層不夠,那就再來一層)

加入了緩存之后,這緩存里面的數據是可以直接用來搜索的,這樣就不用等到flush到磁盤之后,才可以搜索了,這大大的提高了性能,而flush到磁盤,只要時間到了,讓它自個兒慢慢flush就可以了,上面這個流程也叫:持久化 / 持久化變更
寫入和打開一個新段的輕量的過程叫做refresh。默認情況下每個分片會每秒自動刷新一次。這就是為什么我們說 ES是近實時搜索:文檔的變化並不是立即對搜索可見,但會在一秒之內變為可見
刷新是1s以內完成的,這是有時間間隙的,所以會造成:搜索一個文檔時,可能並沒有搜索到,因此:解決辦法就是使用refresh API刷新一下即可
但是這樣也伴隨一個問題:雖然這種從內存刷新到緩存中看起來不錯,但是還是有性能開銷的。並不是所有的情況都需要refresh的, 假如:是在索引日志文件呢?去refresh干嘛,浪費性能而已,所以此時:你要的是查詢速度,而不是近實時搜索,因此:可以通過一個配置來進行改動,從而降低每個索引的刷新頻率
http://ip:port/index_name/_settings // 請求方式:put
// 請求體內容
{
"settings": {
"refresh_interval": "60s"
}
}
refresh_interval 可以在既存索引上進行動態更新。在生產環境中,當你正在建立一個大的新索引時,可以先關閉自動刷新,待開始使用該索引時,再把它們調回來。雖然有點麻煩,但是按照ES這個玩意兒來說,確實需要這么做比較好
// 關閉自動刷新
http://ip:port/users/_settings // 請求方式:put
// 請求體內容
{
"refresh_interval": -1
}
// 每一秒刷新
http://ip:port/users/_settings // 請求方式:put
// 請求體內容
{
"refresh_interval": "1s"
}
另外:不斷進行更新就會導致很多的段出現(在內存刷寫到磁盤那里,會造成很多的磁盤文件),因此:在哪里利用了文檔合並的功能(也就是段的能力,合並文檔,從而讓刷寫到磁盤中的文檔變成一份)
文檔分析
試想:我們在瀏覽器中,輸入一條信息,如:搜索“博客園紫邪情”,為什么連“博客園也搜索出來了?我要的是不是這個結果澀”

這就是全文檢索,就是ES干的事情( 過濾數據、檢索嘛 ),但是:它做了哪些操作呢?
在ES中有一個文檔分析的過程,文檔分析的過程也很簡單:
- 將文本拆成適合於倒排索引的獨立的詞條,然后把這些詞條統一變為一個標准格式,從而使文本具有“可搜索性”。 而這個文檔分析的過程在ES是由一個叫做“分析器 analyzer”的東西來做的,這個分析器里面做了三個步驟
- 字符過濾器:就是用來處理一些字符的嘛,像什么將 & 變為 and 啊、去掉HTML元素啊之類的。它是文本字符串在經過分詞之前的一個步驟,文本字符串是按文本順序經過每個字符串過濾器從而處理字符串
- 分詞器:見名知意,就是用來分詞的,也就是將字符串拆分成詞條( 字 / 詞組 ),這一步和Java中String的split()一樣的,通過指定的要求,把內容進行拆分,如:空格、標點符號
- Token過濾器:這個玩意兒的作用就是 詞條經過每個Token過濾器,從而對數據再次進行篩選,如:字母大寫變小寫、去掉一些不重要的詞條內容、添加一些詞條( 如:同義詞 )
上述的內容不理解沒事,待會兒會用IK中文分詞器來演示,從而能夠更直觀的看到效果
在ES中,有提供好的內置分析器、我們也可以自定義、當然還有就是前面說的IK分詞器也可以做到。而這里重點需要了解的就是IK中文分詞器
在演示在前,先玩kibana吧,原本打算放在后面的,但是越早熟悉越好嘛,所以先把kibana說明了
kibana
1、去Elastic 官網 下載kibana。 但是需要注意:kibana的版本必須和ES的版本一致
下載好了kibana之后,解壓到自己想要的目錄( 注:加壓會有點久,因為是用Vue寫的,里面有模塊module 要是加壓快的話,可能還下錯了 ),然后點擊bin/kibana.bat即可啟動kibana、第一次進去會有一個選擇頁面,add … / explore,選擇explore就可以了,進去之后就是如下界面:

這是英文版,要是沒玩過大數據的話,那么里面的一些專業名詞根據英文來看根本不知道
所以:漢化吧。 kibana本身就提供得有漢化的功能,只需要改動一個配置即可。就是一個i1bn配置而已
進入config/kibana.yml,刷到最底部

加上上面的信息,然后重啟kibana就可以了
但是:個人建議,先漢化一段時間,等熟悉哪些名詞了,然后再轉成英文 ,總之最后建議用英文,一是增加英文詞匯量,二是熟悉英文專業詞
kibana遵循的是rest風格( get、put、delete、post..... ),具體用法接下來玩分析器和后面都會慢慢熟悉
內置分析器
標准分析器 standard
這是根據Unicode定義的單詞邊界來划分文本,將字母轉成小寫,去掉大部分的標點符號,從而得到的各種語言的最常用文本選擇,另外:這是ES的默認分析器。 接下來演示一下
1、啟動ES和kibana,打開控制台

2、編寫指令

GET _analyze
{
"analyzer": "standard", // analyzer 分析器 standard 標准分析器
"text": "my name is ZiXieQing" // text 文本標識 my name is ZiXieQing 自定義的文本內容
}
// 響應內容
{
"tokens" : [
{
"token" : "my", // 分詞之后的詞條
"start_offset" : 0,
"end_offset" : 2, // start和end叫偏移量
"type" : "<ALPHANUM>",
"position" : 0 // 當前詞條在整個文本中所處的位置
},
{
"token" : "name",
"start_offset" : 3,
"end_offset" : 7,
"type" : "<ALPHANUM>",
"position" : 1
},
{
"token" : "is",
"start_offset" : 8,
"end_offset" : 10,
"type" : "<ALPHANUM>",
"position" : 2
},
{
"token" : "zixieqing",
"start_offset" : 11,
"end_offset" : 20,
"type" : "<ALPHANUM>",
"position" : 3
}
]
}

從上圖可以看出:所謂標准分析器是將文本通過標點符號來分詞的( 空格、逗號... ,不信可以自行利用這些標點測試一下,觀察右邊分詞的結果 ),同時大寫轉小寫
簡單分析器 simple
簡單分析器是“按非字母的字符分詞,例如:數字、標點符號、特殊字符等,會去掉非字母的詞,大寫字母統一轉換成小寫”

空格分析器 whitespace
是簡單按照空格進行分詞,相當於按照空格split了一下,大寫字母不會轉換成小寫

去詞分析器 stop
會去掉無意義的詞(此無意義是指語氣助詞等修飾性詞,補語文:語氣詞是疑問語氣、祈使語氣、感嘆語氣、肯定語氣和停頓語氣),例如:the、a、an 、this等,大寫字母統一轉換成小寫

不拆分分析器 keyword
就是將整個文本當作一個詞

IK中文分詞器
來個實驗:

它把我的名字進行拆分了,這不是我想要的,我想要的“紫邪情”應該是一個完整的詞,同樣道理:想要特定的詞匯,如:ID號、用戶名....,這些不應該拆分,而ES內置分析器並不能做到,所以需要IK中文分詞器(專門用來處理中文的 )
1、下載IK分詞器: https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.8.0
注意:版本對應關系, 還是和ES版本對應,https://github.com/medcl/elasticsearch-analysis-ik 這個鏈接進去之后有詳細的版本對應

2、把IK解壓到ES/plugins中去。 如我的:

3、重啟ES即可。 kibana開着的話,也要關了重啟 ,注意觀察:重啟時會有一個IK加載過程

經過如上的操作之后,IK中文分詞器就配置成功了,接下來就來體驗一下( 啟動ES和kibana ),主要是為了了解IK中的另外兩種分詞方式:ik_max_word 和 ik_smart
-
ik_max_word 是細粒度的分詞,就是:窮盡詞匯的各種組成。 4個字是一個詞,繼續看3個字是不是一個詞,再看2個字又是不是一個詞,以此窮盡..........

-
ik_smart 是粗粒度的分詞。 如:那個叼毛也是一個程序員,就先看整句話是不是一個詞(length = 11),不是的話,就看length-1是不是一個詞.....,如果某個長度是一個詞了,那么這長度內的內容就不看了,繼續看其他的是不是一個詞,如“那個"是一個詞,那就看后面的內容,繼續length、length-1、length-2........

回到前面的問題,“紫邪情”是名字,我不想讓它分詞,怎么做?上面哪些分詞都是在一個“詞典”中,所以我們自己搞一個詞典即可
1、創建一個.dic文件 dic就是dictionary詞典的簡寫

2、在創建的dic文件中添加不分詞的詞組,保存

3、把自定義的詞典放到ik中去,保存

4、重啟ES和kibana
5、測試

可見,現在就把“紫邪情”組成詞組不拆分了,前面玩的kibana漢化是怎么做的?和這個的原理差不多
多玩幾次kibana
在第一篇高級篇中我邊說過:kibana重要,只是經過前面這些介紹了使用之后,並不算熟悉,因此:多玩幾次吧
另外:就是前面說的kibana遵循rest風格,在ES中是怎么玩的?總結下來其實就下面這些,要上手簡單得很,但理論卻是一直弄到現在

現在用kibana來演示幾個,其他在postman中怎么弄,換一下即可( 其實不建議用postman測試,專業的人做專業的事,kibana才是我們后端玩的 )
1、創建索引

2、查看索引

3、創建文檔( 隨機id值,想要自定義id值,在后面加上即可 )
4、刪除索引

5、創建文檔( 自定義id )

6、查看文檔( 通過id查詢 )

7、修改文檔( 局部修改 )

驗證一下:

8、建字段類型

其他的也是差不多的玩法,在基礎篇中怎么玩,稍微變一下就是kibana的玩法了
拼音分詞器/自定義分析器
官網:https://github.com/medcl/elasticsearch-analysis-pinyin
安裝和IK分詞器一樣
- 下載
- 上傳解壓
- 重啟es
測試拼音分詞器

由上可知,伴隨2個問題:
- 只進行了拼音分詞,漢字分詞不見了
- 只采用拼音分詞會出現一種情況:同音字,如“獅子”,“虱子”,這樣的話明明想搜索的是“獅子”,結果“虱子”也出來了,所以這種搜索效果不好
因此:需要定制,讓漢字分詞出現,同時搜索時使用的漢字是什么就是什么,別弄同音字
要完成上面的需求,就需要結合前面的文檔分析的過程
在ES中有一個文檔分析的過程,文檔分析的過程也很簡單:
- 將文本拆成適合於倒排索引的獨立的詞條,然后把這些詞條統一變為一個標准格式,從而使文本具有“可搜索性”。 而這個文檔分析的過程在ES是由一個叫做“分析器 analyzer”的東西來做的,這個分析器里面做了三個步驟
- 字符過濾器(character filters):就是用來處理一些字符的嘛,像什么將 & 變為 and 啊、去掉HTML元素啊之類的。它是文本字符串在經過分詞之前的一個步驟,文本字符串是按文本順序經過每個字符串過濾器從而處理字符串
- 分詞器(tokenizer):見名知意,就是用來分詞的,也就是將字符串拆分成詞條( 字 / 詞組 ),這一步和Java中String的split()一樣的,通過指定的要求,把內容進行拆分,如:空格、標點符號
- Token過濾器(tokenizer filter):這個玩意兒的作用就是 詞條經過每個Token過濾器,從而對數據再次進行篩選,如:字母大寫變小寫、去掉一些不重要的詞條內容、添加一些詞條( 如:同義詞 )
舉例理解:character filters、tokenizer、tokenizer filter)

因此現在自定義分詞器就變成如下的樣子:
注: 是建立索引時自定義分詞器,即自定義的分詞器只對當前索引庫有效
PUT /test
{
"settings": {
"analysis": {
"analyzer": { // 自定義分詞器
"my_analyzer": { // 分詞器名稱
"tokenizer": "ik_max_word",
"filter": "py"
}
},
"filter": { // 自定義tokenizer filter
"py": { // 過濾器名稱
"type": "pinyin", // 過濾器類型,這里是pinyin,這些參數都在 拼音分詞器官網有
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "my_analyzer", // 指明在索引時使用的分詞器
"search_analyzer": "ik_smart" // 指明搜索時使用的分詞器
}
}
}
}
使用自定義分詞器:

文檔控制-了解
所謂的文檔控制就是:不斷更新的情況,試想:多進程不斷去更新文檔,會造成什么情況?會把其他人更新過的文檔進行覆蓋更新了,而ES是怎么解決這個問題的?
就是弄了一個鎖來實現的,和Redis一樣,也是用的樂觀鎖來實現的,這個其實沒什么好說的,只需要看一下就知道了

上圖中·的三個字段就和鎖掛鈎的,version,版本號嘛,每次更新都會有一個版本號,這樣就解決了多進程修改從而造成的文檔沖突了( 必須等到一個進程更新完了,另一個進程才可以更新 ),當然:需要注意舊版本的ES在請求中加上version即可,但是新版本的ES需要使用 if"_seq_no" 和"if_primary_term" 來達到version的效果
ES的優化
ES的所有索引和文檔數據都是存儲在本地的磁盤中的,所以:磁盤能處理的吞吐量越大,節點就越穩定
要修改的話,是在config/elasticsearch.yml中改動

硬件方面
1、選用固態硬盤( 即:SSD ),它比機械硬盤的好是因為:機械硬盤是通過旋轉馬達的驅動來進行的,所以這就會造成發熱、磨損,就會影響ES的效率,而SSD是使用芯片式的閃存來存儲數據的,性能比機械硬盤好得多
2、使用RAID 0 ( 獨立磁盤冗余陣列 ),它是把連續的數據分散到多個磁盤上存取,這樣,系統有數據請求就可以被多個磁盤並行的執行,每個磁盤執行屬於它自己的那部分數據請求。這種數據上的並行操作可以充分利用總線的帶寬,顯著提高磁盤整體存取性能

3、由上面的RAID 0可以聯想到另外一個解決方式:使用多塊硬盤,也就可以達到同樣的效果了( 有錢就行 ),是通過path data目錄配置把數據條分配到這些磁盤上面
4、不要把ES掛載到遠程上去存儲
分片策略
分片和副本不是亂分配的!分片處在不同節點還可以( 前提是節點中存的數據多 ),這樣就類似於關系型中分表,確實可以算得到優化,但是:如果一個節點中有多個分片了,那么就會分片之間的資源競爭,這就會導致性能降低
所以分片和副本遵循下面的原則就可以了
- 每個分片占用的磁盤容量不得超過ES的JVM的堆空間設置( 一般最大為32G ),假如:索引容量為1024G,那么節點數量為:1024 / 32 = 32左右
- 分片數不超過節點數的3倍,就是為了預防一個節點上有多個分片的情況,萬一當前節點死了,那么就算做了副本,也很容易導致集群丟失數據
- 節點數 <= 主節點數 * ( 副本數 + 1 )
- 推遲分片分配。有可能一個節點宕掉了,但是后面它又恢復了,而這個節點原有數據是還在的,所以:推遲分片分配,從而減少ES的開銷,具體做法如下:
PUT /_all/_settings
{
"settings": {
"index.unassigned.node_left.delayed_timeout": "5m"
}
}

可以全局修改,也可以在建索引時修改
帶路由查詢
前面說過:路由計算公式 shard = hash( routing ) % number_of_primary_shards
而routing默認值就是文檔id,所以查詢時把文檔id帶上,如:前面玩kibana做的操作

不帶路由就會把分片和副本都查出來,然后進行輪詢,這效率想都想得到會慢一點嘛
內存優化
修改es的config/jvm.options

把上面的數字改了
Xms 表示堆的初始大小, Xmx 表示可分配的最大內存,ES默認是1G,這個數字在現實中是遠遠不夠了,改它的目的是:為了能夠在 Java 垃圾回收機制清理完堆內存后不需要重新分隔計算堆內存的大小而浪費資源,可以減輕伸縮堆大小帶來的壓力,但是也需要注意:改這兩個數值,需要確保 Xmx 和 Xms 的大小是相同的,另外就是:這兩個數值別操作32G啊,前面已經講過了
附上一些配置說明
| 參數名 | 參數值 | 說明 |
|---|---|---|
| cluster.name | elasticsearch | 配置 ES 的集群名稱,默認值是 ES,建議改成與所存數據相關的名稱, ES 會自動發現在同一網段下的 集群名稱相同的節點 |
| node.name | node-1001 | 集群中的節點名,在同一個集群中不能重復。節點 的名稱一旦設置,就不能再改變了。當然,也可以 設 置 成 服 務 器 的 主 機 名 稱 , 例 如 node.name: $ |
| node.master | true | 指定該節點是否有資格被選舉成為 Master 節點,默 認是 True,如果被設置為 True,則只是有資格成為 Master 節點,具體能否成為 Master 節點,需要通過選舉產生 |
| node.data | true | 指定該節點是否存儲索引數據,默認為 True。數據的增、刪、改、查都是在 Data 節點完成的 |
| index.number_of_shards | 1 | 設置索引分片個數,默認是 1 片。也可以在創建索引時設置該值,具體設置為多大值要根據數據量的大小來定。如果數據量不大,則設置成 1 時效率最高 |
| index.number_of_replicas | 1 | 設置默認的索引副本個數,默認為 1 個。副本數越多,集群的可用性越好,但是寫索引時需要同步的數據越多 |
| transport.tcp.compress | true | 設置在節點間傳輸數據時是否壓縮,默認為 False |
| discovery.zen.minimum_master_nodes | 1 | 設置在選舉 Master 節點時需要參與的最少的候選主節點數,默認為 1。如果使用默認值,則當網絡不穩定時有可能會出現腦裂。 合理的 數 值 為 ( master_eligible_nodes / 2 )+1 , 其 中 master_eligible_nodes 表示集群中的候選主節點數 |
| discovery.zen.ping.timeout | 3s | 設置在集群中自動發現其他節點時 Ping 連接的超時時間,同時也是選主節點的延遲時間,默認為 3 秒。 在較差的網絡環境下需要設置得大一點,防止因誤判該節點的存活狀態而導致分片的轉移 |
說一些另外的理論吧
ES的master主節點選舉流程
- 首先選主是由ZenDiscovery來完成的。ZenDiscovery做了兩件事:
- 一個是Ping過程,發現節點嘛
- 二是Unicast過程,控制哪些節點需要Ping通
- 對所有可以成為master的節點(文件中設置的node.master: true)根據nodeId字典排序,每次“選舉節點(即:參與投票選舉主節點的那個節點)”都把自己知道的節點排一次序,就是把排好序的第一個節點(第0位)認為是主節點(投一票)
- 當某個節點的投票數達到一個值時,此值為 (可以成為master節點數n / 2 ) + 1,而該節點也投自己,那么這個節點就是master節點,否則重新開始,直到選出master
另外注意: master節點的職責主要包括集群、節點和索引的管理,不負責文檔級別的管理;data節點可以關閉http功能
ES中的節點職責如下:
| 節點類型 | 配置參數 | 默認值 | 節點職責 |
|---|---|---|---|
| master eligible | node.master | true | 備選主節點:主節點可以管理和記錄集群狀態、決定分片在哪個節點、處理創建和刪除索引庫的請求 |
| data | node.data | true | 數據節點:存儲數據、搜索、聚合、CRUD |
| ingest | node.ingest | true | 數據存儲之前的預處理 但:若是已經使用Java代碼進行了預處理,那么此配置就無效了 |
| coordinating | 上面3個參數都為false則為coordinating節點 | 無 | 協調節點,路由請求到其它節點合並其它節點處理的結果,返回給用戶 |
默認情況下,集群中的任何一個節點都同時具備上述四種角色
但是真實的集群一定要將集群職責分離:
- master節點:對CPU要求高,但是內存要求第
- data節點:對CPU和內存要求都高
- coordinating節點:對網絡帶寬、CPU要求高
職責分離可以讓我們根據不同節點的需求分配不同的硬件去部署。而且避免業務之間的互相干擾
ES的集群腦裂問題
所謂的腦裂一句話來概括就是老大(master節點)“沒了”,然后小弟(有資格成為主節點的節點)重新選舉出老大,結果最后舊老大回來了,從而造成新舊老大整合的數據不一樣,最后就攤上事兒了
導致的原因:
- 網絡問題:集群間的網絡延遲導致一些候選主節點(文件中設置的node.master: true,即:可以成為主節點的節點)訪問不到master, 認為master 掛掉了從而選舉出新的master,並對master上的分片和副本標紅,分配新的主分片
- 節點負載:主節點的角色既為master又為data,訪問量較大時可能會導致ES停止響應造成大面積延遲,此時其他節點得不到主節點的響應認為主節點掛掉了,會重新選取主節點
- 內存回收:data 節點上的ES進程占用的內存較大,引發JVM的大規模內存回收,造成ES進程失去響應
腦裂問題解決方案:
-
減少誤判:discovery.zen ping_ timeout 節點狀態的響應時間,默認為3s。可以適當調大,如果master在該響應時間的范圍內沒有做出響應應答,判斷該節點已經掛掉了。調大參數( 如6s,discovery.zen.ping_timeout:6 ),可適當減少誤判
-
選舉觸發:discovery.zen.minimum_master_nodes:1,該參數是用於控制選舉行為發生的最小集群主節點數量。當備選主節點的個數大於等於該參數的值,且備選主節點中有該參數個節點認為主節點掛了,進行選舉。官方建議為(n / 2) +1,n為有資格成為主節點的節點個數)
- 多提一嘴:為了避免腦裂,要求選票超過 (n+1) / 2 才能當選為主,n為有資格成為主節點的節點個數(即:候選主節點個數)。因此n候選主節點數最好是奇數,對應配置就是上面的 discovery.zen.minimum_master_nodes 。當然,在es7.0以后,已經成為默認配置,es會自動去計算候選主節點的數量,從而進行配置,所以一般不會發生腦裂
-
角色分離:即master節點與data節點分離,限制角色
-
主節點配置為:node master: true,node data: false
-
從節點置為:node master: false,node data: true
-
