由于 Elasticsearch(后文简称es) 的简单易用及其在大数据处理方面的良好性能,越来越多的公司选用 es 作为自己的业务解决方案。然而在引入新的解决方案前,不免要做一番调研和测试,本文便是介绍官方的一个 es 压测工具 esrally,希望能为大家带来帮助。
关于压测,我们先来看下百度百科上的一个定义。
压测,即压力测试,是确立系统稳定性的一种测试方法,通常在系统正常运作范围之外进行,以考察其功能极限和隐患。
从定义不难看出压测的目的,是要测出一个系统的极限,提早发现隐患,早作打算。那么对于 es 来讲,我认为压测一般有以下几个目的:
现在我们知道压测的目的了,接下来该如何进行压测呢?一般有以下几个方案:
各个压测方案各有优劣,大家可以根据自己的需求和工具熟悉度来选择自己的压测工具。接下来我们就来具体了解下 esrally。
esrally 是 elastic 官方开源的一款基于 python3 实现的针对 es 的压测工具,源码地址为https://github.com/elastic/rally,相关博客介绍在这里。esrally主要功能如下:
elastic 官方也是基于 esrally 进行 es 的性能测试,并将结果实时发布到 https://elasticsearch-benchmarks.elastic.co/ ,大家可以从该网站上直接查看 es 的性能。官方使用两台服务器进行压测,一台运行 esrally ,一台运行 es,服务器的配置如下:
CPU: Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz
RAM: 32 GB
SSD: Crucial MX200
OS: Linux Kernel version 4.8.0-53
JVM: Oracle JDK 1.8.0_131-b11
网站顶部的 Geonames、Geopoint、Percolator等都是针对不同的数据集做的压测,比如下面这些图展示了 logging 日志类数据的压测结果。
esrally 的文档在这里,这里简单说下安装与运行。
esrally 对于软件环境的要求如下:
安装方法为:
pip3 install esrally
Tips:
可以使用国内的pip源,比如豆瓣或者阿里的,这样安装会快很多。
安装完毕后执行如下的配置命令,确认一些数据存放的路径即可。
esrally configure
接下来就可以开跑了,比如下面这条命令是针对 es 5.0.0 版本进行压力测试。
esrally --distribution-version=5.0.0
运行结束后,会得到如下的结果。
对于第一次见到压测结果的同学来说可能有些晕,这么多数据,该怎么看?!别急,一步步来!
Tips:
由于 esrally 的测试数据存储在国外 aws 上,导致下载很慢甚至会超时失败,从而导致整个压测无法进行。后面我会把这些测试数据的压缩包放到国内,大家可以下载后直接放到 esrally 的数据文件夹下面,保证压测的正常进行。另外由于数据量过大,压测的时间一般会很久,可能在1个小时左右,所以大家要有耐心哦~
如果你只是想体验下,可以加上 --test-mode 的参数,此时只会下载1000条文档进行测试。
rally 是汽车拉力赛的意思,也就是说 esrally 是将压测比作了汽车拉力赛,因此其中的很多术语都是从汽车拉力赛中借鉴来的。
track 是赛道的意思,在这里是指压测用的数据和测试策略,详细文档在这里。esrally 自带的track都在 github 上,地址在这里 https://github.com/elastic/rally-tracks。在该 repository 中,有很多测试数据,比如 geonames geopoint logging nested 等,每个数据文件夹中的 README.md 中有详细的数据介绍,而 track.json 便是压测策略的定义文件。
我们来看下 loggins/track.json 文件
{% import "rally.helpers" as rally with context %}
{
"short-description": "Logging benchmark",
"description": "This benchmark indexes HTTP server log data from the 1998 world cup.",
"data-url": "http://benchmarks.elasticsearch.org.s3.amazonaws.com/corpora/logging",
"indices": [
{
"name": "logs-181998",
"types": [
{
"name": "type",
"mapping": "mappings.json",
"documents": "documents-181998.json.bz2",
"document-count": 2708746,
"compressed-bytes": 13815456,
"uncompressed-bytes": 363512754
}
]
},
{
"name": "logs-191998",
"types": [
{
"name": "type",
"mapping": "mappings.json",
"documents": "documents-191998.json.bz2",
"document-count": 9697882,
"compressed-bytes": 49439633,
"uncompressed-bytes": 1301732149
}
]
}
],
"operations": [
{{ rally.collect(parts="operations/*.json") }}
],
"challenges": [
{{ rally.collect(parts="challenges/*.json") }}
]
}
该 json 文件主要包含下面几个部分:
operations/default.json 中的一个定义如下:
{
"name": "index-append",
"operation-type": "index",
"bulk-size": 5000
}
其中 operation-type 包含 index、force-merge、index-stats、node-stats、search等,每一个operation-type都有自己的可定义参数,比如 index 中可以通过指定 bulk-size 来决定批量写入的文档数。
challenges/default.json 中的一个定义如下:
{
"name": "append-no-conflicts",
"description": "",
"default": true,
"index-settings": {
"index.number_of_replicas": 0
},
"schedule": [
{
"operation": "index-append",
"warmup-time-period": 240,
"clients": 8
},
{
"operation": "force-merge",
"clients": 1
},
{
"operation": "index-stats",
"clients": 1,
"warmup-iterations": 100,
"iterations": 100,
"target-throughput": 50
},
{
"operation": "node-stats",
"clients": 1,
"warmup-iterations": 100,
"iterations": 100,
"target-throughput": 50
},
{
"operation": "default",
"clients": 1,
"warmup-iterations": 100,
"iterations": 500,
"target-throughput": 10
},
{
"operation": "term",
"clients": 1,
"warmup-iterations": 100,
"iterations": 500,
"target-throughput": 60
},
{
"operation": "range",
"clients": 1,
"warmup-iterations": 100,
"iterations": 200,
"target-throughput": 2
},
{
"operation": "hourly_agg",
"clients": 1,
"warmup-iterations": 100,
"iterations": 100,
"target-throughput": 0.2
},
{
"operation": "scroll",
"clients": 1,
"warmup-iterations": 100,
"iterations": 200,
"target-throughput": 10
}
]
}
这里定义了一个名为 append-no-conflicts 的 challenge。由于每次压测只能运行一个challenge,这里的 default 参数是指当压测未指定时默认运行的 challenge。schedule 中指定了该 challenge 中按顺序执行 index-append、force-merge、index-stats、node-stats、default、term、range、hourly_agg、scroll 等 9 个task,其中每个 task 都指定了 一个 operation,除此之外还可以设定 clients (并发客户端数)、warmup-iterations(预热的循环次数)、iterations(operation 执行的循环次数)等,详情请参见此处。
通过下面的命令可以查看当前 esrally 可用使用的track。
esrally list tracks
esrally 的 track 数据位于 rally 目录(mac默认是 ~/.rally
)中 benchmarks/tracks/
下面。
car 是赛车的意思,这里是指不同配置的 es 实例。通过下面的命令可以查看 esrally 当前可用的 car。
esrally list cars
Name
----------
16gheap
1gheap
2gheap
4gheap
8gheap
defaults
ea
verbose_iw
cars 的配置位于 rally 目录(mac默认是 ~/.rally
)中 benchmarks/teams/default/cars/
下面。具体配置可以参见 cars 的文档,除了 heap 的配置,所有的 es 配置都可以修改。
race 是一次比赛的意思,这里是指某一次压测。要比赛,就要有赛道和赛车,如果不指定赛车,就用 default 配置,如果不指定赛道,则默认使用 geonames track。通过下面的命令来执行一次 race。
esrally race --track=logging --challenge=append-no-conflicts --car="4gheap"
上面的命令便是执行一次压测,并指定使用 logging 的track,运行该 track 中的 append-no-conflicts 的 challenge,指定的 car 为 4gheap 的 es 实例。详情可以查看 race 相关文档。
tournament 是锦标赛的意思,是由多个 race 组成的。通过下面的命令可以查看所有的 race。
esrally list races
Recent races:
Race Timestamp Track Challenge Car User Tag
---------------- ------- ------------------- -------- ------------------------------
20160518T122341Z pmc append-no-conflicts defaults intention:reduce_alloc_1234
20160518T112057Z pmc append-no-conflicts defaults intention:baseline_github_1234
20160518T101957Z pmc append-no-conflicts defaults
当有了多个 race 后,可以通过下面的命令方便地比较不同 race 之间的数据。
esrally compare --baseline=20160518T112057Z --contender=20160518T112341Z
详细信息可以参见 tournament 的文档。
Pipeline 在这里是指压测的一个流程,通过下面的命令可以查看已有的pipeline。
esrally list pipeline
Name Description
----------------------- ---------------------------------------------------------------------------------------------
from-sources-complete Builds and provisions Elasticsearch, runs a benchmark and reports results.
from-sources-skip-build Provisions Elasticsearch (skips the build), runs a benchmark and reports results.
from-distribution Downloads an Elasticsearch distribution, provisions it, runs a benchmark and reports results.
benchmark-only Assumes an already running Elasticsearch instance, runs a benchmark and reports results
详细信息请参见 pipeline 的文档。
esrally 的压测流程主要分为以下三个步骤:
压测结束后,esrally 会将结果输出到终端和结果文件(位于 esrally 目录logs
和 benchmarks/races
)中,如下图所示:
在 Metric 一栏,有非常多的指标数据,详细的解释可以参见该文档。一般要关注的数据有:
先搞懂每个 metric 的含义,然后根据自己的需求去确认自己要关注的指标。
每一次压测都会以压测时的时间命名,比如 logs/rally_out_20170822T082858Z.log
,这个日志便是记录的 2017年8月22日 8:28:58开始的压测日志。而在 benchmarks/races/2017-08-22-08-28-58
中记录着最终的结果和 es 的运行日志。
另外对于 benchmark-only 模式的测试,即针对已有集群的压力测试,也可以通过安装 X-Pack Basic 版本进行监控(Monitoring),在压测的过程中就能查看相关指标。
esrally 可以在配置的时候指定将所有的 race 压测结果数据存入一个指定的 es 实例中,配置如下(在 esrally 目录中 rally.ini 文件中):
[reporting]
datastore.type = elasticsearch
datastore.host = localhost
datastore.port = 9200
datastore.secure = False
datastore.user =
datastore.password =
esrally 会将数据存储在如下 3 个index中,下面 * 代指月份,即按月存储结果数据。
第一列时间是指某一次压测的时间,第二列时间是指标采集的时间,第三列 operation 指具体执行的操作,operation 为空的指标都是总计类的,比如indexing total time 记录的是总索引数据的时间、segments_count 是总段数等等。其他的 operation 都记录了每一个操作的数据。需要注意的是,这里记录的是 operation 的所有采样数据,不是一个最终的汇总数据。上面截图中也可以看出同一个 hour_agg 的operation 有多项名为 service_time 的指标数据,但他们的采集时间是不同的。基于这些数据,我们可以做出某一次 race 中某个指标的可视化图表,比如你想观察本次 race 中 index-log 这个 task 的 throughput 指标数据,便可以通过如下图的方式实现。
rally-result-* 该索引分指标记录了每次 race 的最终汇总结果,比如下面这条数据。
{
"user-tag": "shardSizeTest:size6",
"distribution-major-version": 5,
"environment": "local",
"car": "external",
"plugins": [
"x-pack"
],
"track": "logging",
"active": true,
"distribution-version": "5.5.2",
"node-count": 1,
"value": {
"50_0": 19.147876358032228,
"90_0": 21.03116340637207,
"99_0": 41.644479789733886,
"100_0": 47.20634460449219
},
"operation": "term",
"challenge": "default-index",
"trial-timestamp": "20170831T063724Z",
"name": "latency"
}
这个记录了 term operation 的 latency 指标数据,汇总值以 percentile(百分位数) 的形式展示。基于该数据,我们可以绘制针对某个指标的多race对比,比如下图便是对比多 race 之间 hourly_agg(按小时做聚合)、default(match_all 查询)、term(term查询)、range(range查询)的latency(延迟时间)对比。
除了es相关指标数据外,esrally 还会同时记录测试的一些环境信息,比如操作系统、JVM等等,你可以方便的查看本次测试的软硬件环境。
终于到了开赛的时候,下面我们采用问答的形式来进行,希望大家看到问题后先自己思考下再看答案。
提问:如何对比 5.5.0 相比 2.4.6 的性能改进?
回答:
分别针对 5.5.0 和 2.4.6 做一次压测,然后比较两者两者的相关指标即可,这里我们的 track 和 challenge 如下:
测试步骤如下:
esrally race --distribution-version=2.4.6 --track=nyc_taxis --challenge=append-no-conflicts --user-tag="version:2.4.6"
esrally race --distribution-version=5.5.0 --track=nyc_taxis --challenge=append-no-conflicts --user-tag="version:5.5.0"
esrally list races
esrally compare --baseline=[2.4.6 race] --contender=[5.5.0 race]
Tips:
--user-tag 用于为 race 打标签,方便后续查找
如果只是试一下,可以加上 --test-mode ,用测试数据来跑,很快。
提问:如何测试 _all 关闭后对于写性能的影响?
回答:
针对 5.5.0 版本的 es 做两次测试,第一次开启 _all,第二次关闭 _all,对比两次的结果,由于只测试写性能,所以我们只需要 index 类型的 operation执行。这里我们的 track 和 challenge 如下:
测试步骤如下:
esrally race --distribution-version=5.5.0 --track=nyc_taxis --challenge=append-no-conflicts --user-tag="enableAll:false" --include-tasks="type:index"
benchmarks/tracks/default/nyc_taxis/mappings.json
,修改 _all.enabled 为 true。esrally race --distribution-version=5.5.0 --track=nyc_taxis --challenge=append-no-conflicts --user-tag="enableAll:true" --include-tasks="type:index"
esrally list races
esrally compare --baseline=[enableAll race] --contender=[disableAll race]
下图是我在 --test-mode 模式下运行的对比结果,也可以看出关闭 _all 可以提升写性能。
Tips:
--include-tasks 用于只运行 challenge 中的部分 task
提问:如何测试已有集群的性能?
回答:
使用 benchmark-only 的 pipeline 即可,这里我们的 track 和 challenge 如下:
测试步骤如下:
esrally race --pipeline=benchmark-only --target-hosts=127.0.0.1:9200 --cluster-health=yellow --track=nyc_taxis --challenge=append-no-conflicts
Tips:
--cluster-health=yellow 默认 esrally 会检查集群状态,非 green 状态会直接退出。添加该参数可以避免该情况
希望这三个问答可以帮助到大家快速掌握 esrally 的用法。
前面讲解 car 的时候,我们提到 esrally 已经自带了一些可用的 es 配置,但是如果这些还不能满足你的时候,可以通过下面两个方案解决。
benchmarks/teams/default/cars
,在这里新增一个自己的 car 配置文件就可以了。这里就不赘述了,感兴趣的可以查阅 car 的文档。虽然 esrally 自带了很多 track,而且这些数据本身也不小,简单列在下面:
Track | 压缩数据大小 | 解压数据大小 | 文档数 |
---|---|---|---|
geonames | 252 MB | 3.3 GB | 11396505 |
geopoint | 482 MB | 2.3 GB | 60844404 |
logging | 1.2 GB | 31 GB | 247249096 |
nested | 663 MB | 3.3 GB | 11203029 |
noaa | 947 MB | 9 GB | 33659481 |
nyc_taxis | 4.5 GB | 74 GB | 165346692 |
percolator | 103KB | 105 MB | 2000000 |
pmc | 5.5 GB | 22 GB | 574199 |
这些数据文件位于 esrally 目录 benchmarks/data
下面。不同的 Track 有不同的测试目的,详情可以去该 github repo 下面去查看。
当我们做定向测试的时候,还是希望针对自己的数据进行压测,此时可以自定义 track。操作也很简单,详情可以参考官方文档。这里简单列一下操作步骤。
esrally list rack
就可以看到自定义的 track。esrally 还支持分布式压测,即如果一个节点的 esrally 无法达到要求的并发数、请求数,那么可以将 esrally 分布到多台机器上去同时执行。分布式压测文档在这里,此处用到了 esrally dameon,对应命令是 esrallyd
。简单讲就是 esrally 通过 esrallyd 将多台机器组合成一个集群,然后 esrally 在执行测试任务的时候通过制定 --load-driver-hosts
便可以将测试任务分发到对应的机器上执行。这里便不赘述了,感兴趣的去看前面提到的文档。
让我们回到开头提到的容量规划的问题吧!
提问:一个 index 的 shard 数该如何确认?
回答:
其实针对这个提问,还可以再问下面两个问题。
要回到这两个问题,我们得先知道 shard 的作用。shard 是 es 实现分布式特性的基石,文档在索引进 es 时,es 会根据一个路由算法,将每一个文档分配到对应的 shard 上。每个 shard 实际对应一个 lucene index。那么每个 shard 能存储的文档数是否有上限呢?答案是有!每个shard最多存储 2^31 个文档,即 20亿。这是 lucene 设计决定的。那是不是只要我的文档数没有超过20亿,就可以只用一个或者很少的shard 呢?不尽然。因为随着 shard 体积的增大,其查询效率会下降,而且数据迁移和恢复的成本也会增高。官方建议单个 shard 大小不要超过 50GB,可以参见讨论一和讨论二。
现在回答上面的两个问题。
shard数过小不一定好,如果数据量很大,导致每个 shard 体积过大,会影响查询性能。
shard数过大也不一定好,因为 es 的每次查询是要分发给所有的 shard 来查询,然后再对结果做聚合处理,如果 shard 数过多也会影响查询性能。因此 shard 的数量需要根据自己的情况测出来。
官方文档有一节关于容量规划的章节,建议大家去看一下,链接在这里,其给出的步骤如下:
测试的过程中,关注相关指标数据,比如索引性能、查询性能,如果在某一个点相关性能数据超出了你的预期值,那么此时的 shard size大小便是符合你预期的单个 shard size的大小。接下来通过下面这个简单的计算公式便大致能确定一个 index 需要设定的 shard 数了。
shard数 = index 的数据总大小/单个shard size的极限值
比如你测出单个 shard size 最大为 20 GB,而你预测该索引数据最大量在1年或者2年内不会超过 200GB,那么你的 shard 数就可以设置为10。
接下来要做的事情也很明确,我们要用 esrally 完成上面的压测步骤:
自定义 track,这里有以下两个重点:
iterations
参数,即循环进行同一个操作,这样也可以测试大数据量的写性能。operations
中是可以定义自己的查询语句的,比如下面这个{
"name": "hourly_agg",
"operation-type": "search",
"index": "logs-*",
"type": "type",
"body": {
"size": 0,
"aggs": {
"by_hour": {
"date_histogram": {
"field": "@timestamp",
"interval": "hour"
}
}
}
}
}
其中的 body 便是自定义的查询语句,所以你可以通过自己的需求来设定查询语句,以贴近实际使用的情况。
Tips:
esrally 默认在每次压测是会删除已有的索引后再重新创建索引,如果你不想这样,可以在每个 index 的配置中设置auto-managed
为 false,具体文档在这里。
通过这个参数,你就可以单独压测查询性能了,而不用每次都要先经过漫长的导入数据的过程。
esrally 针对 es 的压测设计了一套完备的基于配置文件的测试流程,极大地简化了操作难度,并且提供了可重复验证的方式。对国内用户来讲,我认为最大的难处还是在于 esrally 自带的 track 文件太大,从 国外 aws 下载很慢。好在可以自定义 track,不必完全依赖自带的 track。