Enovy proxy中的数据统计

做者:Matt Klein 
译者:王帅俭 
原文: blog.envoyproxy.io/envoy-stats… 
本文转载自: ServcieMesher 社区

这是我在Envoy架构系列中的第3篇文章。这篇文章基于之前关于Envoy的线程模型热重启功能的帖子。若是您尚未阅读这些帖子,请先阅读。 须要指出的是,随着预演的结束,咱们如今能够进入更有趣的话题!git

统计概述

到目前为止,Envoy所作的最重要的事情是为分布式系统的可观测性提供了一个健壮的平台。这包括统计数据、日志记录和分布式跟踪。这篇文章将集中在统计数据和Envoy是如何实现容许高容量的同时保持卓越性能的。Envoy目前支持三种不一样的统计数据:程序员

  • Counter(计数器):只能增长不会减小的无符号整数。 例如,总请求。github

  • Gauge(计量):能够同时增长和减小的无符号整数。 例如,目前有效的请求。数据库

  • Timer/hitogram(计时器/直方图):无符号整数,最终将产生汇总百分位值。Envoy不区分计时器(一般以毫秒为单位)和原始直方图(能够是任何单位)。 例如,上游请求时间(以毫秒为单位)。后端

Envoy目前不支持任何浮点统计数据。缓存

Envoy生成不少对调试分布式系统有用的数据!

统计子系统目标

Envoy统计子系统的整体目标以下:安全

  • 粗略的线性吞吐量:能够与任意数量的工做线程一块儿扩展。另外一种说法是:在稳定状态下,使用stats时应该没有跨线程争用。数据结构

  • 在使用热重启时,状态应该在逻辑上保持一致。这意味着即便有两个Envoy进程在运行,当逻辑上认为是单个进程时,全部计数器、量规和直方图都应该是一致的。(有关这方面的更多信息,请参阅热重启这篇文章)。多线程

  • 统计数据应该包含在做用域内并做为一个组释放。做用域是具备公共前缀的统计数据的逻辑分组。例如:http.admin.*。这一点很重要,由于Envoy具备动态性。Envoy支持各类管理API,如监听器发现服务(LDS)和集群发现服务(CDS) API。为了避免耗尽内存,Envoy须要清理再也不使用的统计数据。架构

  • 统计范围应该可以重叠和正确的引用计数。这意味着若是做用域A使用一个名为foo.bar.baz的属性,做用域B也使用foo.bar.baz属性,那么foo.bar.baz的属性的引用计数应该是2。这对于热重启(两个进程将在一段时间内写入相同的统计数据)和动态管理API(在一段时间内,更新的监听器或集群将引用与旧监听器或集群相同的统计数据)都是必需的。

  • 统计数据子系统应该可以很好地执行直到数据平面处理开始时才知道的统计信息。许多统计数据本质上是“固定的”,能够在加载配置或动态API从新配置数据平面时建立(例如,cluster.foo.upstream_rq_5xx)。这些都是低频事件。其余统计信息,例如详细的HTTP响应代码度量(例如,cluster.foo.upstream_rq_503),在数据开始流动以前都不知道。使用“动态”的统计数据永远不会像使用“固定”的统计数据那样快,可是即便在处理每一个内核每秒数千个请求的10次时,性能仍然应该是足够的。

做为一个总体,上述目标须要一个复杂的系统来知足。咱们如今将深刻研究这个系统是如何工做的。

数据架构

图1:高级统计架构,蓝色统计数据显示了一个做用域分组。

图1显示了Envoy数据统计子系统的高级架构。它由如下几个部分组成。

存储

stat存储是Envoy内部的一个单例对象,并提供了一个简单的接口,经过该接口,其他代码能够得到做用域、计数器、计量和直方图的句柄。调用代码负责维护全部建立的做用域的全部权语义。看成用域被销毁时,全部包含的统计数据的引用计数都会减小1。若是任何统计数据达到0引用计数,它们将被释放。

统计数据

如前所述,统计数据包括计数器、量规和直方图。从终端用户的角度来看,这些接口使用起来很是简单。例如,计数器和计量都包括inc()dec()方法,而只有计量包括set()方法。程序员看不到任何潜在的存储复杂性。

