ElasticSearch 集群架构
核心概念
搭建三节点ES集群
ES安全认证
生产环境常见集群部署方式
ES跨集群搜索(CCS)
分片的设计和管理
ES底层读写工作原理
如何提升集群的读写性能
高可用性
可扩展性
ES集群架构的优势:
- 提高系统的可用性,部分节点停止服务,整个集群的服务不受影响
- 存储的水平扩容
一个集群可以有一个或者多个节点
不同的集群通过不同的名字来区分,默认名字“elasticsearch“
通过配置文件修改,或者在命令行中-E cluster.name=es-cluster
进行设定
节点是一个Elasticsearch的实例
本质上就是一个JAVA进程
一台机器上可以运行多个Elasticsearch进程,但是生产环境一般建议一台机器上只运行一个Elasticsearch实例
每一个节点都有名字,通过配置文件配置,或者启动时候-E node.name=node1
指定
每一个节点在启动之后,会分配一个UID,保存在data目录下
节点类型 | 配置参数 | 默认值 |
---|---|---|
master eligible | node.master | true |
data | node.data | true |
ingest | node.ingest | true |
coordinating only | 无 | true |
machine learning | node.ml | true(需要enable x-pack) |
node.master: false
禁止处理创建,删除索引等请求,负责索引的创建与删除
决定分片被分配到哪个节点
维护并且更新Cluster State
Master节点非常重要,在部署上需要考虑解决单点的问题
为一个集群设置多个Master节点,每个节点只承担Master的单一角色
互相Ping对方,Node ld低的会成为被选举的节点
其他节点会加入集群,但是不承担Master节点的角色。一旦发现被选中的主节点丢失,就会选举出新的Master节点
可以保存数据的节点,叫做Data Node,负责保存分片数据。在数据扩展上起到了至关重要的作用
节点启动后,默认就是数据节点。可以设置node.data: false
禁止
由Master Node决定如何把分片分发到数据节点上
通过增加数据节点可以解决数据水平扩展和解决数据单点问题
负责接受Client的请求,将请求分发到合适的节点,最终把结果汇集到一起
每个节点默认都起到了Coordinating Node的职责
后续不允许修改,除非Reindex
指定索引的主分片和副本分片数
PUT /blogs
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
}
}
分片的设定
对于生产环境中分片的设定,需要提前做好容量规划
查看集群的健康状况
GET _cluster/health
GET /_cat/nodes?v #查看节点信息
GET /_cat/health?v #查看集群当前状态:红、黄、绿
GET /_cat/shards?v #查看各shard的详细情况
GET /_cat/shards/{index}?v #查看指定分片的详细情况
GET /_cat/master?v #查看master节点信息
GET /_cat/indices?v #查看集群中所有index的详细信息
GET /_cat/indices/{index}?v #查看集群中指定index的详细信息
172.27.232.121 es-node1
172.27.232.120 es-node2
172.22.247.104 es-ndoe3
# 指定集群名称3个节点必须一致
cluster.name: es-cluster
#指定节点名称,每个节点名字唯一
node.name: node-1
#是否有资格为master节点,默认为true
node.master: true
#是否为data节点,默认为true
node.data: true
# 绑定ip,开启远程访问,可以配置0.0.0.0
network.host: 0.0.0.0
#指定web端口
http.port: 9200
#指定tcp端口
transport.tcp.port: 9300
#用于节点发现
discovery.seed_hosts: ["es-node1", "es-node2", "es-node3"]
#7.0新引入的配置项,初始仲裁,仅在整个集群首次启动时才需要初始仲裁。
#该选项配置为node.name的值,指定可以初始化集群节点的名称
cluster.initial_master_nodes: ["node-1","node-2","node-3"]
#解决跨域问题
http.cors.enabled: true
http.cors.allow-origin: "*"
Cerebro 可以查看分片分配和通过图形界面执行常见的索引操作。 完全开源,并且它允许添加用户,密码或 LDAP 身份验证问网络界面。
Cerebro 基于 Scala 的Play 框架编写,用于后端 REST 和 Elasticsearch 通信。 它使用通过 AngularJS 编写的单页应用程序(SPA)前端。
项目网址:https://github.com/lmenezes/cerebro
安装 Cerebro
下载地址:https://github.com/lmenezes/cerebro/releases/download/v0.9.4/cerebro-0.9.4.zip
运行 cerebro
cerebro-0.9.4/bin/cerebro
#后台启动
nohup bin/cerebro > cerebro.log &
访问:http://x.x.x.x:9000/
输入ES集群节点:http://192.168.65.192:9200,建立连接:
修改kibana配置
vim config/kibana.yml
server.port: 5601
server.host: "0.0.0.0"
elasticsearch.hosts: ["http://node-1:9200","http://node-2:9200","http://node-3:9200"]
i18n.locale: "zh-CN"
运行Kibana
提示:Kibana对外的 tcp 端口是5601,使用netstat -tunlp|grep 5601即可查看进程
#后台启动
nohup bin/kibana &
访问Kibana: http://x.x.x.x:5601/
参考文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.17/configuring-stack-security.html
ElasticSearch集群内部的数据是通过9300进行传输的,如果不对数据加密,可能会造成数据被抓包,敏感信息泄露
TLS 协议要求Trusted Certificate Authority (CA)签发x.509的证书。证书认证的不同级别:
# 为集群创建一个证书颁发机构
bin/elasticsearch-certutil ca
# 为集群中的每个节点生成证书和私钥
bin/elasticsearch-certutil cert --ca elastic-stack-ca.p12
# 移动到config目录下
mv *.p12 config/
将如上命令生成的两个证书文件拷贝到另外两个节点作为通信依据。
拷贝到x.x.x.x
scp *.p12 [email protected]:/home/es/elasticsearch-7.17.5/config
三个ES节点增加如下配置:
## elasticsearch.yml 配置
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.client_authentication: required
xpack.security.transport.ssl.keystore.path: elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: elastic-certificates.p12
xpack.security.enabled: true # 开启xpack认证机制
使用Curl访问ES,返回401错误
curl 'localhost:9200/_cat/nodes?pretty'
浏览器访问http://x.x.x.x:9200/
需要输入用户名密码
ES中内置了几个管理其他集成组件的账号即:apm_system, beats_system, elastic, kibana,
logstash_system, remote_monitoring_user,使用之前,首先需要添加一下密码。
bin/elasticsearch-setup-passwords interactive
curl -u elastic 'localhost:9200/_cat/nodes?pretty'
开启了安全认证之后,kibana连接es以及访问es都需要认证。
修改kibana.yml
elasticsearch.username: "kibana_system"
elasticsearch.password: "xxxxxxx"
启动kibana服务 nohup bin/kibana &
vim conf/application.conf
hosts = [
{
host = "http://x.x.x.x:9200"
name = "es-cluster"
auth = {
username = "elastic"
password = "xxxxxxxx"
}
}
]
启动cerebro服务 nohup bin/cerebro > cerebro.log &
不同角色的节点:Master eligible / Data / Ingest / Coordinating /Machine Learning在开发环境中,一个节点可承担多种角色。
在生产环境中:
一个节点只承担一个角色的配置
#Master节点
node.master: true
node.ingest: false
node.data: false
#data节点
node.master: false
node.ingest: false
node.data: true
#ingest 节点
node.master: false
node.ingest: true
node.data: false
#coordinate节点
node.master: false
node.ingest: false
node.data: false
这种单一角色职责分离的好处:
生产环境中,建议为一些大的集群配置Coordinating Only Nodes
单一 master eligible nodes, 从高可用&避免脑裂的角度出发:
这些都有可能影响Master节点,导致集群的不稳定
集群处在三个数据中心,数据三写,GTM分发读请求
全局流量管理(GTM)和负载均衡(SLB)的区别:
GTM 是通过DNS将域名解析到多个IP地址,不同用户访问不同的IP地址,来实现应用服务流量的分配。同时通过健康检查动态更新DNS解析IP列表,实现故障隔离以及故障切换。最终用户的访问直接连接服务的IP地址,并不通过GTM。而 SLB 是通过代理用户访问请求的形式将用户访问请求实时分发到不同的服务器,最终用户的访问流量必须要经过SLB。 一般来说,相同Region使用SLB进行负载均衡,不同region的多个SLB地址时,则可以使用GTM进行负载均衡
ES 跨集群复制 (Cross-Cluster Replication)是ES 6.7的的一个全局高可用特性。CCR允许不同的索引复制到一个或多个ES 集群中。
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/ccr-apis.html
为什么要设计Hot & Warm 架构?
ES数据通常不会有 Update操作;
适用于Time based索引数据,同时数据量比较大的场景。
引入 Warm节点,低配置大容量的机器存放老数据,以降低部署成本
两类数据节点,不同的硬件配置:
用于数据的写入:
indexing 对 CPU和IO都有很高的要求,所以需要使用高配置的机器
存储的性能要好,建议使用SSD
使用Shard Filtering实现Hot&Warm node间的数据迁移
node.attr来指定node属性:hot或是warm
在index的settings里通过index.routing.allocation
来指定索引(index)到一个满足要求的node
设置 | 分配索引到节点,节点的属性规则 |
---|---|
index.routing.allocation.include.{attr} | 至少包含一个值 |
index.routing.allocation.exclude.{attr} | 不能包含任何一个值 |
index.routing.allocation.require.{attr} | 所有值都需要包含 |
使用 Shard Filtering,步骤分为以下几步:
需要通过“node.attr”来标记一个节点
# 标记一个 Hot 节点
elasticsearch -E node.name=hotnode -E cluster.name=testESCluster \
-E http.port=9200 -E path.data=hot_data -E node.attr.my_node_type=hot
# 标记一个 warm 节点
elasticsearch -E node.name=warmnode -E cluster.name=testESCluster \
-E http.port=9201 -E path.data=warm_data -E node.attr.my_node_type=warm
# 查看节点
GET /_cat/nodeattrs?v
创建索引时候,指定将其创建在hot节点上
# 配置到 Hot节点
PUT /index-2022-05
{
"settings":{
"number_of_shards":2,
"number_of_replicas":0,
"index.routing.allocation.require.my_node_type":"hot"
}
}
POST /index-2022-05/_doc
{
"create_time":"2022-05-27"
}
#查看索引文档的分布
GET _cat/shards/index-2022-05?v
index.routing.allocation是一个索引级的dynamic setting,可以通过API在后期进行设定
# 配置到 warm 节点
PUT /index-2022-05/_settings
{
"index.routing.allocation.require.my_node_type":"warm"
}
GET _cat/shards/index-2022-05?v
一个集群总共需要多少个节点?一个索引需要设置几个分片?
规划上需要保持一定的余量,当负载出现波动,节点出现丢失时,还能正常运行。
31*16
= 496 G,加上预留空间。所以每个节点最多400G数据,至少需要5个数据节点31*50
= 1550 GB,2个数据节点即可特性:
估算索引的的数据量,然后确定分片的大小:
如果业务上有大量的查询是基于一个字段进行Filter,该字段又是一个数量有限的枚举值。
例如订单所在的地区。可以考虑以地区进行索引拆分
如果在单个索引有大量的数据,可以考虑将索引拆分成多个索引:
查询性能可以得到提高
如果要对多个索引进行查询,还是可以在查询中指定多个索引得以实现
如果业务上有大量的查询是基于一个字段进行Filter,该字段数值并不固定
可以启用Routing 功能,按照filter 字段的值分布到集群中不同的shard,降低查询时相关的shard数提高CPU利用率
es分片路由的规则:
shard_num = hash(_routing) % num_primary_shards
_routing字段的取值,默认是_id字段,可以自定义。
PUT /users
{
"settings": {
"number_of_shards":2
}
}
POST /users/_create/1?routing=fox
{
"name":"fox"
}
基于Date Math方式建立索引
比如:假设当前日期 2022-05-27
: indexName-2022.05.27
: indexName-2022.05
# PUT /<logs-{now/d}
PUT /%3Clogs-%7Bnow%2Fd%7D%3E
# POST /<logs-{now/d}>/_search
POST /%3Clogs-%7Bnow%2Fd%7D%3E/_search
基于Index Alias索引最新的数据
PUT /logs_2022-05-27
PUT /logs_2022-05-26
#可以每天晚上定时执行
POST /_aliases
{
"actions": [
{
"add": {
"index": "logs_2022-05-27",
"alias": "logs_write"
}
},
{
"remove": {
"index": "logs_2022-05-26",
"alias": "logs_write"
}
}
]
}
GET /logs_write
早期Tribe Node 的方案存在一定的问题,现已被弃用。Elasticsearch 5.3引入了跨集群搜索的功能(Cross Cluster Search),推荐使用
允许任何节点扮演联合节点,以轻量的方式,将搜索请求进行代理
不需要以Client Node的形式加入其他集群
//启动3个集群
elasticsearch.bat -E node.name=cluster0node -E cluster.name=cluster0 -E path.data=cluster0_data -E discovery.type=single-node -E http.port=9200 -E transport.port=9300
elasticsearch.bat -E node.name=cluster1node -E cluster.name=cluster1 -E path.data=cluster1_data -E discovery.type=single-node -E http.port=9201 -E transport.port=9301
elasticsearch.bat -E node.name=cluster2node -E cluster.name=cluster2 -E path.data=cluster2_data -E discovery.type=single-node -E http.port=9202 -E transport.port=9302
//在每个集群上设置动态的设置
PUT _cluster/settings
{
"persistent": {
"cluster": {
"remote": {
"cluster0": {
"seeds": [
"127.0.0.1:9300"
],
"transport.ping_schedule": "30s"
},
"cluster1": {
"seeds": [
"127.0.0.1:9301"
],
"transport.compress": true,
"skip_unavailable": true
},
"cluster2": {
"seeds": [
"127.0.0.1:9302"
]
}
}
}
}
}
#在不同集群上执行
# cluster0 localhost:9200
POST /users/_doc
{
"name":"test1",
"age":"30"
}
#cluster1 localhost:9201
POST /users/_doc
{
"name":"test2",
"age":"33"
}
#cluster2 localhost:9202
POST /users/_doc
{
"name":"test3",
"age":"35"
}
查询结果获取到所有集群符合要求的数据
GET /users,cluster1:users,cluster2:users/_search
{
"query": {
"range": {
"age": {
"gte": 30,
"lte": 40
}
}
}
}
7.0开始,新创建一个索引时,默认只有一个主分片。单个分片,查询算分,聚合不准的问题都可以得以避免
单个索引,单个分片时候,集群无法实现水平扩展。即使增加新的节点,无法实现水平扩展
集群增加一个节点后,Elasticsearch 会自动进行分片的移动,也叫 Shard Rebalancing
相关性算分在分片之间是相互独立的,每个分片都基于自己的分片上的数据进行相关度计算。这会导致打分偏离的情况,特别是数据量很少时。当文档总数很少的情况下,如果主分片大于1,主分片数越多,相关性算分会越不准
PUT /es_blogs
{
"settings":{
"number_of_shards" : "3"
}
}
POST /es_blogs/_doc/1?routing=fox
{
"content":"Cross Cluster elasticsearch Search"
}
POST /es_blogs/_doc/2?routing=fox2
{
"content":"elasticsearch Search"
}
POST /es_blogs/_doc/3?routing=fox3
{
"content":"elasticsearch"
}
search 算法不准展示
GET /es_blogs/_search
{
"query": {
"match": {
"content": "elasticsearch"
}
}
}
response
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 3,
"successful" : 3,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 0.2876821,
"hits" : [
{
"_index" : "es_blogs",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.2876821,
"_routing" : "fox",
"_source" : {
"content" : "Cross Cluster elasticsearch Search"
}
},
{
"_index" : "es_blogs",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.21110919,
"_routing" : "fox3",
"_source" : {
"content" : "elasticsearch"
}
},
{
"_index" : "es_blogs",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.160443,
"_routing" : "fox2",
"_source" : {
"content" : "elasticsearch Search"
}
}
]
}
}
#解决算分不准的问题
GET /es_blogs/_search?search_type=dfs_query_then_fetch
{
"query": {
"match": {
"content": "elasticsearch"
}
}
}
response
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 3,
"successful" : 3,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 0.17426977,
"hits" : [
{
"_index" : "es_blogs",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.17426977,
"_routing" : "fox3",
"_source" : {
"content" : "elasticsearch"
}
},
{
"_index" : "es_blogs",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.14181954,
"_routing" : "fox2",
"_source" : {
"content" : "elasticsearch Search"
}
},
{
"_index" : "es_blogs",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.10333586,
"_routing" : "fox",
"_source" : {
"content" : "Cross Cluster elasticsearch Search"
}
}
]
}
}
Shard是Elasticsearch 实现集群水平扩展的最小单位。过多设置分片数会带来一些潜在的问题:
index.routing.allocation.total_shards_per_node
,index级别的,表示这个index每个Node总共允许存在多少个shard,默认值是-1表示无穷多个;cluster.routing.allocation.total_shards_per_node
,cluster级别,表示集群范围内每个Node允许存在有多少个shard。默认值是-1表示无穷多个。如果目标Node的Shard数超过了配置的上限,则不允许分配Shard到该Node上。注意:index级别的配置会覆盖cluster级别的配置。
写请求是写入 primary shard,然后同步给所有的 replica shard;读请求可以从 primary shard 或 replica shard 读取,采用的是随机轮询算法。
根据 doc id 进行 hash,判断出来当时把 doc id 分配到了哪个 shard 上面去,从那个 shard 去查询。
再次回表查询
)segment file: 存储倒排索引的文件,每个segment本质上就是一个倒排索引,每秒都会生成一个segment文件,当文件过多时es会自动进行segment merge(合并文件),合并时会同时将已经标注删除的文档物理删除。
commit point: 记录当前所有可用的segment,每个commit point都会维护一个.del文件,即每个.del文件都有一个commit point文件(es删除数据本质是不属于物理删除),当es做删改操作时首先会在.del文件中声明某个document已经被删除,文件内记录了在某个segment内某个文档已经被删除,当查询请求过来时在segment中被删除的文件是能够查出来的,但是当返回结果时会根据commit point维护的那个.del文件把已经删除的文档过滤掉
translog日志文件: 为了防止elasticsearch宕机造成数据丢失保证可靠存储,es会将每次写入数据同时写到translog日志中。
os cache:操作系统里面,磁盘文件其实都有一个东西,叫做os cache,操作系统缓存,就是说数据写入磁盘文件之前,会先进入os cache,先进入操作系统级别的一个内存缓存中去
Refresh
将文档先保存在Index buffer中,以refresh_interval为间隔时间,定期清空buffer,生成 segment,借助文件系统缓存的特性,先将segment放在文件系统缓存中,并开放查询,以提升搜索的实时性
Translog
Segment没有写入磁盘,即便发生了当机,重启后,数据也能恢复,从ES6.0开始默认配置是每次请求都会落盘
Flush
删除旧的translog 文件
生成Segment并写入磁盘│更新commit point并写入磁盘。ES自动完成,可优化点不多
提升集群读取性能的方法
put es_blogs/_bulk
{"index":{"_id":1}}
{"content": "elasticsearch"}
{"index":{"_id":2}}
{"content": "Cross Cluster elasticsearch Search"}
{"index":{"_id":3}}
{"content": "elasticsearch Search"}
#避免查询时脚本
GET es_blogs/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"content": "elasticsearch"
}
}
],
"filter": {
"script": {
"script": {
"source": "doc['content.keyword'].value.length()>15"
}
}
}
}
}
}
GET /es_test/_search
{
"query": {
"wildcard": {
"address": {
"value": "*白云*"
}
}
}
}
一个查询需要访问每一个分片,分片过多,会导致不必要的查询开销
Search: 20GB
Logging: 40GB
使用基于时间序列的索引,将只读的索引进行force merge,减少segment数量
#手动force merge
POST /my_index/_forcemerge
注意:ES 的默认设置,已经综合考虑了数据可靠性,搜索的实时性,写入速度,一般不要盲目修改。一切优化,都要基于高质量的数据建模。
如果需要追求极致的写入速度,可以牺牲数据可靠性及搜索实时性以换取性能:
PUT /my_index/_settings
{
"index" : {
"refresh_interval" : "10s"
}
}
DELETE myindex
PUT myindex
{
"settings": {
"index": {
"refresh_interval": "30s", #30s一次refresh
"number_of_shards": "2"
},
"routing": {
"allocation": {
"total_shards_per_node": "3" #控制分片,避免数据热点
}
},
"translog": {
"sync_interval": "30s",
"durability": "async" #降低translog落盘频率
},
"number_of_replicas": 0
},
"mappings": {
"dynamic": false, #避免不必要的字段索引,必要时可以通过update by query
索引必要的字段
"properties": {}
}
}
https://developer.aliyun.com/article/802049