Elasticsearch大数据量下的优化方法

前言

十一在家待着玩了两天COD,突然想起来似乎放假前有一个Elasticsearch的问题需要处理,的确,现在距离开工也只剩下四天了,哎,假期的日子过的飞快,又到时候说拜拜,又要开始社畜的生活啦,想到心里就万分悲痛。

悲痛完了之后,也的确要想办法去解决问题,所以,今天(10/5),我研究了一下ElasticSearch检索/导入大文件的一些方案,并且输出了一些我个人的建议,这些基本都是实战建议,不会那么偏向理论,所以理论家们,就不要挑我的刺啦,想看理论,咱们回头去写源码解析!多多支持!

问题描述

现在有个ElasticSearch做为基础的检索服务,里面存储很多文档,这些文档多种多样,有PDF,DOC等等,需要对这些文档进行全文检索,所以将这些文档的内容也导入进ElasticSearch当中,文档的内容可能会很大,导入/查询的时候出现了如下问题

  1. 导入一些比较大的文件的时候ElasticSearch节点直接Down机,出现了内存溢出OOM的问题,如下所示
1
java.lang.OutOfMemoryError: Java heap space
  1. 检索的时候,返回的比较慢,当数据量大于100w条,并且基本都要支持全文检索的时候,翻页/高亮,查询非常慢,经常转很久的菊花出不来,翻页也慢,干嘛都慢。

基础环境分析

首先需要分析下我自己的干活儿环境,对症下药。

ElasticSearch的集群配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cluster.name: Es_test
node.name: es_node1
node.master: true
node.data: true
index.number_of_shards: 5
index.number_of_replicas: 1
network.host: 10.0.6.244
http.port: 9200
transport.tcp.port: 9300
discovery.zen.ping.unicast.hosts: ["10.0.6.244"]
discovery.zen.minimum_master_nodes: 1
http.cors.enabled: true
http.cors.allow-origin: "*"
network.bind_host: "::"

然后就没了,算是最基础的ElasticSearch配置了

jvm.options设置为

1
2
xms: 1g
xmx: 1g

基础的环境配置就是这样

优化查询和导入的方法

1. 配置优化

有一句话怎么说的来着,当一切都可以通过加钱来解决的时候,那就不是个问题嘛。

你看看我那个堆内存配置,只有1个G,而且总共测试的机器内存只有8G,这个根本没办法再加,公司的默认测试机都是8G,除开这个还要跑其他服务,例如存储/web等,我想啊,本来就是拖拉机你还想当跑车开?吃肉的东西你让他吃草能行吗,所以再怎么优化,数据量上来了最终还是要沦落到加钱上好机器的最终结局。

不过现在只有拼命在这个烂机器的基础上去优化了

所以,需要修改一波jvm.options配置

1
2
xms: 4g
xmx: 4g

这里修改了过后,一般就能解决大部分问题,但如果需要支撑大规模的数据检索和近乎实时的查询任务,这个是非常不靠谱的机器配置,完全是无法支撑业务的,但是领导非要往里面塞这个,还要求优化到极限,我能怎么办。

言归正传,第一步就是

调整JVM的配置

将JVM的配置调整为整个机器内存的一半,比如你的机器是64G内存,你设置xms和xmx就设置成32G,这个是官方推荐的,靠谱!

2. 减少refresh_interval刷新比率

这个可能抽象一点啊,首先这个refresh_interval是干嘛的呢,这玩意儿说白了就是个刷新器,你每次导入数据到ElasticSearch中不能马上被查到,有个固定刷新间隔,也就是refresh_interval控制的,刷新成功之后,数据才能被马上查到。

refresh_interval 如果不设置的话,默认是1s一次,像我们这种导入数据比较频繁的,刷新比率过高,会导致CPU/Memory就像开了氮气加速一样极速前进。。。所以把这东西设置一下,如果不是实时比率要求高的,直接改成30s刷新一次,或者1分钟刷新一次,不要害怕丢数据之类的,当你节点down机,Es自己都保不住了,还管刷新比率?

修改refresh_interval刷新比率,直接在elasticsearch的配置文件修改

1
"refresh_interval": "30s"

3. 查询缓慢,响应时间长的解决方法

这里我更倾向于问题描述是从ElasticSearch获取数据比较慢,比较慢的愿意可能有哪些呢?

  1. 文档过大,可能某个字段非常长,返回的太慢。
  2. ElasticSearch响应慢,CPU/内存占比过高
  3. 网络响应慢

我们针对这些问题一个个去解决,对症下药。

3.1 文档过大,某个字段大,返回慢

首先看看能不能把过长的字段给拆分出去,拆成另外一张表。如果不可以拆分出去,就在显示的部分想办法。

首先举个例子,假设我们有个字段叫content,里面存的都是解析出来的PDF,DOC文件的文字,这种一般文字都非常多,但是我们需要进行全文检索并且返回检索到的值,Es查询到值以后就会全部返回,这样就非常慢,我们可以通过一些别的手段解决这个问题。

例如,我们原本的返回字段是

1
2
3
4
5
{
"name": "xxx",
"id": "xxx",
"content": "............"
}
  1. 展示部分,不展示此字段,对此字段,也不进行返回,在ElasticSearch请求返回值的步骤时,强制性不返回Content字段(返回指定字段),减少查询压力,这样就无需等待全部字段返回了,加快查询速度。
1
2
3
4
5
6
{
"_source":{
"includes":["name","id"],
"excludes":["desc"]
}
}
1
2
3
4
{
"name": "xxx",
"id": "xxx"
}
  1. 检索部分,当发现检索关键字的时候,进行全文检索,配置highlight高亮,返回检索结果时只返回highlight结果,只匹配第一个highlight结果,其他结果直接丢弃。加快返回的速度。