Flusher

为了得到高性能,使用原子CPU指令在内部缓冲全部的状态变化。在可配置的间隔内,全部计数器和计量都被冲到flusher中。注意,在当前的架构中,直方图值直接发送到接收器。下面将更详细地描述这一点。Flusher在main线程中运行。

Sink

统计数据接收器是一个接口,它接受通用的统计数据并将其转换为特定于后端的连线格式。全部接收器都使用TLS,这样在刷新输出时就不会出现争用。然而,在实践中,目前只有主线会冲掉计数器和量规。全部线程都刷新直方图。

目前Envoy只支持TCP和UDP statsd协议。statsd是一种很是简单但获得普遍支持的传输格式。在将来,极可能会实现其余本地统计数据接收器,如PrometheusWavefrontInfluxDB。还要注意Envoy目前不支持维度或标签统计。这将在下面的工做部分中进一步讨论。

Admin

从操做的角度来看,可以实时地到达一个节点并转储当前状态是很是有用的。Envoy能够经过/stats管理端点实现此功能。管理端点直接查看存储库以加载全部计数器和计量并打印它们。这个端点目前不输出任何直方图数据。这一样是因为在当前的实现中直方图值是直接写入接收器的,所以存储不知道它们。

直方图的架构

正如已经屡次提到的,Envoy目前不维护进程内直方图数据。除了开发效率以外,没有什么特别的缘由;Lyft使用的statsd摄取管道提供了本身的直方图支持,并但愿直方图值直接发送到它。所以,直方图值目前不能经过管理端点查看。将来咱们极可能直接在Envoy内部实现HDR直方图。这一点将在下面进一步讨论。

线程本地热重启的能力存储

以上全部的背景都完成了,如今是时候深刻到有趣的部分:实践中是如何工做的?

统计项

图2:共享内存中单独的计数器/计量统计项

正如咱们在热重启文章中已经讨论过的那样,最终,全部统计数据都存储在共享内存中,以即可以在全部进程中使用它们。图2显示了单个stat条目。它由如下几个部分组成:

  • Name:彻底解析的属性名,例如http.admin.downstream_cx_active。目前限制为128个字符。

  • Value:属性的当前值。该数据包含量具的当前值和计数器的当前总价值。全部的数据写操做都使用原子操做,因此它们在多线程环境下是安全的。

  • Pending increment:此数据仅供计数器使用。除了值以外,每一个增量都是原子式的。之因此这样作,是由于大多数统计数据接收器想要获取刷新之间的增量而不是总数。所以,在冲洗期间计数器是锁住的。挂起的增量被写入计数器,而后归零。

  • Flags:目前只支持标志used。这表示若是统计数据被写过,那么代码可以区分零和从未写过。Envoy不会刷新历来没有使用过的统计数据,以免压倒性的统计后端不多使用的统计数据。

  • Ref count:Ref count容许重叠范围(可能在多个进程中)使用相同的底层统计数据。只有当ref计数为0时,才释放统计数据内存供未来使用。

存储

图3:线程本地热重启支持的存储体系结构

