本章,我们来看下Elasticsearch中的读写优化。ES中几乎所有的操作都是读操作或写操作,所以我们需要掌握一些对读写操作优化的方法。
一、数据写入优化
1.1 bulk批量写入
如果我们要往Elasticsearch中批量写入数据的话,尽量采用bulk方式,每次批量写个几百条的样子。
bulk批量写入的性能比一条一条写入大量的document的性能要好很多。但是,如果要知道一个bulk请求最佳的大小,需要对单个ES node的单个shard做压测。比如,先往bulk写入100个document,然后200个,500个,以此类推,每次都将bulk size加倍一次。如果bulk写入性能开始变平缓,那么这个就是最佳的bulk大小。
1.2 多线程写入
单线程发送bulk请求是无法最大化Elasticsearch集群写入吞吐量的。如果要利用集群的所有资源,就需要使用多线程并发的将数据bulk写入集群中。多线程并发写入,可以减少每次磁盘fsync的次数和开销。
线程的数量如何确定?
对单个ES node的单个shard做压测,比如说,先是2个线程,然后是4个线程,然后是8个线程……,每次线程数量倍增。一旦发现ES返回了TOO_MANY_REQUESTS
的错误,那么就说明ES集群已经达到了并发写入的最大瓶颈,此时我们就知道最多只能支撑这么高的并发写入了。
1.3 增加refresh间隔
默认的refresh间隔是1s,用index.refresh_interval
参数可以设置,这样会其强迫Elasticsearch每隔1s就将创建一个新的segment file。如果我们可以接受写入的数据较长时间后才查询到,比如30s,就可以调大这个参数,此时就可以获取更大的写入吞吐量,因为30s内都是写内存的,每隔30s才会创建一个segment file。
1.4 禁止refresh和replia
如果我们要一次性加载大批量的数据进Elasticsearch,可以先禁止refresh和replia复制,将index.refresh_interval
设置为-1,将·index.number_of_replicas·设置为0。此时就没有refresh和replica机制了,写入的速度会非常快,一旦写完之后,我可以将refresh和replica修改回正常的状态。
1.5 禁止swapping
之前讲解过,可以将swapping内存页交换禁止掉,因为swapping会导致大量磁盘IO,性能很差。
1.6 增加filesystem cache大小
filesystem cache被用来执行更多的IO操作,如果我们能给filesystem cache更多的内存资源,那么Elasticsearch的写入性能会好很多。
1.7 使用自动生成的id
如果我们要手动给es document设置一个id,那么es需要每次都去确认一下那个id是否存在,这个过程是比较耗费时间的。如果我们使用自动生成的id,那么es就可以跳过这个步骤,写入性能会更好。对于你的业务表中的id,可以作为es document的一个field。
1.8 提升硬件
我们可以给filesystem cache更多的内存,也可以使用SSD替代机械硬盘,避免使用NAS等网络存储,考虑使用RAID 0来提升磁盘并行读写效率等等。
1.9 index buffer
如果我们的写入并发量非常高,那么最好将index buffer调大一些,可以通过indices.memory.index_buffer_size
参数设置。Elasticsearch会将这个设置作为每个shard共享的index buffer,对于每个shard来说,最多给512MB。默认这个参数的值是10%,也就是jvm heap的10%,如果我们给jvm heap分配10gb内存,那么这个index buffer就有1gb,对于两个shard共享来说是足够的了。
二、数据搜索优化
2.1 增加filesystem cache大小
Elasticsearch的搜索引擎严重依赖于底层的filesystem cache,你如果给filesystem cache更多的内存,尽量让内存可以容纳所有的indx segment file索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高。
举个例子,如果有3个ES节点(3台机器),每台机器64G内存,每台机器给es jvm heap是32G,剩下的留给filesystem cache,那么集群给filesystem cache的总内存就是96G。
如果你的索引数据文件有1T,那么由于filesystem cache的内存才100g,所以只有十分之一的数据可以放入内存,其它的都在磁盘,此时搜索操作大部分都是走磁盘,性能肯定差。
归根结底,要让Elasticsearch性能要好,你的机器的总内存至少要容纳ES总数据量的一半。
2.2 提升硬件
- 给filesystem cache更多的内存资源;
- 用SSD固态硬盘;
- 使用本地存储系统,不要用NFS等网络存储系统;
- 给更多的CPU资源。
2.3 document模型设计
document模型设计是非常重要的,一般有两个思路:
- 在写入数据的时候,就设计好模型,把处理好的数据放到一个新字段,避免ES执行复杂的搜索;
- 自己用Java/Python之类的程序封装,Elasticsearch仅做普通数据搜索,在Java/Python程序里去做复杂的业务逻辑处理。
2.3 预热filesystem cache
如果我们重启了Elasticsearch,那么filesystem cache是空的,就需要不断的查询才能重新让filesystem cache热起来,所以我们可以先做数据预热。
比如说,本来一个查询,要用户点击以后才执行,才能从磁盘加载到filesystem cache里,第一次执行要10s,以后每次就几百毫秒。那么完全可以,后台先将数据加载到filesystem cahce,以后用户真的访问时消耗时间就大大缩短。
2.6 避免使用script脚本
生产环境中,一般是避免使用Elasticsearch script,性能不高。
2.7 使用固定范围的日期查询
尽量不要使用now这种内置函数来执行日期查询,因为默认now是到毫秒级的,是无法缓存结果,尽量使用一个阶段范围,比如分钟级,那么如果一分钟内都执行这个查询,是可以取用查询缓存的:
PUT index/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"
}
}
}
}
}
}
三、磁盘读写优化
磁盘读写优化,主要是优化磁盘空间的占用,减少磁盘空间的占用,让更多的数据可以进入filesystem cache。比如原来磁盘空间占用1T,内存只有512G,现在优化了磁盘空间占用后,减少了数据量,可能数据量就只有512G了,那么就可以全部进入内存。
3.1 禁用不用的功能
禁用不用的功能是指,如果我们不需要使用Elasticsearch的以下功能,完全可以禁用掉以提升性能:
- 聚合:doc values
- 搜索:倒排索引,index
- 评分:norms
- 近似匹配:index_options(freqs)
上述任何一个功能不需要,就把对应的存储数据给清理掉,这样可以节约磁盘空间的占用,也可以优化磁盘的读写性能。
禁用倒排索引
默认情况下,Elasticsearch在写入document到索引的时候,都会给大多数的field增加一份doc values
,就是正排索引,用来进行聚合或者排序。比如说,如果我们有一个叫做foo
的数字类型field,我们要对这个字段运行histograms aggr聚合操作,但是可能我们并不需要对这个字段进行搜索,那么就可以禁止为这个字段生成倒排索引,只需要doc value正排索引即可。禁用倒排索引的方式如下:
PUT index
{
"mappings": {
"properties": {
"foo": {
"type": "integer",
"index": false
}
}
}
}
禁用评分
text类型的field会存储norm值,用来计算doc的相关度分数,如果我们需要对一个text field进行搜索,但是不关心这个field的分数,那么可以禁用norm值:
PUT index
{
"mappings": {
"properties": {
"foo": {
"type": "text",
"norms": false
}
}
}
}
禁用近似匹配
text field还会存储出现频率以及位置,出现频率也是用来计算相关度分数的,位置是用来进行phrase query这种近似匹配操作的,如果我们不需要执行phrase query近似匹配,那么可以禁用这个属性:
PUT index
{
"mappings": {
"properties": {
"foo": {
"type": "text",
"index_options": "freqs"
}
}
}
}
3.2 禁用动态string类型映射
默认的动态string类型映射,会将string类型的field同时映射为text类型以及keyword类型,这会浪费磁盘空间。如果我们不是两种类型都需要,可以通过手动设置mappings映射来避免字符串类型的field被自动映射为text和keyword:
PUT index
{
"mappings": {
"dynamic_templates": [
{
"strings": {
"match_mapping_type": "string",
"mapping": {
"type": "keyword"
}
}
}
]
}
}
3.3 禁止_all field
_all field
会将document中所有field的值都合并在一起进行索引,很耗费空间,如果不需要一次性对所有的field都进行搜索,那么最好禁用_all field
。
3.4 使用best_compression
_source field
和其他field都很耗费磁盘空间,最好是对其使用best_compression
进行压缩。用elasticsearch.yml中的index.codec来设置,将其设置为best_compression即可。
3.5 合理使用数字类型
Elasticsearch支持4种数字类型:byte
、short
、integer
、long
。如果最小的类型就合适,那么就用最小的类型,以减少doc大小。
四、总结
本章,我介绍了一些Elasticsearch中的常用读写优化建议。ES中几乎所有的操作都是读操作或写操作,所以我们需要掌握这些对读写操作优化的方法。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。