1 Data Replication 簡(jiǎn)介
作為一個(gè)分布式數(shù)據(jù)系統(tǒng),Elasticsearch/OpenSearch 也有主副本( Primary Shard )和副本( Replica Shard )的概念。
Replica 是 Primary 的 Clone ( 復(fù)制體 ),這樣就可以實(shí)現(xiàn)系統(tǒng)的高可用,具備容災(zāi)能力。
Replica 從 Primary 復(fù)制數(shù)據(jù)的過程被成為數(shù)據(jù)復(fù)制( Data Replication ),Data Replication 的核心考量指標(biāo)是 Replica 和 Primary 的延遲( Lag )大小,如果 Lag 一直為0,那么就是實(shí)時(shí)復(fù)制,可靠性最高。
Data Replication 的方案有很多,接下來主要介紹基于文檔的復(fù)制方案( Document Replication ) 和基于文件的復(fù)制方案 ( Segment Replication )。
1.1 Document Replication
Elasticsearch/OpenSearch 目前采用的是基于文檔的復(fù)制方案,整個(gè)過程如下圖所示:
- Client 發(fā)送寫請(qǐng)求到 Primary Shard Node
- Primary Shard Node 將相關(guān)文檔先寫入本地的 translog,按需進(jìn)行 refresh
- 上述步驟執(zhí)行成功后,Primary Shard Node 轉(zhuǎn)發(fā)寫請(qǐng)求到 Replica Shard Nodes,此處轉(zhuǎn)發(fā)的內(nèi)容是實(shí)際的文檔
- Replica Shard Node 接收到寫請(qǐng)求后,先寫入本地的 translog,按需進(jìn)行 refresh,返回 Primary Shard Node 執(zhí)行成功
- Primary Shard Node 返回 Client 寫成功。
- 后續(xù) Primary Shard Node 和 Replica Shard Node 會(huì)按照各自的配置獨(dú)立進(jìn)行 refresh 行為,生成各自的 segment 文件。
這里要注意的一點(diǎn)是:Primary Shard 和 Replica Shard 的 refresh 是獨(dú)立的任務(wù),執(zhí)行時(shí)機(jī)和時(shí)間會(huì)有所差異,這也會(huì)導(dǎo)致兩邊實(shí)際生成和使用的 segment 文件有差異。
以上便是 Document Replication 的簡(jiǎn)易流程,對(duì)完整流程感興趣的,可以通過下面的連接查看更詳細(xì)的介紹。
1.2 Segment Replication
elasticsearch 數(shù)據(jù)寫入最耗時(shí)的部分是生成 segment 文件的過程,因?yàn)檫@里涉及到分詞、字典生成等等步驟,需要很多 CPU 和 Memory 資源。
而 Document Replication 方案需要在 Primary Node 和 Replica Nodes 上都執(zhí)行 segment 文件的生成步驟,但是在 Replica Nodes 上的執(zhí)行實(shí)際是一次浪費(fèi),如果可以避免這次運(yùn)算,將節(jié)省不少 CPU 和 Memory 資源。
解決的方法也很簡(jiǎn)單,等 Primary Node 運(yùn)行完畢后,直接將生成的 segment 文件復(fù)制到 Replica Nodes 就好了。這種方案就是 Segment Replication。
Segment Replication 的大致流程如下圖所示:
- Client 發(fā)送寫請(qǐng)求到 Primary Shard Node
- Primary Shard Node 將相關(guān)文檔先寫入本地的 translog,按需和相關(guān)配置進(jìn)行 refresh,此處不是一定觸發(fā) refresh
- 上述步驟執(zhí)行成功后,Primary Shard Node 轉(zhuǎn)發(fā)寫請(qǐng)求到 Replica Shard Nodes,此處轉(zhuǎn)發(fā)的內(nèi)容是實(shí)際的文檔
- Replica Shard Node 接收到寫請(qǐng)求后,寫入本地的 translog,然后返回 Primary Shard Node 執(zhí)行成功
- Primary Shard Node 返回 Client 寫成功。
- Primary Shard Node 在觸發(fā) refresh 后,會(huì)通知 Replica Shard Nodes 同步新的 segment 文件。
- Replica Shard Nodes 會(huì)對(duì)比本地和 Primary Shard Node 上的 segment 文件列表差異,然后請(qǐng)求同步本地缺失和發(fā)生變更的 segment 文件。
- Primary Shard Node 根據(jù) Replica Shard Nodes 的相關(guān)請(qǐng)求完成 segment 文件的發(fā)送
- Replica Shard Nodes 在完整接收 segment 文件后,刷新 Lucene 的 DirectoryReader 載入最新的文件,使新文檔可以被查詢
這里和 Document Replication 最大的不同是 Replica Shard Nodes 不會(huì)在獨(dú)立生成 segment 文件,而是直接從 Primary Shard Node 同步,本地的 translog 只是為了實(shí)現(xiàn)數(shù)據(jù)的可靠性,在 segment 文件同步過來后,就可以刪除。
以上便是 Segment Replication 的簡(jiǎn)易流程,對(duì)完整流程感興趣的,可以通過下面的連接查看更詳細(xì)的介紹。
2 Segment Replication 初體驗(yàn)
OpenSearch 在 2.3 版本中發(fā)布了實(shí)驗(yàn)版本的 Segment Replication 功能,接下來就讓我們一起體驗(yàn)一下吧~
2.1 準(zhǔn)備 docker 環(huán)境和相關(guān)文件
本次體驗(yàn)基于 docker-compose 來執(zhí)行,如下為相關(guān)內(nèi)容(docker-compose.yml
):
version: '3'
services:
opensearch-node1:
image: opensearchproject/opensearch:2.3.0
container_name: os23-node1
environment:
- cluster.name=opensearch-cluster
- node.name=opensearch-node1
- discovery.seed_hosts=opensearch-node1,opensearch-node2
- cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2
- bootstrap.memory_lock=true # along with the memlock settings below, disables swapping
- plugins.security.disabled=true
- "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m -Dopensearch.experimental.feature.replication_type.enabled=true" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536 # maximum number of open files for the OpenSearch user, set to at least 65536 on modern systems
hard: 65536
volumes:
- ./os23data1:/usr/share/opensearch/data
ports:
- 9200:9200
- 9600:9600 # required for Performance Analyzer
networks:
- opensearch-net
opensearch-node2:
image: opensearchproject/opensearch:2.3.0
container_name: os23-node2
environment:
- cluster.name=opensearch-cluster
- node.name=opensearch-node2
- discovery.seed_hosts=opensearch-node1,opensearch-node2
- cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2
- bootstrap.memory_lock=true
- plugins.security.disabled=true
- "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m -Dopensearch.experimental.feature.replication_type.enabled=true"
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
volumes:
- ./os23data2:/usr/share/opensearch/data
networks:
- opensearch-net
opensearch-dashboards:
image: opensearchproject/opensearch-dashboards:2.3.0
container_name: os23-dashboards
ports:
- 5601:5601
expose:
- "5601"
environment:
OPENSEARCH_HOSTS: '["http://opensearch-node1:9200","http://opensearch-node2:9200"]'
DISABLE_SECURITY_DASHBOARDS_PLUGIN: "true"
networks:
- opensearch-net
networks:
opensearch-net:
簡(jiǎn)單說明如下:
- 為了演示方便,關(guān)閉了安全特性
- 要在
OPENSEARCH_JAVA_OPTS
中添加-Dopensearch.experimental.feature.replication_type.enabled=true
才能開啟segment replication 功能
2.2 運(yùn)行 OpenSearch 集群
執(zhí)行如下命令運(yùn)行 OpenSearch Cluster:
docker-compose -f docker-compose.yml up
運(yùn)行成功后,可以訪問 http://127.0.0.1:5601 打開 Dashboards 界面,進(jìn)入 Dev Tools 中執(zhí)行后續(xù)的操作
2.3 測(cè)試 Segment Replication
測(cè)試思路如下:
- 創(chuàng)建兩個(gè) index,一個(gè)默認(rèn)配置,一個(gè)啟用 segment replication,主分片數(shù)為1,副本數(shù)為1
- 向兩個(gè) index 中插入若干條數(shù)據(jù)
- 比較兩個(gè) index 中 segment file 的數(shù)量和大小
相關(guān)命令如下:
PUT /test-rep-by-doc
{
"settings": {
"index": {
"number_of_shards": 1,
"number_of_replicas": 1
}
}
}
GET test-rep-by-doc/_settings
POST test-rep-by-doc/_doc
{
"name": "rep by doc"
}
GET _cat/shards/test-rep-by-doc?v
GET _cat/segments/test-rep-by-doc?v&h=index,shard,prirep,segment,generation,docs.count,docs.deleted,size&s=index,segment,prirep
PUT /test-rep-by-seg
{
"settings": {
"index": {
"replication.type": "SEGMENT",
"number_of_shards": 1,
"number_of_replicas": 1
}
}
}
GET test-rep-by-seg/_settings
POST test-rep-by-seg/_doc
{
"name": "rep by seg"
}
GET _cat/shards/test-rep-by-seg
GET _cat/segments/test-rep-by-seg?v&h=index,shard,prirep,segment,generation,docs.count,docs.deleted,size&s=index,segment,prirep
插入文檔后,通過 _cat/segments
可以得到 segment file 列表,然后通過 size 一列可以對(duì)比 segment 文件大小。
如下是默認(rèn)基于文檔復(fù)制的結(jié)果:
index shard prirep segment generation docs.count docs.deleted size
test-rep-by-doc 0 p _0 0 2 0 3.7kb
test-rep-by-doc 0 r _0 0 1 0 3.6kb
test-rep-by-doc 0 p _1 1 2 0 3.7kb
test-rep-by-doc 0 r _1 1 3 0 3.8kb
test-rep-by-doc 0 p _2 2 1 0 3.6kb
test-rep-by-doc 0 r _2 2 3 0 3.8kb
test-rep-by-doc 0 p _3 3 6 0 3.9kb
test-rep-by-doc 0 r _3 3 6 0 3.9kb
test-rep-by-doc 0 p _4 4 5 0 3.9kb
test-rep-by-doc 0 r _4 4 6 0 3.9kb
test-rep-by-doc 0 p _5 5 6 0 3.9kb
test-rep-by-doc 0 r _5 5 6 0 3.9kb
test-rep-by-doc 0 p _6 6 4 0 3.8kb
test-rep-by-doc 0 r _6 6 1 0 3.6kb
從中可以看到,雖然 Primary Shard 和 Replica Shard 的 segment 數(shù)相同,但是 size 大小是不同的,這也說明其底層的 segment 文件是獨(dú)立管理的。
如下是基于 Segment 復(fù)制的結(jié)果:
index shard prirep segment generation docs.count docs.deleted size
test-rep-by-seg 0 p _0 0 2 0 3.7kb
test-rep-by-seg 0 r _0 0 2 0 3.7kb
test-rep-by-seg 0 p _1 1 7 0 4kb
test-rep-by-seg 0 r _1 1 7 0 4kb
test-rep-by-seg 0 p _2 2 5 0 3.9kb
test-rep-by-seg 0 r _2 2 5 0 3.9kb
從中可以看到 Primary Shard 和 Replica Shard 的 segment 完全一致。
除此之外也可以從磁盤文件中對(duì)比,同樣可以得出相同的結(jié)論:Segment Replication 是基于文件的數(shù)據(jù)復(fù)制方案,Primary 和 Replica 的 segment 文件列表完全相同。
3 總結(jié)
根據(jù) OpenSearch 社區(qū)的初步測(cè)試,Segment Replication 相較于 Document Replication 的性能結(jié)果如下:
- 在 Replica Nodes 上,CPU 和 Memory 資源減少 40%~50%
- 寫入性能方面,整體吞吐量提升約 50%,P99 延遲下降了 20% 左右
這個(gè)測(cè)試結(jié)果還是很誘人的,但 Segment Replication 也有其自身的局限,下面簡(jiǎn)單列幾點(diǎn)( 不一定準(zhǔn)確 ):
- Segment Replication 對(duì)于網(wǎng)絡(luò)帶寬資源要求更高,目前測(cè)試中發(fā)現(xiàn)有近1倍的增長(zhǎng),需要更合理的分配 Primary Shard 到不同的 Node 上,以分散網(wǎng)絡(luò)帶寬壓力
- Segment Replication 可能會(huì)由于文件傳輸?shù)难舆t而導(dǎo)致 Replica Shard 上可搜索的文檔短時(shí)間內(nèi)與 Primary Shard 不一致
- Replica Shard 升級(jí)為 Primary Shard 的時(shí)間可能會(huì)因?yàn)橹胤?translog 文件而變長(zhǎng),導(dǎo)致 Cluster 不穩(wěn)定
友情提示下,由于該特性目前還是實(shí)驗(yàn)階段,還不具備上生產(chǎn)環(huán)境的能力,大家可以持續(xù)關(guān)注~