【橘子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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值