易于获取当前系统状态对于规模化构建、维护基础设施相当重要。因为Uber的商业持续扩张,咱们的基础设施在规模和复杂性上不断增长,使得咱们必要时获取所需信息变得很困难。node
为了解决这个问题,咱们开发了Grail,一个聚合状态信息并在一个全局视图中展现、横跨多个数据中心和区域的平台。有了Grail,咱们能够更容易地开发快速、健壮的运维工具。git
继续阅读以了解Grail如何经过图模型,根本性地改变Uber工程部门操做存储的方式,使团队更容易缝合不一样源头的数据。github
2016年底,为了支撑不断增长的负载,咱们把全部数据库主机从旋转式硬盘更新到固态硬盘。有一步很重要,就是依然可以鉴别和追踪使用旧硬件的成千上万数据库。redis
那时候咱们没有容易的方式获取设备的当前状态,而且还要追踪大量脚本和任务。这驱使咱们寻找不一样的方法来开发大规模运维工具,需求以下:数据库
不像Metricbeat和osquery等相似信息收集系统,Grail不收集特定领域的信息,它的角色是一个平台,以高可用和响应式的方式聚合、连接和查询来自不一样数据源的数据,例如主机、数据库、部署和全部权等信息。它高效隐藏了实现细节。json
此外,你能够接近实时的方式,快速获取下面问题的答案:bash
若是你的服务和主机不多,这些问题就不重要。你只须要写一个脚本,在须要的时候直接收集信息就好了。可是以Uber的规模,当你有一堆服务和数十万主机时,这种方法就失效了。节点太多响应就会慢,查询完后数据关联会出错,结果也不能反映真实状况了。大规模场景下很难及时收集状态。架构
一个关键结论是“不存在惟一的真理来源”。数据中心和系统的信息老是分布在多个地方,只有把它们关联起来才能作决策。更复杂的是这些状态一直在变:主机的空闲磁盘空间在变、供应新的存储集群、并行发生的其它事件。整个系统的状态不可能实时获取,只能接近它。运维
Uber的存储平台团队开发维护的存储系统支撑了拍字节的关键任务数据,咱们的运维工具备一套标准的自我修复范式,有三个简单步骤:首先咱们收集系统状态,而后和正常状态比较,最后处理异常数据。分布式
如前文所述,在大规模场景下,不使用Grail这样的聚合平台是很难收集状态。举个例子,当咱们想获取全部运行主机当前状态时,好比trips数据。首先咱们要先找出哪些主机包含这个数据。接下来咱们要链接到这些主机并收集当前状态。最后转换并展现结果。
有了Grail,咱们只须要运行一条查询语句,就能够得到须要的信息:
TRAVERSE datastore:trips (
SCAN cluster (
SCAN db (
SCAN host (
FIELD HostInfo
)
)
)
)
复制代码
结果以json文档的形式返回,与查询结构很是类似,对代码友好。下面的代码片断展现了运行上面查询语句的精简版结果:
{
"__id": "datastore:trips",
"cluster": [{
"__id": "cluster-trips-us1-44",
"db": [{
"__id": "cluster-trips-us1-44-db26",
"host": [{
"__id": "host:database862-sic1",
"HostInfo": {
"cpuCount": 24,
"puppetRole": "database",
"memory": {
"freeBytes": 1323212425,
"totalBytes": 137438953472
},
"disk": {
"freeBytes": 48289601723,
"totalBytes": 1598689906787
}
}
}]
}]
}]
}
复制代码
Grail围绕对Uber基础设施的两项观察进行设计。第一,基础设施中节点和节点间的联系能够很天然地建模为图。
模型图中的节点经过惟一的键进行标识,键由类型和名字以type:name
的形式构成。数据源使用节点键将包括属性和链接的数据附加到节点上,所以数据就好像被节点键标识同样。节点的键空间是全局的,而属性和链接的键空间相对于节点是局部的。
Grail的对象模型是这样的,建模图中的节点由数据源生成的属性和链接隐式定义,这意味着下面条件至少知足一条节点A存在:
第二点是单个基础设施概念,好比主机或数据库的信息是去中心化的。这意味着获取完整数据视图须要结合不一样系统的信息。
Grail的方法是让每一个数据源提供本身所属子图来解决去中心化问题。这些子图可能会有重叠,由于数据源可能把属性和链接附加到同一个节点。
上图最上面展现了三个子图。实线和颜色表示子图由哪些数据源提供,虚线表示整个图。下面的图表示从Grail用户的角度看到的视图。
经过方法,咱们能够自动更新数据源的全部数据。对不一样数据源,咱们可以以不一样的速度并行更新数据。
上图中,数据源1在键HostInfo
下附加属性,数据源2键ServiceInfo
下附加属性,并将此节点和关联类型Service
下的一系列服务创建联系。
随着设计的实施,咱们须要一种简单的方法,可以在图中执行特定遍历。咱们调研的技术中没有能很好符合需求的。好比,GraphQL须要定义模式,且不支持映射和节点间命名关联。Gremlin彷佛能够,但实现并单独使用它很是复杂。因此咱们开发了本身的方案。
咱们的查询语言YQL,用户只须要指定一个开始节点集,而后经过后面的条件遍历图,同时与沿图属性中的字段交互。举个例子,下面的查询语句列出了全部知足条件的主机:空闲内存大于40G、剩余磁盘空间大于100G且是SSD:
TRAVERSE host:* (
FIELD HostInfo
WHERE HostInfo.disk.media = “SSD“
WHERE HostInfo.disk.free > (100*1024^3)
WHERE HostInfo.memory.free > (40*1024^3)
)
复制代码
从发布起Grail的架构经历屡次迭代。起初,它是咱们以前数据库运维工具的内部组件。初版迭代受TAO启发,基于Python开发,使用redis存储图。当它变得低效时,咱们决定把它做为一个单独服务用Go重写,使用共享的ElasticSearch集群存储。可是随着时间的推移,咱们发现这个方案在快速、有效摄取和查询所需信息时,缺乏伸缩性和低延迟。
咱们从新思考它的架构,把以前存到共享ElasticSearch集群中的数据迁移,改成直接存储到每一个查询节点上定制的内存数据库里。
当前Grail的高层架构包含三个组件:
Ingesters周期性从预配置的数据源中收集数据,而后经过Coordination集群传输,最后存储到每个查询节点上的datastore中。Coordination由定制的内存Raft集群实现,基于etcd Raft库开发。Raft协议确保数据更新和被存储到datastore的顺序,同时确保重启后数据一致。Coordination Nodes
和Query Nodes
都包含了存储在datastore中的每一个数据源最新数据更新。当Raft-logs被截断时,Coordination Nodes只使用datastore中的数据来建立当前数据的快照。
datastore是一个简单的键/值抽象,数据源的名字做为键,不一样的键下面存储每一个数据源最新的数据更新。全部数据源的数据分开存储,只有在执行查询时才聚合起来。
Grail经过在每一个区域运行各自的实例,为咱们提供基础设施的全局视图。每一个实例负责从本地主机和服务收集数据。查询节点根据配置追踪本地和远程区域上的raft-log。当执行查询时,查询引擎把本地和远程信息结合起来。
为了扩展Grail,咱们能够部署多个coordination集群、扩展查询引擎来支撑分布式查询,以便未来能够增长数据吞吐量和大小。
在与分布式系统交互时,考虑到信息不许确很是重要。无论数据如何提供,来自聚合平台或直接来自源头,在系统变化时不可避免会有延迟。分布式系统不是事务的,你不能用一致的快照获取它。无论基础设施规模如何,这些条件都是对的。
咱们的运维工具使用Grail的信息作决策。当这些决策须要改变系统时,在应用改变以前,咱们老是确保双重检查源头的信息。举个例子,当主机端的代理程序被分配任务时,代理程序在执行任务前会先检查先决条件是否知足,好比判断主机是否有足够的磁盘空间。
正如前面所讨论的,高效基础设施管理须要深入洞察系统状态。当规模很小时这很简单,你只须要按需查询数据便可。可是这个方法不适用大规模系统,这时你要将信息聚合到一处。正如咱们在实践中学到的,当有数十万主机和许多系统时,快速获取合理且最新的系统状态很重要。
最后Grail的优点能够总结为三点:
目前,Grail服务咱们存储方案的大多运维工具,而且对基础设施的各个方面都有几乎无数的使用案例。事实上随着信息范围不断增长,会有更多的使用案例。
Grail has made it easy for us to build tooling that quickly and robustly performs complex operational tasks.
这句话很容易意会,但用准确、精炼的语言表达出来还很难。我是这么翻译的有了Grail,咱们能够更容易地开发快速、健壮的运维工具。
In figure 5, Data Source 1 attaches a property under the HostInfo key, while Data Source 2 attaches a property under the ServiceInfo key and associates the node with a range of other service nodes in the graph under the association type Service.
这句话后一句很差理解、很差翻译
lets users specify a starting collection of nodes and then traverse the graph by following associations while interacting with the fields stored in the properties along the way
很差翻译,借鉴工具后翻译以下让用户指定一个节点集,而后经过后面的关联遍历图,同时与沿图属性中的字段交互
。
Over time, however, this solution also lacked the scalability and latency we needed for fast, efficient ingestion and querying.
可是随着时间的推移,咱们发现这个方案在快速、有效摄取和查询所需信息时,缺乏伸缩性和低延迟。
Distributed systems are not transactional—you cannot capture it with a consistent snapshot.
本文介绍Uber存储平台团队是如何管理本身的存储基础设施的。它们须要收集状态,而后根据这些信息执行运维任务、制定决策。
他们开发了Grail来管理,不断迭代来提高性能。将基础设施抽象成图模型,而且本身开发了工具来快速遍历图。他们面临多个挑战:分布式系统的数据延迟、不许确问题,查询性能低等。最后都很好的解决了,这些方法对咱们有借鉴意义。
公众号[QuanTalk],专一于计算机科学与技术、独立思考、阅读分享。欢迎关注交流