一、ES组件介绍
1.shard
一个Shard就是一个Lucene实例,是一个完整的搜索引擎【本人未用过Lucene,这句话摘自官网】。
主分片的数量在index创建的时候就决定好了, 副本分片的数量可以随时改变。很多博主说 “主分片数量不可以被修改”,那么为什么呢?
路由计算 任一条数据计算其对应分片的方式如下
shard = hash(routing) % number_of_primary_shards
每个数据都有一个 routing 参数,默认情况下,就使用其 _id
值。将其 _id
值计算哈希后,对索引的主分片数取余,就是数据实际应该存储到的分片 ID。
由于取余这个计算,完全依赖于分母,所以导致 ES 索引有一个限制,索引的主分片数,不可以随意修改。因为一旦主分片数不一样,所以数据的存储位置计算结果都会发生改变,索引数据就完全不可读了。
shard 分片大小确定 ?
a. 分片数过多会导致检索时打开比较多的文件,多台服务器之间通讯成本加大。
b. 而分片数过少会导至单个分片索引过大,所以检索速度也会慢。
c. 建议单个分片最多存储10G-20G左右的索引数据,并且尽量集群的所有节点都分片数一致,不要出现分片数不一样导致的一个实例负载过大,等待合并的时间变长;
2.shard副本
使用副本的优点:数据备份,提高对大索引的查询效率,建议副本在1-2个左右,过多的副本会延迟合并时间以及磁盘使用率提高,性价比不高
当要导入大量数据时,设置副本为0,之后动态添加副本 //( 效率较大)当导入大量索引时,设置了副本数,es会同时打开副本同步,消耗系统资源,同时需要额外提供主副之间的通信
设置副本数curl -XPOST 'http://localhost:9200/{_index}/_settings' -d '{"index":{"number_of_replicas":1}}'
3.segment
每个shard分片包含多个segment,每一个segment都是一个倒排索引;在查询的时,会把所有的segment查询结果汇总归并后返回;
Segment是什么?
每个分片包含多个segment(段),每一个segment都是一个倒排索引。默认的最大 segment 大小是 5GB。
在查询的时,会把所有的segment查询结果汇总归并后返回
1.segment是不可变的,物理上你并不能从中删除信息,所以在删除文档的时候,是在文档上面打上一个删除的标记,然后在执行段合并的时候,进行删除
2.索引segment段的个数越多,搜索性能越低且消耗内存更多
4.document
一个document即一条记录。
在Elasticsearch中, 需要搞清楚几个名词,如segment/doc/term/token/shard/index等, 其实segment/doc/term/token都是lucene中的概念(然而,原谅本人并未用到过Lucene)
index: 在ES中类似数据库中db
document: 索引和搜索的主要数据载体,对应写入到ES中的一个doc。
field: document中的各个字段。
term: 词项,搜索时的一个单位,代表文本中的某个词。
token: 词条,词项(term)在字段(field)中的一次出现,包括词项的文本、开始和结束的位移、类型等信息。
Lucene内部使用的是倒排索引的数据结构, 将词项(term)映射到文档(document)。
例如:某3个文档,假设某个字段的文本如下
ElasticSearch Server (文档1)
Matering ElasticSearch (文档2)
Apache solr 4 Cookbook (文档3)
term | 次数 | doc id |
4 | 1 | 3 |
Apache | 1 | 3 |
Cookbook | 1 | 3 |
ElasticSearch | 2 | 1,2 |
Matering | 1 | 1 |
Server | 1 | 1 |
solr | 1 | 3 |
那么涉及优化首当其冲:首先对不必要的字段不做分词也就是不做索引,禁止内存交换
二、ES 读写流程
先来认识一下三个重要过程 refresh translog flush
refresh : buffer 缓冲区 -> fileSystem cache 文件系统缓存。 是为了提升实时性存在,segment 不落盘,但可以及时被搜索;
translog : 是为了可靠性存在的,将所有未落盘的写相关操作都落盘记录,和 refresh flush 不相关;
flush : fileSystem cache 文件系统缓存 --> 磁盘。是 segment 落盘的操作,在这个操作的同时会触发一次 refresh,落盘后也会把 translog 清空
1. 写入index buffer 缓冲区。
在创建索引的时候,elasticsearch会把文档信息写到内存buffer中(为了安全,也一起写到translog)
2. refresh到 fileSystem cache 系统缓存,生成一个新的segment。
实现近实时搜索关键。每个shard每隔1秒都会refresh一次,每次refresh都会创建一个新的segment,并且打开segment段使得搜索可见。即此时刚写入的 document 可查。
refresh 完成后, 缓存被清空但是Translog事务日志不会
3. segment被flush到磁盘。
虽然写入的segment可查询,但是还没有持久化到磁盘上。因此,还是会存在丢失的可能性的。 所以,elasticsearch会执行flush操作,把segment持久化到磁盘上并清除translog的数据(因为这个时候,数据已经写到磁盘上,不在需要了)。
flush 之后,segment段被全量提交,并且事务日志被清空
shard分片每30分钟被自动 flush 刷新,或者在 translog 大于500m时刷新
flush API 支持执行一个手工的 flush 刷新
POST /openai_detail_log/_flush
POST /_flush?wait_for_ongoing
flush 刷新 openai_detail_log 索引。
flush 刷新所有的索引并且并且等待所有刷新在返回前完成。
4. segment合并。
合并很多小的segment为更大的segment。.当索引数据不断增长时,对应的segment也会不断的增多,查询性能可能就会下降。因此,Elasticsearch会触发segment合并的线程,把很多小的segment合并成更大的segment,然后删除小的segment。
a. 合并的segment可以是磁盘上已经commit过的索引,也可以在内存中还未commit的segment
b. segment是不可变的,当我们更新一个文档时,会把老的数据打上已删除的标记,然后写一条新的文档。在执行flush操作的时候,才会把已删除的记录物理删除掉 [ 标记删除的document不会被合并到新的更大的segment里面 ]。
c. merge进程会在后台选择一些小体积的segments,然后将其合并成一个更大的segment,这个过程不会打断当前的索引和搜索功能。一旦merge完成,旧的segments就会被删除。
segment 合并
merge细节如下
被标记删除的数据是在merge的时候才会被真正的删除。
merge的时候会去修改commit point文件。
1. 新的segment会被flush到磁盘
2. 然后会生成新的commit point文件,包含新的segment名称,并排除掉旧的segment和那些被合并过的小的segment
3. 接着新的segment会被打开用于搜索
4. 最后旧的segment会被删除掉
至此原来标记伪删除的document都会被清理掉,如果不加控制,合并一个大的segment会消耗比较多的io和cpu资源,同时也会搜索性能造成影响,所以默认情况下es已经对合并线程做了资源限额以便于它不会搜索性能造成太大影响。
merge是有很多策略的,如下引用官网
index.merge.policy.floor_segment 默认 2MB,小于这个大小的 segment,优先被归并。
index.merge.policy.max_merge_at_once 默认一次最多归并 10 个 segment
index.merge.policy.max_merge_at_once_explicit 默认 forcemerge 时一次最多归并 30 个 segment。
index.merge.policy.max_merged_segment 默认 5 GB,大于这个大小的 segment,不用参与归并。forcemerge 除外。
根据这段策略,其实我们也可以从另一个角度考虑如何减少 segment 归并的消耗以及提高响应的办法:加大 flush 间隔,尽量让每次新生成的 segment 本身大小就比较大。
merge动作利弊分析
1. merge的动作越少,merge带来的压力也就越少,写入性能会更好;
但是segment file的文件数就越多,消耗文件句柄、内存、cpu也就越多;并且查询需要check每个segment file,查询性能也更差。
2.merge的动作越频繁,则segment file越少,毋庸置疑查询的性能越好,但是频繁的merge会损害写入性能。
什么是提交点 Commit Point ?
一次完整的提交会将segment段刷到磁盘,并写入一个包含所有段列表的提交点。
Elasticsearch 在启动或重新打开一个索引的过程中使用这个提交点来判断哪些段隶属于当前分片。
5. translog
索引数据的一致性通过 translog 保证。那么 translog 文件自己呢?
默认情况下,Elasticsearch 每 5 秒,或每次请求操作结束前,会强制刷新 translog 日志到磁盘上。
后者是 Elasticsearch 2.0 新加入的特性。为了保证不丢数据,每次 index、bulk、delete、update 完成的时候,一定触发刷新 translog 到磁盘上,才给请求返回 200 OK。这个改变在提高数据安全性的同时当然也降低了一点性能。
三、副本一致性
阅读完官网后发现分布式的副本一致性,也是巧妙地雷同 -- 此处简直就是kafka的亲兄弟。
官网实在是写的太好了,强烈建议去读一下官网。你想要的都在那里。
作为分布式系统,数据副本可算是一个标配。ES 数据写入流程,自然也涉及到副本。在有副本配置的情况下,数据从发向 ES 节点,到接到 ES 节点响应返回,流向如下:
- 客户端请求发送给 Node 1 节点,注意图中 Node 1 是 Master 节点,实际完全可以不是。
- Node 1 用数据的
_id
取余计算得到应该讲数据存储到 shard 0 上。通过 cluster state 信息发现 shard 0 的主分片已经分配到了 Node 3 上。Node 1 转发请求数据给 Node 3。 - Node 3 完成请求数据的索引过程,存入主分片 0。然后并行转发数据给分配有 shard 0 的副本分片的 Node 1 和 Node 2。当收到任一节点汇报副本分片数据写入成功,Node 3 即返回给初始的接收节点 Node 1,宣布数据写入成功。Node 1 返回成功响应给客户端。
此处要区分一下 分片shard 和 replicas 副本。
这个过程中,有几个参数可以用来控制或变更其行为:
- wait_for_active_shards 上面示例中,2 个副本分片只要有 1 个成功,就可以返回给客户端了。这点也是有配置项的。其默认值的计算来源如下:
int( (primary + number_of_replicas) / 2 ) + 1
根据需要,也可以将参数设置为 one,表示仅写完主分片就返回,等同于 async;还可以设置为 all,表示等所有副本分片都写完才能返回。
- timeout 如果集群出现异常,有些分片当前不可用,ES 默认会等待 1 分钟看分片能否恢复。可以使用
?timeout=30s
参数来缩短这个等待时间。
副本配置和分片配置不一样,是可以随时调整的。有些较大的索引,甚至可以在做 forcemerge 前,先把副本全部取消掉,等 optimize 完后,再重新开启副本,节约单个 segment 的重复归并消耗。
# curl -XPUT http://127.0.0.1:9200/logstash-mweibo-2015.05.02/_settings -d '{
"index": { "number_of_replicas" : 0 }
}'
四、ES索引写入性能优化
1.用bulk批量写入
2.增大refresh间隔
默认的refresh间隔是1s,用index.refresh_interval参数可以设置,这样会其强迫es每秒中都将内存中的数据写入磁盘中,创建一个新的segment file。
正是这个间隔,让我们每次写入数据后,1s以后才能看到。
但是如果我们将这个间隔调大,比如30s,可以接受写入的数据30s后才看到,那么我们就可以获取更大的写入吞吐量,因为30s内都是写内存的,每隔30s才会创建一个segment file。
3.使用自动生成的id
如果我们要手动给es document设置一个id,那么es需要每次都去确认一下那个id是否存在,这个过程是比较耗费时间的。
如果我们使用自动生成的id,那么es就可以跳过这个步骤,写入性能会更好。对于你的业务中的表id,可以作为es document的一个field
五、ES如何做到亿级数据查询毫秒级返回?
偶然发现了这篇文章,内容为。为公司新项目作预热:每日2000+w图片数据写入,需要随机检索20w。
原文地址 https://mp.weixin.qq.com/s/yNXcFhZ3OKEXXMq2lw085w
博主主要观点如下
1. 性能优化的杀手锏——filesystem cache
es 的搜索引擎严重依赖于底层的 filesystem cache,你如果给 filesystem cache 更多的内存,尽量让内存可以容纳所有的 idx segment file 索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高。
那么,可以对不必要的字段不做分词也就是不做索引,禁止内存交换;当然也可以把非索引字段放入Hbase,那么架构为 ES + Hbase
2.数据预热
对于比较热的、经常会有人访问的数据,最好做一个专门的缓存预热子系统,就是对热数据每隔一段时间,就提前访问一下,让数据进入 filesystem cache 里面去。这样下次别人访问的时候,性能一定会好很多。
3.冷热分离
es 可以做类似于 mysql 的水平拆分,就是说将大量的访问很少、频率很低的数据,单独写一个索引,然后将访问很频繁的热数据单独写一个索引。最好是将冷数据写入一个索引中,然后热数据写入另外一个索引中,这样可以确保热数据在被预热之后,尽量都让他们留在 filesystem os cache 里,别让冷数据给冲刷掉。
4.document 模型设计
es 里面的复杂的关联查询尽量别用,一旦用了性能一般都不太好。
对于一些太复杂的操作,比如 join/nested/parent-child 搜索都要尽量避免,性能都很差。
5.分页性能优化
假如你每页是 10 条数据,你现在要查询第 100 页,实际上是会把每个 shard 上存储的前 1000 条数据都查到一个协调节点上,如果你有个 5 个 shard,那么就有 5000 条数据,接着协调节点对这 5000 条数据进行一些合并、处理,再获取到最终第 100 页的 10 条数据。
分布式的,要查第 100 页的 10 条数据,不可能说从 5 个 shard,每个 shard 就查 2 条数据,最后到协调节点合并成 10 条数据吧?你必须得从每个 shard 都查 1000 条数据过来,然后根据你的需求进行排序、筛选等等操作,最后再次分页,拿到里面第 100 页的数据。你翻页的时候,翻的越深,每个 shard 返回的数据就越多,而且协调节点处理的时间越长,非常坑爹。所以用 es 做分页的时候,你会发现越翻到后面,就越是慢。
我们之前也是遇到过这个问题,用 es 作分页,前几页就几十毫秒,翻到 10 页或者几十页的时候,基本上就要 5~10 秒才能查出来一页数据了。
有什么解决方案吗?
不允许深度分页(默认深度分页性能很差)
跟产品经理说,你系统不允许翻那么深的页,默认翻的越深,性能就越差。
类似于 app 里的推荐商品不断下拉出来一页一页的
类似于微博中,下拉刷微博,刷出来一页一页的,你可以用 scroll api,关于如何使用,自行上网搜索。
scroll 会一次性给你生成所有数据的一个快照,然后每次滑动向后翻页就是通过游标 scroll_id移动,获取下一页下一页这样子,性能会比上面说的那种分页性能要高很多很多,基本上都是毫秒级的。但是,唯一的一点就是,这个适合于那种类似微博下拉翻页的,不能随意跳到任何一页的场景。也就是说,你不能先进入第 10 页,然后去第 120 页,然后又回到第 58 页,不能随意乱跳页。所以现在很多产品,都是不允许你随意翻页的,app,也有一些网站,做的就是你只能往下拉,一页一页的翻。
初始化时必须指定 scroll 参数,告诉 es 要保存此次搜索的上下文多长时间。你需要确保用户不会持续不断翻页翻几个小时,否则可能因为超时而失败。
除了用 scroll api,你也可以用 search_after 来做,search_after 的思想是使用前一页的结果来帮助检索下一页的数据,显然,这种方式也不允许你随意翻页,你只能一页页往后翻。初始化时,需要使用一个唯一值的字段作为 sort 字段。
网上基本上是炒来炒去,有些还抄错了