当前位置: 首页 > news >正文

【橘子ES】熔断器Circuit breaker

一、相关概念

我们在日常的开发中,关于服务之间的熔断操作似乎很常见,当请求超过了我们服务所认为可以承受的一个上限阈值的时候,我们为了保护服务不会被进一步的高负载压崩溃,我们有时候会选择熔断请求,此时服务不再对外提供服务,处于一种和外界断开的状态,这种操作我们称之为熔断。

而在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 (默认),true,false。

    limited :启用正则表达式,但使用 script.painless.regex.limit-factor 设置。这个配置为静态配置,限制 Painless 脚本中的正则表达式可以考虑的字符数。Elasticsearch 通过将设置值乘以脚本输入的字符长度来计算此限制。
    例如,输入 foobarbaz 的字符长度为 9。如果 script.painless.regex.limit-factor 是 6,foobarbaz 上的正则表达式 最多可以考虑 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 [request=0/0b, fielddata=num/numKB, in_flight_requests=num/numGB, accounting=num/numGB]

此外你还可以及时检查 JVM 内存使用情况。如果您启用了堆栈监控,则可以在 Kibana 中查看 JVM 内存使用情况。在主菜单中,单击 Stack Monitoring。在堆栈监控概述上 页面上,单击 Nodes (节点)。JVM Heap 列列出了每个节点的当前内存使用情况。
如果你没有开启监控,那你可以使用api来获取。

GET _cat/nodes?v=true&h=name,node*,heap*

要获取每个断路器的 JVM 内存使用情况,请使用 节点统计 API 的 API 进行 API 的处理。

GET _nodes/stats/breaker

3、处理方法

3.1、内存监控

高 JVM 内存压力通常会导致断路器错误。看 JVM 内存压力高。

你可以使用命令来查看你的节点内存使用情况。

GET _nodes/stats?filter_path=nodes.*.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?fielddata=true

3.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的内存,从ES7(Lucene)已经换成堆外内存的方式实现。

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?h=name,*heap*,*memory*,*Cache*&format=json

查询某个索引部分内存占用情况:

GET _cat/indices/twitter?h=*memory*&format=jsonGET _cat/indices/?s=segmentsMemory:desc&v&h=index,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,List<BreakerSettings> 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 Setting<Boolean> USE_REAL_MEMORY_USAGE_SETTING = Setting.boolSetting("indices.breaker.total.use_real_memory",true,Property.NodeScope
);public static final Setting<ByteSizeValue> 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 Setting<ByteSizeValue> REQUEST_CIRCUIT_BREAKER_LIMIT_SETTING = Setting.memorySizeSetting("indices.breaker.request.limit","60%",Property.Dynamic,Property.NodeScope
);
public static final Setting<Double> REQUEST_CIRCUIT_BREAKER_OVERHEAD_SETTING = Setting.doubleSetting("indices.breaker.request.overhead",1.0d,0.0d,Property.Dynamic,Property.NodeScope
);

如果你还记得上面的配置,你就会发现它其实就是把这些默认值加载进来了,当然你要是用api动态修改动态属性,这里的值都会变。他会提供api来处理。
我们看到他有个map,breakers,
private final Map<String, 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 parent's 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));

最后在一步一步往下查的时候,查完了就会根据断路器分析这次的占用是不是要熔断了。

相关文章:

【橘子ES】熔断器Circuit breaker

一、相关概念 我们在日常的开发中&#xff0c;关于服务之间的熔断操作似乎很常见&#xff0c;当请求超过了我们服务所认为可以承受的一个上限阈值的时候&#xff0c;我们为了保护服务不会被进一步的高负载压崩溃&#xff0c;我们有时候会选择熔断请求&#xff0c;此时服务不再…...

6.4 CPU性能分析--Intel处理器跟踪技术

Intel处理器跟踪PT技术是记录程序执行过程的技术&#xff0c;它把记录信息编码报文存到高压缩率的二进制文件中。该二进制文件结合每条指令的时间戳重建执行流。PT技术覆盖度大&#xff0c;开销小&#xff0c;有关开销的信息详见&#xff0c;主要用于性能问题的事后分析和根因定…...

期权懂|如何用第三方平台开通期权?