这里我感觉我说的更抽象了,其实说白了,我们需要配置ElasticSearch的Highlight返回,指定content字段才可以高亮,当检索到匹配值的时候,Es会返回一个highlight的切片(列表),无论如何,我们只取第一个highlight结果,其他结果全部丢弃,这样可以加快检索,返回给前端的部分也有了,这样岂不是很棒?

4. 分页部分优化

ElasticSearch的分页机制有两种,第一种是From + Size的机制,这个相对来说比较简单,还可以进行控制,另一种是Scroll机制,这个就复杂一些,我下面说。

  1. From + Size 它的基础逻辑就是,它首先会确定doc的顺序,进行排序,然后再进行返回,逻辑上来说,它也需要取出所有数据,所以当数据量非常大的时候,从ElasticSearch中取出数据会占用大量CPU/内存,如果跳页过大,例如从第1页瞬间跳到第100页,就会取出第1页到第99页的所有数据,然后排序,然后进行切分,然后再展示第100页数据。。。这个又抽象了,说白了就是,这个分页比较适合一页页往后翻,你要闲的没事儿天天跳来跳去,人家谁Hold住.

  2. Scroll的基础逻辑就是,首先,Scroll会维护一个游标,记录你当前读取的doc位置,而不是取出来做除法切分,这个更适合一次性拿一大堆数据出来,它其实类似一个快照,每次你查询的时候,都会记录你上次查询的位置,下次访问就直接从这个位置/快照开始,免去了拿取所有数据的步骤,有更好的检索效率,CPU/内存使用也不会那么大。

4.1 限定分页的数量

首先无论如何,我们先限定死,分页的数量,最大1000页,多了不让翻。这是为了保证From + Size机制使用时,跳页数量过大,导致CPU/内存飙升导致服务OOM或者是挂掉,这个可以让前端同学强制限制一波。

4.2 根据数据量大小,逐步替换From + Size 翻页逻辑

如果数量过大,前面我也说了,你整From + Size肯定是不行,而且你把客户想象成熊孩子,没事儿干就乱点,乱跳页,动不动就CPU/内存暴增,那不就拉垮了嘛。所以快速切换 From + Size 为 Scroll就得了。

5. 建表(索引)时候的优化

在创建表的时候,需要指定一些字段属性,这样会减少查询和检索的内存消耗或者是,这里给出几个我总结的字段优化方法

  1. 全文检索的时候,指定的字段类型一定得是 text
  2. 其他检索的时候,需要检索字段完整值,需要使用 keyword,如果想做模糊匹配,需要使用wildcard或者*检索
  3. 时间字段可以使用date,可以自己定义数据格式
  4. 不确定数据长度,需要用long进行设置,避免以后长度超出。
  5. 建表的时候,需要设置索引的压缩功能,减少存储空间占用,设置的方法就像这样,”codec”: “best_compression”。
  6. 避免自定义Doc的ID,尽量用人家Es自己生成的,你自己整ID一般都是UUID时间戳,还容易重复也慢。
  7. 定义分词器没必要所有字段都用,指定几个需要的字段就行了。
  8. 确定不会修改的doc需要设置dynamic,禁止更新。

6. ElasticSearch存储位置的转移

安装好ElasticSearch之后,默认的位置是在系统盘。

我估摸着数据量一大,没多久你就给系统盘整崩溃了,所以,改换系统盘位置,做好数据迁移是非常有必要的。

修改配置文件,手动切换存储位置, 这里举个简单例子

1
path.data: /media/data/elasticsearch

迁移数据文件,修改文件夹权限

1
2
3
mv /var/lib/elasticsearch/nodes /media/data/elasticsearch

chown -R elasticsearch:elasticsearch *

7. ElasticSearch写入性能优化

7.1 推荐使用bulk批量写入

其实按照我现在的使用场景,发现PDF/DOC有更新/上传到文件系统,就传入到ElasticSearch当中,其实ElasticSearch的批量写入bulk的效率比一条条写效率高多了,这里建议批量写入数据,而不是一条条传入。

7.2 多线程写入ElasticSearch

多线程并发写可以利用集群的资源,我这里用Golang做了后端,直接走了一波协程 + 线程,这样可以减底层fsync开销,可以减少单个Es节点压力。

7.3 Translog事务日志的优化

translog是用来恢复数据的。Es用“后写”的套路来加快写入速度 — 写入的索引并没有实时落盘到索引文件,而是先双写到内存和translog文件,

es存储数据时,先把输出存储在内存中,等到refresh(该时间可以在设置mapping时的setting中设置intavel_refresh=xxx)时间后,才把数据存储到lucene中的segment中,清空内存缓冲区,往磁盘里写入commit point信息,文件系统的page cache(segments) fsync到磁盘,之后把translog旧日志删除掉。

按照逻辑来说,如果降低translog可以提高效率,但是会降低容灾能力。

修改配置,不需要每次都刷新。

1
2
index.translog.durability: async
index.translog.sync_interval: 3600s

结尾

这次我主要开发了一个ElasticSearch的检索服务器,主要就是用apache tika去读取对象存储中的PDF/DOC等文件,解析文字,传入ElasticSearch当中,主要是全文检索,这个真的费了点功夫,因为我的服务器,实在是太烂了,只有他娘的8个G,还要跑Ceph存储等等等等,优化起来那个麻烦啊,这就相当于你开拖拉机,要我改车给你改成法拉利的速度,你这不是扯嘛。

预告一下下一篇blog,TIKA提取对象存储中的PDF/DOC中的文字,有点儿绕是吧,绕就对了!哈哈哈,走你,下次见!

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • © 2019-2020 Yemilice lau
  • Powered by Hexo Theme Ayer
  • PV: UV:

觉得帮到你了么?赏我点儿~

支付宝
微信