hi,我是蛋挞,一个初出茅庐的后端开发,希望可以和大家共同努力、共同进步!
开启掘金成长之旅!这是我参与「掘金日新计划 · 4 月更文挑战」的第 12 天,点击查看活动详情
- 起始标记->生产环境中的集群运维(10讲):「70 | 提升集群读性能」
- 结尾标记->生产环境中的集群运维(10讲):「74 | 一些运维的相关建议」
提升集群读性能
尽量 Denormalize 数据
- Elasticsearch !=关系型数据库
- 尽可能 Denormalize 数据,从而获取最佳的性能
- 使用 Nested 类型的数据。查询速度会慢几倍
- 使用 Parent /Child 关系。查询速度会慢几百倍
数据建模
- 尽量将数据先行计算,然后保存到 Elasticsearch 中。尽量避免查询时的 Script 计算
- 尽量使用 Filter Context,利用缓存机制,减少不必要的算分
- 结合 profile,explain API 分析慢查询的问题,持续优化数据模型
- 严禁使用*开头通配符 Terms 查询
避免查询时脚本
- 可以在 Index 文档时,使用Ingest Pipeline,计算并写入某个字段
常见的查询性能问题-使用 Query Context
聚合文档消耗内存
- 聚合查询会消耗内存,特别是针对很大的数据集进行聚合运算
- 如果可以控制聚合的数量,就能减少内存的开销
- 当需要使用不同的 Query Scope,可以使用 Filter Bucket
通配符开始的正则表达
- 通配符开头的正则,性能非常糟糕.需避免使用
优化分片
- 避免 Over Sharing
- 一个查询需要访问每一个分片,分片过多,会导致不必要的查询开销
- 结合应用场景,控制单个分片的尺寸
- Search: 20GBo
- Logging: 40GB0
- Force-merge Read-only 索引
- 使用基于时间序列的索引,将只读的索引进行 force merge,减少 segment 数量
CodeDemo
PUT blogs/_doc/1
{
"title":"elasticsearch"
}
GET blogs/_search
{
"query": {
"bool": {
"must": [
{"match": {
"title": "elasticsearch"
}}
],
"filter": {
"script": {
"script": {
"source": "doc['title.keyword'].value.length()>5"
}
}
}
}
}
}
GET blogs/_search
{
"query": {
"bool": {
"must": [
{"match": {"title": "elasticsearch"}},
{
"range": {
"publish_date": {
"gte": 2017,
"lte": 2019
}
}
}
]
}
}
}
相关阅读
本节知识小节
介绍了一些如何提升Elasticsearch搜索的小技巧,推荐在进行数据建模的时候使用Denormalize的方式对数据进行建模,也分享了一些优化查询语句的技巧,结合profile,explain API 分析慢查询的问题,持续优化数据模型。
集群压力测试
压力测试
- 压力测试的目的
- 容量规划/性能优化/版本间性能比较 /性能问题诊断
- 确定系统稳定性,考察系统功能极限和隐患
- 压力测试的方法与步骤
- 测试计划(确定测试场景和测试数据集)
- 脚本开发
- 测试环境搭建(不同的软硬件配置) & 运行测试
- 分析比较结果
测试目标 & 测试数据
- 测试目标
- 测试集群的读写性能/做集群容量规划0
- 对ES配置参数进行修改,评估优化效果
- 修改 Mapping 和 Setting,对数据建模进行优化,并测试评估性能改进
- 测试 ES 新版本,结合实际场景和老版本进行比较,评估是否进行升级
- 测试数据
- 数据量/数据分布
测试脚本
- ES 本身提供了 REST AP,所以,可以通过很多传统的性能测试工具
- Load Runner (商业软件,支持录制+重放 + DSL)
- JMeter ( Apache 开源 ,Record & Play)
- Gatling (开源,支持写 Scala 代码 + DSL)
- 专门为 Elasticsearch 设计的工具
- ES Pref & Elasticsearch-stress-test
- Elastic Rally
ES Rally 简介
- Elastic 官方开源,基于 Python 3 的压力测试工具
- 功能介绍
- 自动创建,配置,运行测试,并且销毁 ES 集群
- 支持不同的测试数据的比较,也支持将数据导入 ES集群,进行二次分析
- 支持测试时指标数据的搜集,方便对测试结果进行深度的分析
Rally 的安装以及入门
- 安装运行
- Python 3.4+和 pip3 /JDK 8 / git 1.9+0
- 运行 pip3 install esrally
- 运行 esrally configure
- 运行
- 运行 esrally -distribution-version=7.1.0
- 运行 1000 条测试数据:esrally -distribution-version=7.1.0 --test-mode
Rally 基本概念讲解
- Tournament-淀义测试目标,由多个race 组成
- Esrally list races
- Track - 赛道: 测试数据和测试场景与策略
- github.com/elastic/ral…
- esrally list tracks
- Car- 执行测试方案
- 不同配置的es 实例
- Award一测试结果和报告
Benchmark Reports
什么是压测的流程
- Pipeline 指的是压测的一个流程
- Esrally list pipelines
- 默认的流程
- From-source-complete
- From-source-skip-build
- From-distribution
- Benchmark-only(对已有的集群进行测试
自定义 & 分布式测试
- Car
- 0esrally.readthedocs.io/en/latest/c…
- 使用自建的集群
- Track
- 自带的测试数据集: Nyctaxis 4.5 G /logging 1.2G
- 更多的测试数据集: github.com/elastic/ral…
- 分布式测试
实例:比较不同的版本的性能
- 测试
- esrally race --distribution-version=6.0.0 --track=nyc_taxis --challenge=append-no-conflicts -user-tag="version:6.0.0
- esrally race --distribution-version=7.1.0 --track=nyc taxis --challenge=append-no-conflicts --0user-tag="version:7.1.0"
- 比较结果
- esrally list races
- esrally compare --baseline=[6.0.0 race]--contender=[7.1.0 race]
实例:比较不同 Mapping 的性能
- 测试
- esrally race --distribution-version=7.1.0 --track=nyc taxis --challenge=append-no-conflicts --user-tag="enableSource:true"--include-tasks="type:index
- 修改: benchmarks/tracks/default/nyc_taxis/mappings.json,修改_source.enabled 为 false
- esraly race --distribution-version=7.1.0 --track=nyc taxis --challenge=append-no-conflicts --user-tag="enableSource:false"--include-tasks="type:index
- 比较
- esrally compare --baseline=[enableAll race] --contender=[disableAll race]
实例:测试现有集群的性能
- 测试
- esrally race --pipeline=benchmark-only --target-hosts=127.0.0.1:9200 --track=geonames0--challenge=append-no-conflicts
CodeDemo
pip3 install esrally
esrally configure
# 只测试 1000条数据
esrally --distribution-version=7.1.0 --test-mode
# 测试完整数据
esrally --distribution-version=7.1.0
相关阅读
本节知识小节
介绍了ES Really,它是一款Elasticsearch公司开发的开源的压测工具,当我们要做性能的优化的时候,当我们要做容量规划的时候,他都可以通过ES Really去帮助我们完成一个性能的测试。
段合并优化及注意事项
Lucene Index 原理回顾
- 在 Lucene 中,单个倒排索引文件被称为Segment。Segment 是自包含的,不可变更的多个 Segments 汇总在一起,称为 Lucene 的Index,其对应的就是 ES 中的 Shard
- 当有新文档写入时,并且执行 Refresh,就会会生成一个新 Segment。 Lucene 中有一个文件,用来记录所有 Segments 信息,叫做Commit Point。查询时会同时查询所有Segments,并且对结果汇总。
- 删除的文档信息,保存在“.del”文件中,查询后会进行过滤。
- Segment 会定期 Merge,合并成一个,同时删除已删除文档
Merge 优化
- ES 和 Lucene 会自动进行 Merge 操作
- Merge 操作相对比较重,需要优化,降低对系统的影响
- 优化点一:降低分段产生的数量/频率了
- 可以将 Refresh interval 调整到分钟级别 indices.memory.index_buffer_size(默认是 10%)
- 尽量避免文档的更新操作
Merge 优化
- 优化点二: 降低最大分段大小,避免较大的分段继续参与 Merge,节省系统资源。(最终会有多个分段)
- Index.merge.policy.segments_per_tier,默认为 10,越小需要越多的合并操作0
- Index.merge.policy.max_merged _segment,默认5 GB,操作此大小以后,就不再参与后续的合并操作
Force Merge
- 当Index 不再有写入操作的时候,建议对其进行 force merge
- 提升查询速度/减少内存开销
- 最终分成几个 segments 比较合适?
- 越少越好,最好可以 force merge 成1个,但是,Force Merge 会占用大量的网络,IO 和 CPU
- 如果不能在业务高峰期之前做完,就需要考虑增大最终的分段数
- Shard 的大小/Index.merge.policy.max_merged_segment 的大小
本节知识小节
对Lucene Index做了回顾,也介绍了对segment进行Merge是可以提高查询的性能的,当索引不再有数据写入的时候,我们要对索引进行一个Force Merge的操作。Force Merge最后分成多少个segments要根据业务场景进行设定。
缓存及使用Breaker限制内存使用
Node Query Cache
- 每一个节点有一个 Node Query 缓存
- 由该节点的所有 Shard 共享,只缓存 Filter Context 相关内容
- Cache 采用 LRU算法
- 静态配置,需要设置在每个 Data Node 上
- Node Level -indices.queries.cache.size:”10%
- Index Level: index.queries.cache.enabled: true
Shard Request Cache
- 缓存每个分片上的查询结果
- 只会缓存设置了 size=0 的查询对应的结果。不会缓存hits。但是会缓存 Aggregations 和 Suggestions
- Cache Key
- LRU 算法,将整个 JSON 查询串作为 Key,与 JSON对象的顺序相关
- 静态配置
- 数据节点:indices.requests.cache.size:“1%
Fielddata Cache
- 除了 Text 类型,默认都采用 doc_values。节约了内存
- Aggregation 的 Global ordinals 也保存在 Fielddata cache 中
- Text 类型的字段需要打开 Fileddata 才能对其进行聚合和排序
- Text 经过分词,排序和聚合效果不佳,建议不要轻易使用
- 配置
- 可以控制 Indices.fielddata.cache.size,避免产生 GC(默认无限制)
缓存失效
- Node Query Cache
- 保存的是 Segment 级缓存命中的结果。Segment 被合并后,缓存会失效o
- Shard Request Cache
- 分片 Refresh 时候,Shard Request Cache 会失效。如果 Shard 对应的数据频繁发生变化,该缓存的效0率会很差
- Fielddata Cache
- Segment 被合并后,会失效
管理内存的重要性
- Elasticsearch 高效运维依赖于内存的合理分配
- 可用内存一半分配给 JVM,一半留给操作系统,缓存索引文件
- 内存问题,引发的问题
- 长时间 GC,影响节点,导致集群响应缓慢
- OOM,导致丢节点
诊断内存状况
- 查看各个节点的内存状况
- GET_cat/nodes?v
- GET_nodes/stats/indices?pretty
- GETcat/nodes?v&h=name,queryCacheMemory,queryCacheEvictions,requestCacheMemory,requestCacheHitCount,request_cache.miss count
- GETcat/nodes?h=name,port,segments.memory,segments.index writer_memory,fielddata.memory_size,query_cache.memory_size,request cache.memory_size&v
一些常见的内存问题
- Segments 个数过多,导致 full GC
- 现象:集群整体响应缓慢,也没有特别多的数据读写。但是发现节点在持续进行 Full GC
- 分析: 查看 Elasticsearch 的内存使用,发现 segments.memory 占用很大空间
- 解决: 通过 force merge,把 segments 合并成一个
- 建议: 对于不在写入和更新的索引,可以将其设置成只读。同时,进行 force merge 操作问题依然存在,则需要考虑扩容。此外,对索引进行 force merge ,还可以减少对global_ordinals 数据结构的构建,减少对 fielddata cache 的开销
一些常见的内存问题
- 复杂的嵌套聚合,导致集群 full GC
- 现象: 节点响应缓慢,持续进行 Full GC
- 分析:导出 Dump 分析。发现内存中有大量 bucket 对象,查看 日志,发现复杂的嵌套聚合
- 解决:优化聚合
- 建议:在大量数据集上进行嵌套聚合查询,需要很大的堆内存来完成。如果业务场景确实需要则需要增加硬件进行扩展。同时,为了避免这类查询影响整个集群,需要设置 Circuit Break和 search.maxbuckets 的数值
Circuit Breaker
- 包含多种断路器,避免不合理操作引发的 OOM,每个断路器可以指定内存使用的限制
- Parent circuit breaker: 设置所有的熔断器可以使用的内存的总量
- Fielddata circuit breaker: 加载 fielddata 所需要的内存
- Request circuit breaker: 防止每个请求级数据结构超过一定的内存(例如聚合计算的内存)
- In flight circuit breaker: Request中的断路器
- Accounting request circuit breaker: 请求结束后不能释放的对象所占用的内存
Circuit Breaker 统计信息
- GET /_nodes/stats/breaker?
- Tripped 大于 0,说明有过熔断
- Limit size 与 estimated size 约接近,越可能引发熔断
- 千万不要触发了熔断,就盲目调大参数,有可能会导致集群出问题,也不因该盲目调小,需要进行评估
- 建议将集群升级到 7.x,更好的 Circuit Breaker 实现机制
- 增加了 indices.breaker.total.use_real_memory 配置项,可以更加精准的分析内存状况,避免 OOM
CodeDemo
GET _cat/nodes?v
GET _nodes/stats/indices?pretty
GET _cat/nodes?v&h=name,queryCacheMemory,queryCacheEvictions,requestCacheMemory,requestCacheHitCount,request_cache.miss_count
GET _cat/nodes?h=name,port,segments.memory,segments.index_writer_memory,fielddata.memory_size,query_cache.memory_size,request_cache.memory_size&v
PUT /_cluster/settings
{
"persistent" : {
"indices.breaker.request.limit" : "90%"
}
}
相关阅读
本节知识小节
介绍了Elasticsearch的缓存,内存管理对运维来说非常重要。也介绍了通过设置一些Circuit Breaker避免 OOM
一些运维的相关建议
集群的生命周期管理
- 预上线
- 评估用户的需求及使用场景 /数据建模 /容量规划/选择合适的部架构/性能测试0
- 上线
- 监控流量/定期检查潜在问题(防患于未然,发现错误的使用方式,及时增加机器)0
- 对索引进行优化 (Index Lifecycle Management)检测是否存在不均衡而导致有部分节点过热0
- 定期数据备份/滚动升级
- 下架前监控流量,实现 Stage Decommission
部署的建议
- 根据实际场景,选择合适的部署方式,选择合理的硬件配置
- 搜索类
- 日志/指标
- 部署要考虑,反亲和性 (Anti-Affinity)
- 尽量将机器分散在不同的机架。例如,3 台 Master 节点必须分散在不同的机架上
- 善用 Shard Filtering 进行配置
使用要遵循一定的规范
- Mapping
- 生产环境中索引应考虑禁止 Dynamic IndexMapping,避免过多字段导致 Cluster State占用过多
- 禁止索引自动创建的功能,创建时必须提供Mapping 或通过 Index Template 进行设定
使用要遵循一定的规范
- 设置 Slowlogs,发现一些性能不好,甚至是错误的使用 Pattern
- 例如:错误的将网址映射成 keyword,然后用通配符查询。应该使用 Text,结合 URL 分词器
- 严禁一切“*”开头的通配符查询
对重要的数据进行备份
定期更新到新版本
- ES在新版本中会持续对性能作出优化;提供更多的新功能
- Circuit breaker 实现的改进
- 修复一些已知的 bug 和安全隐患
ES的版本
- Elasticsearch 的版本格式是: XY.Z
- X: Major
- Y: Minor
- Z: Patch
- Elasticsearch 可以使用上一个主版本的索引
- 7.x 可以使用 6.x/7.x 不支持使用 5.x
- 5.x 可以使用 2.x
Rolling Upgrade v.s Full Cluster Restart
- Rolling Upgrade
- 没有 Downtime
- www.elastic.co/guide/en/el…l
- Full Cluster Restart
- 集群在更新期间不可用
- 升级更快
Full Restart 的步骤
- 停止索引数据,同时备份集群
- (Persistent)Disable Shard Allocation
- 执行 Synced Flush
- 关闭并更新所有节点
- 先运行所有 Master 节点/再运行其他节点
- 等集群变黄后打开 Shard Allocation
运维 Cheat Sheet:移动分片
- 从一个节点移动分片到另外一个节点
- 使用场景:
- 当一个数据节点上有过多 Hot Shards;可以通过手动分配分片到特定的节点解决
运维 Cheat Sheet : 从集群中移除一个节点
使用场景:当你想移除一个节点,或者对一个机器进行维护。同时你又不希望导致集群的颜色变
黄或者变红
运维 Cheat Sheet : 控制 Allocation 和 Recovery
- 使用场景:控制 Allocation 和 Recovery 的速率
运维 Cheat Sheet :Synced Flush
- 使用场景:需要重启一个节点。
- 通过 synced flush,可以在索引上放置一个 sync D。这样可以提供这些分片的 Recovery 的时间
运维 Cheat Sheet: 清空节点上的缓存
- 使用场景: 节点上出现了高内存占用。可以执行清除缓存的操作。这个操作会影响集群的性能但是会避免你的集群出现 OOM 的问题
运维 Cheat Sheet : 控制搜索的队列
- 使用场景: 当搜索的响应时间过长,看到有“reiect”指标的增加,都可以适当增加该数值
运维 Cheat Sheet : 设置 Circuit Breaker
- 使用场景: 设置各类 Circuit Breaker。避免 OOM 的发生
CodeDemo
# 移动一个分片从一个节点到另外一个节点
POST _cluster/reroute
{
"commands": [
{
"move": {
"index": "index_name",
"shard": 0,
"from_node": "node_name_1",
"to_node": "node_name_2"
}
}
]
}
# Fore the allocation of an unassinged shard with a reason
POST _cluster/reroute?explain
{
"commands": [
{
"allocate": {
"index": "index_name",
"shard": 0,
"node": "nodename"
}
}
]
}
# remove the nodes from cluster
PUT _cluster/settings
{
"transient": {
"cluster.routing.allocation.exclude._ip":"the_IP_of_your_node"
}
}
# Force a synced flush
POST _flush/synced
# change the number of moving shards to balance the cluster
PUT /_cluster/settings
{
"transient": {"cluster.routing.allocation.cluster_concurrent_rebalance":2}
}
# change the number of shards being recovered simultanceously per node
PUT _cluster/settings
{
"transient": {"cluster.routing.allocation.node_concurrent_recoveries":5}
}
# Change the recovery speed
PUT /_cluster/settings
{
"transient": {"indices.recovery.max_bytes_per_sec": "80mb"}
}
# Change the number of concurrent streams for a recovery on a single node
PUT _cluster/settings
{
"transient": {"indices.recovery.concurrent_streams":6}
}
# Change the sinze of the search queue
PUT _cluster/settings
{
"transient": {
"threadpool.search.queue_size":2000
}
}
# Clear the cache on a node
POST _cache/clear
#Adjust the circuit breakers
PUT _cluster/settings
{
"persistent": {
"indices.breaker.total.limit":"40%"
}
}
本节知识小节
介绍了一些运维Elasticsearch的最佳实践,建议在上线之前了解用户的使用场景,选择一个最合理的部署方式,同时在运维的过程中定期的去执行各种各样的检查,发现一些潜在的问题,同时我们应该合理的备份
此文章为4月Day12学习笔记,内容来源于极客时间《Elasticsearch 核心技术与实战》