期权小懂每日分享期权知识&#xff0c;帮助期权新手及时有效地掌握即市趋势与新资讯&#xff01; 如何用第三方平台开通期权&#xff1f; 如果不能满足常规的期权开户条件&#xff0c;可以考虑以下几种方法来尝试开户&#xff1a; 一、选择第三方平台&#xff1a; 通过网络搜…...

JS中const有没有变量提升

在JavaScript中&#xff0c;const 关键字用于声明一个只读的常量&#xff0c;其值在初始化后不能被重新赋值。关于变量提升&#xff08;Hoisting&#xff09;&#xff0c;它是JavaScript中一个重要的概念&#xff0c;指的是无论变量或函数声明在何处&#xff0c;它们都会被“提…...

Axure RP全面介绍:功能、应用与中文替代方案

Axure RP是一款功能强大的原型设计工具&#xff0c;它被广泛应用于网页和移动应用的设计领域。Axure RP集成了设计、原型制作和文档管理&#xff0c;为产品管理人员、设计师和开发人员提供了一个综合的平台。让我们一步步了解Axure的基本功能、使用技巧以及中文支持平台——“在…...

WordPress用户首次登录强制修改密码

有些企业网站要求很高&#xff0c;比如用户首次登录强制要求修改密码&#xff0c;这里提供一段代码&#xff0c;用于实现强制修改密码供参考。 通过代码可以实现&#xff0c;用户正常注册或者管理员在后台添加用户时&#xff0c;会添加首次登录标记&#xff0c;用户首次登录后会…...

AI开源南京分享会回顾录

AI 开源南京分享会&#xff0c;已于2024年11月30日下午在国浩律师&#xff08;南京&#xff09;事务所5楼会议厅成功举办。此次活动由 KCC南京、PowerData、RISC-Verse 联合主办&#xff0c;国浩律师&#xff08;南京&#xff09;事务所协办。 活动以“开源视角的 AI 对话”为主…...

基于事件驱动的websocket简单实现

websocket的实现 什么是websocket&#xff1f; WebSocket 是一种网络通信协议&#xff0c;旨在为客户端和服务器之间提供全双工、实时的通信通道。它是在 HTML5 规范中引入的&#xff0c;可以让浏览器与服务器进行持久化连接&#xff0c;以便实现低延迟的数据交换。 WebSock…...

【leetcode100】反转链表

1、题目描述 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 2、初始思路 2.1 思路 # Definition for singly-linked list. # class ListNode: # …...

禅道Bug的一次迁移

一、场景 平时工作记录在公司禅道上的问题想备份一份到本地&#xff0c;但是又没有公司禅道的数据库信息&#xff0c;有时候出测试报告想批量调整数据方便截图很困难&#xff0c;同时也为了学习禅道数据流转过程&#xff0c;所以有了把缺陷保存到本地一份的想法。 实际上禅道支…...

c段和旁站讲解(附查询网址)

1. C段&#xff08;C类子网段&#xff09; C段就是一个IP地址的小范围。比如&#xff0c;假设你有一个家庭Wi-Fi网络&#xff0c;Wi-Fi会分配给你一组IP地址&#xff08;每个设备一个IP地址&#xff09;。如果你的网络分配的是类似 192.168.1.0 这样的IP地址&#xff0c;那么这…...

Linux编译Kernel时的文件zImage、文件dtb(dtbs)、核心模块分别是什么东西?

zImage文件的介绍 在编译Linux内核时&#xff0c;zImage 是一种内核映像文件&#xff0c;它是内核的压缩版本&#xff0c;通常用于引导嵌入式设备或其他资源有限的环境。 zImage 的具体含义 zImage 是 “Compressed Kernel Image” 的缩写。它是通过压缩原始的内核映像&…...

【深度学习】深刻理解“变形金刚”——Transformer

Transformer 是一种用于处理序列数据的深度学习模型架构&#xff0c;最初由 Vaswani 等人在 2017 年的论文《Attention is All You Need》中提出。它彻底改变了自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;成为许多高级任务&#xff08;如机器翻译、文本生成、问答…...

75_pandas.DataFrame 中查看和复制

75_pandas.DataFrame 中查看和复制 与pandas的DataFrame与NumPy数组ndarray类似&#xff0c;也有视图&#xff08;view&#xff09;和拷贝&#xff08;copy&#xff09;。 当使用loc[]或iloc[]等选择DataFrame的一部分以生成新的DataFrame时&#xff0c;与原对象共享内存的对…...

