78建筑网站,黄石网站建设报价,品牌网上旗舰店和商场质量一样吗,十三五专业建设规划一、相关概念
我们在日常的开发中#xff0c;关于服务之间的熔断操作似乎很常见#xff0c;当请求超过了我们服务所认为可以承受的一个上限阈值的时候#xff0c;我们为了保护服务不会被进一步的高负载压崩溃#xff0c;我们有时候会选择熔断请求#xff0c;此时服务不再…一、相关概念
我们在日常的开发中关于服务之间的熔断操作似乎很常见当请求超过了我们服务所认为可以承受的一个上限阈值的时候我们为了保护服务不会被进一步的高负载压崩溃我们有时候会选择熔断请求此时服务不再对外提供服务处于一种和外界断开的状态这种操作我们称之为熔断。
而在Elasticsearch 包含多个熔断器用于防止操作使用过多的内存。每个断路器都会跟踪某些操作使用的内存并指定它可以跟踪的内存量的限制。此外还有一个父级 breaker 用于指定可在所有 breaker 中跟踪的内存总量。
当熔断器达到其限制时Elasticsearch 将拒绝进一步的操作。此时会触发熔断异常而我们如何观察这些异常以及如何解决这些异常就显得非常重要。
熔断器不会跟踪 Elasticsearch 中的所有内存使用情况因此仅提供不完整的保护以防止内存过度使用。如果 Elasticsearch 使用过多内存则它可能会遇到性能问题节点甚至可能会因 OutOfMemoryError 而失败。所以我们不能全部依赖于熔断器而是需要我们做好关于jvm内存监控来完善我们的集群保护。 下面我们来看一下es中的熔断器。
二、熔断器
1、父级熔断器(parent circuit breaker)
父级断路器可使用以下设置进行配置
indices.breaker.total.use_real_memory: 该配置为静态配置所谓静态配置就指的是一旦我们在配置文件中配置好并且只能在配置文件配置该配置在服务运行期间无法通过api命令修改。 该配置为布尔类型默认为true为true时父断路器置考虑实际内存使用量 而不会去根据子熔断器的逻辑。 当为false时仅考虑子熔断器预留的内存量也就是只看子熔断器的内存配置是不是超了。一般我们这个值就默认即可父级熔断器是个总的兜底的下面我们会看到。indices.breaker.total.limit 该配置为动态配置你可以在集群运行期间随时修改这个值修改的方式是通过api来操作。 PUT /_cluster/settings { “indices.breaker.total.limit”:“80%” } 他的含义表示的是总体的父级熔断器的一个阈值。如果indices.breaker.total.use_real_memory为false则默认为JVM堆的70%。如果indices.breaker.total.use_real_memory为true则默认为JVM堆的95% 换言之就是说当indices.breaker.total.use_real_memory为默认值true的时候他这个值就是百分之95也就是说当你堆内存占用到达95%的时候此时会触发父级熔断器此时你的服务就被熔断了。这其实也没啥你都95了再不熔断怕不是要oom了但是实际上你要观察如果经常触发95熔断这时候你可能要对你的服务做一些调整因为这不正常。而要是indices.breaker.total.use_real_memory为fasle这时候就是百分之75因为他这时候就考虑子熔断器的内存限制了所以其实没那么大了阈值就低了一些。可以理解。
2、字段熔断器(fielddata circuit breaker)
字段数据断路器估计将字段加载到字段数据缓存中所需的堆内存。如果加载字段将导致高速缓存超过预定义的内存限制则断路器将停止操作并返回错误。 这个机制主要针对我们的fielddata我们在es中经常会对字段做聚合做排序但是你做聚合排序这类操作是不能对text类型的字段做的因为大多数字段可以将索引时生产的磁盘 doc_values 用于此数据访问模式但是文本text字段不支持 doc_values。所以我们需要一种替代方案文本text字段使用查询时内存中的数据结构称为 fielddata。 当我们首次将该字段用于聚合排序或在脚本中使用时将按需构建此数据结构懒加载的。 它是通过从磁盘读取每个段的整个反向索引反转你的分词结果变成文档并将结果存储在 JVM 堆中的内存中来构建的。 Fielddata 会占用大量堆空间尤其是在加载大量的文本字段时。 一旦将字段数据加载到堆中它在该段的生命周期内将一直保留在那里他无法被gc回收。 同样加载字段数据是一个昂贵的过程可能导致用户遇到延迟的情况。 这就是默认情况下禁用字段数据的原因。这个机制默认是关闭的。 字段熔断器可使用以下设置进行配置
indices.breaker.fielddata.limit 这个限制默认为 JVM 堆的 40%。也就是说我们每次处理fielddata加载的时候都会判断是不是触发了该限制如果超出限制就触发熔断。indices.breaker.fielddata.overhead 估算因子在计算内存是需要与估算因子相乘得到内存估算值。默认1.03是一个系数也不是直接内存到了百分之40就熔断而是要乘以这个系数不过1.03和不乘也差不多。反正你自己计算的时候估计量的时候最好计算上比较精准。
这两个配置均为动态配置你可以随时使用命令修改。
3、请求熔断器(request circuit breaker)
请求熔断器使 Elasticsearch 可以防止每个请求的数据结构例如 用于在请求期间计算聚合的内存 超过一定数量的内存 。我说直接一点就是他是限制你每次请求的大小的比如这个限制是5m那每次请求不能超过这个值各自请求是独立的。
请求熔断器可使用以下设置进行配置
indices.breaker.request.limit 请求中断器的限制默认为 JVM 堆的 60%也就是每次请求大小不能超过堆内存的60%那你可能要问了百分之六十这个比例那真不小他们各个请求独立都不能超过60但是多个请求叠加起来那完全有可能超过百分之百直接干崩。假如你是个杠精完全可以想到每个请求都占百分之59这样没超了请求熔断但是两个加起来就干一百多了这时候不是崩了你es连两个请求都容不下我那你玩毛。你不要忘了我们还有父级熔断器在呢你虽然没超请求熔断但是父级熔断也会给你限制住。别想卡bug。indices.breaker.request.overhead 一个常量所有请求估计值都与该常量相乘以确定最终估计值。默认值为 1。
以上配置均为动态配置。
4、进行中的请求熔路器(In flight requests circuit breaker)
进行中的请求熔路器使 ES 可以限制 transport 或 HTTP 级别上所有当前活动的即将传入请求的内存使用使其不超过节点上的一定内存量。 内存使用情况取决于请求本身的内容长度。 该断路器还认为 不仅需要内存来表示原始请求 而且还需要将其作为结构化对象 这由默认开销 反映出来。 这个熔断器其实用的不多。
network.breaker.inflight_requests.limit 正在进行的请求熔断器的限制默认为 JVM 堆的 100%。这意味着它受为父熔断器配置的限制的约束。因为受到父级熔断限制其实这个一般不用配置。network.breaker.inflight_requests.overhead 一个常数所有飞行请求估计值都乘以确定最终估计值。默认值为 2。
以上均为动态配置。
5、脚本编译熔断器(Script compilation circuit breaker)
与之前的基于内存的断路器略有不同脚本编译断路器限制了一段时间内内联脚本编译的次数。 关于如何使用es中的脚本我们可以参考脚本 他的配置很简单就一个参数。
script.max_compilations_rate 该配置为动态配置限制特定间隔内允许编译的唯一动态脚本的数量。默认为 150/5m即每 5 分钟 150 次。
6、正则表达式熔断器(regex circuit breaker)
编写不当的正则表达式会降低集群稳定性并且 性能。正则表达式断路器限制了 Painless 脚本中的 regex 来获取。 该熔断器的配置如下 script.painless.regex.enabled 该配置为静态配置在 Painless 脚本中启用正则表达式。可以配置三个值分别为。limited 默认truefalse。 limited 启用正则表达式但使用 script.painless.regex.limit-factor 设置。这个配置为静态配置限制 Painless 脚本中的正则表达式可以考虑的字符数。Elasticsearch 通过将设置值乘以脚本输入的字符长度来计算此限制。 例如输入 foobarbaz 的字符长度为 9。如果 script.painless.regex.limit-factor 是 6foobarbaz 上的正则表达式 最多可以考虑 54 9 * 6 个字符。如果表达式超过此限制则 它会触发 Regex 断路器并返回错误。 true启用没有复杂度限制的正则表达式。禁用 regex 断路器。 false:禁用 regex。任何包含正则表达式的 Painless 脚本都会返回错误。
三、熔断器错误
1、熔断器错误的意义
我们知道了什么时候触发熔断也知道了es 使用熔断器来防止节点耗尽 JVM 堆内存。如果 Elasticsearch 估计某个操作将超过熔断器则会停止该操作并返回错误。 那么如果熔断了是什么表现呢换言之我们如何知道如何定位熔断。 默认情况下父断路器在 JVM 内存使用率达到 95% 时触发。为防止错误我们建议在你的服务使用率经常超过85%的时候就及时采取措施来减轻内存压力。或者优化内存或者提高机器配置。
2、错误表现观察手段
如果请求触发了熔断器Elasticsearch 将返回带有 429 HTTP 状态代码的错误。
{error: {type: circuit_breaking_exception,reason: [parent] Data too large, data for [http_request] would be [123848638/118.1mb], which is larger than the limit of [123273216/117.5mb], real usage: [120182112/114.6mb], new bytes reserved: [3666526/3.4mb],bytes_wanted: 123848638,bytes_limit: 123273216,durability: TRANSIENT},status: 429
}Elasticsearch 还会将断路器错误写入 elasticsearch.log。当自动化过程如分片的重分配等等触发断路器时这非常有用。
Caused by: org.elasticsearch.common.breaker.CircuitBreakingException: [parent] Data too large, data for [transport_request] would be [num/numGB], which is larger than the limit of [num/numGB], usages [request0/0b, fielddatanum/numKB, in_flight_requestsnum/numGB, accountingnum/numGB]此外你还可以及时检查 JVM 内存使用情况。如果您启用了堆栈监控则可以在 Kibana 中查看 JVM 内存使用情况。在主菜单中单击 Stack Monitoring。在堆栈监控概述上 页面上单击 Nodes 节点。JVM Heap 列列出了每个节点的当前内存使用情况。 如果你没有开启监控那你可以使用api来获取。
GET _cat/nodes?vtruehname,node*,heap*要获取每个断路器的 JVM 内存使用情况请使用 节点统计 API 的 API 进行 API 的处理。
GET _nodes/stats/breaker3、处理方法
3.1、内存监控
高 JVM 内存压力通常会导致断路器错误。看 JVM 内存压力高。
你可以使用命令来查看你的节点内存使用情况。
GET _nodes/stats?filter_pathnodes.*.jvm.mem.pools.old然后来计算内存压力如下所示 JVM 内存压力 used_in_bytes / max_in_bytes
3.2、gc日志
作为java开发的应用你在做内存监控的时候很难不使用gc日志。 随着内存使用量的增加垃圾回收变得更加频繁并且需要 长。您可以在 elasticsearch.log. 例如以下事件指出 Elasticsearch 在过去 40 秒内花费了超过 50%21 秒的时间执行垃圾回收。
[timestamp_short_interval_from_last][INFO ][o.e.m.j.JvmGcMonitorService] [node_id] [gc][number] overhead, spent [21s] collecting in the last [40s]3.3、减少jvm占用
1、减少分片数量 每个分片都使用内存。在大多数情况下一小部分大型分片使用较少的 资源。有关减少分片计数的提示请参阅 分片减少 2、避免昂贵的搜索 昂贵的搜索可能会占用大量内存。为了更好地跟踪集群上昂贵的搜索请启用慢日志。 昂贵的搜索可能具有较大的 size 参数 使用具有大量存储桶的聚合或者包括 昂贵的查询。为了防止昂贵的搜索请考虑以下设置更改 使用 index.max_result_window 索引设置。 使用 search.max_buckets 集群设置。 使用 search.allow_expensive_queries cluster 设置。
PUT _settings
{index.max_result_window: 5000
}PUT _cluster/settings
{persistent: {search.max_buckets: 20000,search.allow_expensive_queries: false}
}es中认为的复杂检索
3.4、防止映射爆炸
定义过多的字段或嵌套字段太深可能会导致 映射使用大量内存的爆炸。要防止映射爆炸请使用 mapping limit 设置来限制字段映射的数量。
3.5、分散批量请求
虽然比单个请求更高效但 bulk indexing 或者 multi-search请求仍会造成较高的 JVM 内存压力。如果可能请提交较小的请求并在请求之间留出更多时间。
3.6、避免在文本字段上使用 fielddata
对于高基数文本字段fielddata 可以使用大量的 JVM 内存。为避免这种情况Elasticsearch 默认在文本字段上禁用 fielddata。如果您已启用 fielddata 并触发了 fielddata 断路器请考虑禁用它并改用关键字字段。请参阅 fielddata mapping 参数。 并且你还可以在触发了字段熔断器的时候清除他的缓存。
POST _cache/clear?fielddatatrue3.7、升级节点内存
加机器永远是最稳的。
四、内存部分
ES使用的JVM内存的中存在几大类无法GC的缓存
1、内存占用
1.1、QueryCache
支持调用API进行清理。实现类org.elasticsearch.indices.IndicesQueryCache 查询缓存负责缓存查询结果。每个节点有一个查询缓存由所有分片共享。查询缓存只缓存在过滤器上下文中使用的查询即用来缓存filter查询。 以下设置是静态的必须在群集中的每个数据节点elasticsearch.yml上配置 indices.queries.cache.size默认10%。
以下设置是静态的索引级别 index.queries.cache.enabled是否对某个索引开启查询缓存默认true。创建索引时配置settings中。
1.2、RequestCache
支持调用API进行清理。实现类org.elasticsearch.indices.IndicesRequestCache 分片级请求缓存模块在每个分片上缓存本地结果。这使得频繁使用的搜索请求可能很繁重几乎能立即返回结果。请求缓存非常适合日志场景在日志场景中只有最新的索引会被主动更新而较早索引的结果将直接从缓存中提供。 以下设置是静态的必须在群集中的每个节点elasticsearch.yml上配置 indices.requests.cache.size: 默认1%
以下设置是索引分片级别 index.requests.cache.enable开启true关闭false创建索引时配置settings中。
1.3、FieldDataCache
支持调用API进行清理。 实现类org.elasticsearch.indices.fielddata.cache.IndicesFieldDataCache 字段数据缓存主要用于对字段进行排序或计算聚合。它将所有字段值加载到内存中以便基于文档快速访问这些值。为一个字段建立字段数据缓存的成本可能很高因此建议有足够的内存来分配它并保持它处于加载状态。 indices.fielddata.cache.size默认无界属于静态配置。
1.4、SegmentsCache
不支持调用API进行清理。 Lucene的segment需要加载到JVM的内存从ES7Lucene已经换成堆外内存的方式实现。
1.5、indexing-buffer
索引缓冲区用于存储新索引的文档。当缓冲区满时缓冲区中的文档就会被写入磁盘上的一个区段。节点上的所有分片共享这个缓冲区。
以下设置是静态的必须在群集中的每个数据节点上配置 indices.memory.index_buffer_size可以配置成百分比或字节大小值的形式。默认值为 10%这意味着分配给节点的堆总量的 10%将用作所有分片共享的索引缓冲区大小。 indices.memory.min_index_buffer_size当 index_buffer_size 指定为百分比则可以使用此设置指定绝对最小值。默认值为 48MB。 indices.memory.max_index_buffer_size如果 index_buffer_size 指定为百分比则可以使用此设置指定绝对最大值。默认值为无限制。
2、清理api
清除缓存API:
# 按照索引粒度清除缓存
POST /twitter/_cache/clear
POST /kimchy,elasticsearch/_cache/clear
# 清除所有索引的缓存
POST /_cache/clear
查询节点的部分内存使用情况和缓存命中情况:
GET _cat/nodes?hname,*heap*,*memory*,*Cache*formatjson
查询某个索引部分内存占用情况:
GET _cat/indices/twitter?h*memory*formatjsonGET _cat/indices/?ssegmentsMemory:descvhindex,segmentsCount,segmentsMemory,memoryTotal,mergesCurrent,mergesCurrentDocs,storeSizeGET _cat/segments/twitter?v
查询节点的熔断状况
GET _nodes/stats/breaker
在业务使用了别名或跨索引查询时实际查询的索引数量过大查询高峰期时父熔断器没能敏捷的触发熔断限流导致节点JVM出现OOM最终节点挂掉不可用。 可以适当调整熔断器的熔断值减少熔断触发比例,但是这种总归不是办法如果你的版本比较低可能存在实现问题7版本之前的实现无法感知及时进而触发父熔断。 为了增加父熔断器的触发灵敏度ES7重新实现了触发校验使用JDK自带的MemoryMXBean几乎实时获取到内存的实际使用量来进行校验判断
MemoryMXBean MEMORY_MX_BEAN ManagementFactory.getMemoryMXBean();
return MEMORY_MX_BEAN.getHeapMemoryUsage().getUsed();五、源码解读
我们来分析一下源码关于熔断器的实现。本文所有内容均基于es7.17.7 在我们开始分析之前我们先来聊一个逻辑这个逻辑曾经在说springmvc的时候提到过。我们拿到一个源码在你没有头绪的时候如何开始。比如我们这个例子我们是分析熔断器的那么熔断器是啥时候加载的呢这个很重要。肯定是服务启动的时候他就被启动了或者初始化了或者注册了反正就是他开始了那么我们其实应该第一就是去看集群启动的地方而集群是一个一个的节点组成的。所以我们应该关注的是Node类。看看他有没有这个熔断器。 当然你会觉得这很上帝视角实际上我从0开始定位熔断器的源码的时候。我首先找到的是org.elasticsearch.common.breaker.CircuitBreaker这个接口然后一步一步根据在哪里使用了这个接口或者他的实现类或者他的一些属性来追踪的。这个第一次来百分之五十可能要靠运气和猜测了猜测也不是瞎猜这种大型成熟组件命名是很考究的多数讲究见名知意所以可以适当看看命名。 好了我们来看Node类。 首先当一个节点node启动的时候在org.elasticsearch.node.Node类中初始化了一个熔断器实现
final CircuitBreakerService circuitBreakerService createCircuitBreakerService(settingsModule.getSettings(),pluginCircuitBreakers,settingsModule.getClusterSettings());其中circuitBreakerService实现为public static CircuitBreakerService createCircuitBreakerService(Settings settings,ListBreakerSettings breakerSettings,ClusterSettings clusterSettings
) {// 默认为hierarchyString type BREAKER_TYPE_KEY.get(settings);if (type.equals(hierarchy)) {// 默认返回return new HierarchyCircuitBreakerService(settings, breakerSettings, clusterSettings);} else if (type.equals(none)) {return new NoneCircuitBreakerService();} else {throw new IllegalArgumentException(Unknown circuit breaker type [ type ]);}
}所以我们看到他在node节点中初始化了HierarchyCircuitBreakerService这个组件于是我们就来到HierarchyCircuitBreakerService这个里面。
我们来到org.elasticsearch.indices.breaker.HierarchyCircuitBreakerService这个类里面看一下。 这个类非常庞大很多地方声明了一些配置的初始化值我们只看核心逻辑不要被那些细节逻辑卡住。 我们就看父级熔断器和请求熔断器相关的在一开始他初始化了一个配置。就是关于这两个熔断器的。
public static final SettingBoolean USE_REAL_MEMORY_USAGE_SETTING Setting.boolSetting(indices.breaker.total.use_real_memory,true,Property.NodeScope
);public static final SettingByteSizeValue TOTAL_CIRCUIT_BREAKER_LIMIT_SETTING Setting.memorySizeSetting(indices.breaker.total.limit,settings - {if (USE_REAL_MEMORY_USAGE_SETTING.get(settings)) {return 95%;} else {return 70%;}},Property.Dynamic,Property.NodeScope
);public static final SettingByteSizeValue REQUEST_CIRCUIT_BREAKER_LIMIT_SETTING Setting.memorySizeSetting(indices.breaker.request.limit,60%,Property.Dynamic,Property.NodeScope
);
public static final SettingDouble REQUEST_CIRCUIT_BREAKER_OVERHEAD_SETTING Setting.doubleSetting(indices.breaker.request.overhead,1.0d,0.0d,Property.Dynamic,Property.NodeScope
);如果你还记得上面的配置你就会发现它其实就是把这些默认值加载进来了当然你要是用api动态修改动态属性这里的值都会变。他会提供api来处理。 我们看到他有个mapbreakers private final MapString, CircuitBreaker breakers;这个map中存储着所有的子级熔断器其中key为熔断器的名称。我们这里看请求熔断器自然就是CircuitBreaker.REQUEST “request”,value就是CircuitBreaker这个接口这里要注意一点所有的子熔断器都是CircuitBreaker。只不过不同名字而已。他们的落脚点没有区别。
// 所有的子熔断器都被这个方法包装你能看到他其实就是ChildMemoryCircuitBreaker因为我们
// 一般不用Noop,noop这个啥也不做我们不分析他。不要被细节卡住。
private CircuitBreaker validateAndCreateBreaker(BreakerSettings breakerSettings) {// Validate the settingsvalidateSettings(new BreakerSettings[] { breakerSettings });return breakerSettings.getType() CircuitBreaker.Type.NOOP? new NoopCircuitBreaker(breakerSettings.getName()): new ChildMemoryCircuitBreaker(breakerSettings,LogManager.getLogger(CHILD_LOGGER_PREFIX breakerSettings.getName()),this,breakerSettings.getName());
}所以我们简单分析之后可以知道所有的子熔断都最后是一个ChildMemoryCircuitBreaker。 而父熔断器没有被放到这个map中他是被保存在了 private volatile BreakerSettings parentSettings;这个变量中之所以加volatile是因为可能多个并发用api在修改他的动态配置。 好了我们继续往下走。 我们现在得到一点就是所有的子熔断都最后是一个ChildMemoryCircuitBreaker并且都被存在了breakers这个map中而父级熔断器被放在了 BreakerSettings parentSettings
所以我们现在确定了子熔断器的逻辑都在ChildMemoryCircuitBreaker了那我们就来看看org.elasticsearch.common.breaker.ChildMemoryCircuitBreaker这个类。
/** Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one* or more contributor license agreements. Licensed under the Elastic License* 2.0 and the Server Side Public License, v 1; you may not use this file except* in compliance with, at your election, the Elastic License 2.0 or the Server* Side Public License, v 1.*/package org.elasticsearch.common.breaker;import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.indices.breaker.BreakerSettings;
import org.elasticsearch.indices.breaker.HierarchyCircuitBreakerService;import java.util.concurrent.atomic.AtomicLong;/*** Breaker that will check a parents when incrementing*/
public class ChildMemoryCircuitBreaker implements CircuitBreaker {private volatile LimitAndOverhead limitAndOverhead;private final Durability durability;private final AtomicLong used;private final AtomicLong trippedCount;private final Logger logger;private final HierarchyCircuitBreakerService parent;private final String name;/*** fieldName:就是你熔断器的名称* bytesNeeded就是触发熔断时候的内存* 所以我们看到这个方法是统一封装了一个异常信息构建通过熔断器名称区别。*/Overridepublic void circuitBreak(String fieldName, long bytesNeeded) {final long memoryBytesLimit this.limitAndOverhead.limit;this.trippedCount.incrementAndGet();final String message [ this.name ] Data too large, data for [ fieldName ] would be [ bytesNeeded / new ByteSizeValue(bytesNeeded) ] , which is larger than the limit of [ memoryBytesLimit / new ByteSizeValue(memoryBytesLimit) ];logger.debug(() - new ParameterizedMessage({}, message));throw new CircuitBreakingException(message, bytesNeeded, memoryBytesLimit, durability);}/*** 这里就是做熔断计算的地方判断你是不是要熔断了* bytes:申请的大小* lable:熔断器的名称*/Overridepublic void addEstimateBytesAndMaybeBreak(long bytes, String label) throws CircuitBreakingException {final LimitAndOverhead limitAndOverhead this.limitAndOverhead;final long memoryBytesLimit limitAndOverhead.limit;final double overheadConstant limitAndOverhead.overhead;// short-circuit on no data allowed, immediately throwing an exception// 配置为0立刻触发熔断。if (memoryBytesLimit 0) {circuitBreak(label, bytes);}long newUsed;// 如果你配置了-1那就无限制打印一句警告日志if (memoryBytesLimit -1) {newUsed noLimit(bytes, label);} else {/*** 这里就是正常的配置那种既不是不限制也不是无限制这里会判断你的当前使用大小* 和你配置的阈值如果触发了就进入circuitBreak()发生熔断否则就更新一下当前的* 占用量(cas更新因为多个请求)。这里我们看到其实就是* memoryBytesLimit 0 newUsedWithOverhead memoryBytesLimit* 来判断是否熔断了所以这里其实就是子熔断器的进入地方。*/newUsed limit(bytes, label, overheadConstant, memoryBytesLimit);}/*** 当你走到这里还没抛出熔断异常的时候那就是子熔断器都过去了此时我们要判断父熔断器。* 还记得我们上面说的子熔断器是封装在ChildMemoryCircuitBreaker中的而父熔断器就是在* org.elasticsearch.indices.breaker.HierarchyCircuitBreakerService* 这个里面的parentSettings中所以这里我不用看也知道checkParentLimit是跳到* HierarchyCircuitBreakerService中了。*/try {parent.checkParentLimit((long) (bytes * overheadConstant), label);} catch (CircuitBreakingException e) {// If the parent breaker is tripped, this breaker has to be// adjusted back down because the allocation is blocked but the// breaker has already been incrementedthis.addWithoutBreaking(-bytes);throw e;}assert newUsed 0 : Used bytes: [ newUsed ] must be 0;}......
}
我们就接着父熔断器的跳入来看org.elasticsearch.indices.breaker.HierarchyCircuitBreakerService#checkParentLimit
public void checkParentLimit(long newBytesReserved, String label) throws CircuitBreakingException {// 父级熔断器判断的不是请求申请或者分配看的是总的堆占用final MemoryUsage memoryUsed memoryUsed(newBytesReserved);// 父熔断器的配置限制long parentLimit this.parentSettings.getLimit();// 做判断检查是不是超过了配置if (memoryUsed.totalUsage parentLimit overLimitStrategy.overLimit(memoryUsed).totalUsage parentLimit) {this.parentTripCount.incrementAndGet();final String messageString buildParentTripMessage(newBytesReserved,label,memoryUsed,parentLimit,this.trackRealMemoryUsage,this.breakers);// 根据策略检查是不是要看子熔断器的占用。默认是需要的。// derive durability of a tripped parent breaker depending on whether the majority of memory tracked by// child circuit breakers is categorized as transient or permanent.CircuitBreaker.Durability durability memoryUsed.transientChildUsage memoryUsed.permanentChildUsage? CircuitBreaker.Durability.TRANSIENT: CircuitBreaker.Durability.PERMANENT;logger.debug(() - new ParameterizedMessage({}, messageString));throw new CircuitBreakingException(messageString, memoryUsed.totalUsage, parentLimit, durability);}
}所以这就是熔断器的源码脉络分析。那么还有一个问题就是他在哪触发的呢我们就知道他是咋工作的不知道他在哪里工作的你可以用idea的功能点中看看哪里用了我可以告诉你他实现的很笨。他真的是代码埋点。 也就是你每次请求比如你做了一次聚合这个聚合请求在发起的时候会包装一个断路器然后请求的时候在代码里面判断。其实我想着是不是可以做成类似aop的模式不需要这么做。我就不去跟源码了我们用一个测试用例看一下org.elasticsearch.search.aggregations.support.AggregationContext
这个类在初始化的时候包装了断路器
MultiBucketConsumer consumer new MultiBucketConsumer(maxBucket, breakerService.getBreaker(CircuitBreaker.REQUEST));最后在一步一步往下查的时候查完了就会根据断路器分析这次的占用是不是要熔断了。