您的位置:首页 > 科技 > 能源 > 个人网站如何赚钱_网络服务合同纠纷包括哪些_推广工具有哪些_南京网站制作公司

个人网站如何赚钱_网络服务合同纠纷包括哪些_推广工具有哪些_南京网站制作公司

2024/12/23 21:57:26 来源:https://blog.csdn.net/liuwenqiang1314/article/details/144382147  浏览:    关键词:个人网站如何赚钱_网络服务合同纠纷包括哪些_推广工具有哪些_南京网站制作公司
个人网站如何赚钱_网络服务合同纠纷包括哪些_推广工具有哪些_南京网站制作公司

一、相关概念

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

而在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));

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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com