打电话玩手机识别-支持YOLO,COCO,VOC格式的标记,超高识别率可检测到手持打电话, 非接触式打电话,玩手机自拍等

打电话玩手机识别-支持YOLO&#xff0c;COCO&#xff0c;VOC格式的标记&#xff0c;超高识别率可检测到手持打电话&#xff0c; 非接触式打电话&#xff0c;玩手机自拍等1275个图片。 手持打电话&#xff1a; 非接触打电话 玩手机 数据集下载 yolov11:https://download.csdn…...

生产慎用之调试日志对空间矢量数据批量插入的性能影响-以MybatisPlus为例

目录 前言 一、一些缘由 1、性能分析 二、插入方式调整 1、批量插入的实现 2、MP的批量插入实现 3、日志的配置 三、默认处理方式 1、基础程序代码 2、执行情况 四、提升调试日志等级 1、在logback中进行设置 2、提升后的效果 五、总结 前言 在现代软件开发中&#xff0c;性能优…...

单片机:实现倒计时(附带源码)

使用单片机实现倒计时功能是一个常见的嵌入式应用&#xff0c;它能帮助你更好地理解如何进行时间控制和如何通过定时器实现精确的倒计时。通过该项目&#xff0c;你将学习如何使用单片机的定时器来进行时间计算&#xff0c;并通过LED或LCD显示倒计时的结果。 1. 项目概述 倒计…...

什么是多线程中的上下文切换

什么是多线程中的上下文切换 回答 上下文切换是指CPU从一个线程转到另一个线程时&#xff0c;需要保存当前线程的上下文状态&#xff0c;恢复另一个线程的上下文状态&#xff0c;以便于下一次恢复执行该线程时能够正确地运行。 在多线程编程中&#xff0c;上下文切换是一种常…...

如何在windwos批量拉取go mod

golang go-zero微服务开发,分的rpc项目太多了,变更了公共包,需要手动去拉取,直接一键拉取就好了,创建一个windwos脚本文件 文件名 tidy_all_go_mod.ps1 代码 # 辅助工具拉取go mod tidy # 根目录v99main执行 ./tidy_all_go_mod.ps1 # 定义项目的根目录 $RootDir Get-Locat…...

【Three.js基础学习】29.Hologram Shader

前言 three.js 通过着色器如何实现全息影像&#xff0c;以及一些动态的效果。 一些难点的思维&#xff0c;代码目录 下面图是摄像机视角观看影响上的时候&#xff0c;如何实现光影的渐变&#xff0c;透视以及叠加等。 一、代码 1.index.html <!DOCTYPE html> <html …...

文件包含进阶玩法以及绕过姿态

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文整理文件包含漏洞的进阶玩法与绕过姿态 不涉及基础原理了 特殊玩法汇总 本地包含 文件包含上传文件 原理: php的文件包含有着把其他文件类型当做php代码执行的功效&#xff0c;文件上传一般会限制后缀&am…...

Markdown编辑器工具--Typora

下载链接...

PyTorch 的 torch.unbind 函数详解与进阶应用:中英双语

中文版 PyTorch 的 torch.unbind 函数详解与进阶应用 在深度学习中&#xff0c;张量的维度操作是基础又重要的内容。PyTorch 提供了许多方便的工具来完成这些操作&#xff0c;其中之一便是 torch.unbind。与常见的堆叠函数&#xff08;如 torch.stack&#xff09;相辅相成&am…...

四十六:如何使用Wireshark解密TLS/SSL报文?

TLS/SSL是保护网络通信的重要协议&#xff0c;其加密机制可以有效地防止敏感信息被窃取。然而&#xff0c;在调试网络应用或分析安全问题时&#xff0c;解密TLS/SSL流量是不可避免的需求。本文将介绍如何使用Wireshark解密TLS/SSL报文。 前提条件 在解密TLS/SSL报文之前&…...

【人工智能】OpenAI O1模型:超越GPT-4的长上下文RAG性能详解与优化指南

在人工智能&#xff08;AI&#xff09;领域&#xff0c;长上下文生成与检索&#xff08;RAG&#xff09; 已成为提升自然语言处理&#xff08;NLP&#xff09;模型性能的关键技术之一。随着数据规模与应用场景的不断扩展&#xff0c;如何高效地处理海量上下文信息&#xff0c;成…...

