From 9e96b69952a28c235833105e6ece9d0ceb0c67b3 Mon Sep 17 00:00:00 2001 From: YuCheng Hu Date: Mon, 19 Jul 2021 16:53:42 -0400 Subject: [PATCH 1/5] =?UTF-8?q?=E5=B0=8F=E5=86=99=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=A4=B9=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTACT.md | 4 +- Configuration/configuration.md | 2 +- DataIngestion/datamanage.md | 4 +- DataIngestion/faq.md | 2 +- DataIngestion/hadoopbased.md | 2 +- DataIngestion/ingestion.md | 12 ++-- DataIngestion/native.md | 2 +- DataIngestion/schemadesign.md | 14 ++-- DataIngestion/taskrefer.md | 2 +- SUMMARY.md | 66 +++++++++---------- design/Historical.md | 2 +- .../AggregationGranularity.md | 0 {Querying => querying}/Aggregations.md | 0 {Querying => querying}/datasource.md | 0 .../datasourcemetadataquery.md | 0 {Querying => querying}/dimensionspec.md | 0 {Querying => querying}/druidsql.md | 0 {Querying => querying}/expression.md | 0 {Querying => querying}/filters.md | 0 {Querying => querying}/granularity.md | 0 {Querying => querying}/groupby.md | 0 {Querying => querying}/having.md | 0 {Querying => querying}/index.md | 0 {Querying => querying}/joins.md | 0 {Querying => querying}/limitspec.md | 0 {Querying => querying}/lookups.md | 0 {Querying => querying}/makeNativeQueries.md | 0 .../multi-value-dimensions.md | 0 {Querying => querying}/multitenancy.md | 0 {Querying => querying}/postaggregation.md | 0 {Querying => querying}/query-context.md | 0 {Querying => querying}/querycached.md | 0 {Querying => querying}/queryexecution.md | 0 {Querying => querying}/scan.md | 0 {Querying => querying}/searchquery.md | 0 {Querying => querying}/segmentMetadata.md | 0 {Querying => querying}/sorting-orders.md | 0 {Querying => querying}/spatialfilter.md | 0 {Querying => querying}/timeboundaryquery.md | 0 {Querying => querying}/timeseriesquery.md | 0 {Querying => querying}/topn.md | 0 {Querying => querying}/topnsorting.md | 0 {Querying => querying}/virtual-columns.md | 0 tutorials/chapter-2.md | 2 +- tutorials/chapter-4.md | 4 +- tutorials/tutorial-batch.md | 2 +- 46 files changed, 60 insertions(+), 60 deletions(-) rename {Querying => querying}/AggregationGranularity.md (100%) rename {Querying => querying}/Aggregations.md (100%) rename {Querying => querying}/datasource.md (100%) rename {Querying => querying}/datasourcemetadataquery.md (100%) rename {Querying => querying}/dimensionspec.md (100%) rename {Querying => querying}/druidsql.md (100%) rename {Querying => querying}/expression.md (100%) rename {Querying => querying}/filters.md (100%) rename {Querying => querying}/granularity.md (100%) rename {Querying => querying}/groupby.md (100%) rename {Querying => querying}/having.md (100%) rename {Querying => querying}/index.md (100%) rename {Querying => querying}/joins.md (100%) rename {Querying => querying}/limitspec.md (100%) rename {Querying => querying}/lookups.md (100%) rename {Querying => querying}/makeNativeQueries.md (100%) rename {Querying => querying}/multi-value-dimensions.md (100%) rename {Querying => querying}/multitenancy.md (100%) rename {Querying => querying}/postaggregation.md (100%) rename {Querying => querying}/query-context.md (100%) rename {Querying => querying}/querycached.md (100%) rename {Querying => querying}/queryexecution.md (100%) rename {Querying => querying}/scan.md (100%) rename {Querying => querying}/searchquery.md (100%) rename {Querying => querying}/segmentMetadata.md (100%) rename {Querying => querying}/sorting-orders.md (100%) rename {Querying => querying}/spatialfilter.md (100%) rename {Querying => querying}/timeboundaryquery.md (100%) rename {Querying => querying}/timeseriesquery.md (100%) rename {Querying => querying}/topn.md (100%) rename {Querying => querying}/topnsorting.md (100%) rename {Querying => querying}/virtual-columns.md (100%) diff --git a/CONTACT.md b/CONTACT.md index cb8f3b5..7d647af 100644 --- a/CONTACT.md +++ b/CONTACT.md @@ -6,12 +6,12 @@ |---|---| | 电子邮件 | [service@ossez.com](mailto:service@ossez.com) | | QQ 或微信 | 103899765 | -| QQ 交流群 Spring | 15186112 | +| QQ 交流群 | 15186112 | | 社区论坛 | [https://www.ossez.com/](https://www.ossez.com/) | ## 公众平台 -我们建议您通过社区论坛来和我们进行沟通,请关注我们公众平台上的账号 +我们建议您通过社区论坛来和我们进行沟通,请关注我们公众平台上的账号。 ### 微信公众号 ![](https://cdn.ossez.com/img/cwikius/cwikius-qr-wechat-search-w400.png) diff --git a/Configuration/configuration.md b/Configuration/configuration.md index 6004e40..b8a2c69 100644 --- a/Configuration/configuration.md +++ b/Configuration/configuration.md @@ -59,7 +59,7 @@ jvm.config runtime.properties 在我们的所有进程中有四个需要配置的JVM参数 -1. `-Duser.timezone=UTC` 该参数将JVM的默认时区设置为UTC。我们总是这样设置,不使用其他默认时区进行测试,因此本地时区可能会工作,但它们也可能会发现奇怪和有趣的错误。要在非UTC时区中发出查询,请参阅 [查询粒度](../Querying/granularity.md) +1. `-Duser.timezone=UTC` 该参数将JVM的默认时区设置为UTC。我们总是这样设置,不使用其他默认时区进行测试,因此本地时区可能会工作,但它们也可能会发现奇怪和有趣的错误。要在非UTC时区中发出查询,请参阅 [查询粒度](../querying/granularity.md) 2. `-Dfile.encoding=UTF-8` 这类似于时区,我们假设UTF-8进行测试。本地编码可能有效,但也可能导致奇怪和有趣的错误。 3. `-Djava.io.tmpdir=` 系统中与文件系统交互的各个部分都是通过临时文件完成的,这些文件可能会变得有些大。许多生产系统都被设置为具有小的(但是很快的)`/tmp`目录,这对于Druid来说可能是个问题,因此我们建议将JVM的tmp目录指向一些有更多内容的目录。此目录不应为volatile tmpfs。这个目录还应该具有良好的读写速度,因此应该强烈避免NFS挂载。 4. `-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager` 这允许log4j2处理使用标准java日志的非log4j2组件(如jetty)的日志。 diff --git a/DataIngestion/datamanage.md b/DataIngestion/datamanage.md index 1a78965..015c7de 100644 --- a/DataIngestion/datamanage.md +++ b/DataIngestion/datamanage.md @@ -101,7 +101,7 @@ foo_2015-01-03/2015-01-04_v1_2 除非所有输入段具有相同的元数据,否则输出段可以具有与输入段不同的元数据。 * Dimensions: 由于Apache Druid支持schema更改,因此即使是同一个数据源的一部分,各个段之间的维度也可能不同。如果输入段具有不同的维度,则输出段基本上包括输入段的所有维度。但是,即使输入段具有相同的维度集,维度顺序或维度的数据类型也可能不同。例如,某些维度的数据类型可以从 `字符串` 类型更改为基本类型,或者可以更改维度的顺序以获得更好的局部性。在这种情况下,在数据类型和排序方面,最近段的维度先于旧段的维度。这是因为最近的段更有可能具有所需的新顺序和数据类型。如果要使用自己的顺序和类型,可以在压缩任务规范中指定自定义 `dimensionsSpec`。 -* Roll-up: 仅当为所有输入段设置了 `rollup` 时,才会汇总输出段。有关详细信息,请参见 [rollup](ingestion.md#rollup)。您可以使用 [段元数据查询](../Querying/segmentMetadata.md) 检查段是否已被rollup。 +* Roll-up: 仅当为所有输入段设置了 `rollup` 时,才会汇总输出段。有关详细信息,请参见 [rollup](ingestion.md#rollup)。您可以使用 [段元数据查询](../querying/segmentMetadata.md) 检查段是否已被rollup。 #### 压缩合并的IOConfig 压缩IOConfig需要指定 `inputSpec`,如下所示。 @@ -139,7 +139,7 @@ Druid不支持按主键更新单个记录。 #### 使用lookups -如果有需要经常更新值的维度,请首先尝试使用 [lookups](../Querying/lookups.md)。lookups的一个典型用例是,在Druid段中存储一个ID维度,并希望将ID维度映射到一个人类可读的字符串值,该字符串值可能需要定期更新。 +如果有需要经常更新值的维度,请首先尝试使用 [lookups](../querying/lookups.md)。lookups的一个典型用例是,在Druid段中存储一个ID维度,并希望将ID维度映射到一个人类可读的字符串值,该字符串值可能需要定期更新。 #### 重新摄取数据 diff --git a/DataIngestion/faq.md b/DataIngestion/faq.md index 5fcfb6b..18104f0 100644 --- a/DataIngestion/faq.md +++ b/DataIngestion/faq.md @@ -62,7 +62,7 @@ Druid会拒绝时间窗口之外的事件, 确认事件是否被拒绝了的 ### 查询返回来了空结果 -您可以对为数据源创建的dimension和metric使用段 [元数据查询](../Querying/segmentMetadata.md)。确保您在查询中使用的聚合器的名称与这些metric之一匹配,还要确保指定的查询间隔与存在数据的有效时间范围匹配。 +您可以对为数据源创建的dimension和metric使用段 [元数据查询](../querying/segmentMetadata.md)。确保您在查询中使用的聚合器的名称与这些metric之一匹配,还要确保指定的查询间隔与存在数据的有效时间范围匹配。 ### schema变化时如何在Druid中重新索引现有数据 diff --git a/DataIngestion/hadoopbased.md b/DataIngestion/hadoopbased.md index 9e44a52..2c4afc7 100644 --- a/DataIngestion/hadoopbased.md +++ b/DataIngestion/hadoopbased.md @@ -203,7 +203,7 @@ s3n://billy-bucket/the/data/is/here/y=2012/m=06/d=01/H=23 | `dataSource` | String | Druid数据源名称,从该数据源读取数据 | 是 | | `intervals` | List | ISO-8601时间间隔的字符串List | 是 | | `segments` | List | 从中读取数据的段的列表,默认情况下自动获取。您可以通过向Coordinator的接口 `/druid/Coordinator/v1/metadata/datasources/segments?full` 进行POST查询来获取要放在这里的段列表。例如["2012-01-01T00:00:00.000/2012-01-03T00:00:00.000","2012-01-05T00:00:00.000/2012-01-07T00:00:00.000"]. 您可能希望手动提供此列表,以确保读取的段与任务提交时的段完全相同,如果用户提供的列表与任务实际运行时的数据库状态不匹配,则任务将失败 | 否 | -| `filter` | JSON | 查看 [Filter](../Querying/filters.md) | 否 | +| `filter` | JSON | 查看 [Filter](../querying/filters.md) | 否 | | `dimensions` | String数组 | 要加载的维度列的名称。默认情况下,列表将根据 `parseSpec` 构造。如果 `parseSpec` 没有维度的显式列表,则将读取存储数据中的所有维度列。 | 否 | | `metrics` | String数组 | 要加载的Metric列的名称。默认情况下,列表将根据所有已配置聚合器的"name"构造。 | 否 | | `ignoreWhenNoSegments` | boolean | 如果找不到段,是否忽略此 `ingestionSpec`。默认行为是在找不到段时引发错误。| 否 | diff --git a/DataIngestion/ingestion.md b/DataIngestion/ingestion.md index a8bab72..f60a49f 100644 --- a/DataIngestion/ingestion.md +++ b/DataIngestion/ingestion.md @@ -102,7 +102,7 @@ Rollup由 `granularitySpec` 中的 `rollup` 配置项控制。 默认情况下 有关如何配置Rollup以及该特性将如何修改数据的示例,请参阅[Rollup教程](../tutorials/chapter-5.md)。 #### 最大化rollup比率 -通过比较Druid中的行数和接收的事件数,可以测量数据源的汇总率。这个数字越高,从汇总中获得的好处就越多。一种方法是使用[Druid SQL](../Querying/druidsql.md)查询,比如: +通过比较Druid中的行数和接收的事件数,可以测量数据源的汇总率。这个数字越高,从汇总中获得的好处就越多。一种方法是使用[Druid SQL](../querying/druidsql.md)查询,比如: ```json SELECT SUM("cnt") / COUNT(*) * 1.0 FROM datasource ``` @@ -162,7 +162,7 @@ Druid数据源总是按时间划分为*时间块*,每个时间块包含一个 > > 注意,当然,划分数据的一种方法是将其加载到分开的数据源中。这是一种完全可行的方法,当数据源的数量不会导致每个数据源的开销过大时,它可以很好地工作。如果使用这种方法,那么可以忽略这一部分,因为这部分描述了如何在单个数据源中设置分区。 > -> 有关将数据拆分为单独数据源的详细信息以及潜在的操作注意事项,请参阅 [多租户注意事项](../Querying/multitenancy.md)。 +> 有关将数据拆分为单独数据源的详细信息以及潜在的操作注意事项,请参阅 [多租户注意事项](../querying/multitenancy.md)。 ### 摄入规范 @@ -347,7 +347,7 @@ Druid数据源总是按时间划分为*时间块*,每个时间块包含一个 |-|-|-| | dimensions | 维度名称或者对象的列表,在 `dimensions` 和 `dimensionExclusions` 中不能包含相同的列。

如果该配置为一个空数组,Druid将会把所有未出现在 `dimensionExclusions` 中的非时间、非指标列当做字符串类型的维度列,参见[Inclusions and exclusions](#Inclusions-and-exclusions)。 | `[]` | | dimensionExclusions | 在摄取中需要排除的列名称,在该配置中只支持名称,不支持对象。在 `dimensions` 和 `dimensionExclusions` 中不能包含相同的列。 | `[]` | -| spatialDimensions | 一个[空间维度](../Querying/spatialfilter.md)的数组 | `[]` | +| spatialDimensions | 一个[空间维度](../querying/spatialfilter.md)的数组 | `[]` | ###### `Dimension objects` 在 `dimensions` 列的每一个维度可以是一个名称,也可以是一个对象。 提供一个名称等价于提供了一个给定名称的 `string` 类型的维度对象。例如: `page` 等价于 `{"name": "page", "type": "string"}`。 @@ -378,7 +378,7 @@ Druid以两种可能的方式来解释 `dimensionsSpec` : *normal* 和 *schemale ##### `metricsSpec` -`metricsSpec` 位于 `dataSchema` -> `metricsSpec` 中,是一个在摄入阶段要应用的 [聚合器](../Querying/Aggregations.md) 列表。 在启用了 [rollup](#rollup) 时是很有用的,因为它将配置如何在摄入阶段进行聚合。 +`metricsSpec` 位于 `dataSchema` -> `metricsSpec` 中,是一个在摄入阶段要应用的 [聚合器](../querying/Aggregations.md) 列表。 在启用了 [rollup](#rollup) 时是很有用的,因为它将配置如何在摄入阶段进行聚合。 一个 `metricsSpec` 实例如下: ```json @@ -389,7 +389,7 @@ Druid以两种可能的方式来解释 `dimensionsSpec` : *normal* 和 *schemale ] ``` > [!WARNING] -> 通常,当 [rollup](#rollup) 被禁用时,应该有一个空的 `metricsSpec`(因为没有rollup,Druid不会在摄取时进行任何的聚合,所以没有理由包含摄取时聚合器)。但是,在某些情况下,定义Metrics仍然是有意义的:例如,如果要创建一个复杂的列作为 [近似聚合](../Querying/Aggregations.md#近似聚合) 的预计算部分,则只能通过在 `metricsSpec` 中定义度量来实现 +> 通常,当 [rollup](#rollup) 被禁用时,应该有一个空的 `metricsSpec`(因为没有rollup,Druid不会在摄取时进行任何的聚合,所以没有理由包含摄取时聚合器)。但是,在某些情况下,定义Metrics仍然是有意义的:例如,如果要创建一个复杂的列作为 [近似聚合](../querying/Aggregations.md#近似聚合) 的预计算部分,则只能通过在 `metricsSpec` 中定义度量来实现 ##### `granularitySpec` @@ -419,7 +419,7 @@ Druid以两种可能的方式来解释 `dimensionsSpec` : *normal* 和 *schemale |-|-|-| | type | `uniform` 或者 `arbitrary` ,大多数时候使用 `uniform` | `uniform` | | segmentGranularity | 数据源的 [时间分块](../design/Design.md#数据源和段) 粒度。每个时间块可以创建多个段, 例如,当设置为 `day` 时,同一天的事件属于同一时间块,该时间块可以根据其他配置和输入大小进一步划分为多个段。这里可以提供任何粒度。请注意,同一时间块中的所有段应具有相同的段粒度。

如果 `type` 字段设置为 `arbitrary` 则忽略 | `day` | -| queryGranularity | 每个段内时间戳存储的分辨率, 必须等于或比 `segmentGranularity` 更细。这将是您可以查询的最细粒度,并且仍然可以查询到合理的结果。但是请注意,您仍然可以在比此粒度更粗的场景进行查询,例如 "`minute`"的值意味着记录将以分钟的粒度存储,并且可以在分钟的任意倍数(包括分钟、5分钟、小时等)进行查询。

这里可以提供任何 [粒度](../Querying/AggregationGranularity.md) 。使用 `none` 按原样存储时间戳,而不进行任何截断。请注意,即使将 `queryGranularity` 设置为 `none`,也将应用 `rollup`。 | `none` | +| queryGranularity | 每个段内时间戳存储的分辨率, 必须等于或比 `segmentGranularity` 更细。这将是您可以查询的最细粒度,并且仍然可以查询到合理的结果。但是请注意,您仍然可以在比此粒度更粗的场景进行查询,例如 "`minute`"的值意味着记录将以分钟的粒度存储,并且可以在分钟的任意倍数(包括分钟、5分钟、小时等)进行查询。

这里可以提供任何 [粒度](../querying/AggregationGranularity.md) 。使用 `none` 按原样存储时间戳,而不进行任何截断。请注意,即使将 `queryGranularity` 设置为 `none`,也将应用 `rollup`。 | `none` | | rollup | 是否在摄取时使用 [rollup](#rollup)。 注意:即使 `queryGranularity` 设置为 `none`,rollup也仍然是有效的,当数据具有相同的时间戳时数据将被汇总 | `true` | | interval | 描述应该创建段的时间块的间隔列表。如果 `type` 设置为`uniform`,则此列表将根据 `segmentGranularity` 进行拆分和舍入。如果 `type` 设置为 `arbitrary` ,则将按原样使用此列表。

如果该值不提供或者为空值,则批处理摄取任务通常会根据在输入数据中找到的时间戳来确定要输出的时间块。

如果指定,批处理摄取任务可以跳过确定分区阶段,这可能会导致更快的摄取。批量摄取任务也可以预先请求它们的所有锁,而不是逐个请求。批处理摄取任务将丢弃任何时间戳超出指定间隔的记录。

在任何形式的流摄取中忽略该配置。 | `null` | diff --git a/DataIngestion/native.md b/DataIngestion/native.md index 1db4413..af99b22 100644 --- a/DataIngestion/native.md +++ b/DataIngestion/native.md @@ -1095,7 +1095,7 @@ Druid输入源支持直接从现有的Druid段读取数据,可能使用新的 | `interval` | ISO-8601时间间隔的字符串,它定义了获取数据的时间范围。 | 是 | | `dimensions` | 包含要从Druid数据源中选择的维度列名称的字符串列表。如果列表为空,则不返回维度。如果为空,则返回所有维度。 | 否 | | `metrics` | 包含要选择的Metric列名称的字符串列表。如果列表为空,则不返回任何度量。如果为空,则返回所有Metric。 | 否 | -| `filter` | 详情请查看 [filters](../Querying/filters.html) 如果指定,则只返回与筛选器匹配的行。 | 否 | +| `filter` | 详情请查看 [filters](../querying/filters.html) 如果指定,则只返回与筛选器匹配的行。 | 否 | DruidInputSource规范的最小示例如下所示: ```json diff --git a/DataIngestion/schemadesign.md b/DataIngestion/schemadesign.md index 98ddcc3..acd1c02 100644 --- a/DataIngestion/schemadesign.md +++ b/DataIngestion/schemadesign.md @@ -22,23 +22,23 @@ * 除了timestamp列之外,Druid数据源中的所有列都是dimensions或metrics。这遵循 [OLAP数据的标准命名约定](https://en.wikipedia.org/wiki/Online_analytical_processing#Overview_of_OLAP_systems)。 * 典型的生产数据源有几十到几百列。 * [dimension列](ingestion.md#维度) 按原样存储,因此可以在查询时对其进行筛选、分组或聚合。它们总是单个字符串、字符串数组、单个long、单个double或单个float。 -* [Metrics列](ingestion.md#指标) 是 [预聚合](../Querying/Aggregations.md) 存储的,因此它们只能在查询时聚合(不能按筛选或分组)。它们通常存储为数字(整数或浮点数),但也可以存储为复杂对象,如[HyperLogLog草图或近似分位数草图](../Querying/Aggregations.md)。即使禁用了rollup,也可以在接收时配置metrics,但在启用汇总时最有用。 +* [Metrics列](ingestion.md#指标) 是 [预聚合](../querying/Aggregations.md) 存储的,因此它们只能在查询时聚合(不能按筛选或分组)。它们通常存储为数字(整数或浮点数),但也可以存储为复杂对象,如[HyperLogLog草图或近似分位数草图](../querying/Aggregations.md)。即使禁用了rollup,也可以在接收时配置metrics,但在启用汇总时最有用。 ### 与其他设计模式类比 #### 关系模型 (如 Hive 或者 PostgreSQL) -Druid数据源通常相当于关系数据库中的表。Druid的 [lookups特性](../Querying/lookups.md) 可以类似于数据仓库样式的维度表,但是正如您将在下面看到的,如果您能够摆脱它,通常建议您进行非规范化。 +Druid数据源通常相当于关系数据库中的表。Druid的 [lookups特性](../querying/lookups.md) 可以类似于数据仓库样式的维度表,但是正如您将在下面看到的,如果您能够摆脱它,通常建议您进行非规范化。 关系数据建模的常见实践涉及 [规范化](https://en.wikipedia.org/wiki/Database_normalization) 的思想:将数据拆分为多个表,从而减少或消除数据冗余。例如,在"sales"表中,最佳实践关系建模要求将"product id"列作为外键放入单独的"products"表中,该表依次具有"product id"、"product name"和"product category"列, 这可以防止产品名称和类别需要在"sales"表中引用同一产品的不同行上重复。 另一方面,在Druid中,通常使用在查询时不需要连接的完全平坦的数据源。在"sales"表的例子中,在Druid中,通常直接将"product_id"、"product_name"和"product_category"作为维度存储在Druid "sales"数据源中,而不使用单独的"products"表。完全平坦的模式大大提高了性能,因为查询时不需要连接。作为一个额外的速度提升,这也允许Druid的查询层直接操作压缩字典编码的数据。因为Druid使用字典编码来有效地为字符串列每行存储一个整数, 所以可能与直觉相反,这并*没有*显著增加相对于规范化模式的存储空间。 -如果需要的话,可以通过使用 [lookups](../Querying/lookups.md) 规范化Druid数据源,这大致相当于关系数据库中的维度表。在查询时,您将使用Druid的SQL `LOOKUP` 查找函数或者原生 `lookup` 提取函数,而不是像在关系数据库中那样使用JOIN关键字。由于lookup表会增加内存占用并在查询时产生更多的计算开销,因此仅当需要更新lookup表并立即反映主表中已摄取行的更改时,才建议执行此操作。 +如果需要的话,可以通过使用 [lookups](../querying/lookups.md) 规范化Druid数据源,这大致相当于关系数据库中的维度表。在查询时,您将使用Druid的SQL `LOOKUP` 查找函数或者原生 `lookup` 提取函数,而不是像在关系数据库中那样使用JOIN关键字。由于lookup表会增加内存占用并在查询时产生更多的计算开销,因此仅当需要更新lookup表并立即反映主表中已摄取行的更改时,才建议执行此操作。 在Druid中建模关系数据的技巧: * Druid数据源没有主键或唯一键,所以跳过这些。 -* 如果可能的话,去规格化。如果需要定期更新dimensions/lookup并将这些更改反映在已接收的数据中,请考虑使用 [lookups](../Querying/lookups.md) 进行部分规范化。 +* 如果可能的话,去规格化。如果需要定期更新dimensions/lookup并将这些更改反映在已接收的数据中,请考虑使用 [lookups](../querying/lookups.md) 进行部分规范化。 * 如果需要将两个大型的分布式表连接起来,则必须在将数据加载到Druid之前执行此操作。Druid不支持两个数据源的查询时间连接。lookup在这里没有帮助,因为每个lookup表的完整副本存储在每个Druid服务器上,所以对于大型表来说,它们不是一个好的选择。 * 考虑是否要为预聚合启用[rollup](ingestion.md#rollup),或者是否要禁用rollup并按原样加载现有数据。Druid中的Rollup类似于在关系模型中创建摘要表。 @@ -53,7 +53,7 @@ Druid数据源通常相当于关系数据库中的表。Druid的 [lookups特性] * Druid并不认为数据点是"时间序列"的一部分。相反,Druid对每一点分别进行摄取和聚合 * 创建一个维度,该维度指示数据点所属系列的名称。这个维度通常被称为"metric"或"name"。不要将名为"metric"的维度与Druid Metrics的概念混淆。将它放在"dimensionsSpec"中维度列表的第一个位置,以获得最佳性能(这有助于提高局部性;有关详细信息,请参阅下面的 [分区和排序](ingestion.md#分区)) * 为附着到数据点的属性创建其他维度。在时序数据库系统中,这些通常称为"标签" -* 创建与您希望能够查询的聚合类型相对应的 [Druid Metrics](ingestion.md#指标)。通常这包括"sum"、"min"和"max"(在long、float或double中的一种)。如果你想计算百分位数或分位数,可以使用Druid的 [近似聚合器](../Querying/Aggregations.md) +* 创建与您希望能够查询的聚合类型相对应的 [Druid Metrics](ingestion.md#指标)。通常这包括"sum"、"min"和"max"(在long、float或double中的一种)。如果你想计算百分位数或分位数,可以使用Druid的 [近似聚合器](../querying/Aggregations.md) * 考虑启用 [rollup](ingestion.md#rollup),这将允许Druid潜在地将多个点合并到Druid数据源中的一行中。如果希望以不同于原始发出的时间粒度存储数据,则这可能非常有用。如果要在同一个数据源中组合时序和非时序数据,它也很有用 * 如果您提前不知道要摄取哪些列,请使用空的维度列表来触发 [维度列的自动检测](#无schema的维度列) @@ -86,7 +86,7 @@ Druid可以在接收数据时将其汇总,以最小化需要存储的原始数 草图(sketches)减少了查询时的内存占用,因为它们限制了需要在服务器之间洗牌的数据量。例如,在分位数计算中,Druid不需要将所有数据点发送到中心位置,以便对它们进行排序和计算分位数,而只需要发送点的草图。这可以将数据传输需要减少到仅千字节。 -有关Druid中可用的草图的详细信息,请参阅 [近似聚合器页面](../Querying/Aggregations.md)。 +有关Druid中可用的草图的详细信息,请参阅 [近似聚合器页面](../querying/Aggregations.md)。 如果你更喜欢 [视频](https://www.youtube.com/watch?v=Hpd3f_MLdXo),那就看一看吧!,一个讨论Druid Sketches的会议。 @@ -104,7 +104,7 @@ Druid schema必须始终包含一个主时间戳, 主时间戳用于对数据进 如果数据有多个时间戳,则可以将其他时间戳作为辅助时间戳摄取。最好的方法是将它们作为 [毫秒格式的Long类型维度](ingestion.md#dimensionsspec) 摄取。如有必要,可以使用 [`transformSpec`](ingestion.md#transformspec) 和 `timestamp_parse` 等 [表达式](../misc/expression.md) 将它们转换成这种格式,后者返回毫秒时间戳。 -在查询时,可以使用诸如 `MILLIS_TO_TIMESTAMP`、`TIME_FLOOR` 等 [SQL时间函数](../Querying/druidsql.md) 查询辅助时间戳。如果您使用的是原生Druid查询,那么可以使用 [表达式](../misc/expression.md)。 +在查询时,可以使用诸如 `MILLIS_TO_TIMESTAMP`、`TIME_FLOOR` 等 [SQL时间函数](../querying/druidsql.md) 查询辅助时间戳。如果您使用的是原生Druid查询,那么可以使用 [表达式](../misc/expression.md)。 #### 嵌套维度 diff --git a/DataIngestion/taskrefer.md b/DataIngestion/taskrefer.md index 0f25789..1d2d3fa 100644 --- a/DataIngestion/taskrefer.md +++ b/DataIngestion/taskrefer.md @@ -22,7 +22,7 @@ 任务API主要在两个地方是可用的: * [Overlord](../design/Overlord.md) 进程提供HTTP API接口来进行提交任务、取消任务、检查任务状态、查看任务日志与报告等。 查看 [任务API文档](../Operations/api.md) 可以看到完整列表 -* Druid SQL包括了一个 [`sys.tasks`](../Querying/druidsql.md#系统Schema) ,保存了当前任务运行的信息。 此表是只读的,并且可以通过Overlord API查询完整信息的有限制的子集。 +* Druid SQL包括了一个 [`sys.tasks`](../querying/druidsql.md#系统Schema) ,保存了当前任务运行的信息。 此表是只读的,并且可以通过Overlord API查询完整信息的有限制的子集。 ### 任务报告 diff --git a/SUMMARY.md b/SUMMARY.md index 72bf859..70ad27c 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -66,39 +66,39 @@ * [问题FAQ](DataIngestion/faq.md) * [数据查询]() - * [Druid SQL](Querying/druidsql.md) - * [原生查询](Querying/makeNativeQueries.md) - * [查询执行](Querying/queryexecution.md) - * [一些概念](Querying/datasource.md) - * [数据源](Querying/datasource.md) - * [Joins](Querying/joins.md) - * [Lookups](Querying/lookups.md) - * [多值维度](Querying/multi-value-dimensions.md) - * [多租户](Querying/multitenancy.md) - * [查询缓存](Querying/querycached.md) - * [上下文参数](Querying/query-context.md) - * [原生查询类型](Querying/timeseriesquery.md) - * [Timeseries](Querying/timeseriesquery.md) - * [TopN](Querying/topn.md) - * [GroupBy](Querying/groupby.md) - * [Scan](Querying/scan.md) - * [Search](Querying/searchquery.md) - * [TimeBoundary](Querying/timeboundaryquery.md) - * [SegmentMetadata](Querying/segmentMetadata.md) - * [DatasourceMetadata](Querying/datasourcemetadataquery.md) - * [原生查询组件](Querying/filters.md) - * [过滤](Querying/filters.md) - * [粒度](Querying/granularity.md) - * [维度](Querying/dimensionspec.md) - * [聚合](Querying/Aggregations.md) - * [后聚合](Querying/postaggregation.md) - * [表达式](Querying/expression.md) - * [Having(GroupBy)](Querying/having.md) - * [排序和Limit(GroupBy)](Querying/limitspec.md) - * [排序(TopN)](Querying/topnsorting.md) - * [字符串比较器(String Comparators)](Querying/sorting-orders.md) - * [虚拟列(Virtual Columns)](Querying/virtual-columns.md) - * [空间过滤器(Spatial Filter)](Querying/spatialfilter.md) + * [Druid SQL](querying/druidsql.md) + * [原生查询](querying/makeNativeQueries.md) + * [查询执行](querying/queryexecution.md) + * [一些概念](querying/datasource.md) + * [数据源](querying/datasource.md) + * [Joins](querying/joins.md) + * [Lookups](querying/lookups.md) + * [多值维度](querying/multi-value-dimensions.md) + * [多租户](querying/multitenancy.md) + * [查询缓存](querying/querycached.md) + * [上下文参数](querying/query-context.md) + * [原生查询类型](querying/timeseriesquery.md) + * [Timeseries](querying/timeseriesquery.md) + * [TopN](querying/topn.md) + * [GroupBy](querying/groupby.md) + * [Scan](querying/scan.md) + * [Search](querying/searchquery.md) + * [TimeBoundary](querying/timeboundaryquery.md) + * [SegmentMetadata](querying/segmentMetadata.md) + * [DatasourceMetadata](querying/datasourcemetadataquery.md) + * [原生查询组件](querying/filters.md) + * [过滤](querying/filters.md) + * [粒度](querying/granularity.md) + * [维度](querying/dimensionspec.md) + * [聚合](querying/Aggregations.md) + * [后聚合](querying/postaggregation.md) + * [表达式](querying/expression.md) + * [Having(GroupBy)](querying/having.md) + * [排序和Limit(GroupBy)](querying/limitspec.md) + * [排序(TopN)](querying/topnsorting.md) + * [字符串比较器(String Comparators)](querying/sorting-orders.md) + * [虚拟列(Virtual Columns)](querying/virtual-columns.md) + * [空间过滤器(Spatial Filter)](querying/spatialfilter.md) * [配置列表]() * [配置列表](Configuration/configuration.md) diff --git a/design/Historical.md b/design/Historical.md index 3c902ed..8b41a98 100644 --- a/design/Historical.md +++ b/design/Historical.md @@ -39,6 +39,6 @@ org.apache.druid.cli.Main server historical ### 查询段 -有关查询Historical的详细信息,请参阅 [数据查询](../Querying/makeNativeQueries.md)。 +有关查询Historical的详细信息,请参阅 [数据查询](../querying/makeNativeQueries.md)。 Historical可以被配置记录和报告每个服务查询的指标。 \ No newline at end of file diff --git a/Querying/AggregationGranularity.md b/querying/AggregationGranularity.md similarity index 100% rename from Querying/AggregationGranularity.md rename to querying/AggregationGranularity.md diff --git a/Querying/Aggregations.md b/querying/Aggregations.md similarity index 100% rename from Querying/Aggregations.md rename to querying/Aggregations.md diff --git a/Querying/datasource.md b/querying/datasource.md similarity index 100% rename from Querying/datasource.md rename to querying/datasource.md diff --git a/Querying/datasourcemetadataquery.md b/querying/datasourcemetadataquery.md similarity index 100% rename from Querying/datasourcemetadataquery.md rename to querying/datasourcemetadataquery.md diff --git a/Querying/dimensionspec.md b/querying/dimensionspec.md similarity index 100% rename from Querying/dimensionspec.md rename to querying/dimensionspec.md diff --git a/Querying/druidsql.md b/querying/druidsql.md similarity index 100% rename from Querying/druidsql.md rename to querying/druidsql.md diff --git a/Querying/expression.md b/querying/expression.md similarity index 100% rename from Querying/expression.md rename to querying/expression.md diff --git a/Querying/filters.md b/querying/filters.md similarity index 100% rename from Querying/filters.md rename to querying/filters.md diff --git a/Querying/granularity.md b/querying/granularity.md similarity index 100% rename from Querying/granularity.md rename to querying/granularity.md diff --git a/Querying/groupby.md b/querying/groupby.md similarity index 100% rename from Querying/groupby.md rename to querying/groupby.md diff --git a/Querying/having.md b/querying/having.md similarity index 100% rename from Querying/having.md rename to querying/having.md diff --git a/Querying/index.md b/querying/index.md similarity index 100% rename from Querying/index.md rename to querying/index.md diff --git a/Querying/joins.md b/querying/joins.md similarity index 100% rename from Querying/joins.md rename to querying/joins.md diff --git a/Querying/limitspec.md b/querying/limitspec.md similarity index 100% rename from Querying/limitspec.md rename to querying/limitspec.md diff --git a/Querying/lookups.md b/querying/lookups.md similarity index 100% rename from Querying/lookups.md rename to querying/lookups.md diff --git a/Querying/makeNativeQueries.md b/querying/makeNativeQueries.md similarity index 100% rename from Querying/makeNativeQueries.md rename to querying/makeNativeQueries.md diff --git a/Querying/multi-value-dimensions.md b/querying/multi-value-dimensions.md similarity index 100% rename from Querying/multi-value-dimensions.md rename to querying/multi-value-dimensions.md diff --git a/Querying/multitenancy.md b/querying/multitenancy.md similarity index 100% rename from Querying/multitenancy.md rename to querying/multitenancy.md diff --git a/Querying/postaggregation.md b/querying/postaggregation.md similarity index 100% rename from Querying/postaggregation.md rename to querying/postaggregation.md diff --git a/Querying/query-context.md b/querying/query-context.md similarity index 100% rename from Querying/query-context.md rename to querying/query-context.md diff --git a/Querying/querycached.md b/querying/querycached.md similarity index 100% rename from Querying/querycached.md rename to querying/querycached.md diff --git a/Querying/queryexecution.md b/querying/queryexecution.md similarity index 100% rename from Querying/queryexecution.md rename to querying/queryexecution.md diff --git a/Querying/scan.md b/querying/scan.md similarity index 100% rename from Querying/scan.md rename to querying/scan.md diff --git a/Querying/searchquery.md b/querying/searchquery.md similarity index 100% rename from Querying/searchquery.md rename to querying/searchquery.md diff --git a/Querying/segmentMetadata.md b/querying/segmentMetadata.md similarity index 100% rename from Querying/segmentMetadata.md rename to querying/segmentMetadata.md diff --git a/Querying/sorting-orders.md b/querying/sorting-orders.md similarity index 100% rename from Querying/sorting-orders.md rename to querying/sorting-orders.md diff --git a/Querying/spatialfilter.md b/querying/spatialfilter.md similarity index 100% rename from Querying/spatialfilter.md rename to querying/spatialfilter.md diff --git a/Querying/timeboundaryquery.md b/querying/timeboundaryquery.md similarity index 100% rename from Querying/timeboundaryquery.md rename to querying/timeboundaryquery.md diff --git a/Querying/timeseriesquery.md b/querying/timeseriesquery.md similarity index 100% rename from Querying/timeseriesquery.md rename to querying/timeseriesquery.md diff --git a/Querying/topn.md b/querying/topn.md similarity index 100% rename from Querying/topn.md rename to querying/topn.md diff --git a/Querying/topnsorting.md b/querying/topnsorting.md similarity index 100% rename from Querying/topnsorting.md rename to querying/topnsorting.md diff --git a/Querying/virtual-columns.md b/querying/virtual-columns.md similarity index 100% rename from Querying/virtual-columns.md rename to querying/virtual-columns.md diff --git a/tutorials/chapter-2.md b/tutorials/chapter-2.md index ab05f8d..dc1d479 100644 --- a/tutorials/chapter-2.md +++ b/tutorials/chapter-2.md @@ -132,7 +132,7 @@ Druid的体系结构需要一个主时间列(内部存储为名为__time的列 ![](img-2/tutorial-kafka-data-loader-12.png) -查看[查询教程](../Querying/makeNativeQueries.md)以对新加载的数据运行一些示例查询。 +查看[查询教程](../querying/makeNativeQueries.md)以对新加载的数据运行一些示例查询。 #### 通过控制台提交supervisor diff --git a/tutorials/chapter-4.md b/tutorials/chapter-4.md index 94ac6d2..722e378 100644 --- a/tutorials/chapter-4.md +++ b/tutorials/chapter-4.md @@ -284,5 +284,5 @@ curl -X 'POST' -H 'Content-Type:application/json' -d @quickstart/tutorial/wikipe ### 进一步阅读 -[查询文档](../Querying/makeNativeQueries.md)有更多关于Druid原生JSON查询的信息 -[Druid SQL文档](../Querying/druidsql.md)有更多关于Druid SQL查询的信息 \ No newline at end of file +[查询文档](../querying/makeNativeQueries.md)有更多关于Druid原生JSON查询的信息 +[Druid SQL文档](../querying/druidsql.md)有更多关于Druid SQL查询的信息 \ No newline at end of file diff --git a/tutorials/tutorial-batch.md b/tutorials/tutorial-batch.md index c78bd02..b96b772 100644 --- a/tutorials/tutorial-batch.md +++ b/tutorials/tutorial-batch.md @@ -91,7 +91,7 @@ Druid的体系结构需要一个主时间列(内部存储为名为__time的列 运行 `SELECT * FROM wikipedia` 查询可以看到详细的结果。 -查看[查询教程](../Querying/makeNativeQueries.md)以对新加载的数据运行一些示例查询。 +查看[查询教程](../querying/makeNativeQueries.md)以对新加载的数据运行一些示例查询。 ### 使用spec加载数据(通过控制台) From f712ddaa8476fe4fa738e61f42d581623800a1c8 Mon Sep 17 00:00:00 2001 From: YuCheng Hu Date: Thu, 22 Jul 2021 10:00:20 -0400 Subject: [PATCH 2/5] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=81=94=E7=B3=BB?= =?UTF-8?q?=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTACT.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTACT.md b/CONTACT.md index 7d647af..1702af6 100644 --- a/CONTACT.md +++ b/CONTACT.md @@ -6,8 +6,10 @@ |---|---| | 电子邮件 | [service@ossez.com](mailto:service@ossez.com) | | QQ 或微信 | 103899765 | -| QQ 交流群 | 15186112 | +| QQ 交流群 | 15186112 | | 社区论坛 | [https://www.ossez.com/](https://www.ossez.com/) | +| WIKI 维基 | [https://www.cwiki.us/](https://www.cwiki.us/) | +| CN 博客 | [https://www.cwikius.cn/](https://www.cwikius.cn/) | ## 公众平台 From 390c4b719524747f41c9483eb5e3bef9e285cff8 Mon Sep 17 00:00:00 2001 From: YuCheng Hu Date: Thu, 22 Jul 2021 11:04:41 -0400 Subject: [PATCH 3/5] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Getting=20started=20?= =?UTF-8?q?=E5=88=86=E7=B1=BB=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GettingStarted/Docker.md | 63 +++ GettingStarted/chapter-1.md | 60 +++ GettingStarted/chapter-2.md | 163 +++++++ GettingStarted/chapter-3.md | 69 +++ GettingStarted/chapter-4.md | 421 ++++++++++++++++++ GettingStarted/img/tutorial-quickstart-01.png | Bin 0 -> 67013 bytes design/index.md | 107 +++-- 7 files changed, 840 insertions(+), 43 deletions(-) create mode 100644 GettingStarted/Docker.md create mode 100644 GettingStarted/chapter-1.md create mode 100644 GettingStarted/chapter-2.md create mode 100644 GettingStarted/chapter-3.md create mode 100644 GettingStarted/chapter-4.md create mode 100644 GettingStarted/img/tutorial-quickstart-01.png diff --git a/GettingStarted/Docker.md b/GettingStarted/Docker.md new file mode 100644 index 0000000..9285881 --- /dev/null +++ b/GettingStarted/Docker.md @@ -0,0 +1,63 @@ + + + + + + +## Docker + +在这个部分中,我们将从 [Docker Hub](https://hub.docker.com/r/apache/druid) 下载Apache Druid镜像,并使用 [Docker](https://www.docker.com/get-started) 和 [Docker Compose](https://docs.docker.com/compose/) 在一台机器上安装它。完成此初始设置后,集群将准备好加载数据。 + +在开始快速启动之前,阅读 [Druid概述](chapter-1.md) 和 [摄取概述](../DataIngestion/ingestion.md) 是很有帮助的,因为教程将参考这些页面上讨论的概念。此外,建议熟悉Docker。 + +### 前提条件 + +* Docker + +### 快速开始 + +Druid源代码包含一个 [示例docker-compose.yml](https://github.com/apache/druid/blob/master/distribution/docker/docker-compose.yml) 它可以从Docker Hub中提取一个镜像,适合用作示例环境,并用于试验基于Docker的Druid配置和部署。 + +#### Compose文件 + +示例 `docker-compose.yml` 将为每个Druid服务创建一个容器,包括Zookeeper和作为元数据存储PostgreSQL容器。深度存储将是本地目录,默认配置为相对于 `docker-compose.yml`文件的 `./storage`,并将作为 `/opt/data` 挂载,并在需要访问深层存储的Druid容器之间共享。Druid容器是通过 [环境文件](https://github.com/apache/druid/blob/master/distribution/docker/environment) 配置的。 + +#### 配置 + +Druid Docker容器的配置是通过环境变量完成的,环境变量还可以指定到 [标准Druid配置文件](../Configuration/configuration.md) 的路径 + +特殊环境变量: + +* `JAVA_OPTS` -- 设置 java options +* `DRUID_LOG4J` -- 设置完成的 `log4j.xml` +* `DRUID_LOG_LEVEL` -- 覆盖在log4j中的默认日志级别 +* `DRUID_XMX` -- 设置 Java `Xmx` +* `DRUID_XMS` -- 设置 Java `Xms` +* `DRUID_MAXNEWSIZE` -- 设置 Java最大新生代大小 +* `DRUID_NEWSIZE` -- 设置 Java 新生代大小 +* `DRUID_MAXDIRECTMEMORYSIZE` -- 设置Java最大直接内存大小 +* `DRUID_CONFIG_COMMON` -- druid "common"属性文件的完整路径 +* `DRUID_CONFIG_${service}` -- druid "service"属性文件的完整路径 + +除了特殊的环境变量外,在容器中启动Druid的脚本还将尝试使用以 `druid_`前缀开头的任何环境变量作为命令行配置。例如,Druid容器进程中的环境变量`druid_metadata_storage_type=postgresql` 将被转换为 `-Ddruid.metadata.storage.type=postgresql` + +Druid `docker-compose.yml` 示例使用单个环境文件来指定完整的Druid配置;但是,在生产用例中,我们建议使用 `DRUID_COMMON_CONFIG` 和`DRUID_CONFIG_${service}` 或专门定制的特定于服务的环境文件。 + +### 启动集群 + +运行 `docker-compose up` 启动附加shell的集群,或运行 `docker-compose up -d` 在后台运行集群。如果直接使用示例文件,这个命令应该从Druid安装目录中的 `distribution/docker/` 运行。 + +启动集群后,可以导航到 [http://localhost:8888](http://localhost/) 。服务于 [Druid控制台](../Operations/druid-console.md) 的 [Druid路由进程](../Design/Router.md) 位于这个地址。 + +![](img/tutorial-quickstart-01.png) + +所有Druid进程需要几秒钟才能完全启动。如果在启动服务后立即打开控制台,可能会看到一些可以安全忽略的错误。 + +从这里你可以跟着 [标准教程](chapter-2.md),或者详细说明你的 `docker-compose.yml` 根据需要添加任何其他外部服务依赖项。 \ No newline at end of file diff --git a/GettingStarted/chapter-1.md b/GettingStarted/chapter-1.md new file mode 100644 index 0000000..26d0ceb --- /dev/null +++ b/GettingStarted/chapter-1.md @@ -0,0 +1,60 @@ + + + + + + +### Druid是什么 + +Apache Druid是一个实时分析型数据库,旨在对大型数据集进行快速的查询分析("[OLAP](https://en.wikipedia.org/wiki/Online_analytical_processing)"查询)。Druid最常被当做数据库来用以支持实时摄取、高性能查询和高稳定运行的应用场景,同时,Druid也通常被用来助力分析型应用的图形化界面,或者当做需要快速聚合的高并发后端API,Druid最适合应用于面向事件类型的数据。 + +Druid通常应用于以下场景: + +* 点击流分析(Web端和移动端) +* 网络监测分析(网络性能监控) +* 服务指标存储 +* 供应链分析(制造类指标) +* 应用性能指标分析 +* 数字广告分析 +* 商务智能 / OLAP + +Druid的核心架构吸收和结合了[数据仓库](https://en.wikipedia.org/wiki/Data_warehouse)、[时序数据库](https://en.wikipedia.org/wiki/Time_series_database)以及[检索系统](https://en.wikipedia.org/wiki/Search_engine_(computing))的优势,其主要特征如下: + +1. **列式存储**,Druid使用列式存储,这意味着在一个特定的数据查询中它只需要查询特定的列,这样极地提高了部分列查询场景的性能。另外,每一列数据都针对特定数据类型做了优化存储,从而支持快速的扫描和聚合。 +2. **可扩展的分布式系统**,Druid通常部署在数十到数百台服务器的集群中,并且可以提供每秒数百万条记录的接收速率,数万亿条记录的保留存储以及亚秒级到几秒的查询延迟。 +3. **大规模并行处理**,Druid可以在整个集群中并行处理查询。 +4. **实时或批量摄取**,Druid可以实时(已经被摄取的数据可立即用于查询)或批量摄取数据。 +5. **自修复、自平衡、易于操作**,作为集群运维操作人员,要伸缩集群只需添加或删除服务,集群就会在后台自动重新平衡自身,而不会造成任何停机。如果任何一台Druid服务器发生故障,系统将自动绕过损坏。 Druid设计为7*24全天候运行,无需出于任何原因而导致计划内停机,包括配置更改和软件更新。 +6. **不会丢失数据的云原生容错架构**,一旦Druid摄取了数据,副本就安全地存储在[深度存储介质](Design/../chapter-1.md)(通常是云存储,HDFS或共享文件系统)中。即使某个Druid服务发生故障,也可以从深度存储中恢复您的数据。对于仅影响少数Druid服务的有限故障,副本可确保在系统恢复时仍然可以进行查询。 +7. **用于快速过滤的索引**,Druid使用[CONCISE](https://arxiv.org/pdf/1004.0403.pdf)或[Roaring](https://roaringbitmap.org/)压缩的位图索引来创建索引,以支持快速过滤和跨多列搜索。 +8. **基于时间的分区**,Druid首先按时间对数据进行分区,另外同时可以根据其他字段进行分区。这意味着基于时间的查询将仅访问与查询时间范围匹配的分区,这将大大提高基于时间的数据的性能。 +9. **近似算法**,Druid应用了近似count-distinct,近似排序以及近似直方图和分位数计算的算法。这些算法占用有限的内存使用量,通常比精确计算要快得多。对于精度要求比速度更重要的场景,Druid还提供了精确count-distinct和精确排序。 +10. **摄取时自动汇总聚合**,Druid支持在数据摄取阶段可选地进行数据汇总,这种汇总会部分预先聚合您的数据,并可以节省大量成本并提高性能。 + +### 什么场景下应该使用Druid + +许多公司都已经将Druid应用于多种不同的应用场景,详情可查看[Powered by Apache Druid](https://druid.apache.org/druid-powered)页面。 + +如果您的使用场景符合以下的几个特征,那么Druid是一个非常不错的选择: + +* 数据插入频率比较高,但较少更新数据 +* 大多数查询场景为聚合查询和分组查询(GroupBy),同时还有一定得检索与扫描查询 +* 将数据查询延迟目标定位100毫秒到几秒钟之间 +* 数据具有时间属性(Druid针对时间做了优化和设计) +* 在多表场景下,每次查询仅命中一个大的分布式表,查询又可能命中多个较小的lookup表 +* 场景中包含高基维度数据列(例如URL,用户ID等),并且需要对其进行快速计数和排序 +* 需要从Kafka、HDFS、对象存储(如Amazon S3)中加载数据 + +如果您的使用场景符合以下特征,那么使用Druid可能是一个不好的选择: + +* 根据主键对现有数据进行低延迟更新操作。Druid支持流式插入,但不支持流式更新(更新操作是通过后台批处理作业完成) +* 延迟不重要的离线数据系统 +* 场景中包括大连接(将一个大事实表连接到另一个大事实表),并且可以接受花费很长时间来完成这些查询 + diff --git a/GettingStarted/chapter-2.md b/GettingStarted/chapter-2.md new file mode 100644 index 0000000..053e536 --- /dev/null +++ b/GettingStarted/chapter-2.md @@ -0,0 +1,163 @@ + + + + + +### 快速开始 + +在本快速入门教程中,我们将下载Druid并将其安装在一台服务器上,完成初始安装后,向集群中加载数据。 + +在开始快速入门之前,阅读[Druid概述](./chapter-1.md)和[数据摄取概述](../DataIngestion/index.md)会很有帮助,因为当前教程会引用这些页面上讨论的概念。 + +#### 预备条件 +##### 软件 +* **Java 8(8u92+)** +* Linux, Mac OS X, 或者其他类UNIX系统(Windows不支持) + +> [!WARNING] +> Druid服务运行依赖Java 8,可以使用环境变量`DRUID_JAVA_HOME`或`JAVA_HOME`指定在何处查找Java,有关更多详细信息,请运行`verify-java`脚本。 + +##### 硬件 + +Druid安装包提供了几个[单服务器配置](./chapter-3.md)的示例,以及使用这些配置启动Druid进程的脚本。 + +如果您正在使用便携式等小型计算机上运行服务,则配置为4CPU/16GB RAM环境的`micro-quickstart`配置是一个不错的选择。 + +如果您打算在本教程之外使用单机部署进行进一步试验评估,则建议使用比`micro-quickstart`更大的配置。 + +#### 入门开始 + +[下载](https://www.apache.org/dyn/closer.cgi?path=/druid/0.17.0/apache-druid-0.17.0-bin.tar.gz)Druid最新0.17.0release安装包 + +在终端中运行以下命令来提取Druid + +```json +tar -xzf apache-druid-0.17.0-bin.tar.gz +cd apache-druid-0.17.0 +``` + +在安装包中有以下文件: + +* `LICENSE`和`NOTICE`文件 +* `bin/*` - 启停等脚本 +* `conf/*` - 用于单节点部署和集群部署的示例配置 +* `extensions/*` - Druid核心扩展 +* `hadoop-dependencies/*` - Druid Hadoop依赖 +* `lib/*` - Druid核心库和依赖 +* `quickstart/*` - 配置文件,样例数据,以及快速入门教材的其他文件 + +#### 启动服务 + +以下命令假定您使用的是`micro-quickstart`单机配置,如果使用的是其他配置,在`bin`目录下有每一种配置对应的脚本,如`bin/start-single-server-small` + +在`apache-druid-0.17.0`安装包的根目录下执行命令: + +```json +./bin/start-micro-quickstart +``` +然后将在本地计算机上启动Zookeeper和Druid服务实例,例如: + +```json +$ ./bin/start-micro-quickstart +[Fri May 3 11:40:50 2019] Running command[zk], logging to[/apache-druid-0.17.0/var/sv/zk.log]: bin/run-zk conf +[Fri May 3 11:40:50 2019] Running command[coordinator-overlord], logging to[/apache-druid-0.17.0/var/sv/coordinator-overlord.log]: bin/run-druid coordinator-overlord conf/druid/single-server/micro-quickstart +[Fri May 3 11:40:50 2019] Running command[broker], logging to[/apache-druid-0.17.0/var/sv/broker.log]: bin/run-druid broker conf/druid/single-server/micro-quickstart +[Fri May 3 11:40:50 2019] Running command[router], logging to[/apache-druid-0.17.0/var/sv/router.log]: bin/run-druid router conf/druid/single-server/micro-quickstart +[Fri May 3 11:40:50 2019] Running command[historical], logging to[/apache-druid-0.17.0/var/sv/historical.log]: bin/run-druid historical conf/druid/single-server/micro-quickstart +[Fri May 3 11:40:50 2019] Running command[middleManager], logging to[/apache-druid-0.17.0/var/sv/middleManager.log]: bin/run-druid middleManager conf/druid/single-server/micro-quickstart +``` + +所有的状态(例如集群元数据存储和服务的segment文件)将保留在`apache-druid-0.17.0`软件包根目录下的`var`目录中, 服务的日志位于 `var/sv`。 + +稍后,如果您想停止服务,请按`CTRL-C`退出`bin/start-micro-quickstart`脚本,该脚本将终止Druid进程。 + +集群启动后,可以访问[http://localhost:8888](http://localhost:8888)来Druid控制台,控制台由Druid Router进程启动。 + +![tutorial-quickstart](img/tutorial-quickstart-01.png) + +所有Druid进程完全启动需要花费几秒钟。 如果在启动服务后立即打开控制台,则可能会看到一些可以安全忽略的错误。 + +#### 加载数据 +##### 教程使用的数据集 + +对于以下数据加载教程,我们提供了一个示例数据文件,其中包含2015年9月12日发生的Wikipedia页面编辑事件。 + +该样本数据位于Druid包根目录的`quickstart/tutorial/wikiticker-2015-09-12-sampled.json.gz`中,页面编辑事件作为JSON对象存储在文本文件中。 + +示例数据包含以下几列,示例事件如下所示: + +* added +* channel +* cityName +* comment +* countryIsoCode +* countryName +* deleted +* delta +* isAnonymous +* isMinor +* isNew +* isRobot +* isUnpatrolled +* metroCode +* namespace +* page +* regionIsoCode +* regionName +* user + +```json +{ + "timestamp":"2015-09-12T20:03:45.018Z", + "channel":"#en.wikipedia", + "namespace":"Main", + "page":"Spider-Man's powers and equipment", + "user":"foobar", + "comment":"/* Artificial web-shooters */", + "cityName":"New York", + "regionName":"New York", + "regionIsoCode":"NY", + "countryName":"United States", + "countryIsoCode":"US", + "isAnonymous":false, + "isNew":false, + "isMinor":false, + "isRobot":false, + "isUnpatrolled":false, + "added":99, + "delta":99, + "deleted":0, +} +``` + +##### 数据加载 + +以下教程演示了将数据加载到Druid的各种方法,包括批处理和流处理用例。 所有教程均假定您使用的是上面提到的`micro-quickstart`单机配置。 + +* [加载本地文件](../Tutorials/chapter-1.md) - 本教程演示了如何使用Druid的本地批处理摄取来执行批文件加载 +* [从Kafka加载流数据](../Tutorials/chapter-2.md) - 本教程演示了如何从Kafka主题加载流数据 +* [从Hadoop加载数据](../Tutorials/chapter-3.md) - 本教程演示了如何使用远程Hadoop集群执行批处理文件加载 +* [编写一个自己的数据摄取规范](../Tutorials/chapter-10.md) - 本教程演示了如何编写新的数据摄取规范并使用它来加载数据 + +##### 重置集群状态 + +如果要在清理服务后重新启动,请删除`var`目录,然后再次运行`bin/start-micro-quickstart`脚本。 + +一旦每个服务都启动,您就可以加载数据了。 + +##### 重置Kafka + +如果您完成了[教程:从Kafka加载流数据](../Tutorials/chapter-2.md)并希望重置集群状态,则还应该清除所有Kafka状态。 + +在停止ZooKeeper和Druid服务之前,使用`CTRL-C`关闭`Kafka Broker`,然后删除`/tmp/kafka-logs`中的Kafka日志目录: + +``` +rm -rf /tmp/kafka-logs +``` diff --git a/GettingStarted/chapter-3.md b/GettingStarted/chapter-3.md new file mode 100644 index 0000000..a232c9e --- /dev/null +++ b/GettingStarted/chapter-3.md @@ -0,0 +1,69 @@ + + + + + + +### 单服务器部署 + +Druid包括一组参考配置和用于单机部署的启动脚本: + +* `nano-quickstart` +* `micro-quickstart` +* `small` +* `medium` +* `large` +* `large` +* `xlarge` + +`micro-quickstart`适合于笔记本电脑等小型机器,旨在用于快速评估测试使用场景。 + +`nano-quickstart`是一种甚至更小的配置,目标是具有1个CPU和4GB内存的计算机。它旨在在资源受限的环境(例如小型Docker容器)中进行有限的评估测试。 + +其他配置旨在用于一般用途的单机部署,它们的大小适合大致基于亚马逊i3系列EC2实例的硬件。 + +这些示例配置的启动脚本与Druid服务一起运行单个ZK实例,您也可以选择单独部署ZK。 + +通过[Coordinator配置文档](../Configuration/configuration.md#Coordinator)中描述的可选配置`druid.coordinator.asOverlord.enabled = true`可以在单个进程中同时运行Druid Coordinator和Overlord。 + +虽然为大型单台计算机提供了示例配置,但在更高规模下,我们建议在集群部署中运行Druid,以实现容错和减少资源争用。 + +#### 单服务器参考配置 +##### Nano-Quickstart: 1 CPU, 4GB 内存 + +* 启动命令: `bin/start-nano-quickstart` +* 配置目录: `conf/druid/single-server/nano-quickstart` + +##### Micro-Quickstart: 4 CPU, 16GB 内存 + +* 启动命令: `bin/start-micro-quickstart` +* 配置目录: `conf/druid/single-server/micro-quickstart` + +##### Small: 8 CPU, 64GB 内存 (~i3.2xlarge) + +* 启动命令: `bin/start-small` +* 配置目录: `conf/druid/single-server/small` + +##### Medium: 16 CPU, 128GB 内存 (~i3.4xlarge) + +* 启动命令: `bin/start-medium` +* 配置目录: `conf/druid/single-server/medium` + +##### Large: 32 CPU, 256GB 内存 (~i3.8xlarge) + +* 启动命令: `bin/start-large` +* 配置目录: `conf/druid/single-server/large` + +##### X-Large: 64 CPU, 512GB 内存 (~i3.16xlarge) + +* 启动命令: `bin/start-xlarge` +* 配置目录: `conf/druid/single-server/xlarge` + +--- \ No newline at end of file diff --git a/GettingStarted/chapter-4.md b/GettingStarted/chapter-4.md new file mode 100644 index 0000000..597c3b5 --- /dev/null +++ b/GettingStarted/chapter-4.md @@ -0,0 +1,421 @@ + + + + + + +## 集群部署 + +Apache Druid旨在作为可伸缩的容错集群进行部署。 + +在本文档中,我们将安装一个简单的集群,并讨论如何对其进行进一步配置以满足您的需求。 + +这个简单的集群将具有以下特点: +* 一个Master服务同时起Coordinator和Overlord进程 +* 两个可伸缩、容错的Data服务来运行Historical和MiddleManager进程 +* 一个Query服务,运行Druid Broker和Router进程 + +在生产中,我们建议根据您的特定容错需求部署多个Master服务器和多个Query服务器,但是您可以使用一台Master服务器和一台Query服务器将服务快速运行起来,然后再添加更多服务器。 +### 选择硬件 +#### 首次部署 + +如果您现在没有Druid集群,并打算首次以集群模式部署运行Druid,则本指南提供了一个包含预先配置的集群部署示例。 + +##### Master服务 + +Coordinator进程和Overlord进程负责处理集群的元数据和协调需求,它们可以运行在同一台服务器上。 + +在本示例中,我们将在等效于AWS[m5.2xlarge](https://aws.amazon.com/ec2/instance-types/m5/)实例的硬件环境上部署。 + +硬件规格为: + +* 8核CPU +* 31GB内存 + +可以在`conf/druid/cluster/master`下找到适用于此硬件规格的Master示例服务配置。 + +##### Data服务 + +Historical和MiddleManager可以分配在同一台服务器上运行,以处理集群中的实际数据,这两个服务受益于CPU、内存和固态硬盘。 + +在本示例中,我们将在等效于AWS[i3.4xlarge](https://aws.amazon.com/cn/ec2/instance-types/i3/)实例的硬件环境上部署。 + +硬件规格为: +* 16核CPU +* 122GB内存 +* 2 * 1.9TB 固态硬盘 + +可以在`conf/druid/cluster/data`下找到适用于此硬件规格的Data示例服务配置。 + +##### Query服务 + +Druid Broker服务接收查询请求,并将其转发到集群中的其他部分,同时其可以可选的配置内存缓存。 Broker服务受益于CPU和内存。 + +在本示例中,我们将在等效于AWS[m5.2xlarge](https://aws.amazon.com/ec2/instance-types/m5/)实例的硬件环境上部署。 + +硬件规格为: + +* 8核CPU +* 31GB内存 + +您可以考虑将所有的其他开源UI工具或者查询依赖等与Broker服务部署在同一台服务器上。 + +可以在`conf/druid/cluster/query`下找到适用于此硬件规格的Query示例服务配置。 + +##### 其他硬件配置 + +上面的示例集群是从多种确定Druid集群大小的可能方式中选择的一个示例。 + +您可以根据自己的特定需求和限制选择较小/较大的硬件或较少/更多的服务器。 + +如果您的使用场景具有复杂的扩展要求,则还可以选择不将Druid服务混合部署(例如,独立的Historical Server)。 + +[基本集群调整指南](../Operations/basicClusterTuning.md)中的信息可以帮助您进行决策,并可以调整配置大小。 + +#### 从单服务器环境迁移部署 + +如果您现在已有单服务器部署的环境,例如[单服务器部署示例](./chapter-3.md)中的部署,并且希望迁移到类似规模的集群部署,则以下部分包含一些选择Master/Data/Query服务等效硬件的准则。 + +##### Master服务 + +Master服务的主要考虑点是可用CPU以及用于Coordinator和Overlord进程的堆内存。 + +首先计算出来在单服务器环境下Coordinator和Overlord已分配堆内存之和,然后选择具有足够内存的Master服务硬件,同时还需要考虑到为服务器上其他进程预留一些额外的内存。 + +对于CPU,可以选择接近于单服务器环境核数1/4的硬件。 + +##### Data服务 + +在为集群Data服务选择硬件时,主要考虑可用的CPU和内存,可行时使用SSD存储。 + +在集群化部署时,出于容错的考虑,最好是部署多个Data服务。 + +在选择Data服务的硬件时,可以假定一个分裂因子`N`,将原来的单服务器环境的CPU和内存除以`N`,然后在新集群中部署`N`个硬件规格缩小的Data服务。 + +##### Query服务 + +Query服务的硬件选择主要考虑可用的CPU、Broker服务的堆内和堆外内存、Router服务的堆内存。 + +首先计算出来在单服务器环境下Broker和Router已分配堆内存之和,然后选择可以覆盖Broker和Router内存的Query服务硬件,同时还需要考虑到为服务器上其他进程预留一些额外的内存。 + +对于CPU,可以选择接近于单服务器环境核数1/4的硬件。 + +[基本集群调优指南](../Operations/basicClusterTuning.md)包含有关如何计算Broker和Router服务内存使用量的信息。 + +### 选择操作系统 + +我们建议运行您喜欢的Linux发行版,同时还需要: + +* **Java 8** + +> [!WARNING] +> Druid服务运行依赖Java 8,可以使用环境变量`DRUID_JAVA_HOME`或`JAVA_HOME`指定在何处查找Java,有关更多详细信息,请运行`verify-java`脚本。 + +### 下载发行版 + +首先,下载并解压缩发布安装包。最好首先在单台计算机上执行此操作,因为您将编辑配置,然后将修改后的配置分发到所有服务器上。 + +[下载](https://www.apache.org/dyn/closer.cgi?path=/druid/0.17.0/apache-druid-0.17.0-bin.tar.gz)Druid最新0.17.0release安装包 + +在终端中运行以下命令来提取Druid + +``` +tar -xzf apache-druid-0.17.0-bin.tar.gz +cd apache-druid-0.17.0 +``` + +在安装包中有以下文件: + +* `LICENSE`和`NOTICE`文件 +* `bin/*` - 启停等脚本 +* `conf/druid/cluster/*` - 用于集群部署的模板配置 +* `extensions/*` - Druid核心扩展 +* `hadoop-dependencies/*` - Druid Hadoop依赖 +* `lib/*` - Druid核心库和依赖 +* `quickstart/*` - 与[快速入门](./chapter-2.md)相关的文件 + +我们主要是编辑`conf/druid/cluster/`中的文件。 + +#### 从单服务器环境迁移部署 + +在以下各节中,我们将在`conf/druid/cluster`下编辑配置。 + +如果您已经有一个单服务器部署,请将您的现有配置复制到`conf/druid /cluster`以保留您所做的所有配置更改。 + +### 配置元数据存储和深度存储 +#### 从单服务器环境迁移部署 + +如果您已经有一个单服务器部署,并且希望在整个迁移过程中保留数据,请在更新元数据/深层存储配置之前,按照[元数据迁移](../Operations/metadataMigration.md)和[深层存储迁移](../Operations/DeepstorageMigration.md)中的说明进行操作。 + +这些指南针对使用Derby元数据存储和本地深度存储的单服务器部署。 如果您已经在单服务器集群中使用了非Derby元数据存储,则可以在新集群中可以继续使用当前的元数据存储。 + +这些指南还提供了有关从本地深度存储迁移段的信息。集群部署需要分布式深度存储,例如S3或HDFS。 如果单服务器部署已在使用分布式深度存储,则可以在新集群中继续使用当前的深度存储。 + +#### 元数据存储 + +在`conf/druid/cluster/_common/common.runtime.properties`中,使用您将用作元数据存储的服务器地址来替换"metadata.storage.*": + +* `druid.metadata.storage.connector.connectURI` +* `druid.metadata.storage.connector.host` + +在生产部署中,我们建议运行专用的元数据存储,例如具有复制功能的MySQL或PostgreSQL,与Druid服务器分开部署。 + +[MySQL扩展](../Configuration/core-ext/mysql.md)和[PostgreSQL](../Configuration/core-ext/postgresql.md)扩展文档包含有关扩展配置和初始数据库安装的说明。 + +#### 深度存储 + +Druid依赖于分布式文件系统或大对象(blob)存储来存储数据,最常用的深度存储实现是S3(适合于在AWS上)和HDFS(适合于已有Hadoop集群)。 + +##### S3 + +在`conf/druid/cluster/_common/common.runtime.properties`中, + +* 在`druid.extension.loadList`配置项中增加"druid-s3-extensions"扩展 +* 注释掉配置文件中用于本地存储的"Deep Storage"和"Indexing service logs" +* 打开配置文件中关于"For S3"部分中"Deep Storage"和"Indexing service logs"的配置 + +上述操作之后,您将看到以下的变化: + +```json +druid.extensions.loadList=["druid-s3-extensions"] + +#druid.storage.type=local +#druid.storage.storageDirectory=var/druid/segments + +druid.storage.type=s3 +druid.storage.bucket=your-bucket +druid.storage.baseKey=druid/segments +druid.s3.accessKey=... +druid.s3.secretKey=... + +#druid.indexer.logs.type=file +#druid.indexer.logs.directory=var/druid/indexing-logs + +druid.indexer.logs.type=s3 +druid.indexer.logs.s3Bucket=your-bucket +druid.indexer.logs.s3Prefix=druid/indexing-logs +``` +更多信息可以看[S3扩展](../Configuration/core-ext/s3.md)部分的文档。 + +##### HDFS + +在`conf/druid/cluster/_common/common.runtime.properties`中, + +* 在`druid.extension.loadList`配置项中增加"druid-hdfs-storage"扩展 +* 注释掉配置文件中用于本地存储的"Deep Storage"和"Indexing service logs" +* 打开配置文件中关于"For HDFS"部分中"Deep Storage"和"Indexing service logs"的配置 + +上述操作之后,您将看到以下的变化: + +```json +druid.extensions.loadList=["druid-hdfs-storage"] + +#druid.storage.type=local +#druid.storage.storageDirectory=var/druid/segments + +druid.storage.type=hdfs +druid.storage.storageDirectory=/druid/segments + +#druid.indexer.logs.type=file +#druid.indexer.logs.directory=var/druid/indexing-logs + +druid.indexer.logs.type=hdfs +druid.indexer.logs.directory=/druid/indexing-logs +``` + +同时: + +* 需要将Hadoop的配置文件(core-site.xml, hdfs-site.xml, yarn-site.xml, mapred-site.xml)放置在Druid进程的classpath中,可以将他们拷贝到`conf/druid/cluster/_common`目录中 + +更多信息可以看[HDFS扩展](../Configuration/core-ext/hdfs.md)部分的文档。 + +### Hadoop连接配置 + +如果要从Hadoop集群加载数据,那么此时应对Druid做如下配置: + +* 在`conf/druid/cluster/_common/common.runtime.properties`文件中更新`druid.indexer.task.hadoopWorkingPath`配置项,将其更新为您期望的一个用于临时文件存储的HDFS路径。 通常会配置为`druid.indexer.task.hadoopWorkingPath=/tmp/druid-indexing` +* 需要将Hadoop的配置文件(core-site.xml, hdfs-site.xml, yarn-site.xml, mapred-site.xml)放置在Druid进程的classpath中,可以将他们拷贝到`conf/druid/cluster/_common`目录中 + +请注意,您无需为了可以从Hadoop加载数据而使用HDFS深度存储。例如,如果您的集群在Amazon Web Services上运行,即使您使用Hadoop或Elastic MapReduce加载数据,我们也建议使用S3进行深度存储。 + +更多信息可以看[基于Hadoop的数据摄取](../DataIngestion/hadoopbased.md)部分的文档。 + +### Zookeeper连接配置 + +在生产集群中,我们建议使用专用的ZK集群,该集群与Druid服务器分开部署。 + +在 `conf/druid/cluster/_common/common.runtime.properties` 中,将 `druid.zk.service.host` 设置为包含用逗号分隔的host:port对列表的连接字符串,每个对与ZK中的ZooKeeper服务器相对应。(例如" 127.0.0.1:4545"或"127.0.0.1:3000,127.0.0.1:3001、127.0.0.1:3002") + +您也可以选择在Master服务上运行ZK,而不使用专用的ZK集群。如果这样做,我们建议部署3个Master服务,以便您具有ZK仲裁。 + +### 配置调整 +#### 从单服务器环境迁移部署 +##### Master服务 + +如果您使用的是[单服务器部署示例](./chapter-3.md)中的示例配置,则这些示例中将Coordinator和Overlord进程合并为一个合并的进程。 + +`conf/druid/cluster/master/coordinator-overlord` 下的示例配置同样合并了Coordinator和Overlord进程。 + +您可以将现有的 `coordinator-overlord` 配置从单服务器部署复制到`conf/druid/cluster/master/coordinator-overlord` + +##### Data服务 + +假设我们正在从一个32CPU和256GB内存的单服务器部署环境进行迁移,在老的环境中,Historical和MiddleManager使用了如下的配置: + +Historical(单服务器) + +```json +druid.processing.buffer.sizeBytes=500000000 +druid.processing.numMergeBuffers=8 +druid.processing.numThreads=31 +``` + +MiddleManager(单服务器) + +```json +druid.worker.capacity=8 +druid.indexer.fork.property.druid.processing.numMergeBuffers=2 +druid.indexer.fork.property.druid.processing.buffer.sizeBytes=100000000 +druid.indexer.fork.property.druid.processing.numThreads=1 +``` + +在集群部署中,我们选择一个分裂因子(假设为2),则部署2个16CPU和128GB内存的Data服务,各项的调整如下: + +Historical + +* `druid.processing.numThreads`设置为新硬件的(`CPU核数 - 1`) +* `druid.processing.numMergeBuffers` 使用分裂因子去除单服务部署环境的值 +* `druid.processing.buffer.sizeBytes` 该值保持不变 + +MiddleManager: + +* `druid.worker.capacity`: 使用分裂因子去除单服务部署环境的值 +* `druid.indexer.fork.property.druid.processing.numMergeBuffers`: 该值保持不变 +* `druid.indexer.fork.property.druid.processing.buffer.sizeBytes`: 该值保持不变 +* `druid.indexer.fork.property.druid.processing.numThreads`: 该值保持不变 + +调整后的结果配置如下: + +新的Historical(2 Data服务器) + +```json + druid.processing.buffer.sizeBytes=500000000 + druid.processing.numMergeBuffers=8 + druid.processing.numThreads=31 + ``` + +新的MiddleManager(2 Data服务器) + +```json +druid.worker.capacity=4 +druid.indexer.fork.property.druid.processing.numMergeBuffers=2 +druid.indexer.fork.property.druid.processing.buffer.sizeBytes=100000000 +druid.indexer.fork.property.druid.processing.numThreads=1 +``` + +##### Query服务 + +您可以将现有的Broker和Router配置复制到`conf/druid/cluster/query`下的目录中,无需进行任何修改. + +#### 首次部署 + +如果您正在使用如下描述的示例集群规格: + +* 1 Master 服务器(m5.2xlarge) +* 2 Data 服务器(i3.4xlarge) +* 1 Query 服务器(m5.2xlarge) + +`conf/druid/cluster`下的配置已经为此硬件确定了,一般情况下您无需做进一步的修改。 + +如果您选择了其他硬件,则[基本的集群调整指南](../Operations/basicClusterTuning.md)可以帮助您调整配置大小。 + +### 开启端口(如果使用了防火墙) + +如果您正在使用防火墙或其他仅允许特定端口上流量准入的系统,请在以下端口上允许入站连接: + +#### Master服务 + +* 1527(Derby元数据存储,如果您正在使用一个像MySQL或者PostgreSQL的分离的元数据存储则不需要) +* 2181(Zookeeper,如果使用了独立的ZK集群则不需要) +* 8081(Coordinator) +* 8090(Overlord) + +#### Data服务 + +* 8083(Historical) +* 8091,8100-8199(Druid MiddleManager,如果`druid.worker.capacity`参数设置较大的话,则需要更多高于8199的端口) + +#### Query服务 + +* 8082(Broker) +* 8088(Router,如果使用了) + +> [!WARNING] +> 在生产中,我们建议将ZooKeeper和元数据存储部署在其专用硬件上,而不是在Master服务器上。 + +### 启动Master服务 + +将Druid发行版和您编辑的配置文件复制到Master服务器上。 + +如果您一直在本地计算机上编辑配置,则可以使用rsync复制它们: + +```json +rsync -az apache-druid-0.17.0/ MASTER_SERVER:apache-druid-0.17.0/ +``` + +#### 不带Zookeeper启动 + +在发行版根目录中,运行以下命令以启动Master服务: +```json +bin/start-cluster-master-no-zk-server +``` + +#### 带Zookeeper启动 + +如果计划在Master服务器上运行ZK,请首先更新`conf/zoo.cfg`以标识您计划如何运行ZK,然后,您可以使用以下命令与ZK一起启动Master服务进程: +```json +bin/start-cluster-master-with-zk-server +``` + +> [!WARNING] +> 在生产中,我们建议将ZooKeeper运行在其专用硬件上。 + +### 启动Data服务 + +将Druid发行版和您编辑的配置文件复制到您的Data服务器。 + +在发行版根目录中,运行以下命令以启动Data服务: +```json +bin/start-cluster-data-server +``` + +您可以在需要的时候增加更多的Data服务器。 + +> [!WARNING] +> 对于具有复杂资源分配需求的集群,您可以将Historical和MiddleManager分开部署,并分别扩容组件。这也使您能够利用Druid的内置MiddleManager自动伸缩功能。 + +### 启动Query服务 +将Druid发行版和您编辑的配置文件复制到您的Query服务器。 + +在发行版根目录中,运行以下命令以启动Query服务: + +```json +bin/start-cluster-query-server +``` + +您可以根据查询负载添加更多查询服务器。 如果增加了查询服务器的数量,请确保按照[基本集群调优指南](../Operations/basicClusterTuning.md)中的说明调整Historical和Task上的连接池。 + +### 加载数据 + +恭喜,您现在有了Druid集群!下一步是根据使用场景来了解将数据加载到Druid的推荐方法。 + +了解有关[加载数据](../DataIngestion/index.md)的更多信息。 + + diff --git a/GettingStarted/img/tutorial-quickstart-01.png b/GettingStarted/img/tutorial-quickstart-01.png new file mode 100644 index 0000000000000000000000000000000000000000..d6ee11bac1798c414db42dab81f99d2ef54471d4 GIT binary patch literal 67013 zcma%jbyQVd*RLW6ZdBsX($dlm(hUlSI)F$w$f3Kt?&f*l@B8E4 z$1(00dpPzu?6uZhzZrY(xqj1dWknfOWJ2U;&z_;m$x5m|dj=1C_6#lv@fGk3yX`c~ zvuEVbZ3JdAWv%g$H z3P@hcU{?ksTtzr)v46iJo`vvvz+;j>|Mv_03=SJc`HaKq-MlB&}#Ssg;>wo+7*qCyQ8zf z@FoJ`>M)HVSFFS`GZzx01KgYdr=I;PZghDTMG*B}L;+BLuQ!eq@DCbH|fl zu>T%Tr^Dm07y{SXsFn$ccwM2Vup0G<(mhCti3R2OaH?IQgTWVQ*=aS{Pa1S4gG=SK zP+zXUI$llfaM0a%TxvlT?Zj(xyGDL%)C?0ziZSP2xnzjv#xGNNa*@$Qu|-|(FU&e) zmsy4zZILOJ;6n8%;fAeOJ)O*@98OCuBv!w&I5ofg(no^z$t(#q$I*fquV@u4#~2+K zTRsQIWdCp#70sjx*{^ZNfqjA|!uk2V=RRo)Sfq}6iFb?nvx5R{$L-`Ytgn9eh|hJ( za*EeK0Mcqc@Mp2PY`kY4N-n+-6~g4ax6HYsY3YT1rc=51m8&g#9@%A*QA;xI5uR^w zGid-?x;!0<@G9BliGgCwr2~=@CZFt^Jbh!BuJp0Yz&1?F!*VtwqB6&?6=Yy zS_y|mu_L)q2DN)#tKJ({n!Du7XT4@MyfWLK6V)(8;uO9)BuEbv1k>qt=+9Q$Jz}Wc z@2_9;sR>}Ss*(pUqXg~=I7^cJCO_YrZssoK**AvIQxJv?8ByycMIsi=qvO}UXCa#E zx>nHoZg%+2Kq`sp8YiAwG5F=+^j6D*gyC95cud9S_U{V>gIFk9^4s@uFv?RNG`rT` zqmwSHX4gMg1GQdQ`1lN2%Op$;pL>kqWm<6N-mnmvKCT2hA8m!B#Y_@V986WrYugzA zWYD@OZ*spRB^C9)`I&twq45J}nXY}ALT_X*ibUZnxyT30Tl{IK#SOzYvQn($jwjzn z{|GP1R;t_lX`oi#aev5@3a)Giw~=GhePhp?#w(mJS$LkVG~k361J=Q+rOTf5vRb3F z3MB6V6S5P)R znsfO$%dZiXfaj#oAyIF3m}BO)o+03a9#5wbQ5roj&?e%rgI%m~?fX8n7M}H<>Os;_ ztD&+V(h8KN+qQT8QW|6VF*AmFbvPItrh0aJ6WJ&FWa*ex^oE2}5Ov5b#h+aq!@U zi{4l{uSqXE21~%UlWFdLRYDc|{1h6&f(jJB^!Ko;nl);B z&1Ta?m*>+J1JZ?Z$+9}VN@$BV*7^L5Yf<#se@{j<^8CU1OvB(&{IY-w%ELtdOLe4TUr7X}Dp53lDk84~5bD2D{LUl-nJo7h|-KKEX|i z1OC;=#(Sr%6TIlWZ;L( zsOsvWMBy13#k)^lJE^+7rJvsujOE<^h%2drXxK^Y!}w2ecg7eqDiZX`wuAXf>R3j2F}Cj%QpDRdEB0%?IR92; zA<8kiHRS1*U5BXaiD%S*Q9d@+<9BUa^q-dmCvcgK%AXtAg}nvOc&?mi+?~w(m5qE` z*J<#`8@3~Mhu3=Tww}d-QKH*yT09@wT2efqD>KbwI`I}6(d%=MLWuI!EU@(r!sxNwz_dQ9L`pi#xcv8Mv>f6 znX|EYTX3jf%)@wuVM4;f>}^hXYXhk-NP}#vzOtMCm>#04)J!^5sbBD9)IT4n-JEIj z=PhK_jscK;SASBF^yTJYQE;KdlMFO>DGrlFVlTErFdz6zN}83$7kG>ukjjIQQnlzj zk)il_P#bFWfvozUVD&PDsR)AGV}0nWJ>8vzvNmqgDjj z4SIG*m3#elz?S3e^hXx@Ar{8VVE5W&^(%r46qDdO z z^cw#RjL$wEsw0g13@Ft1p^tFK;&Bsg0jTOMM!GJ3XKM^R4)X;J1!Mf&J!nq#E>X>e>^7^ zLQbh%_>rjcB92DQ=X^=wzF~hZOa;qIQrmaid!#FGSmu7>MYHH|C1+zC6PySodVXvnVUQeZe znDge6f7oIB*?K!d>^I}3g`?wR&E?KJZ`WMa&r>@Faq5k_FS*Kf2%_gNRsHY#9_>@S zR<7Hh4n>80%~K_BP7NSK2530?faqf#mUR-DF=(X2yDMD zyc1IPZ{2O!F|Fw#OjzfnEAAFv%S69zH{F1bdUSZpta?{I&DtTYwSKQp_JvjI?mdhV`~T_ z&}(OP*`~^1ovvkZAk`&x`J|Nr$;8yc`-_o861x*cZ+FYld9`<7{dX|W93|7&|N~HL8;t#4%UxEvvKYS*Xgk5eAg!dk9 z(L3lf$jAy5LqZ*V$yK_pijCSa^M3`}FU8buvaXCcw6gnlA<|sjHM`TbwY4edUg63A zv75G2_L>z^S4&iGq3WfuAfUvCLR1S&dJ}snYKacHfkmw=` zInAIc`yO!mMx9v=4%Jq@YZfKN`Dg}dNbja~v15gV?v3V1W(T*^)HE6c%k!it+h5tk;o zvyGMH_O-?fao@wjya2m3Pc0-DhxAFAqz}ssF7_9&q?rcaM_RA}SgLJ_H!P=mRYHhg-eLmH!?>-~m9JzlBb$Xa^wEV4K6&|o^7nMh$Q zNom8n_H`faxuH6vnyUZutLRj7_@ZH<$}5r7%ST$&-H^E#y={~=T0?u@kINU5BbRa~ zRR(ORu~|YYk+#Wl7wHu`CB!@R2NBa%FTQ`ckbIxniqG^guh(RqpYs}@YSDYgxn8N;H#0<@Hj1w?Rot$!16Q`U zKl(Mh!v2h|ZgJWZo4Y7)aLj2c@Yh)W3^ut>tQGUJuO(lysy6G9{sfQop5ohvqM$Aw z$Fi#-e9PuYHq{?c&_HwFY~=>Mt^(FPv+(AchzS9nV5wC;HcZkPh5lmQmo7g-3~z6B zm5pms1+hdd;YfYZe`at0dC}&xmv-U{{T&7%)9iZTwQ}xng2*n(zTGyI8J6X1{B_Z| zEIoWG1wH%YhBb7M;X8}3X?1}{MIkm4mli-64sR?MkKBxE)1C`mHo{{G?l73k-TbEF zwf#tlC3Dud92S$i3`XzPT)$PdwfLEg-e@z+y#9_n0avbuF6wn9H^p126Z0!BJKXhQ za?@}`$w1~i)If*jinfySkV0IBFL<%pLTU+=a`&zEYNV-@Y$2W>dL;YeK63J!CyQs~ z{pRx(&)^xb`CTYTDje{MHFB+QfD_O8afK=bB%(>#JK?klx}WFZnlxN)^1u9;h8=Ev zGdRJ|7Ul12Y5$21#$-KV;-7i%*a3)?iG|dTCi!Eh9Ck@(KB!6I5 zmukn|@%h~)4qkjiL>nE}ef*ViOtrpGE1sMG
P#PXWg{V)$1b8d(LCOo=Mu%@>9W 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 Iol`;+0Kf24BLDyZ literal 0 HcmV?d00001 diff --git a/design/index.md b/design/index.md index d803a70..64fdba5 100644 --- a/design/index.md +++ b/design/index.md @@ -1,28 +1,7 @@ ---- -id: index -title: "Introduction to Apache Druid" ---- +# Druid 介绍 +本页面对 Druid 的基本情况进行了一些介绍和简要说明。 - - -## What is Druid? +## 什么是 Druid Apache Druid is a real-time analytics database designed for fast slice-and-dice analytics ("[OLAP](http://en.wikipedia.org/wiki/Online_analytical_processing)" queries) on large data sets. Druid is most often @@ -74,27 +53,69 @@ offers exact count-distinct and exact ranking. 10. **Automatic summarization at ingest time.** Druid optionally supports data summarization at ingestion time. This summarization partially pre-aggregates your data, and can lead to big costs savings and performance boosts. -## When should I use Druid? -Druid is used by many companies of various sizes for many different use cases. Check out the -[Powered by Apache Druid](/druid-powered) page +Apache Druid是一个实时分析型数据库,旨在对大型数据集进行快速的查询分析("[OLAP](https://en.wikipedia.org/wiki/Online_analytical_processing)"查询)。Druid最常被当做数据库来用以支持实时摄取、高性能查询和高稳定运行的应用场景,同时,Druid也通常被用来助力分析型应用的图形化界面,或者当做需要快速聚合的高并发后端API,Druid最适合应用于面向事件类型的数据。 -Druid is likely a good choice if your use case fits a few of the following descriptors: +Druid通常应用于以下场景: -- Insert rates are very high, but updates are less common. -- Most of your queries are aggregation and reporting queries ("group by" queries). You may also have searching and -scanning queries. -- You are targeting query latencies of 100ms to a few seconds. -- Your data has a time component (Druid includes optimizations and design choices specifically related to time). -- You may have more than one table, but each query hits just one big distributed table. Queries may potentially hit more -than one smaller "lookup" table. -- You have high cardinality data columns (e.g. URLs, user IDs) and need fast counting and ranking over them. -- You want to load data from Kafka, HDFS, flat files, or object storage like Amazon S3. +* 点击流分析(Web端和移动端) +* 网络监测分析(网络性能监控) +* 服务指标存储 +* 供应链分析(制造类指标) +* 应用性能指标分析 +* 数字广告分析 +* 商务智能 / OLAP -Situations where you would likely _not_ want to use Druid include: +Druid的核心架构吸收和结合了[数据仓库](https://en.wikipedia.org/wiki/Data_warehouse)、[时序数据库](https://en.wikipedia.org/wiki/Time_series_database)以及[检索系统](https://en.wikipedia.org/wiki/Search_engine_(computing))的优势,其主要特征如下: -- You need low-latency updates of _existing_ records using a primary key. Druid supports streaming inserts, but not streaming updates (updates are done using -background batch jobs). -- You are building an offline reporting system where query latency is not very important. -- You want to do "big" joins (joining one big fact table to another big fact table) and you are okay with these queries -taking a long time to complete. +1. **列式存储**,Druid使用列式存储,这意味着在一个特定的数据查询中它只需要查询特定的列,这样极地提高了部分列查询场景的性能。另外,每一列数据都针对特定数据类型做了优化存储,从而支持快速的扫描和聚合。 +2. **可扩展的分布式系统**,Druid通常部署在数十到数百台服务器的集群中,并且可以提供每秒数百万条记录的接收速率,数万亿条记录的保留存储以及亚秒级到几秒的查询延迟。 +3. **大规模并行处理**,Druid可以在整个集群中并行处理查询。 +4. **实时或批量摄取**,Druid可以实时(已经被摄取的数据可立即用于查询)或批量摄取数据。 +5. **自修复、自平衡、易于操作**,作为集群运维操作人员,要伸缩集群只需添加或删除服务,集群就会在后台自动重新平衡自身,而不会造成任何停机。如果任何一台Druid服务器发生故障,系统将自动绕过损坏。 Druid设计为7*24全天候运行,无需出于任何原因而导致计划内停机,包括配置更改和软件更新。 +6. **不会丢失数据的云原生容错架构**,一旦Druid摄取了数据,副本就安全地存储在[深度存储介质](Design/../chapter-1.md)(通常是云存储,HDFS或共享文件系统)中。即使某个Druid服务发生故障,也可以从深度存储中恢复您的数据。对于仅影响少数Druid服务的有限故障,副本可确保在系统恢复时仍然可以进行查询。 +7. **用于快速过滤的索引**,Druid使用[CONCISE](https://arxiv.org/pdf/1004.0403.pdf)或[Roaring](https://roaringbitmap.org/)压缩的位图索引来创建索引,以支持快速过滤和跨多列搜索。 +8. **基于时间的分区**,Druid首先按时间对数据进行分区,另外同时可以根据其他字段进行分区。这意味着基于时间的查询将仅访问与查询时间范围匹配的分区,这将大大提高基于时间的数据的性能。 +9. **近似算法**,Druid应用了近似count-distinct,近似排序以及近似直方图和分位数计算的算法。这些算法占用有限的内存使用量,通常比精确计算要快得多。对于精度要求比速度更重要的场景,Druid还提供了精确count-distinct和精确排序。 +10. **摄取时自动汇总聚合**,Druid支持在数据摄取阶段可选地进行数据汇总,这种汇总会部分预先聚合您的数据,并可以节省大量成本并提高性能。 + + +## 我应该在什么时候使用 Druid + +许多公司都已经将 Druid 应用于多种不同的应用场景。请访问 [使用 Apache Druid 的公司](https://druid.apache.org/druid-powered) 页面来了解都有哪些公司使用了 Druid。 + +如果您的使用场景符合下面的一些特性,那么Druid 将会是一个非常不错的选择: + +- 数据的插入频率非常高,但是更新频率非常低。 +- 大部分的查询为聚合查询(aggregation)和报表查询(reporting queries),例如我们常使用的 "group by" 查询。同时还有一些检索和扫描查询。 +- 查询的延迟被限制在 100ms 到 几秒钟之间。 +- 你的数据具有时间组件(属性)。针对时间相关的属性,Druid 进行特殊的设计和优化。 +- 你可能具有多个数据表,但是查询通常只针对一个大型的分布数据表,但是,查询又可能需要查询多个较小的 `lookup` 表。 +- 如果你的数据中具有高基数(high cardinality)数据字段,例如 URLs、用户 IDs,但是你需要对这些字段进行快速计数和排序。 +- 你需要从 Kafka,HDFS,文本文件,或者对象存储(例如,AWS S3)中载入数据。 + + +如果你的使用场景是下面的一些情况的话,Druid **不是**一个较好的选择: + +- 针对一个已经存在的记录,使用主键(primary key)进行低延迟的更新操作。Druid 支持流式插入(streaming inserts)数据,但是并不很好的支持流式更新(streaming updates)数据。 + Druid 的更新操作是通过后台批处理完成的。 +- 你的系统类似的是一个离线的报表系统,查询的延迟不是系统设计的重要考虑。 +- 使用场景中需要对表(Fact Table)进行连接查询,并且针对这个查询你可以介绍比较高的延迟来等待查询的完成。 + + +### 高基数 +在 SQL 中,基数(cardinality)的定义为一个数据列中独一无二数据的数量。 + +高基数(High-Cardinality)的定义为在一个数据列中的数据基本上不重复,或者说重复率非常低。 + +例如我们常见的识别号,邮件地址,用户名等都可以被认为是高基数数据。 +例如我们常定义的 USERS 数据表中的 USER_ID 字段,这个字段中的数据通常被定义为 1 到 n。每一次一个新的用户被作为记录插入到 USERS 表中,一个新的记录将会被创建, +字段 USER_ID 将会使用一个新的数据来标识这个被插入的数据。因为 USER_ID 中插入的数据是独一无二的,因此这个字段的数据技术就可以被考虑认为是 高基数(High-Cardinality) 数据。 + + +### Fact Table +与 Fact Table 对应的表是 Dimension Table。 + +这 2 个表是数据仓库的两个概念,为数据仓库的两种类型表。 从保存数据的角度来说,本质上没区别,都是表。 +区别主要在数据和用途上,Fact Table 用来存 fact 数据,就是一些可以计量的数据和可加性数据,数据数量,金额等。 +Dimension Table 用来存描述性的数据,比如说用来描述 Fact 表中的数据,如区域,销售代表,产品等。 \ No newline at end of file From bfae313cf8c3e781fd4aa9aab45f66941661800f Mon Sep 17 00:00:00 2001 From: YuCheng Hu Date: Thu, 22 Jul 2021 14:43:17 -0400 Subject: [PATCH 4/5] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=BB=80=E4=B9=88=E6=98=AF=20Druid=20=E7=9A=84=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/index.md | 108 ++++++++++++++++++------------------------------ 1 file changed, 40 insertions(+), 68 deletions(-) diff --git a/design/index.md b/design/index.md index 64fdba5..8cc1758 100644 --- a/design/index.md +++ b/design/index.md @@ -3,81 +3,53 @@ ## 什么是 Druid -Apache Druid is a real-time analytics database designed for fast slice-and-dice analytics -("[OLAP](http://en.wikipedia.org/wiki/Online_analytical_processing)" queries) on large data sets. Druid is most often -used as a database for powering use cases where real-time ingest, fast query performance, and high uptime are important. -As such, Druid is commonly used for powering GUIs of analytical applications, or as a backend for highly-concurrent APIs -that need fast aggregations. Druid works best with event-oriented data. +Apache Druid 是一个实时分析型数据库,旨在对大型数据集进行快速查询和分析("[OLAP](https://en.wikipedia.org/wiki/Online_analytical_processing)" 查询)。 -Common application areas for Druid include: +Druid 最常被当做数据库,用以支持实时摄取、高查询性能和高稳定运行的应用场景。 +例如,Druid 通常被用来作为图形分析工具的数据源来提供数据,或当有需要高聚和高并发的后端 API。 +同时 Druid 也非常适合针对面向事件类型的数据。 -- Clickstream analytics (web and mobile analytics) -- Network telemetry analytics (network performance monitoring) -- Server metrics storage -- Supply chain analytics (manufacturing metrics) -- Application performance metrics -- Digital marketing/advertising analytics -- Business intelligence / OLAP +通常可以使用 Druid 作为数据源的系统包括有: +- 点击流量分析(Web 或者移动分析) +- 网络监测分析(网络性能监控) +- 服务器存储指标 +- 供应链分析(生产数据指标) +- 应用性能指标 +- 数字广告分析 +- 商业整合 / OLAP -Druid's core architecture combines ideas from data warehouses, timeseries databases, and logsearch systems. Some of -Druid's key features are: +Druid 的核心架构集合了数据仓库(data warehouses),时序数据库(timeseries databases),日志分析系统(logsearch systems)的概念。 -1. **Columnar storage format.** Druid uses column-oriented storage, meaning it only needs to load the exact columns -needed for a particular query. This gives a huge speed boost to queries that only hit a few columns. In addition, each -column is stored optimized for its particular data type, which supports fast scans and aggregations. -2. **Scalable distributed system.** Druid is typically deployed in clusters of tens to hundreds of servers, and can -offer ingest rates of millions of records/sec, retention of trillions of records, and query latencies of sub-second to a -few seconds. -3. **Massively parallel processing.** Druid can process a query in parallel across the entire cluster. -4. **Realtime or batch ingestion.** Druid can ingest data either real-time (ingested data is immediately available for -querying) or in batches. -5. **Self-healing, self-balancing, easy to operate.** As an operator, to scale the cluster out or in, simply add or -remove servers and the cluster will rebalance itself automatically, in the background, without any downtime. If any -Druid servers fail, the system will automatically route around the damage until those servers can be replaced. Druid -is designed to run 24/7 with no need for planned downtimes for any reason, including configuration changes and software -updates. -6. **Cloud-native, fault-tolerant architecture that won't lose data.** Once Druid has ingested your data, a copy is -stored safely in [deep storage](architecture.html#deep-storage) (typically cloud storage, HDFS, or a shared filesystem). -Your data can be recovered from deep storage even if every single Druid server fails. For more limited failures affecting -just a few Druid servers, replication ensures that queries are still possible while the system recovers. -7. **Indexes for quick filtering.** Druid uses [Roaring](https://roaringbitmap.org/) or -[CONCISE](https://arxiv.org/pdf/1004.0403) compressed bitmap indexes to create indexes that power fast filtering and -searching across multiple columns. -8. **Time-based partitioning.** Druid first partitions data by time, and can additionally partition based on other fields. -This means time-based queries will only access the partitions that match the time range of the query. This leads to -significant performance improvements for time-based data. -9. **Approximate algorithms.** Druid includes algorithms for approximate count-distinct, approximate ranking, and -computation of approximate histograms and quantiles. These algorithms offer bounded memory usage and are often -substantially faster than exact computations. For situations where accuracy is more important than speed, Druid also -offers exact count-distinct and exact ranking. -10. **Automatic summarization at ingest time.** Druid optionally supports data summarization at ingestion time. This -summarization partially pre-aggregates your data, and can lead to big costs savings and performance boosts. +如果你对上面的各种数据类型,数据库不是非常了解的话,那么我们建议你进行一些搜索来了解相关的一些定义和提供的功能。 +Druid 的一些关键特性包括有: +1. **列示存储格式(Columnar storage format)** Druid 使用列式存储,这意味着在一个特定的数据查询中它只需要查询特定的列。 + 这样的设计极大的提高了部分列查询场景性能。另外,每一列数据都针对特定数据类型做了优化存储,从而能够支持快速扫描和聚合。 -Apache Druid是一个实时分析型数据库,旨在对大型数据集进行快速的查询分析("[OLAP](https://en.wikipedia.org/wiki/Online_analytical_processing)"查询)。Druid最常被当做数据库来用以支持实时摄取、高性能查询和高稳定运行的应用场景,同时,Druid也通常被用来助力分析型应用的图形化界面,或者当做需要快速聚合的高并发后端API,Druid最适合应用于面向事件类型的数据。 +2. **可扩展的分布式系统(Scalable distributed system)** Druid通常部署在数十到数百台服务器的集群中, + 并且可以提供每秒数百万级的数据导入,并且保存有万亿级的数据,同时提供 100ms 到 几秒钟之间的查询延迟。 + +3. **高性能并发处理(Massively parallel processing)** Druid 可以在整个集群中并行处理查询。 -Druid通常应用于以下场景: - -* 点击流分析(Web端和移动端) -* 网络监测分析(网络性能监控) -* 服务指标存储 -* 供应链分析(制造类指标) -* 应用性能指标分析 -* 数字广告分析 -* 商务智能 / OLAP - -Druid的核心架构吸收和结合了[数据仓库](https://en.wikipedia.org/wiki/Data_warehouse)、[时序数据库](https://en.wikipedia.org/wiki/Time_series_database)以及[检索系统](https://en.wikipedia.org/wiki/Search_engine_(computing))的优势,其主要特征如下: - -1. **列式存储**,Druid使用列式存储,这意味着在一个特定的数据查询中它只需要查询特定的列,这样极地提高了部分列查询场景的性能。另外,每一列数据都针对特定数据类型做了优化存储,从而支持快速的扫描和聚合。 -2. **可扩展的分布式系统**,Druid通常部署在数十到数百台服务器的集群中,并且可以提供每秒数百万条记录的接收速率,数万亿条记录的保留存储以及亚秒级到几秒的查询延迟。 -3. **大规模并行处理**,Druid可以在整个集群中并行处理查询。 -4. **实时或批量摄取**,Druid可以实时(已经被摄取的数据可立即用于查询)或批量摄取数据。 -5. **自修复、自平衡、易于操作**,作为集群运维操作人员,要伸缩集群只需添加或删除服务,集群就会在后台自动重新平衡自身,而不会造成任何停机。如果任何一台Druid服务器发生故障,系统将自动绕过损坏。 Druid设计为7*24全天候运行,无需出于任何原因而导致计划内停机,包括配置更改和软件更新。 -6. **不会丢失数据的云原生容错架构**,一旦Druid摄取了数据,副本就安全地存储在[深度存储介质](Design/../chapter-1.md)(通常是云存储,HDFS或共享文件系统)中。即使某个Druid服务发生故障,也可以从深度存储中恢复您的数据。对于仅影响少数Druid服务的有限故障,副本可确保在系统恢复时仍然可以进行查询。 -7. **用于快速过滤的索引**,Druid使用[CONCISE](https://arxiv.org/pdf/1004.0403.pdf)或[Roaring](https://roaringbitmap.org/)压缩的位图索引来创建索引,以支持快速过滤和跨多列搜索。 -8. **基于时间的分区**,Druid首先按时间对数据进行分区,另外同时可以根据其他字段进行分区。这意味着基于时间的查询将仅访问与查询时间范围匹配的分区,这将大大提高基于时间的数据的性能。 -9. **近似算法**,Druid应用了近似count-distinct,近似排序以及近似直方图和分位数计算的算法。这些算法占用有限的内存使用量,通常比精确计算要快得多。对于精度要求比速度更重要的场景,Druid还提供了精确count-distinct和精确排序。 -10. **摄取时自动汇总聚合**,Druid支持在数据摄取阶段可选地进行数据汇总,这种汇总会部分预先聚合您的数据,并可以节省大量成本并提高性能。 +4. **实时或者批量数据处理(Realtime or batch ingestion)** Druid 可以实时(已经被导入和摄取的数据可立即用于查询)导入摄取数据库或批量导入摄取数据。 + +5. **自我修复、自我平衡、易于操作(Self-healing, self-balancing, easy to operate)** 为集群运维操作人员,要伸缩集群只需添加或删除服务,集群就会在后台自动重新平衡自身,而不会造成任何停机。 + 如果任何一台 Druid 服务器发生故障,系统将自动绕过损坏的节点而保持无间断运行。 + Druid 被设计为 7*24 运行,无需设计任何原因的计划内停机(例如需要更改配置或者进行软件更新)。 + +6. **原生结合云的容错架构,不丢失数据(Cloud-native, fault-tolerant architecture that won't lose data)** 一旦 Druid 获得了数据,那么获得的数据将会安全的保存在 [深度存储](architecture.md#deep-storage) (通常是云存储,HDFS 或共享文件系统)中。 + 即使单个个 Druid 服务发生故障,你的数据也可以从深度存储中进行恢复。对于仅影响少数 Druid 服务的有限故障,保存的副本可确保在系统恢复期间仍然可以进行查询。 + +7. **针对快速过滤的索引(Indexes for quick filtering)** Druid 使用 [Roaring](https://roaringbitmap.org/) 或 +[CONCISE](https://arxiv.org/pdf/1004.0403) 来压缩 bitmap indexes 后来创建索引,以支持快速过滤和跨多列搜索。 + +8. **基于时间的分区(Time-based partitioning)** Druid 首先按时间对数据进行分区,同时也可以根据其他字段进行分区。 + 这意味着基于时间的查询将仅访问与查询时间范围匹配的分区,这将大大提高基于时间的数据处理性能。 + +9. **近似算法(Approximate algorithms)** Druid应用了近似 `count-distinct`,近似排序以及近似直方图和分位数计算的算法。 + 这些算法占用有限的内存使用量,通常比精确计算要快得多。对于精度要求比速度更重要的场景,Druid 还提供了exact count-distinct 和 exact ranking。 + +10. **在数据摄取的时候自动进行汇总(Automatic summarization at ingest time)** Druid 支持在数据摄取阶段可选地进行数据汇总,这种汇总会部分预先聚合您的数据,并可以节省大量成本并提高性能。 ## 我应该在什么时候使用 Druid From f1d57ee40f24715a407b1590ab167c3bdd09d4f9 Mon Sep 17 00:00:00 2001 From: YuCheng Hu Date: Thu, 22 Jul 2021 14:43:53 -0400 Subject: [PATCH 5/5] =?UTF-8?q?=E5=AF=BC=E5=85=A5=E5=AE=98=E6=96=B9?= =?UTF-8?q?=E7=9A=84=E5=86=85=E5=AE=B9=E6=9D=A5=E5=87=86=E5=A4=87=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E7=BF=BB=E8=AF=91=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/architecture.md | 282 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 design/architecture.md diff --git a/design/architecture.md b/design/architecture.md new file mode 100644 index 0000000..6b360b8 --- /dev/null +++ b/design/architecture.md @@ -0,0 +1,282 @@ +# Druid 系统架构 + + +Druid has a multi-process, distributed architecture that is designed to be cloud-friendly and easy to operate. Each +Druid process type can be configured and scaled independently, giving you maximum flexibility over your cluster. This +design also provides enhanced fault tolerance: an outage of one component will not immediately affect other components. + +## Processes and Servers + +Druid has several process types, briefly described below: + +* [**Coordinator**](../design/coordinator.md) processes manage data availability on the cluster. +* [**Overlord**](../design/overlord.md) processes control the assignment of data ingestion workloads. +* [**Broker**](../design/broker.md) processes handle queries from external clients. +* [**Router**](../design/router.md) processes are optional processes that can route requests to Brokers, Coordinators, and Overlords. +* [**Historical**](../design/historical.md) processes store queryable data. +* [**MiddleManager**](../design/middlemanager.md) processes are responsible for ingesting data. + +Druid processes can be deployed any way you like, but for ease of deployment we suggest organizing them into three server types: Master, Query, and Data. + +* **Master**: Runs Coordinator and Overlord processes, manages data availability and ingestion. +* **Query**: Runs Broker and optional Router processes, handles queries from external clients. +* **Data**: Runs Historical and MiddleManager processes, executes ingestion workloads and stores all queryable data. + +For more details on process and server organization, please see [Druid Processes and Servers](../design/processes.md). + +## External dependencies + +In addition to its built-in process types, Druid also has three external dependencies. These are intended to be able to +leverage existing infrastructure, where present. + +### Deep storage +Shared file storage accessible by every Druid server. In a clustered deployment, this is typically going to +be a distributed object store like S3 or HDFS, or a network mounted filesystem. In a single-server deployment, +this is typically going to be local disk. Druid uses deep storage to store any data that has been ingested into the +system. + +Druid uses deep storage only as a backup of your data and as a way to transfer data in the background between +Druid processes. To respond to queries, Historical processes do not read from deep storage, but instead read prefetched +segments from their local disks before any queries are served. This means that Druid never needs to access deep storage +during a query, helping it offer the best query latencies possible. It also means that you must have enough disk space +both in deep storage and across your Historical processes for the data you plan to load. + +Deep storage is an important part of Druid's elastic, fault-tolerant design. Druid can bootstrap from deep storage even +if every single data server is lost and re-provisioned. + +For more details, please see the [Deep storage](../dependencies/deep-storage.md) page. + +### Metadata storage +The metadata storage holds various shared system metadata such as segment usage information and task information. In a +clustered deployment, this is typically going to be a traditional RDBMS like PostgreSQL or MySQL. In a single-server +deployment, it is typically going to be a locally-stored Apache Derby database. + +For more details, please see the [Metadata storage](../dependencies/metadata-storage.md) page. + +### ZooKeeper +Used for internal service discovery, coordination, and leader election. + +For more details, please see the [ZooKeeper](../dependencies/zookeeper.md) page. + +## Architecture diagram + +The following diagram shows how queries and data flow through this architecture, using the suggested Master/Query/Data server organization: + + + + + +## Storage design + +### Datasources and segments + +Druid data is stored in "datasources", which are similar to tables in a traditional RDBMS. Each datasource is +partitioned by time and, optionally, further partitioned by other attributes. Each time range is called a "chunk" (for +example, a single day, if your datasource is partitioned by day). Within a chunk, data is partitioned into one or more +["segments"](../design/segments.md). Each segment is a single file, typically comprising up to a few million rows of data. Since segments are +organized into time chunks, it's sometimes helpful to think of segments as living on a timeline like the following: + + + +A datasource may have anywhere from just a few segments, up to hundreds of thousands and even millions of segments. Each +segment starts life off being created on a MiddleManager, and at that point, is mutable and uncommitted. The segment +building process includes the following steps, designed to produce a data file that is compact and supports fast +queries: + +- Conversion to columnar format +- Indexing with bitmap indexes +- Compression using various algorithms + - Dictionary encoding with id storage minimization for String columns + - Bitmap compression for bitmap indexes + - Type-aware compression for all columns + +Periodically, segments are committed and published. At this point, they are written to [deep storage](#deep-storage), +become immutable, and move from MiddleManagers to the Historical processes. An entry about the segment is also written +to the [metadata store](#metadata-storage). This entry is a self-describing bit of metadata about the segment, including +things like the schema of the segment, its size, and its location on deep storage. These entries are what the +Coordinator uses to know what data *should* be available on the cluster. + +For details on the segment file format, please see [segment files](segments.md). + +For details on modeling your data in Druid, see [schema design](../ingestion/schema-design.md). + +### Indexing and handoff + +_Indexing_ is the mechanism by which new segments are created, and _handoff_ is the mechanism by which they are published +and begin being served by Historical processes. The mechanism works like this on the indexing side: + +1. An _indexing task_ starts running and building a new segment. It must determine the identifier of the segment before + it starts building it. For a task that is appending (like a Kafka task, or an index task in append mode) this will be + done by calling an "allocate" API on the Overlord to potentially add a new partition to an existing set of segments. For + a task that is overwriting (like a Hadoop task, or an index task _not_ in append mode) this is done by locking an + interval and creating a new version number and new set of segments. +2. If the indexing task is a realtime task (like a Kafka task) then the segment is immediately queryable at this point. + It's available, but unpublished. +3. When the indexing task has finished reading data for the segment, it pushes it to deep storage and then publishes it + by writing a record into the metadata store. +4. If the indexing task is a realtime task, at this point it waits for a Historical process to load the segment. If the + indexing task is not a realtime task, it exits immediately. + +And like this on the Coordinator / Historical side: + +1. The Coordinator polls the metadata store periodically (by default, every 1 minute) for newly published segments. +2. When the Coordinator finds a segment that is published and used, but unavailable, it chooses a Historical process + to load that segment and instructs that Historical to do so. +3. The Historical loads the segment and begins serving it. +4. At this point, if the indexing task was waiting for handoff, it will exit. + +### Segment identifiers + +Segments all have a four-part identifier with the following components: + +- Datasource name. +- Time interval (for the time chunk containing the segment; this corresponds to the `segmentGranularity` specified + at ingestion time). +- Version number (generally an ISO8601 timestamp corresponding to when the segment set was first started). +- Partition number (an integer, unique within a datasource+interval+version; may not necessarily be contiguous). + +For example, this is the identifier for a segment in datasource `clarity-cloud0`, time chunk +`2018-05-21T16:00:00.000Z/2018-05-21T17:00:00.000Z`, version `2018-05-21T15:56:09.909Z`, and partition number 1: + +``` +clarity-cloud0_2018-05-21T16:00:00.000Z_2018-05-21T17:00:00.000Z_2018-05-21T15:56:09.909Z_1 +``` + +Segments with partition number 0 (the first partition in a chunk) omit the partition number, like the following +example, which is a segment in the same time chunk as the previous one, but with partition number 0 instead of 1: + +``` +clarity-cloud0_2018-05-21T16:00:00.000Z_2018-05-21T17:00:00.000Z_2018-05-21T15:56:09.909Z +``` + +### Segment versioning + +You may be wondering what the "version number" described in the previous section is for. Or, you might not be, in which +case good for you and you can skip this section! + +It's there to support batch-mode overwriting. In Druid, if all you ever do is append data, then there will be just a +single version for each time chunk. But when you overwrite data, what happens behind the scenes is that a new set of +segments is created with the same datasource, same time interval, but a higher version number. This is a signal to the +rest of the Druid system that the older version should be removed from the cluster, and the new version should replace +it. + +The switch appears to happen instantaneously to a user, because Druid handles this by first loading the new data (but +not allowing it to be queried), and then, as soon as the new data is all loaded, switching all new queries to use those +new segments. Then it drops the old segments a few minutes later. + +### Segment lifecycle + +Each segment has a lifecycle that involves the following three major areas: + +1. **Metadata store:** Segment metadata (a small JSON payload generally no more than a few KB) is stored in the + [metadata store](../dependencies/metadata-storage.md) once a segment is done being constructed. The act of inserting + a record for a segment into the metadata store is called _publishing_. These metadata records have a boolean flag + named `used`, which controls whether the segment is intended to be queryable or not. Segments created by realtime tasks will be + available before they are published, since they are only published when the segment is complete and will not accept + any additional rows of data. +2. **Deep storage:** Segment data files are pushed to deep storage once a segment is done being constructed. This + happens immediately before publishing metadata to the metadata store. +3. **Availability for querying:** Segments are available for querying on some Druid data server, like a realtime task + or a Historical process. + +You can inspect the state of currently active segments using the Druid SQL +[`sys.segments` table](../querying/sql.md#segments-table). It includes the following flags: + +- `is_published`: True if segment metadata has been published to the metadata store and `used` is true. +- `is_available`: True if the segment is currently available for querying, either on a realtime task or Historical + process. +- `is_realtime`: True if the segment is _only_ available on realtime tasks. For datasources that use realtime ingestion, + this will generally start off `true` and then become `false` as the segment is published and handed off. +- `is_overshadowed`: True if the segment is published (with `used` set to true) and is fully overshadowed by some other + published segments. Generally this is a transient state, and segments in this state will soon have their `used` flag + automatically set to false. + +### Availability and consistency + +Druid has an architectural separation between ingestion and querying, as described above in +[Indexing and handoff](#indexing-and-handoff). This means that when understanding Druid's availability and +consistency properties, we must look at each function separately. + +On the **ingestion side**, Druid's primary [ingestion methods](../ingestion/index.md#ingestion-methods) are all +pull-based and offer transactional guarantees. This means that you are guaranteed that ingestion using these will +publish in an all-or-nothing manner: + +- Supervised "seekable-stream" ingestion methods like [Kafka](../development/extensions-core/kafka-ingestion.md) and + [Kinesis](../development/extensions-core/kinesis-ingestion.md). With these methods, Druid commits stream offsets to its + [metadata store](#metadata-storage) alongside segment metadata, in the same transaction. Note that ingestion of data + that has not yet been published can be rolled back if ingestion tasks fail. In this case, partially-ingested data is + discarded, and Druid will resume ingestion from the last committed set of stream offsets. This ensures exactly-once + publishing behavior. +- [Hadoop-based batch ingestion](../ingestion/hadoop.md). Each task publishes all segment metadata in a single + transaction. +- [Native batch ingestion](../ingestion/native-batch.md). In parallel mode, the supervisor task publishes all segment + metadata in a single transaction after the subtasks are finished. In simple (single-task) mode, the single task + publishes all segment metadata in a single transaction after it is complete. + +Additionally, some ingestion methods offer an _idempotency_ guarantee. This means that repeated executions of the same +ingestion will not cause duplicate data to be ingested: + +- Supervised "seekable-stream" ingestion methods like [Kafka](../development/extensions-core/kafka-ingestion.md) and + [Kinesis](../development/extensions-core/kinesis-ingestion.md) are idempotent due to the fact that stream offsets and + segment metadata are stored together and updated in lock-step. +- [Hadoop-based batch ingestion](../ingestion/hadoop.md) is idempotent unless one of your input sources + is the same Druid datasource that you are ingesting into. In this case, running the same task twice is non-idempotent, + because you are adding to existing data instead of overwriting it. +- [Native batch ingestion](../ingestion/native-batch.md) is idempotent unless + [`appendToExisting`](../ingestion/native-batch.md) is true, or one of your input sources is the same Druid datasource + that you are ingesting into. In either of these two cases, running the same task twice is non-idempotent, because you + are adding to existing data instead of overwriting it. + +On the **query side**, the Druid Broker is responsible for ensuring that a consistent set of segments is involved in a +given query. It selects the appropriate set of segments to use when the query starts based on what is currently +available. This is supported by _atomic replacement_, a feature that ensures that from a user's perspective, queries +flip instantaneously from an older set of data to a newer set of data, with no consistency or performance impact. +This is used for Hadoop-based batch ingestion, native batch ingestion when `appendToExisting` is false, and compaction. + +Note that atomic replacement happens for each time chunk individually. If a batch ingestion task or compaction +involves multiple time chunks, then each time chunk will undergo atomic replacement soon after the task finishes, but +the replacements will not all happen simultaneously. + +Typically, atomic replacement in Druid is based on a _core set_ concept that works in conjunction with segment versions. +When a time chunk is overwritten, a new core set of segments is created with a higher version number. The core set +must _all_ be available before the Broker will use them instead of the older set. There can also only be one core set +per version per time chunk. Druid will also only use a single version at a time per time chunk. Together, these +properties provide Druid's atomic replacement guarantees. + +Druid also supports an experimental _segment locking_ mode that is activated by setting +[`forceTimeChunkLock`](../ingestion/tasks.md#context) to false in the context of an ingestion task. In this case, Druid +creates an _atomic update group_ using the existing version for the time chunk, instead of creating a new core set +with a new version number. There can be multiple atomic update groups with the same version number per time chunk. Each +one replaces a specific set of earlier segments in the same time chunk and with the same version number. Druid will +query the latest one that is fully available. This is a more powerful version of the core set concept, because it +enables atomically replacing a subset of data for a time chunk, as well as doing atomic replacement and appending +simultaneously. + +If segments become unavailable due to multiple Historicals going offline simultaneously (beyond your replication +factor), then Druid queries will include only the segments that are still available. In the background, Druid will +reload these unavailable segments on other Historicals as quickly as possible, at which point they will be included in +queries again. + +## Query processing + +Queries first enter the [Broker](../design/broker.md), where the Broker will identify which segments have data that may pertain to that query. +The list of segments is always pruned by time, and may also be pruned by other attributes depending on how your +datasource is partitioned. The Broker will then identify which [Historicals](../design/historical.md) and +[MiddleManagers](../design/middlemanager.md) are serving those segments and send a rewritten subquery to each of those processes. The Historical/MiddleManager processes will take in the +queries, process them and return results. The Broker receives results and merges them together to get the final answer, +which it returns to the original caller. + +Broker pruning is an important way that Druid limits the amount of data that must be scanned for each query, but it is +not the only way. For filters at a more granular level than what the Broker can use for pruning, indexing structures +inside each segment allow Druid to figure out which (if any) rows match the filter set before looking at any row of +data. Once Druid knows which rows match a particular query, it only accesses the specific columns it needs for that +query. Within those columns, Druid can skip from row to row, avoiding reading data that doesn't match the query filter. + +So Druid uses three different techniques to maximize query performance: + +- Pruning which segments are accessed for each query. +- Within each segment, using indexes to identify which rows must be accessed. +- Within each segment, only reading the specific rows and columns that are relevant to a particular query. + +For more details about how Druid executes queries, refer to the [Query execution](../querying/query-execution.md) +documentation. \ No newline at end of file