4. 再次执行./elasticsearch -d即可启动
5. 在我们配置的es日志目录中,查看日志文件elasticsearch.log,确保es启动成功。
6. 查看elasticssearch进程, 运行"ps -aux|grep elasticsearch" 命令即可
七、Elasticsearch 配置文件详解
elasticsearch的配置文件是在elasticsearch目录下的config文件下的elasticsearch.yml,同时它的日志文件在elasticsearch目录下的logs,由于elasticsearch的日志也是使用log4j来写日志的,所以其配置模式与log4j基本相同。
Cluster部分
cluster.name: kevin-elk (默认值:elasticsearch)
cluster.name可以确定你的集群名称,当你的elasticsearch集群在同一个网段中elasticsearch会自动的找到具有相同cluster.name 的elasticsearch服务。所以当同一个网段具有多个elasticsearch集群时cluster.name就成为同一个集群的标识。
Node部分
node.name: “elk-node01” 节点名,可自动生成也可手动配置。
node.master: true (默认值:true) 允许一个节点是否可以成为一个master节点,es是默认集群中的第一台机器为master,如果这台机器停止就会重新选举master。
node.client 当该值设置为true时,node.master值自动设置为false,不参加master选举。
node.data: true (默认值:true) 允许该节点存储数据。
node.rack 无默认值,为节点添加自定义属性。
node.max_local_storage_nodes: 1 (默认值:1) 设置能运行的节点数目,一般采用默认的1即可,因为我们一般也只在一台机子上部署一个节点。
配置文件中给出了三种配置高性能集群拓扑结构的模式,如下:
workhorse:如果想让节点从不选举为主节点,只用来存储数据,可作为负载器
node.master: false
node.data: true
coordinator:如果想让节点成为主节点,且不存储任何数据,并保有空闲资源,可作为协调器
node.master: true
node.data: false
search load balancer:(fetching data from nodes, aggregating results, etc.理解为搜索的负载均衡节点,从其他的节点收集数据或聚集后的结果等),客户端节点可以直接将请求发到数据存在的节点,而不用查询所有的数据节点,另外可以在它的上面可以进行数据的汇总工作,可以减轻数据节点的压力。
node.master: false
node.data: false
另外配置文件提到了几种监控es集群的API或方法:
Cluster Health API:http://127.0.0.1:9200/_cluster/health
Node Info API:http://127.0.0.1:9200/_nodes
还有图形化工具:
https://www.elastic.co/products/marvel
https://github.com/karmi/elasticsearch-paramedic
https://github.com/hlstudio/bigdesk
https://github.com/mobz/elasticsearch-head
Indices部分
index.number_of_shards: 5 (默认值为5) 设置默认索引分片个数。
index.number_of_replicas: 1(默认值为1) 设置索引的副本个数
服务器够多,可以将分片提高,尽量将数据平均分布到集群中,增加副本数量可以有效的提高搜索性能。
需要注意: “number_of_shards” 是索引创建后一次生成的,后续不可更改设置 “number_of_replicas” 是可以通过update-index-settings API实时修改设置。
Indices Circuit Breaker
elasticsearch包含多个circuit breaker来避免操作的内存溢出。每个breaker都指定可以使用内存的限制。另外有一个父级breaker指定所有的breaker可以使用的总内存
indices.breaker.total.limit 所有breaker使用的内存值,默认值为 JVM 堆内存的70%,当内存达到最高值时会触发内存回收。
Field data circuit breaker 允许elasticsearch预算待加载field的内存,防止field数据加载引发异常
indices.breaker.fielddata.limit field数据使用内存限制,默认为JVM 堆的60%。
indices.breaker.fielddata.overhead elasticsearch使用这个常数乘以所有fielddata的实际值作field的估算值。默认为 1.03。
请求断路器(Request circuit breaker) 允许elasticsearch防止每个请求的数据结构超过了一定量的内存
indices.breaker.request.limit request数量使用内存限制,默认为JVM堆的40%。
indices.breaker.request.overhead elasticsearch使用这个常数乘以所有request占用内存的实际值作为最后的估算值。默认为 1。
Indices Fielddata cache
字段数据缓存主要用于排序字段和计算聚合。将所有的字段值加载到内存中,以便提供基于文档快速访问这些值
indices.fielddata.cache.size:unbounded
设置字段数据缓存的最大值,值可以设置为节点堆空间的百分比,例:30%,可以值绝对值,例:12g。默认为无限。
该设置是静态设置,必须配置到集群的每个节点。
Indices Node query cache
query cache负责缓存查询结果,每个节点都有一个查询缓存共享给所有的分片。缓存实现一个LRU驱逐策略:当缓存使用已满,最近最少使用的数据将被删除,来缓存新的数据。query cache只缓存过滤过的上下文
indices.queries.cache.size
查询请求缓存大小,默认为10%。也可以写为绝对值,例:512m。
该设置是静态设置,必须配置到集群的每个数据节点。
Indexing Buffer
索引缓冲区用于存储新索引的文档。缓冲区写满,缓冲区的文件才会写到硬盘。缓冲区划分给节点上的所有分片。
Indexing Buffer的配置是静态配置,必须配置都集群中的所有数据节点
indices.memory.index_buffer_size
允许配置百分比和字节大小的值。默认10%,节点总内存堆的10%用作索引缓冲区大小。
indices.memory.min_index_buffer_size
如果index_buffer_size被设置为一个百分比,这个设置可以指定一个最小值。默认为 48mb。
indices.memory.max_index_buffer_size
如果index_buffer_size被设置为一个百分比,这个设置可以指定一个最小值。默认为无限。
indices.memory.min_shard_index_buffer_size
设置每个分片的最小索引缓冲区大小。默认为4mb。
Indices Shard request cache
当一个搜索请求是对一个索引或者多个索引的时候,每一个分片都是进行它自己内容的搜索然后把结果返回到协调节点,然后把这些结果合并到一起统一对外提供。分片缓存模块缓存了这个分片的搜索结果。这使得搜索频率高的请求会立即返回。
**注意:**请求缓存只缓存查询条件 size=0的搜索,缓存的内容有hits.total, aggregations, suggestions,不缓存原始的hits。通过now查询的结果将不缓存。
缓存失效:只有在分片的数据实际上发生了变化的时候刷新分片缓存才会失效。刷新的时间间隔越长,缓存的数据越多,当缓存不够的时候,最少使用的数据将被删除。
缓存过期可以手工设置,例如:
curl -XPOST 'localhost:9200/kimchy,elasticsearch/_cache/clear?request_cache=true'
默认情况下缓存未启用,但在创建新的索引时可启用,例如:
curl -XPUT localhost:9200/my_index -d'
{
"settings": {
"index.requests.cache.enable": true
}
}
'
当然也可以通过动态参数配置来进行设置:
curl -XPUT localhost:9200/my_index/_settings -d'
{ "index.requests.cache.enable": true }
'
每请求启用缓存,查询字符串参数request_cache可用于启用或禁用每个请求的缓存。例如:
curl 'localhost:9200/my_index/_search?request_cache=true' -d'
{
"size": 0,
"aggs": {
"popular_colors": {
"terms": {
"field": "colors"
}
}
}
}
'
**注意:**如果你的查询使用了一个脚本,其结果是不确定的(例如,它使用一个随机函数或引用当前时间)应该设置 request_cache=false 禁用请求缓存。
缓存key,数据的缓存是整个JSON,这意味着如果JSON发生了变化 ,例如如果输出的顺序顺序不同,缓存的内容江将会不同。不过大多数JSON库对JSON键的顺序是固定的。
分片请求缓存是在节点级别进行管理的,并有一个默认的值是JVM堆内存大小的1%,可以通过配置文件进行修改。 例如: indices.requests.cache.size: 2%
分片缓存大小的查看方式:
curl 'localhost:9200/_stats/request_cache?pretty&human'
或者
curl 'localhost:9200/_nodes/stats/indices/request_cache?pretty&human'
Indices Recovery
indices.recovery.concurrent_streams 限制从其它分片恢复数据时最大同时打开并发流的个数。默认为 3。
indices.recovery.concurrent_small_file_streams 从其他的分片恢复时打开每个节点的小文件(小于5M)流的数目。默认为 2。
indices.recovery.file_chunk_size 默认为 512kb。
indices.recovery.translog_ops 默认为 1000。
indices.recovery.translog_size 默认为 512kb。
indices.recovery.compress 恢复分片时,是否启用压缩。默认为 true。
indices.recovery.max_bytes_per_sec 限制从其它分片恢复数据时每秒的最大传输速度。默认为 40mb。
Indices TTL interval
indices.ttl.interval 允许设置多久过期的文件会被自动删除。默认值是60s。
indices.ttl.bulk_size 设置批量删除请求的数量。默认值为1000。
Paths部分
path.conf: /path/to/conf 配置文件存储位置。
path.data: /path/to/data 数据存储位置,索引数据可以有多个路径,使用逗号隔开。
path.work: /path/to/work 临时文件的路径 。
path.logs: /path/to/logs 日志文件的路径 。
path.plugins: /path/to/plugins 插件安装路径 。
Memory部分
bootstrap.mlockall: true(默认为false)
锁住内存,当JVM进行内存转换的时候,es的性能会降低,所以可以使用这个属性锁住内存。同时也要允许elasticsearch的进程可以锁住内存,linux下可以通过ulimit -l unlimited
命令,或者在/etc/sysconfig/elasticsearch文件中取消 MAX_LOCKED_MEMORY=unlimited 的注释即可。如果使用该配置则ES_HEAP_SIZE必须设置,设置为当前可用内存的50%,最大不能超过31G,默认配置最小为256M,最大为1G。
可以通过请求查看mlockall的值是否设定:
curl http://localhost:9200/_nodes/process?pretty
如果mlockall的值是false,则设置失败。可能是由于elasticsearch的临时目录(/tmp)挂载的时候没有可执行权限。
可以使用下面的命令来更改临时目录:
./bin/elasticsearch -Djna.tmpdir=/path/to/new/dir
Network 、Transport and HTTP 部分
network.bind_host
设置绑定的ip地址,可以是ipv4或ipv6的。
network.publish_host
设置其它节点和该节点交互的ip地址,如果不设置它会自动设置,值必须是个真实的ip地址。
network.host
同时设置bind_host和publish_host两个参数,值可以为网卡接口、127.0.0.1、私有地址以及公有地址。
http_port
接收http请求的绑定端口。可以为一个值或端口范围,如果是一个端口范围,节点将绑定到第一个可用端口。默认为:9200-9300。
transport.tcp.port
节点通信的绑定端口。可以为一个值或端口范围,如果是一个端口范围,节点将绑定到第一个可用端口。默认为:9300-9400。
transport.tcp.connect_timeout
套接字连接超时设置,默认为 30s。
transport.tcp.compress
设置为true启用节点之间传输的压缩(LZF),默认为false。
transport.ping_schedule
定时发送ping消息保持连接,默认transport客户端为5s,其他为-1(禁用)。
httpd.enabled
是否使用http协议提供服务。默认为:true(开启)。
http.max_content_length
最大http请求内容。默认为100MB。如果设置超过100MB,将会被MAX_VALUE重置为100MB。
http.max_initial_line_length
http的url的最大长度。默认为:4kb。
http.max_header_size
http中header的最大值。默认为8kb。
http.compression
支持压缩(Accept-Encoding)。默认为:false。
http.compression_level
定义压缩等级。默认为:6。
http.cors.enabled
启用或禁用跨域资源共享。默认为:false。
http.cors.allow-origin
启用跨域资源共享后,默认没有源站被允许。在//中填写域名支持正则,例如 /https?😕/localhost(:[0-9]+)?/。 * 是有效的值,但是开放任何域名的跨域请求被认为是有安全风险的elasticsearch实例。
http.cors.max-age
浏览器发送‘preflight’OPTIONS-request 来确定CORS设置。max-age 定义缓存的时间。默认为:1728000 (20天)。
http.cors.allow-methods
允许的http方法。默认为OPTIONS、HEAD、GET、POST、PUT、DELETE。
http.cors.allow-headers
允许的header。默认 X-Requested-With, Content-Type, Content-Length。
http.cors.allow-credentials
是否允许返回Access-Control-Allow-Credentials头部。默认为:false。
http.detailed_errors.enabled
启用或禁用输出详细的错误信息和堆栈跟踪响应输出。默认为:true。
http.pipelining
启用或禁用http管线化。默认为:true。
http.pipelining.max_events
一个http连接关闭之前最大内存中的时间队列。默认为:10000。
Discovery部分
discovery.zen.minimum_master_nodes: 3
预防脑裂(split brain)通过配置大多数节点(总节点数/2+1)。默认为3。
discovery.zen.ping.multicast.enabled: false
设置是否打开组播发现节点。默认false。
discovery.zen.ping.unicast.host
单播发现所使用的主机列表,可以设置一个属组,或者以逗号分隔。每个值格式为 host:port 或 host(端口默认为:9300)。默认为 127.0.0.1,[::1]。
discovery.zen.ping.timeout: 3s
设置集群中自动发现其它节点时ping连接超时时间,默认为3秒,对于比较差的网络环境可以高点的值来防止自动发现时出错。
discovery.zen.join_timeout
节点加入到集群中后,发送请求到master的超时时间,默认值为ping.timeout的20倍。
discovery.zen.master_election.filter_client:true
当值为true时,所有客户端节点(node.client:true或node.date,node.master值都为false)将不参加master选举。默认值为:true。
discovery.zen.master_election.filter_data:false
当值为true时,不合格的master节点(node.data:true和node.master:false)将不参加选举。默认值为:false。
discovery.zen.fd.ping_interval
发送ping监测的时间间隔。默认为:1s。
discovery.zen.fd.ping_timeout
ping的响应超时时间。默认为30s。
discovery.zen.fd.ping_retries
ping监测失败、超时的次数后,节点连接失败。默认为3。
discovery.zen.publish_timeout
通过集群api动态更新设置的超时时间,默认为30s。
discovery.zen.no_master_block
设置无master时,哪些操作将被拒绝。all 所有节点的读、写操作都将被拒绝。write 写操作将被拒绝,可以读取最后已知的集群配置。默认为:write。
Gateway部分
gateway.expected_nodes: 0
设置这个集群中节点的数量,默认为0,一旦这N个节点启动,就会立即进行数据恢复。
gateway.expected_master_nodes
设置这个集群中主节点的数量,默认为0,一旦这N个节点启动,就会立即进行数据恢复。
gateway.expected_data_nodes
设置这个集群中数据节点的数量,默认为0,一旦这N个节点启动,就会立即进行数据恢复。
gateway.recover_after_time: 5m
设置初始化数据恢复进程的超时时间,默认是5分钟。
gateway.recover_after_nodes
设置集群中N个节点启动时进行数据恢复。
gateway.recover_after_master_nodes
设置集群中N个主节点启动时进行数据恢复。
gateway.recover_after_data_nodes
设置集群中N个数据节点启动时进行数据恢复。
八. Elasticsearch常用插件
elasticsearch-head 插件
一个elasticsearch的集群管理工具,它是完全由html5编写的独立网页程序,你可以通过插件把它集成到es。
项目地址:https://github.com/mobz/elasticsearch-head
插件安装方法1
elasticsearch/bin/plugin install mobz/elasticsearch-head
重启elasticsearch
打开http://localhost:9200/_plugin/head/
插件安装方法2
根据地址https://github.com/mobz/elasticsearch-head 下载zip解压
建立elasticsearch/plugins/head/_site文件
将解压后的elasticsearch-head-master文件夹下的文件copy到_site
重启elasticsearch
打开http://localhost:9200/_plugin/head/
bigdesk插件
elasticsearch的一个集群监控工具,可以通过它来查看es集群的各种状态,如:cpu、内存使用情况,索引数据、搜索情况,http连接数等。
项目地址: https://github.com/hlstudio/bigdesk
插件安装方法1
elasticsearch/bin/plugin install hlstudio/bigdesk
重启elasticsearch
打开http://localhost:9200/_plugin/bigdesk/
插件安装方法2
https://github.com/hlstudio/bigdesk下载zip 解压
建立elasticsearch-1.0.0\plugins\bigdesk\_site文件
将解压后的bigdesk-master文件夹下的文件copy到_site
重启elasticsearch
打开http://localhost:9200/_plugin/bigdesk/
Kopf 插件
一个ElasticSearch的管理工具,它也提供了对ES集群操作的API。
项目地址:https://github.com/lmenezes/elasticsearch-kopf
插件安装方法
elasticsearch/bin/plugin install lmenezes/elasticsearch-kopf
重启elasticsearch
打开http://localhost:9200/_plugin/kopf/
九、Elasticsearch 的fielddata内存控制、预加载以及circuit breaker断路器
fielddata核心原理
fielddata加载到内存的过程是lazy加载的,对一个analzyed field执行聚合时,才会加载,而且是field-level加载的一个index的一个field,所有doc都会被加载,而不是少数doc不是index-time创建,是query-time创建
fielddata内存限制
elasticsearch.yml: indices.fielddata.cache.size: 20%,超出限制,清除内存已有fielddata数据fielddata占用的内存超出了这个比例的限制,那么就清除掉内存中已有的fielddata数据默认无限制,限制内存使用,但是会导致频繁evict和reload,大量IO性能损耗,以及内存碎片和gc
监控fielddata内存使用
#各个分片、索引的fielddata在内存中的占用情况
[root@elk-node03 ~]# curl -X GET 'http://10.0.8.47:9200/_stats/fielddata?fields=*'
#每个node的fielddata在内存中的占用情况
[root@elk-node03 ~]# curl -X GET 'http://10.0.8.47:9200/_nodes/stats/indices/fielddata?fields=*'
#每个node中的每个索引的fielddata在内存中的占用情况
[root@elk-node03 ~]# curl -X GET 'http://10.0.8.47:9200/_nodes/stats/indices/fielddata?level=indices&fields=*'
circuit breaker断路由
如果一次query load的feilddata超过总内存,就会oom --> 内存溢出;
circuit breaker会估算query要加载的fielddata大小,如果超出总内存,就短路,query直接失败;
在elasticsearch.yml文件中配置如下内容:
indices.breaker.fielddata.limit: fielddata的内存限制,默认60%
indices.breaker.request.limit: 执行聚合的内存限制,默认40%
indices.breaker.total.limit: 综合上面两个,限制在70%以内
限制内存使用 (Elasticsearch聚合限制内存使用)
通常为了让聚合(或者任何需要访问字段值的请求)能够快点,访问fielddata一定会快点, 这就是为什么加载到内存的原因。但是加载太多的数据到内存会导致垃圾回收(gc)缓慢, 因为JVM试着发现堆里面的额外空间,甚至导致OutOfMemory (即OOM)异常。
然而让人吃惊的发现, Elaticsearch不是只把符合你的查询的值加载到fielddata. 而是把index里的所document都加载到内存,甚至是不同的 _type 的document。逻辑是这样的,如果你在这个查询需要访问documents X,Y和Z, 你可能在下一次查询就需要访问别documents。而一次把所有的值都加载并保存在内存 , 比每次查询都去扫描倒排索引要更方便。
JVM堆是一个有限制的资源需要聪明的使用。有许多现成的机制去限制fielddata对堆内存使用的影响。这些限制非常重要,因为滥用堆将会导致节点的不稳定(多亏缓慢的垃圾回收)或者甚至节点死亡(因为OutOfMemory异常);但是垃圾回收时间过长,在垃圾回收期间,ES节点的性能就会大打折扣,查询就会非常缓慢,直到最后超时。
如何设置堆大小
对于环境变量 $ES_HEAP_SIZE 在设置Elasticsearch堆大小的时候有2个法则可以运用:
-
不超过RAM的50%
Lucene很好的利用了文件系统cache,文件系统cache是由内核管理的。如果没有足够的文件系统cache空间,性能就会变差; -
不超过32G
如果堆小于32GB,JVM能够使用压缩的指针,这会节省许多内存:每个指针就会使用4字节而不是8字节。把对内存从32GB增加到34GB将意味着你将有更少的内存可用,因为所有的指针占用了双倍的空间。同样,更大的堆,垃圾回收变得代价更大并且可能导致节点不稳定;这个限制主要是大内存对fielddata影响比较大。
Fielddata大小
参数 indices.fielddata.cache.size 控制有多少堆内存是分配给fielddata。当你执行一个查询需要访问新的字段值的时候,将会把值加载到内存,然后试着把它们加入到fielddata。如果结果的fielddata大小超过指定的大小 ,为了腾出空间,别的值就会被驱逐出去。默认情况下,这个参数设置的是无限制 — Elasticsearch将永远不会把数据从fielddata里替换出去。
这个默认值是故意选择的:fielddata不是临时的cache。它是一个在内存里为了快速执行必须能被访问的数据结构,而且构建它代价非常昂贵。如果你每个请求都要重新加载数据,性能就会很差。
一个有限的大小强迫数据结构去替换数据。下面来看看什么时候去设置下面的值,首先看一个警告: 这个设置是一个保护措施,而不是一个内存不足的解决方案!
如果你没有足够的内存区保存你的fielddata到内存里,Elasticsearch将会经常性的从磁盘重新加载数据,并且驱逐别的数据区腾出空间。这种数据的驱逐会导致严重的磁盘I/O,并且在内存里产生大量的垃圾,这个会在后面被垃圾回收。
假设你在索引日志,每天使用给一个新的索引。通常情况下你只会对过去1天或者2天的数据感兴趣。即使你把老的索引数据保留着,你也很少查询它们。尽管如此,使用默认的设置, 来自老索引的fielddata也不会被清除出去!fielddata会一直增长直到它触发fielddata circuit breaker --参考
断路器–它将阻止你继续加载fielddata。在那个时候你被卡住了。即使你仍然能够执行访问老的索引里的fielddata的查询, 你再也不能加载任何新的值了。相反,我们应该把老的值清除出去给新的值腾出空间。为了防止这种情景,通过在elasticsearch.yml文件里加上如下的配置给fielddata 设置一个上限:
indices.fielddata.cache.size: 40%
,
可以设置成堆大小的百分比,也可以是一个具体的值,比如 8gb;通过适当设置这个值,最近被访问的fielddata将被清除出去,给新加载数据腾出空间。
在网上可能还会看到另外一个设置参数:
indices.fielddata.cache.expire。
千万不要使用这个设置!这个设置高版本已经废弃!!!
这个设置告诉Elasticsearch把比过期时间老的数据从fielddata里驱逐出去,而不管这个值是否被用到。这对性能是非常可怕的 。驱逐数据是有代价的,并且这个有目的的高效的安排驱逐数据并没有任何真正的收获。**没有任何理由去使用这个设置!!!**我们一点也不能从理论上制造一个假设的有用的情景。现阶段存 在只是为了向后兼容。我们在这个书里提到这个设置是因为这个设置曾经在网络上的各种文章里 被作为一个 ``性能小窍门’’ 被推荐过。记住永远不要使用它!!!
监控fielddata (上面提到了)
监控fielddata使用了多少内存以及是否有数据被驱逐是非常重要的。大量的数据被驱逐会导致严重的资源问题以及不好的性能。
Fielddata使用可以通过下面的方式来监控:
对于单个索引使用 {ref}indices-stats.html[indices-stats API]:
[root@elk-node03 ~]# curl -X GET 'http://10.0.8.47:9200/_stats/fielddata?fields=
对于单个节点使用 {ref}cluster-nodes-stats.html[nodes-stats API]:
[root@elk-node03 ~]# curl -X GET 'http://10.0.8.47:9200/_nodes/stats/indices/fielddata?fields=*'
或者甚至单个节点单个索引
[root@elk-node03 ~]# curl -X GET 'http://10.0.8.47:9200/_nodes/stats/indices/fielddata?level=indices&fields=*'
通过设置 ?fields=* 内存使用按照每个字段分解了.
断路器(breaker)
fielddata的大小是在数据被加载之后才校验的。如果一个查询尝试加载到fielddata的数据比可用的内存大会发生什么情况?答案是不客观的:你将会获得一个OutOfMemory异常。
Elasticsearch包含了一个 fielddata断路器 ,这个就是设计来处理这种情况的。断路器通过检查涉及的字段(它们的类型,基数,大小等等)来估计查询需要的内存。然后检查加 载需要的fielddata会不会导致总的fielddata大小超过设置的堆的百分比。
如果估计的查询大小超过限制,断路器就会触发并且查询会被抛弃返回一个异常。这个发生在数据被加载之前,这就意味着你不会遇到OutOfMemory异常。
Elasticsearch拥有一系列的断路器,所有的这些都是用来保证内存限制不会被突破:
indices.breaker.fielddata.limit
这个 fielddata 断路器限制fielddata的大小为堆大小的60%,默认情况下。
indices.breaker.request.limit
这个 request 断路器估算完成查询的其他部分要求的结构的大小,比如创建一个聚集通, 以及限制它们到堆大小的40%,默认情况下。
indices.breaker.total.limit
这个total断路器封装了 request 和 fielddata 断路器去确保默认情况下这2个 使用的总内存不超过堆大小的70%。
断路器限制可以通过文件 config/elasticsearch.yml 指定,也可以在集群上动态更新:
curl -PUT 'http://10.0.8.47:9200/_cluster/settings{
"persistent" : {
"indices.breaker.fielddata.limit" : 40% (1)
}
}
这个限制设置的是堆的百分比。
最好把断路器设置成一个相对保守的值。记住fielddata需要和堆共享 request 断路器, 索引内存缓冲区,过滤器缓存,打开的索引的Lucene数据结构,以及各种各样别的临时数据 结构。所以默认为相对保守的60%。过分乐观的设置可能会导致潜在的OOM异常,从而导致整 个节点挂掉。从另一方面来说,一个过分保守的值将会简单的返回一个查询异常,这个异常会被应用处理。 异常总比挂掉好。这些异常也会促使你重新评估你的查询:为什么单个的查询需要超过60%的 堆空间。
断路器和Fielddata大小
在 Fielddata大小部分我们谈到了要给fielddata大小增加一个限制去保证老的不使用 的fielddata被驱逐出去。indices.fielddata.cache.size 和 indices.breaker.fielddata.limit 的关系是非常重要的。如果断路器限制比缓冲区大小要小,就会没有数据会被驱逐。为了能够 让它正确的工作,断路器限制必须比缓冲区大小要大。
我们注意到断路器是和总共的堆大小对比查询大小,而不是和真正已经使用的堆内存区比较。 这样做是有一系列技术原因的(比如,堆可能看起来是满的,但是实际上可能正在等待垃圾 回收,这个很难准确的估算)。但是作为终端用户,这意味着设置必须是保守的,因为它是 和整个堆大小比较,而不是空闲的堆比较。
十、Elasticserach索引详解
- 分词器集成
1. 获取 ES-IKAnalyzer插件:
地址: https://github.com/medcl/elasticsearch-analysis-ik/releases
一定要获取匹配的版本
**2.**安装插件
将 ik 的压缩包解压到 ES安装目录的plugins/目录下(最好把解出的目录名改一下,防止安装别的插件时同名冲突),然后重启ES。
**3.**扩展词库
修改配置文件config/IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典-->
<entry key="ext_dict">custom/mydict.dic;custom/single_word_low_freq.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">custom/ext_stopword.dic</entry>
<!--用户可以在这里配置远程扩展字典-->
<entry key="remote_ext_dict">location</entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<entry key="remote_ext_stopwords">http://xxx.com/xxx.dic</entry>
</properties>
**4.**测试 IK
**5.**创建一个索引
curl -XPUT http://localhost:9200/index
**6.**创建一个映射mapping
# curl -XPOST http://localhost:9200/index/fulltext/_mapping -H 'Content-Type:application/json' -d'
{
"properties": {
"content": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
}
}
}'
**7.**索引一些文档
# curl -XPOST http://localhost:9200/index/fulltext/1 -H 'Content-Type:application/json' -d'
{"content":"美国留给伊拉克的是个烂摊子吗"}'
# curl -XPOST http://localhost:9200/index/fulltext/2 -H 'Content-Type:application/json' -d'
{"content":"公安部:各地校车将享最高路权"}'
# curl -XPOST http://localhost:9200/index/fulltext/3 -H 'Content-Type:application/json' -d'
{"content":"中韩渔警冲突调查:韩警平均每天扣1艘中国渔船"}'
**8.**搜索
# curl -XPOST http://localhost:9200/index/fulltext/_search -H 'Content-Type:application/json' -d'
{
"query" : { "match" : { "content" : "中国" }},
"highlight" : {
"pre_tags" : ["<tag1>", "<tag2>"],
"post_tags" : ["</tag1>", "</tag2>"],
"fields" : {
"content" : {}
}
}
}'
- 索引收缩
索引的分片数是不可更改的,如要减少分片数可以通过收缩方式收缩为一个新的索引。新索引分片数必须是原分片数的因子值,如原分片数是8,则新索引分片数可以为4、2、1 。
收缩流程
- 先把所有主分片都转移到一台主机上;
- 在这台主机上创建一个新索引,分片数较小,其他设置和原索引一致;
- 把原索引的所有分片,复制(或硬链接)到新索引的目录下;
- 对新索引进行打开操作恢复分片数据;(可选)重新把新索引的分片均衡到其他节点上。
a) 收缩前准备工作
将原索引设置为只读;将原索引各分片的一个副本重分配到同一个节点上,并且要是健康绿色状态。
PUT /my_source_index/_settings
{
"settings": {
"index.routing.allocation.require._name": "shrink_node_name",
"index.blocks.write": true
}
}
b) 进行收缩
POST my_source_index/_shrink/my_target_index
{
"settings": {
"index.number_of_replicas": 1,
"index.number_of_shards": 1,
"index.codec": "best_compression"
}}
c) 监控收缩过程
GET _cat/recovery?v
GET _cluster/health
- Split Index 拆分索引
当索引的分片容量过大时,可以通过拆分操作将索引拆分为一个倍数分片数的新索引。能拆分为几倍由创建索引时指定的index.number_of_routing_shards 路由分片数决定。这个路由分片数决定了根据一致性hash路由文档到分片的散列空间。如index.number_of_routing_shards = 30 ,指定的分片数是5,则可按如下倍数方式进行拆分:
5 → 10 → 30 (split by 2, then by 3)
5 → 15 → 30 (split by 3, then by 2)
5 → 30 (split by 6)
**注意:**只有在创建时指定了index.number_of_routing_shards 的索引才可以进行拆分,ES7开始将不再有这个限制。
a) 准备一个索引来做拆分
PUT my_source_index
{
"settings": {
"index.number_of_shards" : 1,
"index.number_of_routing_shards": 2
}
}
b) 设置索引只读
PUT /my_source_index/_settings
{
"settings": {
"index.blocks.write": true
}
}
c) 监控拆分过程
GET _cat/recovery?v
GET _cluster/health
- 别名滚动
对于有时效性的索引数据,如日志,过一定时间后,老的索引数据就没有用了。我们可以像数据库中根据时间创建表来存放不同时段的数据一样,在ES中也可用建多个索引的方式来分开存放不同时段的数据。比数据库中更方便的是ES中可以通过别名滚动指向最新的索引的方式,让你通过别名来操作时总是操作的最新的索引。
ES的rollover index API 让我们可以根据满足指定的条件(时间、文档数量、索引大小)创建新的索引,并把别名滚动指向新的索引。
- Rollover Index 示例
- 创建一个名字为logs-0000001 、别名为logs_write 的索引
PUT /logs-000001
{
"aliases": {
"logs_write": {}
}
}
- 如果别名logs_write指向的索引是7天前(含)创建的或索引的文档数>=1000或索引的大小>= 5gb,则会创建一个新索引 logs-000002,并把别名logs_writer指向新创建的logs-000002索引
# Add > 1000 documents to logs-000001
POST /logs_write/_rollover
{
"conditions": {
"max_age": "7d",
"max_docs": 1000,
"max_size": "5gb"
}
}
- Rollover Index 新建索引的命名规则
如果索引的名称是-数字结尾,如logs-000001,则新建索引的名称也会是这个模式,数值增1。
如果索引的名称不是-数值结尾,则在请求rollover api时需指定新索引的名称:
POST /my_alias/_rollover/my_new_index_name
{
"conditions": {
"max_age": "7d",
"max_docs": 1000,
"max_size": "5gb"
}
}
- 在名称中使用Date math(时间表达式)
如果你希望生成的索引名称中带有日期,如logstash-2016.02.03-1 ,则可以在创建索引时采用时间表达式来命名:
# PUT /<logs-{now/d}-1> with URI
encoding:
PUT /%3Clogs-%7Bnow%2Fd%7D-1%3E
{
"aliases": {
"logs_write": {}
}
}
PUT logs_write/_doc/1
{
"message": "a dummy log"
}
POST logs_write/_refresh
# Wait for a day to pass
POST /logs_write/_rollover
{
"conditions": {
"max_docs": "1"
}
}
**注意:**rollover是你请求它才会进行操作,并不是自动在后台进行的。你可以周期性地去请求它。
- 路由
1. 集群组成
集群元信息
Cluster-name:ess
Nodes:
node1 10.0.1.11 master
node2 10.0.1.12
node3 10.0.1.13
Indics:
s0:
shard0:
primay: 10.0.1.11
rep:10.0.1.13
……
2. 创建索引的流程
PUT s1
{
"settings" : {
"index" : {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
}
1. 请求node3创建索引
2. node3请求转发给master节点
3. 选择节点存放分片、副本,记录元信息
4. 通知给参与存放索引分片、副本的节点从节点,创建分片、副本
5. 参与节点向主节点反馈结果
6. 等待时间到了,master向node3反馈结果信息,node3响应请求。
7. 主节点将元信息广播给所有从节点。
3. 节点故障
集群元信息
Cluster-name:ess
Nodes:
node1 10.0.1.11 master 故障
node2 10.0.1.12 master
node3 10.0.1.13
Indics:
s0:
shard0:
primay:10.0.1.12
rep:10.0.1.13
……
节点数据自动重新分配
4. 索引文档
索引文档的步骤:
1. node2计算文档的路由值得到文档存放的分片(假定路由选定的是分片0)。
2. 将文档转发给分片0的主分片节点 node1。
**3.**node1索引文档,同步给副本节点node3索引文档。
4. node1向node2反馈结果
5. node2作出响应
5. 搜索
1. node2解析查询。
2. node2将查询发给索引s1的分片/副本(R1,R2,R0)节点
3. 各节点执行查询,将结果发给Node2
4. Node2合并结果,作出响应。
6. 文档如何路由
1. 文档该存到哪个分片上?
决定文档存放到哪个分片上就是文档路由。ES中通过下面的计算得到每个文档的存放分片:
shard = hash(routing) % number_of_primary_shards
routing 是用来进行hash计算的路由值,默认是使用文档id值。我们可以在索引文档时通过routing参数指定别的路由值,在索引、删除、更新、查询中都可以使用routing参数(可多值)指定操作的分片。
POST twitter/_doc?routing=kimchy
{
"user" : "kimchy",
"post_date": "2009-11-15T14:12:12",
"message" : "trying out Elasticsearch"
}
强制要求给定路由值
PUT my_index2
{
"mappings": {
"_doc": {
"_routing": {
"required": true
}
}
}
}
2. 关系型数据库中有分区表,通过选定分区,可以降低操作的数据量,提高效率。在ES的索引中能不能这样做?
可以:通过指定路由值,让一个分片上存放一个区的数据。如按部门存放数据,则可指定路由值为部门值。
十一、Elasticserach性能优化
1. 硬件选择
目前公司的物理机机型在CPU和内存方面都满足需求,建议使用SSD机型。原因在于,可以快速把 Lucene 的索引文件加载入内存(这在宕机恢复的情况下尤为明显),减少 IO 负载和 IO wait以便CPU不总是在等待IO中断。建议使用多裸盘而非raid,因为 ElasticSearch 本身就支持多目录,raid 要么牺牲空间要么牺牲可用性。
2. 系统配置
ElasticSearch 理论上必须单独部署,并且会独占几乎所有系统资源,因此需要对系统进行配置,以保证运行 ElasticSearch 的用户可以使用足够多的资源。生产集群需要调整的配置如下:
1. 设置 JVM 堆大小;
2. 关闭 swap;
3. 增加文件描述符;
4. 保证足够的虚存;
5. 保证足够的线程;
6. 暂时不建议使用G1GC;
3. 设置 JVM 堆大小
ElasticSearch 需要有足够的 JVM 堆支撑索引数据的加载,对于公司的机型来说,因为都是大于 128GB 的,所以推荐的配置是 32GB(如果 JVM 以不等的初始和最大堆大小启动,则在系统使用过程中可能会因为 JVM 堆的大小调整而容易中断。 为了避免这些调整大小的暂停,最好使用初始堆大小等于最大堆大小的 JVM 来启动),预留足够的 IO Cache 给 Lucene(官方建议超过一半的内存需要预留)。
4. 关闭 swap & 禁用交换
必须要关闭 swap,因为在物理内存不足时,如果发生 FGC,在回收虚拟内存的时候会造成长时间的 stop-the-world,最严重的后果是造成集群雪崩。公司的默认模板是关闭的,但是要巡检一遍,避免有些机器存在问题。设置方法:
Step1. root 用户临时关闭
# swapoff -a
# sysctl vm.swappiness=0
Step2. 修改 /etc/fstab,注释掉 swap 这行
Step3. 修改 /etc/sysctl.conf,添加:
vm.swappiness = 0
Step4. 确认是否生效
# sysctl vm.swappiness
也可以通过修改 yml 配置文件的方式从 ElasticSearch 层面禁止物理内存和交换区之间交换内存,修改 ${PATH_TO_ES_HOME}/config/elasticsearch.yml,添加:
bootstrap.memory_lock: true
====小提示=
Linux 把它的物理 RAM 分成多个内存块,称之为分页。内存交换(swapping)是这样一个过程,它把内存分页复制到预先设定的叫做交换区的硬盘空间上,以此释放内存分页。物理内存和交换区加起来的大小就是虚拟内存的可用额度。
内存交换有个缺点,跟内存比起来硬盘非常慢。内存的读写速度以纳秒来计算,而硬盘是以毫秒来计算,所以访问硬盘比访问内存要慢几万倍。交换次数越多,进程就越慢,所以应该不惜一切代价避免内存交换的发生。
ElasticSearch 的 memory_lock 属性允许 Elasticsearch 节点不交换内存。(注意只有Linux/Unix系统可设置。)这个属性可以在yml文件中设置。
5. 增加文件描述符
单个用户可用的最大进程数量(软限制)&单个用户可用的最大进程数量(硬限制),超过软限制会有警告,但是无法超过硬限制。 ElasticSearch 会使用大量的文件句柄,如果超过限制可能会造成宕机或者数据缺失。
文件描述符是用于跟踪打开“文件”的 Unix 结构体。在Unix中,一切都皆文件。 例如,“文件”可以是物理文件,虚拟文件(例如/proc/loadavg)或网络套接字。 ElasticSearch 需要大量的文件描述符(例如,每个 shard 由多个 segment 和其他文件组成,以及到其他节点的 socket 连接等)。
设置方法(假设是 admin 用户启动的 ElasticSearch 进程):
# Step1. 修改 /etc/security/limits.conf,添加:
admin soft nofile 65536
admin hard nofile 65536
# Step2. 确认是否生效
su - admin
ulimit -n
# Step3. 通过 rest 确认是否生效
GET /_nodes/stats/process?filter_path=**.max_file_descriptors
6. 保证足够的虚存
单进程最多可以占用的内存区域,默认为 65536。Elasticsearch 默认会使用 mmapfs 去存储 indices,默认的 65536 过少,会造成 OOM 异常。设置方法:
# Step1. root 用户修改临时参数
sudo sysctl -w vm.max_map_count=262144
# Step2. 修改 /etc/sysctl.conf,在文末添加:
vm.max_map_count = 262144
# Step3. 确认是否生效
sudo sysctl vm.max_map_count
7. 保证足够的线程
Elasticsearch 通过将请求分成几个阶段,并交给不同的线程池执行(Elasticsearch 中有各种不同的线程池执行器)。 因此,Elasticsearch 需要创建大量线程的能力。进程可创建线程的最大数量确保 Elasticsearch 进程有权在正常使用情况下创建足够的线程。 这可以通过/etc/security/limits.conf
使用 nproc 设置来完成。设置方法:
修改 /etc/security/limits.d/90-nproc.conf,添加:
admin soft nproc 2048
8. 暂时不建议使用G1GC
已知 JDK 8 附带的 HotSpot JVM 的早期版本在启用 G1GC 收集器时会导致索引损坏。受影响的版本是早于 JDK 8u40 附带的HotSpot 的版本,出于稳定性的考虑暂时不建议使用。
十二、Elasticserach内存优化
ElasticSearch 自身对内存管理进行了大量优化,但对于持续增长的业务仍需进行一定程度的内存优化(而不是纯粹的添加节点和扩展物理内存),以防止 OOM 发生。ElasticSearch 使用的 JVM 堆中主要包括以下几类内存使用:
1. Segment Memory;
2. Filter Cache;
3. Field Data Cache;
4. Bulk Queue;
5. Indexing Buffer;
6. Cluster State Buffer;
7. 超大搜索聚合结果集的 fetch;
1. 减少 Segment Memory
- 删除无用的历史索引。删除办法,使用 rest API
# 删除指定某个索引
DELETE /${INDEX_NAME}
# 删除符合 pattern 的某些索引
DELETE /${INDEX_PATTERN}
- 关闭无需实时查询的历史索引,文件仍然存在于磁盘,只是释放掉内存,需要的时候可以重新打开。关闭办法,使用 rest API
# 关闭指定某个索引
POST /${INDEX_NAME}/_close
# 关闭符合 pattern 的某些索引
POST /${INDEX_PATTERN}/_close
- 定期对不再更新的索引做 force merge(会占用大量 IO,建议业务低峰期触发)force merge 办法,使用 rest API
# Step1. 在合并前需要对合并速度进行合理限制,默认是 20mb,SSD可以适当放宽到 80mb:
PUT /_cluster/settings -d '
{
"persistent" : {
"indices.store.throttle.max_bytes_per_sec" : "20mb"
}
}'
# Step2. 强制合并 API,示例表示的是最终合并为一个 segment file:
# 对某个索引做合并
POST /${INDEX_NAME}/_forcemerge?max_num_segments=1
# 对某些索引做合并
POST /${INDEX_PATTERN}/_forcemerge?max_num_segments=1
2. Filter Cache
默认的 10% heap 设置工作得够好,如果实际使用中 heap 没什么压力的情况下,才考虑加大这个设置。
3. Field Data Cache
对需要排序的字段不进行 analyzed,尽量使用 doc values(5.X版本天然支持,不需要特别设置)。对于不参与搜索的字段 ( fields ),将其 index 方法设置为 no,如果对分词没有需求,对参与搜索的字段,其 index 方法设置为 not_analyzed。
4. Bulk Queue
一般来说官方默认的 thread pool 设置已经能很好的工作了,建议不要随意去调优相关的设置,很多时候都是适得其反的效果。
5. Indexing Buffer
这个参数的默认值是10% heap size。根据经验,这个默认值也能够很好的工作,应对很大的索引吞吐量。 但有些用户认为这个 buffer 越大吞吐量越高,因此见过有用户将其设置为 40% 的。到了极端的情况,写入速度很高的时候,40%都被占用,导致OOM。
6. Cluster State Buffer
在超大规模集群的情况下,可以考虑分集群并通过 tribe node 连接做到对用户透明,这样可以保证每个集群里的 state 信息不会膨胀得过大。在单集群情况下,缩减 cluster state buffer 的方法就是减少 shard 数量,shard 数量的确定有以下几条规则:
1. 避免有非常大的分片,因为大分片可能会对集群从故障中恢复的能力产生负面影响。 对于多大的分片没有固定限制,但分片大小为 50GB 通常被界定为适用于各种用例的限制;
2. 尽可能使用基于时间的索引来管理数据。根据保留期(retention period,可以理解成有效期)将数据分组。基于时间的索引还可以轻松地随时间改变主分片和副本分片的数量(以为要生成的下一个索引进行更改)。这简化了适应不断变化的数据量和需求;(周期性的通过删除或者关闭历史索引以减少分片)
3. 小分片会导致小分段(segment),从而增加开销。目的是保持平均分片大小在几GB和几十GB之间。对于具有基于时间数据的用例,通常看到大小在 20GB 和 40GB 之间的分片;
4. 由于每个分片的开销取决于分段数和大小,通过强制操作迫使较小的段合并成较大的段可以减少开销并提高查询性能。一旦没有更多的数据被写入索引,这应该是理想的。请注意,这是一个消耗资源的(昂贵的)操作,较为理想的处理时段应该在非高峰时段执行;(对应使用 force meger 以减少 segment 数量的优化,目的是降低 segment memory 占用)
5. 可以在集群节点上保存的分片数量与可用的堆内存大小成正比,但这在 Elasticsearch 中没有的固定限制。 一个很好的经验法则是:确保每个节点的分片数量保持在低于每 1GB 堆内存对应集群的分片在 20-25 之间。 因此,具有 32GB 堆内存的节点最多可以有 600-750 个分片;
6. 对于单索引的主分片数,有这么 2 个公式:节点数 <= 主分片数 *(副本数 + 1) 以及 (同一索引 shard 数量 * (1 + 副本数)) < 3 * 数据节点数,比如有 3 个节点全是数据节点,1 个副本,那么主分片数大于等于 1.5,同时同一索引总分片数需要小于 4.5,因为副本数为 1,所以单节点主分片最适为 2,索引总分片数最适为 6,这样每个节点的总分片为 4;
7. 单分片小于 20GB 的情况下,采用单分片较为合适,请求不存在网络抖动的顾虑;
**小结:**分片不超 20GB,且单节点总分片不超 600。比如互联网区域,每天新建索引(lw-greenbay-online) 1 个分片 1 个副本,3 个月前的历史索引都关闭,3 节点总共需要扛 90 * 2 = 180 个分片,每个分片大约 6 GB,可谓比较健康的状态。
7. 超大搜索聚合结果集的 fetch
避免用户 fetch 超大搜索聚合结果集,确实需要大量拉取数据可以采用 scan & scroll API 来实现。在 ElasticSearch 上搜索数据时,默认只会返回10条文档,当我们想获取更多结果,或者只要结果中的一个区间的数据时,可以通过 size 和 from 来指定。
GET /_search?size=3&from=20
如上的查询语句,会返回排序后的结果中第 20 到第 22 条数据。ElasticSearch 在收到这样的一个请求之后,每一个分片都会返回一个 top22 的搜索结果,然后将这些结果汇总排序,再选出 top22 ,最后取第 20 到第 22 条数据作为结果返回。这样会带来一个问题,当我们搜索的时候,如果想取出第 10001 条数据,那么就相当于每个一分片都要对数据进行排序,取出前 10001 条文档,然后 ElasticSearch 再将这些结果汇总再次排序,之后取出第 10001 条数据。这样对于 ElasticSearch 来说就会产生相当大的资源和性能开销。如果我们不要求 ElasticSearch 对结果进行排序,那么就会消耗很少的资源,所以针对此种情况,ElasticSearch 提供了scan & scroll的搜索方式。
GET /old_index/_search?search_type=scan&scroll=1m
{
"query": { "match_all": {}},
"size": 1000
}
我们可以首先通过如上的请求发起一个搜索,但是这个请求不会返回任何文档,它会返回一个 _scroll_id ,接下来我们再通过这个 id 来从 ElasticSearch 中读取数据:
GET /_search/scroll?scroll=1m
c2Nhbjs1OzExODpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzExOTpRNV9aY1VyUVM4U0 NMd2pjWlJ3YWlBOzExNjpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzExNzpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzEyMDpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzE7dG90YWxfaGl0czoxOw==
此时除了会返回搜索结果以外,还会再次返回一个 _scroll_id,当我们下次继续取数据时,需要用最新的 id。
十三、Elasticserach存储优化
1. 关闭不需要的功能
默认情况下 ElasticSearch 会将 indexs 和 doc values 添加到大多数字段中,以便可以搜索和聚合它们。 例如,如果有一个名为 foo 的数字字段,需要运行 histograms 但不需要 filter,则可以安全地禁用映射中此字段的索引:
PUT ${INDEX_NAME}
{
"mappings": {
"type": {
"properties": {
"foo": {
"type": "integer",
"index": false
}
}
}
}
}
text 字段在索引中存储规范化因子以便能够对文档进行评分。 如果只需要在 text 字段上使用 matching 功能,但不关心生成的 score,则可以命令 ElasticSearch 配置为不将规范写入索引:
PUT ${INDEX_NAME}
{
"mappings": {
"type": {
"properties": {
"foo": {
"type": "text",
"norms": false
}
}
}
}
}
text 字段也默认存储索引中的频率和位置。 频率用于计算分数,位置用于运行短语查询(phrase queries)。 如果不需要运行短语查询,可以告诉 ElasticSearch 不要索引位置:
PUT ${INDEX_NAME}
{
"mappings": {
"type": {
"properties": {
"foo": {
"type": "text",
"index_options": "freqs"
}
}
}
}
}
此外,如果不关心计分,则可以配置 ElasticSearch 以仅索引每个 term 的匹配文档。 这样做仍然可以在此字段上进行搜索(search),但是短语查询会引发错误,评分将假定 term 在每个文档中只出现一次。
PUT ${INDEX_NAME}
{
"mappings": {
"type": {
"properties": {
"foo": {
"type": "text",
"norms": false,
"index_options": "freqs"
}
}
}
}
}
2. 强制清除已标记删除的数据
Elasticsearch 是建立在 Apache Lucene 基础上的实时分布式搜索引擎,Lucene 为了提高搜索的实时性,采用不可再修改(immutable)方式将文档存储在一个个 segment 中。也就是说,一个 segment 在写入到存储系统之后,将不可以再修改。那么 Lucene 是如何从一个 segment 中删除一个被索引的文档呢?简单的讲,当用户发出命令删除一个被索引的文档#ABC 时,该文档并不会被马上从相应的存储它的 segment 中删除掉,而是通过一个特殊的文件来标记该文档已被删除。当用户再次搜索到 #ABC 时,Elasticsearch 在 segment 中仍能找到 #ABC,但由于 #ABC 文档已经被标记为删除,所以Lucene 会从发回给用户的搜索结果中剔除 #ABC,所以给用户感觉的是 #ABC 已经被删除了。
Elasticseach 会有后台线程根据 Lucene 的合并规则定期进行 segment merging 合并操作,一般不需要用户担心或者采取任何行动。被删除的文档在 segment 合并时,才会被真正删除掉。在此之前,它仍然会占用着JVM heap和操作系统的文件cach 等资源。在某些情况下,需要强制 Elasticsearch 进行 segment merging,已释放其占用的大量系统资源。
POST /${INDEX_NAME}/_forcemerge?max_num_segments=1&only_expunge_deletes=true&wait_for_completion=true
POST /${INDEX_PATTERN}/_forcemerge?max_num_segments=1&only_expunge_deletes=true&wait_for_completion=true
Force Merge 命令可强制进行 segment 合并,并删除所有标记为删除的文档。Segment merging 要消耗 CPU,以及大量的 I/O 资源,所以一定要在 ElasticSearch 集群处于维护窗口期间,并且有足够的 I/O 空间的(如:SSD)的条件下进行;否则很可能造成集群崩溃和数据丢失。
3. 减少副本数
最直接的存储优化手段是调整副本数,默认 ElasticSearch 是有 1 个副本的,假设对可用性要求不高,允许磁盘损坏情况下可能的数据缺失,可以把副本数调整为0,操作如下:
PUT /_template/${TEMPLATE_NAME}
{
"template":"${TEMPLATE_PATTERN}",
"settings" : {
"number_of_replicas" : 0
},
"version" : 1
}
其中 T E M P L A T E _ N A M E 表示模板名称,可以是不存在的,系统会新建。 {TEMPLATE\_NAME} 表示模板名称,可以是不存在的,系统会新建。 TEMPLATE_NAME表示模板名称,可以是不存在的,系统会新建。{TEMPLATE_PATTERN} 是用于匹配索引的表达式,比如 lw-greenbay-online-*。
与此相关的一个系统参数为:index.merge.scheduler.max_thread_count,默认值为 Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2)),这个值在 SSD 上工作没问题,但是 SATA 盘上还是使用 1 个线程为好,因为太多也来不及完成。
# SATA 请设置 merge 线程为 1
PUT /_template/${TEMPLATE_NAME}
{
"template":"${TEMPLATE_PATTERN}",
"settings" : {
"index.merge.scheduler.max_thread_count": 1
},
"version" : 1
}
4. 请勿使用默认的动态字符串映射
默认的动态字符串映射会将字符串字段索引为文本(text)和关键字(keyword)。 如果只需要其中的一个,这样做无疑是浪费的。 通常情况下,一个 id 字段只需要被索引为一个 keyword,而一个 body 字段只需要被索引为一个 text 字段。可以通过在字符串字段上配置显式映射或设置将字符串字段映射为文本(text)或关键字(keyword)的动态模板来禁用此功能。例如下面的模板,可以用来将 strings 字段映射为关键字:
PUT ${INDEX_NAME}
{
"mappings": {
"type": {
"dynamic_templates": [
{
"strings": {
"match_mapping_type": "string",
"mapping": {
"type": "keyword"
}
}
}
]
}
}
}
5. 禁用 _all 字段
_all 字段是由所有字段拼接成的超级字段,如果在查询中已知需要查询的字段,就可以考虑禁用它。
PUT /_template/${TEMPLATE_NAME}
{
"template": "${TEMPLATE_PATTERN}",
"settings" : {...},
"mappings": {
"type_1": {
"_all": {
"enabled": false
},
"properties": {...}
}
},
"version" : 1
}
6. 使用 best_compression
_source 字段和 stored fields 会占用大量存储,可以考虑使用 best_compression 进行压缩。默认的压缩方式为 LZ4,但需要更高压缩比的话,可以通过 inex.codec 进行设置,修改为 DEFLATE,在 force merge 后生效:
# Step1. 修改压缩算法为 best_compression
PUT /_template/${TEMPLATE_NAME}
{
"template":"${TEMPLATE_PATTERN}",
"settings" : {
"index.codec" : "best_compression"
},
"version" : 1
}
# Step2. force merge
POST /${INDEX_NAME}/_forcemerge?max_num_segments=1&wait_for_completion=true
POST /${INDEX_PATTERN}/_forcemerge?max_num_segments=1&wait_for_completion=true
7. 使用最优数据格式
我们为数字数据选择的类型可能会对磁盘使用量产生重大影响。 首先,应使用整数类型(byte,short,integer或long)来存储整数,浮点数应该存储在 scaled_float 中,或者存储在适合用例的最小类型中:使用 float 而不是 double,使用 half_float 而不是 float。
PUT /_template/${TEMPLATE_NAME}
{
"template": "${TEMPLATE_PATTERN}",
"settings" : {...},
"mappings": {
"type_1": {
"${FIELD_NAME}": {
"type": "integer"
},
"properties": {...}
}
},
"version" : 1
}
十四、Elasticserach搜索速度优化
1. 避免Join和Parent-Child
Join会使查询慢数倍、 Parent-Child会使查询慢数百倍,请在进行 query 语句编写的时候尽量避免。
2. 映射
某些数据本身是数字,但并不意味着它应该总是被映射为一个数字字段。 通常存储着标识符的字段(如ISBN)或来自另一个数据库的数字型记录,可能映射为 keyword 而不是 integer 或者 long 会更好些。
3. 避免使用 Scripts
之前 Groovy 脚本曝出了很大的漏洞,总的来说是需要避免使用的。如果必须要使用,尽量用 5.X 以上版本自带的 painless 和 expressions 引擎。
4. 根据四舍五入的日期进行查询
根据 timestamp 字段进行的查询通常不可缓存,因为匹配的范围始终在变化。 但就用户体验而言,以四舍五入对日期进行转换通常是可接受的,这样可以有效利用系统缓存。举例说明,有以下查询:
PUT index/type/1
{
"my_date": "2016-05-11T16:30:55.328Z"
}
GET index/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"my_date": {
"gte": "now-1h",
"lte": "now"
}
}
}
}
}
}
可以对时间范围进行替换:
GET index/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"my_date": {
"gte": "now-1h/m",
"lte": "now/m"
}
}
}
}
}
}
在这种情况下,我们四舍五入到分钟,所以如果当前时间是 16:31:29 ,范围查询将匹配 my_date 字段的值在 15:31:00 和16:31:59 之间的所有内容。 如果多个用户在同一分钟内运行包含这个范围的查询,查询缓存可以帮助加快速度。 用于四舍五入的时间间隔越长,查询缓存可以提供的帮助就越多,但要注意过于积极的舍入也可能会伤害用户体验。
为了能够利用查询缓存,建议将范围分割成大的可缓存部分和更小的不可缓存的部分,如下所示:
GET index/_search
{
"query": {
"constant_score": {
"filter": {
"bool": {
"should": [
{
"range": {
"my_date": {
"gte": "now-1h",
"lte": "now-1h/m"
}
}
},
{
"range": {
"my_date": {
"gt": "now-1h/m",
"lt": "now/m"
}
}
},
{
"range": {
"my_date": {
"gte": "now/m",
"lte": "now"
}
}
}
]
}
}
}
}
}
然而,这种做法可能会使查询在某些情况下运行速度较慢,因为由 bool 查询引入的开销可能会因更好地利用查询缓存而失败。
5. 对只读 indices 进行 force merge
建议将只读索引被合并到一个单独的分段中。 基于时间的索引通常就是这种情况:只有当前时间索引会写入数据,而旧索引是只读索引。
6. 预热 global ordinals
全局序号(global ordinals)是用于在关键字(keyword)字段上运行 terms aggregations 的数据结构。 由于 ElasticSearch 不知道聚合使用哪些字段、哪些字段不使用,所以它们在内存中被加载得很慢。 我们可以通过下面的 API 来告诉 ElasticSearch 通过配置映射来在 refresh 的时候加载全局序号:
PUT index
{
"mappings": {
"type": {
"properties": {
"foo": {
"type": "keyword",
"eager_global_ordinals": true
}
}
}
}
}
十五、Elasticserach写入性能优化
之前描述了 ElasticSearch 在内存管理方面的优化,接下来梳理下如何对写入性能进行优化,写入性能的优化也和 HBase 类似,无非就是增加吞吐,而增加吞吐的方法就是增大刷写间隔、合理设置线程数量、开启异步刷写(允许数据丢失的情况下)。
1. 增大刷写间隔
通过修改主配置文件 elasticsearch.yml 或者 Rest API 都可以对 index.refresh_interval进行修改,增大该属性可以提升写入吞吐。
PUT /_template/{TEMPLATE_NAME}
{
"template":"{INDEX_PATTERN}",
"settings" : {
"index.refresh_interval" : "30s"
}
}
PUT {INDEX_PAATERN}/_settings
{
"index.refresh_interval" : "30s"
}
2. 合理设置线程数量
调整 elasticsearch.yml ,对 bulk/flush 线程池进行调优,根据本机实际配置:
threadpool.bulk.type:fixed
threadpool.bulk.size:8 #(CPU核数)
threadpool.flush.type:fixed
threadpool.flush.size:8 #(CPU核数)
3. 开启异步刷写
如果允许数据丢失,可以对特定 index 开启异步刷写:
PUT /_template/{TEMPLATE_NAME}
{
"template":"{INDEX_PATTERN}",
"settings" : {
"index.translog.durability": "async"
}
}
PUT {INDEX_PAATERN}/_settings
{
"index.translog.durability": "async"
}
十六、Elasticserach审计优化
1. 开启慢查询日志
不论是数据库还是搜索引擎,对于问题的排查,开启慢查询日志是十分必要的,ElasticSearch 开启慢查询的方式有多种,但是最常用的是调用模板 API 进行全局设置:
PUT /_template/{TEMPLATE_NAME}
{
"template":"{INDEX_PATTERN}",
"settings" : {
"index.indexing.slowlog.level": "INFO",
"index.indexing.slowlog.threshold.index.warn": "10s",
"index.indexing.slowlog.threshold.index.info": "5s",
"index.indexing.slowlog.threshold.index.debug": "2s",
"index.indexing.slowlog.threshold.index.trace": "500ms",
"index.indexing.slowlog.source": "1000",
"index.search.slowlog.level": "INFO",
"index.search.slowlog.threshold.query.warn": "10s",
"index.search.slowlog.threshold.query.info": "5s",
"index.search.slowlog.threshold.query.debug": "2s",
"index.search.slowlog.threshold.query.trace": "500ms",
"index.search.slowlog.threshold.fetch.warn": "1s",
"index.search.slowlog.threshold.fetch.info": "800ms",
"index.search.slowlog.threshold.fetch.debug": "500ms",
"index.search.slowlog.threshold.fetch.trace": "200ms"
},
"version" : 1
}
对于已经存在的 index 使用 settings API:
PUT {INDEX_PAATERN}/_settings
{
"index.indexing.slowlog.level": "INFO",
"index.indexing.slowlog.threshold.index.warn": "10s",
"index.indexing.slowlog.threshold.index.info": "5s",
"index.indexing.slowlog.threshold.index.debug": "2s",
"index.indexing.slowlog.threshold.index.trace": "500ms",
"index.indexing.slowlog.source": "1000",
"index.search.slowlog.level": "INFO",
"index.search.slowlog.threshold.query.warn": "10s",
"index.search.slowlog.threshold.query.info": "5s",
"index.search.slowlog.threshold.query.debug": "2s",
"index.search.slowlog.threshold.query.trace": "500ms",
"index.search.slowlog.threshold.fetch.warn": "1s",
"index.search.slowlog.threshold.fetch.info": "800ms",
"index.search.slowlog.threshold.fetch.debug": "500ms",
"index.search.slowlog.threshold.fetch.trace": "200ms"
}
这样,在日志目录下的慢查询日志就会有输出记录必要的信息了。
十七、ElasticSearch运维手册
在管理es集群时,我们可以直接向es安装所在服务器9200端口(默认端口)发送请求查看查询结果。也可使用Cerebro可视化管理工具对es进行管理。
1. ElasticSearch管理工具
通过 GET 请求发送 cat 命名可以列出所有可用的cat API,我们可以使用postman或者浏览器发送请求到es的9200端口。
查询结果如下,请求返回了所有可用的cat API
又或者可以使用Cerebro可视化管理工具对es进行管理。比如我的管理工具访问地址http://172.16.60.14:9002/#/connect
如下图所示,进入到可视化管理界面以后,选择顶部导航菜单more,在下拉选项框中选择cat apis。
进入cat api查询页面
2. 常用cat API介绍
1. 健康检查
/_cat/health 和 /_cat/health?v 查询请求带了?v 将返回表头,在postman中我们可以发起如下请求,可以看到es集群的健康状况,但是结果并不直观。
通过es管理工具很方便的就能使用cat api,下拉框中选中health
点击excute按钮即可对需要管理的es集群进行健康检查。
2. 节点分片数量查询
/_cat/allocation?v
查询每个节点上分配的分片(shard)的数量和每个分片(shard)所使用的硬盘容量
3. 节点统计
/_cat/nodes 或/_cat/nodes?v
显示当前es集群节点的状况。
**4.**文档数量查询
/_cat/count?v
快速查询当前集群document数量
5. 查询master节点
/_cat/master?v
用于显示master的节点ID,绑定IP地址,节点名称。
6. 查询节点自定义属性
/_cat/nodeattrs?v
显示节点自定义属性。
7. 执行任务查询
/_cat/pending_tasks?v
8. 索引分片恢复查询
/_cat/recovery?v
索引分片正在恢复或者已经完成恢复的相关信息。
9. 节点线程查询
/_cat/thread_pool
输出每个节点的线程池统计信息,默认情况下显示正在活动、队列和被拒绝的统计信息。
10. 索引所在分片查询
/_cat/shards
输出节点包含分片的详细信息(当前分片是primary shard还是 replica shard,doc的数量,硬盘上占用的字节已经该节点被分配在哪里等)。
十八、Elasticsearch 是如何做到分布式,可扩展和近实时搜索的
1. 集群(Cluster)
Elasticsearch 的集群搭建很简单,不需要依赖第三方协调管理组件,自身内部就实现了集群的管理功能。Elasticsearch 集群由一个或多个 Elasticsearch 节点组成,每个节点配置相同的 cluster.name 即可加入集群,默认值为 “elasticsearch”。确保不同的环境中使用不同的集群名称,否则最终会导致节点加入错误的集群。一个 Elasticsearch 服务启动实例就是一个节点(Node)。节点通过 node.name 来设置节点名称,如果不设置则在启动时给节点分配一个随机通用唯一标识符作为名称。
① 发现机制
那么有一个问题,**Elasticsearch 内部是如何通过一个相同的设置 cluster.name 就能将不同的节点连接到同一个集群的?**答案是 Zen Discovery。
Zen Discovery 是 Elasticsearch 的内置默认发现模块(发现模块的职责是发现集群中的节点以及选举 Master 节点)。它提供单播和基于文件的发现,并且可以扩展为通过插件支持云环境和其他形式的发现。Zen Discovery 与其他模块集成,例如,节点之间的所有通信都使用 Transport 模块完成。节点使用发现机制通过 Ping 的方式查找其他节点。
Elasticsearch 默认被配置为使用单播发现,以防止节点无意中加入集群。只有在同一台机器上运行的节点才会自动组成集群。如果集群的节点运行在不同的机器上,使用单播,你可以为 Elasticsearch 提供一些它应该去尝试连接的节点列表。
当一个节点联系到单播列表中的成员时,它就会得到整个集群所有节点的状态,然后它会联系 Master 节点,并加入集群。这意味着单播列表不需要包含集群中的所有节点, 它只是需要足够的节点,当一个新节点联系上其中一个并且说上话就可以了。
如果你使用 Master 候选节点作为单播列表,你只要列出三个就可以了。这个配置在 elasticsearch.yml 文件中:
discovery.zen.ping.unicast.hosts: ["host1", "host2:port"]
节点启动后先 Ping ,如果 discovery.zen.ping.unicast.hosts 有设置,则 Ping 设置中的 Host ,否则尝试 ping localhost 的几个端口。Elasticsearch 支持同一个主机启动多个节点,Ping 的 Response 会包含该节点的基本信息以及该节点认为的 Master 节点。
选举开始,先从各节点认为的 Master 中选,规则很简单,按照 ID 的字典序排序,取第一个。如果各节点都没有认为的 Master ,则从所有节点中选择,规则同上。这里有个限制条件就是 discovery.zen.minimum_master_nodes ,如果节点数达不到最小值的限制,则循环上述过程,直到节点数足够可以开始选举。最后选举结果是肯定能选举出一个 Master ,如果只有一个 Local 节点那就选出的是自己。如果当前节点是 Master ,则开始等待节点数达到 discovery.zen.minimum_master_nodes,然后提供服务。如果当前节点不是 Master ,则尝试加入 Master 。Elasticsearch 将以上服务发现以及选主的流程叫做 Zen Discovery 。
由于它支持任意数目的集群( 1- N ),所以不能像 Zookeeper 那样限制节点必须是奇数,也就无法用投票的机制来选主,而是通过一个规则。只要所有的节点都遵循同样的规则,得到的信息都是对等的,选出来的主节点肯定是一致的。但分布式系统的问题就出在信息不对等的情况,这时候很容易出现脑裂(Split-Brain)的问题。大多数解决方案就是设置一个 Quorum 值,要求可用节点必须大于 Quorum(一般是超过半数节点),才能对外提供服务。而 Elasticsearch 中,这个 Quorum 的配置就是 discovery.zen.minimum_master_nodes 。
② 节点的角色
每个节点既可以是候选主节点也可以是数据节点,通过在配置文件 …/config/elasticsearch.yml 中设置即可,默认都为 true。
node.master: true #是否候选主节点
node.data: true #是否数据节点
数据节点负责数据的存储和相关的操作,例如对数据进行增、删、改、查和聚合等操作,所以数据节点(Data 节点)对机器配置要求比较高,对 CPU、内存和 I/O 的消耗很大。
通常随着集群的扩大,需要增加更多的数据节点来提高性能和可用性。候选主节点可以被选举为主节点(Master 节点),集群中只有候选主节点才有选举权和被选举权,其他节点不参与选举的工作。主节点负责创建索引、删除索引、跟踪哪些节点是群集的一部分,并决定哪些分片分配给相关的节点、追踪集群中节点的状态等,稳定的主节点对集群的健康是非常重要的。
一个节点既可以是候选主节点也可以是数据节点,但是由于数据节点对 CPU、内存核 I/O 消耗都很大。所以如果某个节点既是数据节点又是主节点,那么可能会对主节点产生影响从而对整个集群的状态产生影响。
因此为了提高集群的健康性,我们应该对 Elasticsearch 集群中的节点做好角色上的划分和隔离。可以使用几个配置较低的机器群作为候选主节点群。主节点和其他节点之间通过 Ping 的方式互检查,主节点负责 Ping 所有其他节点,判断是否有节点已经挂掉。其他节点也通过 Ping 的方式判断主节点是否处于可用状态。
虽然对节点做了角色区分,但是用户的请求可以发往任何一个节点,并由该节点负责分发请求、收集结果等操作,而不需要主节点转发。这种节点可称之为协调节点,协调节点是不需要指定和配置的,集群中的任何节点都可以充当协调节点的角色。
③ 脑裂现象
同时如果由于网络或其他原因导致集群中选举出多个 Master 节点,使得数据更新时出现不一致,这种现象称之为脑裂,即集群中不同的节点对于 Master 的选择出现了分歧,出现了多个 Master 竞争。
"脑裂"问题可能有以下几个原因造成:
1. 网络问题:集群间的网络延迟导致一些节点访问不到 Master,认为 Master 挂掉了从而选举出新的 Master,并对 Master 上的分片和副本标红,分配新的主分片。
2. 节点负载:主节点的角色既为 Master 又为 Data,访问量较大时可能会导致 ES 停止响应(假死状态)造成大面积延迟,此时其他节点得不到主节点的响应认为主节点挂掉了,会重新选取主节点。
3. 内存回收:主节点的角色既为 Master 又为 Data,当 Data 节点上的 ES 进程占用的内存较大,引发 JVM 的大规模内存回收,造成 ES 进程失去响应。
为了避免脑裂现象的发生,我们可以从原因着手通过以下几个方面来做出优化措施:
**1. 适当调大响应时间,减少误判。**通过参数 discovery.zen.ping_timeout 设置节点状态的响应时间,默认为 3s,可以适当调大。如果 Master 在该响应时间的范围内没有做出响应应答,判断该节点已经挂掉了。调大参数(如 6s,discovery.zen.ping_timeout:6),可适当减少误判。
**2. 选举触发。**我们需要在候选集群中的节点的配置文件中设置参数 discovery.zen.munimum_master_nodes 的值。这个参数表示在选举主节点时需要参与选举的候选主节点的节点数,默认值是 1,官方建议取值(master_eligibel_nodes/2)+1,其中 master_eligibel_nodes 为候选主节点的个数。这样做既能防止脑裂现象的发生,也能最大限度地提升集群的高可用性,因为只要不少于 discovery.zen.munimum_master_nodes 个候选节点存活,选举工作就能正常进行。当小于这个值的时候,无法触发选举行为,集群无法使用,不会造成分片混乱的情况。
**3. 角色分离。**即是上面我们提到的候选主节点和数据节点进行角色分离,这样可以减轻主节点的负担,防止主节点的假死状态发生,减少对主节点“已死”的误判。
2. 分片(Shards)
Elasticsearch 支持PB级全文搜索,当索引上的数据量太大的时候,Elasticsearch 通过水平拆分的方式将一个索引上的数据拆分出来分配到不同的数据块上,拆分出来的数据库块称之为一个分片。这类似于 MySQL 的分库分表,只不过 MySQL 分库分表需要借助第三方组件而 Elasticsearch 内部自身实现了此功能。
在一个多分片的索引中写入数据时,通过路由来确定具体写入哪一个分片中,所以在创建索引的时候需要指定分片的数量,并且分片的数量一旦确定就不能修改。分片的数量和下面介绍的副本数量都是可以通过创建索引时的 Settings 来配置,Elasticsearch 默认为一个索引创建 5 个主分片, 并分别为每个分片创建一个副本。
PUT /myIndex
{
"settings" : {
"number_of_shards" : 5,
"number_of_replicas" : 1
}
}
Elasticsearch 通过分片的功能使得索引在规模上和性能上都得到提升,每个分片都是 Lucene 中的一个索引文件,每个分片必须有一个主分片和零到多个副本。
3. 副本(Replicas)
副本就是对分片的 Copy,每个主分片都有一个或多个副本分片,当主分片异常时,副本可以提供数据的查询等操作。主分片和对应的副本分片是不会在同一个节点上的,所以副本分片数的最大值是 N-1(其中 N 为节点数)。对文档的新建、索引和删除请求都是写操作,必须在主分片上面完成之后才能被复制到相关的副本分片。
Elasticsearch 为了提高写入的能力这个过程是并发写的,同时为了解决并发写的过程中数据冲突的问题,Elasticsearch 通过乐观锁的方式控制,每个文档都有一个 _version (版本)号,当文档被修改时版本号递增。一旦所有的副本分片都报告写成功才会向协调节点报告成功,协调节点向客户端报告成功。
从上图可以看出为了达到高可用,Master 节点会避免将主分片和副本分片放在同一个节点上。假设这时节点 Node1 服务宕机了或者网络不可用了,那么主节点上主分片 S0 也就不可用了。幸运的是还存在另外两个节点能正常工作,这时 ES 会重新选举新的主节点,而且这两个节点上存在我们所需要的 S0 的所有数据。
我们会将 S0 的副本分片提升为主分片,这个提升主分片的过程是瞬间发生的。此时集群的状态将会为 Yellow。为什么我们集群状态是 Yellow 而不是 Green 呢?虽然我们拥有所有的 2 个主分片,但是同时设置了每个主分片需要对应两份副本分片,而此时只存在一份副本分片。所以集群不能为 Green 的状态。
如果我们同样关闭了 Node2 ,我们的程序依然可以保持在不丢失任何数据的情况下运行,因为 Node3 为每一个分片都保留着一份副本。如果我们重新启动 Node1 ,集群可以将缺失的副本分片再次进行分配,那么集群的状态又将恢复到原来的正常状态。如果 Node1 依然拥有着之前的分片,它将尝试去重用它们,只不过这时 Node1 节点上的分片不再是主分片而是副本分片了,如果期间有更改的数据只需要从主分片上复制修改的数据文件即可。
小结:
1. 将数据分片是为了提高可处理数据的容量和易于进行水平扩展,为分片做副本是为了提高集群的稳定性和提高并发量。
2. 副本是乘法,越多消耗越大,但也越保险。分片是除法,分片越多,单分片数据就越少也越分散。
3. 副本越多,集群的可用性就越高,但是由于每个分片都相当于一个 Lucene 的索引文件,会占用一定的文件句柄、内存及 CPU。并且分片间的数据同步也会占用一定的网络带宽,所以索引的分片数和副本数也不是越多越好。
4. 映射(Mapping)
映射是用于定义 ES 对索引中字段的存储类型、分词方式和是否存储等信息,就像数据库中的 Schema ,描述了文档可能具有的字段或属性、每个字段的数据类型。只不过关系型数据库建表时必须指定字段类型,而 ES 对于字段类型可以不指定然后动态对字段类型猜测,也可以在创建索引时具体指定字段的类型。
对字段类型根据数据格式自动识别的映射称之为动态映射(Dynamic Mapping),我们创建索引时具体定义字段类型的映射称之为静态映射或显示映射(Explicit Mapping)。在讲解动态映射和静态映射的使用前,我们先来了解下 ES 中的数据有哪些字段类型?之后我们再讲解为什么我们创建索引时需要建立静态映射而不使用动态映射。
集群的高可用性,因为只要不少于 discovery.zen.munimum_master_nodes 个候选节点存活,选举工作就能正常进行。当小于这个值的时候,无法触发选举行为,集群无法使用,不会造成分片混乱的情况。
**3. 角色分离。**即是上面我们提到的候选主节点和数据节点进行角色分离,这样可以减轻主节点的负担,防止主节点的假死状态发生,减少对主节点“已死”的误判。
2. 分片(Shards)
Elasticsearch 支持PB级全文搜索,当索引上的数据量太大的时候,Elasticsearch 通过水平拆分的方式将一个索引上的数据拆分出来分配到不同的数据块上,拆分出来的数据库块称之为一个分片。这类似于 MySQL 的分库分表,只不过 MySQL 分库分表需要借助第三方组件而 Elasticsearch 内部自身实现了此功能。
在一个多分片的索引中写入数据时,通过路由来确定具体写入哪一个分片中,所以在创建索引的时候需要指定分片的数量,并且分片的数量一旦确定就不能修改。分片的数量和下面介绍的副本数量都是可以通过创建索引时的 Settings 来配置,Elasticsearch 默认为一个索引创建 5 个主分片, 并分别为每个分片创建一个副本。
PUT /myIndex
{
"settings" : {
"number_of_shards" : 5,
"number_of_replicas" : 1
}
}
Elasticsearch 通过分片的功能使得索引在规模上和性能上都得到提升,每个分片都是 Lucene 中的一个索引文件,每个分片必须有一个主分片和零到多个副本。
3. 副本(Replicas)
副本就是对分片的 Copy,每个主分片都有一个或多个副本分片,当主分片异常时,副本可以提供数据的查询等操作。主分片和对应的副本分片是不会在同一个节点上的,所以副本分片数的最大值是 N-1(其中 N 为节点数)。对文档的新建、索引和删除请求都是写操作,必须在主分片上面完成之后才能被复制到相关的副本分片。
Elasticsearch 为了提高写入的能力这个过程是并发写的,同时为了解决并发写的过程中数据冲突的问题,Elasticsearch 通过乐观锁的方式控制,每个文档都有一个 _version (版本)号,当文档被修改时版本号递增。一旦所有的副本分片都报告写成功才会向协调节点报告成功,协调节点向客户端报告成功。
[外链图片转存中…(img-0xR8PWo5-1726977369418)]
从上图可以看出为了达到高可用,Master 节点会避免将主分片和副本分片放在同一个节点上。假设这时节点 Node1 服务宕机了或者网络不可用了,那么主节点上主分片 S0 也就不可用了。幸运的是还存在另外两个节点能正常工作,这时 ES 会重新选举新的主节点,而且这两个节点上存在我们所需要的 S0 的所有数据。
我们会将 S0 的副本分片提升为主分片,这个提升主分片的过程是瞬间发生的。此时集群的状态将会为 Yellow。为什么我们集群状态是 Yellow 而不是 Green 呢?虽然我们拥有所有的 2 个主分片,但是同时设置了每个主分片需要对应两份副本分片,而此时只存在一份副本分片。所以集群不能为 Green 的状态。
如果我们同样关闭了 Node2 ,我们的程序依然可以保持在不丢失任何数据的情况下运行,因为 Node3 为每一个分片都保留着一份副本。如果我们重新启动 Node1 ,集群可以将缺失的副本分片再次进行分配,那么集群的状态又将恢复到原来的正常状态。如果 Node1 依然拥有着之前的分片,它将尝试去重用它们,只不过这时 Node1 节点上的分片不再是主分片而是副本分片了,如果期间有更改的数据只需要从主分片上复制修改的数据文件即可。
小结:
1. 将数据分片是为了提高可处理数据的容量和易于进行水平扩展,为分片做副本是为了提高集群的稳定性和提高并发量。
2. 副本是乘法,越多消耗越大,但也越保险。分片是除法,分片越多,单分片数据就越少也越分散。
3. 副本越多,集群的可用性就越高,但是由于每个分片都相当于一个 Lucene 的索引文件,会占用一定的文件句柄、内存及 CPU。并且分片间的数据同步也会占用一定的网络带宽,所以索引的分片数和副本数也不是越多越好。
4. 映射(Mapping)
映射是用于定义 ES 对索引中字段的存储类型、分词方式和是否存储等信息,就像数据库中的 Schema ,描述了文档可能具有的字段或属性、每个字段的数据类型。只不过关系型数据库建表时必须指定字段类型,而 ES 对于字段类型可以不指定然后动态对字段类型猜测,也可以在创建索引时具体指定字段的类型。
对字段类型根据数据格式自动识别的映射称之为动态映射(Dynamic Mapping),我们创建索引时具体定义字段类型的映射称之为静态映射或显示映射(Explicit Mapping)。在讲解动态映射和静态映射的使用前,我们先来了解下 ES 中的数据有哪些字段类型?之后我们再讲解为什么我们创建索引时需要建立静态映射而不使用动态映射。
[外链图片转存中…(img-IYOMb2V1-1726977369419)]