Ubuntu22.04搭建FTP服务器保姆级教程

在网络环境中&#xff0c;文件传输是一项至关重要的任务。FTP&#xff08;文件传输协议&#xff09;是一种基于客户端/服务器模式的协议&#xff0c;广泛用于在互联网上传输文件。Ubuntu作为一款流行的Linux发行版&#xff0c;因其稳定性和易用性而广受开发者和系统管理员的喜爱…...

操作系统(4)操作系统的结构

一、无序结构&#xff08;整体结构或模块组合结构&#xff09; 1.特点&#xff1a; 以大型表格和队列为中心&#xff0c;操作系统的各部分程序围绕着这些表格进行。操作系统由许多标准的、可兼容的基本单位&#xff08;称为模块&#xff09;构成&#xff0c;模块之间通过规定的…...

Python数据分析(OpenCV视频处理)

处理视频我们引入的还是numpy 和 OpenCV 的包 引入方式如下&#xff1a; import numpy as np import cv2 我们使用OpenCV来加载本地视频&#xff0c;参数就是你视频的路径就可以 #加载视频 cap cv2.VideoCapture(./1.mp4) 下面我们进行读取视频 #读取视频 flag,frame cap.re…...

跨域 Cookie 共享

跨域请求经常遇到需要携带 cookie 的场景&#xff0c;为了确保跨域请求能够携带用户的认证信息或其他状态&#xff0c;浏览器提供了 withCredentials 这个属性。 如何在 Axios 中使用 withCredentials 为了在跨域请求中携带 cookie&#xff0c;需要在 Axios 配置中设置 withCr…...

【视频异常检测】Real-Time Anomaly Detection and Localization in Crowded Scenes 论文阅读

文章信息&#xff1a; 发表于&#xff1a;CVPR2015&#xff08;workshop&#xff09; 原文链接&#xff1a;https://www.cv-foundation.org/openaccess/content_cvpr_workshops_2015/W04/papers/Sabokrou_Real-Time_Anomaly_Detection_2015_CVPR_paper.pdf Real-Time Anomaly D…...

学做馒头面包哪个网站好/常见网络营销推广方法

安装环境 &#xff1a; ubuntu 16.04 树莓派利用基于C的opencv的CaptureVideo函数调用外部摄像头&#xff0c;比如usb摄像头等是没有问题的&#xff0c;但是无法调用自带的摄像头&#xff0c;因此需哟通过调用其他的函数或者改变自带CSI摄像头的属性设置才可以获取实时图像等。…...

成都自适应建站哪家好/app推广方案怎么写

mp.weixin.qq.com/s/mTzu6vPha… 转载于:https://juejin.im/post/5cd8cbfef265da037516c08c...

网站开发设计模板/百度的客服电话是多少

事先声明&#xff0c;本文无意挑起python与其他语言的争端&#xff0c;每一种语言都有自己的适用场景和专业范围&#xff0c;任何一门编程语言都是有所能&#xff0c;有所不能。本文对其他语言没有半点贬低之意&#xff0c;这7门编程语言&#xff0c;我都有过使用&#xff0c;我…...

网站建设策划 流程/在线一键建站系统

Would you like to change the user interface language in any edition of Windows 7 or Vista on your computer? Here’s a free app that can help you do this quickly and easily. 您想在计算机的任何版本的Windows 7或Vista中更改用户界面语言吗&#xff1f; 这是一个…...

网站建设公司做前端/网站建站在线制作

/** 【需求】服务端接收客户端发送过来的数据&#xff0c;并打印在控制台上。* * 建立TCP服务端的思路&#xff1a;* * 1.创建服务端Socket服务&#xff0c;通过ServerSocket。* * 2.服务端必须对外提供一个端口&#xff0c;否则客户端无法连接。* &#xff08;连接服务器&…...

wordpress广告位插件/肇庆seo排名

在创建SpringBoot项目时&#xff0c;勾选Spring Security依赖&#xff0c;会在Maven中导入Spring Security。org.springframework.boot spring-boot-starter-security这个依赖中有一个登陆拦截器&#xff0c;SpringBoot项目访问任意接口(页面)都跳转到login登录页面这是Spring…...