图3 显示了Envoy内部使用的线程本地stat存储的设计。这个版本的商店知足了以前发布的全部设计目标。如今咱们将详细介绍它的工做原理。

  1. 该存储是单例存储,整个Envoy流程都使用它。全部的范围、计数器和标准引用都是从这个单例中心存储库得到的。(本节将不介绍直方图,由于目前直方图不重要,直接刷新到TLS 统计数据接收器)。

  2. 当线程试图经过做用域获取计数器或量规时,它首先在做用域TLS缓存中按名称查找计数器或量规。若是在缓存中找到了统计数据,它将当即返回给调用者,而不须要任何锁定。若是没有找到该属性,则必须从范围中央缓存中获取该属性。

  3. 范围中央缓存经过标准进程范围内的互斥锁锁定(在稳定状态下,它不该该被高度竞争,由于统计信息将在范围TLS缓存中找到)。若是在中心缓存中找到了统计数据,那么它将返回到TLS缓存,在那里存储它以供之后无锁查找。若是在中央缓存中没有找到该属性,则必须从共享内存中分配该属性。

  4. 共享内存包含一系列固定的我的统计条目(图2)。Envoy包含一个很是基本的分配器,搜索统计条目名称相同的槽(支持热重启和重叠范围)或一个空位置,选择初始化槽若是目前空,增长引用计数,并返回它。这是在热重启期间跨进程统计数据的工做方式。两个进程都将从共享内存中分配一个统计数据条目槽,可是其中一个进程最终将引用计数增长到两个(相同的进程在重叠做用域建立期间发生)。若是在共享内存中找不到空间,Envoy将增长一个“panic”属性并返回一个特殊的溢出属性槽,以便进程能够在降级状态下继续运行。一旦一个统计数据槽被分配,它就被包装在一个进程本地数据结构中,存储在范围中心缓存中,存储在范围TLS缓存中,而后最终返回给调用者。

  5. 回想一下,stat子系统的目标之一是使做用域安全可删除。做用域是全局对象,由主线程和单例存储管理。删除做用域时,不一样线程上的做用域TLS缓存可能持有对单个统计数据的引用。为了说明这一点,“做用域缓存刷新”事件经过TLS发送到每一个线程。线程使用线程模型文章中描述的相似RCU的行为释放全部对做用域统计的引用。一旦计数器或表的最后一次引用计数被减小,共享内存统计项插槽也被释放。这是经过在统计数据条目插槽上减小引用计数来完成的。若是这个引用计数如今为零,那么这个槽就被彻底释放了,而且能够被任何进程用于一个新的状态。若是前面的描述有点混乱,总结一下:Envoy中的全部统计数据都由两个引用计数控制。第一个引用计数用于进程内TLS缓存的状态,第二个引用用于多个进程共享的备份状态入口槽。

回顾一下,让咱们看看上面的设计如何知足全部的原始目标:

  • 线性吞吐量:在稳定状态下,全部的统计数据分配都经过做用域TLS缓存进行。对于大量的工做线程来讲这要求不能加锁。

  • 在热从新启动期间逻辑上是一致的:最终,全部同名的数据在共享内存中使用相同的备份存储。这在流程之间建立了逻辑一致性。

  • 统计数据包含在一个做用域内,能够做为一个组释放,也能够重叠:做用域具备彻底独立的中央缓存和TLS缓存,以及独立的每一个统计数据引用计数。一个做用域能够被移除,而且它的全部统计数据的引用计数将会减小,而且可能会被释放。

  • 足够的动态统计数据性能:经过范围TLS缓存查找动态统计数据并使用O(1)哈希表。

将来的工做

虽然Envoystats子系统工做得很好,可是有几个方面在将来能够改进:

  • 维度/标记状态: 大多数更新的状态后端支持维度/标记,而不只仅是一个扁平的层次命名空间。在特使统计数据的某些区域中,这是颇有用的。短时间而言,咱们可能会添加全球标记支持,做为支持它的后端(如Prometheus、Wavefront和流感数据库)的第一步。

  • 线程本地原子缓存: 在worker数量和吞吐量极高的状况下,单个stat值上的原子争用将成为一个问题。这能够经过移动到TLS计数器和压力表来解决,这些计数器和压力表在冲洗以前被汇集到中央存储中。

  • 内置的HDR直方图: 因为几个缘由(管理输出、基于异常值的延迟检测和没有内置直方图支持的接收器),向Envoy添加直接的HDR直方图支持将很是有用。

  • 额外的静态接收器: 如前所述,咱们但愿直接支持更多的后端,如Prometheus、Wavefront、InfluxDB等。幸运的是,接收器接口很简单,添加新的实现并不困难。

结论

为了知足上述目标,Envoy的数据统计子系统的设计是新颖的。到目前为止,它在实践中表现得很是好,对于其余用例来讲,扩展起来应该相对容易。

代码连接

本文中涉及到的一些接口及实现的头文件请参考下面连接:

相关文章
相关标签/搜索