Apache Druid 设计导览:实时分析数据库的架构、存储模型与适用场景
数据库OLAP大数据后端【免费下载链接】druidApache Druid: a high performance real-time analytics database.项目地址https://gitcode.com/gh_mirrors/druid6/druid点击查看免费下载Apache Druid 是一个面向海量数据集的实时分析数据库专为快速切片与切块式 OLAP 查询而设计在实时摄入real-time ingestion、毫秒级到秒级查询延迟与高可用high uptime之间取得了良好平衡。本文以官方设计文档为骨架结合仓库源码与配套文档系统讲解 Druid 的定位、十大关键特性、分布式服务架构、以 Segment 为核心的存储模型以及何时该用、何时不该用 Druid的决策指南帮助读者建立从选型到原理的完整认知。Druid 是什么Apache Druid 是一个实时分析数据库real-time analytics database核心目标是在大型数据集上提供快速的切片与切块式分析即 OLAP 类查询。它最常见的应用形态是作为分析类应用 GUI 的数据库后端或作为需要快速聚合能力的高并发 API 的数据层。Druid 对**事件型数据event-oriented data**最为契合——典型场景包括点击流、监控指标、日志、IoT 事件等。一个典型的 Druid 数据流可以概括为外部数据源Kafka、HDFS、对象存储等→ 摄入任务生成 Segment → 写入 Deep Storage → 由 Historical 服务加载对外提供查询全程由元数据存储与 ZooKeeper 协同调度。典型应用场景官方文档给出了 Druid 最常见的应用领域覆盖从互联网到传统行业的广泛分析需求应用场景说明点击流分析Clickstream analytics分析网站与移动应用上的用户行为理解导航路径、热门内容与用户参与度网络遥测分析Network telemetry analytics监控与分析网络流量及性能指标优化网络效率、定位瓶颈、保障服务质量服务器指标存储Server metrics storage采集 CPU、内存、磁盘 I/O、网络活动等性能指标监控服务器健康并优化资源分配供应链分析Supply chain analytics利用供应链各环节数据优化库存管理、精简物流、预测需求、提升运营效率应用性能指标Application performance metrics监控分析软件应用性能定位改进空间、排查问题、保障用户体验数字营销/广告分析跨社交、搜索、展示广告等渠道追踪分析数字营销与广告投放效果BI/OLAP 分析从大数据集中挖掘洞察、生成报表、支撑数据驱动的业务决策客户分析Customer analytics分析客户偏好、行为与购买模式支撑个性化营销、改善服务与留存IoT 分析处理分析 IoT 设备产生的数据支撑自动化、优化与预测性维护金融分析评估财务数据、管理风险、检测欺诈、辅助投资决策医疗分析改善患者预后、优化医疗服务、降低成本、识别疾病趋势与模式社交媒体分析监控分析点赞、分享、评论等社交行为理解受众情绪、追踪品牌认知这些场景的共同特征是数据量大、持续追加写入、查询以聚合和报表为主、对查询延迟敏感——这正是 Druid 擅长的领域。Druid 的十大关键特性Druid 的核心架构融合了数据仓库data warehouse、时序数据库timeseries database与日志搜索系统log search system的设计思想。官方文档将其关键特性归纳为以下十点下文逐条展开并补充实现层面的细节。1. 列式存储格式Columnar storage formatDruid 采用列式存储查询时只加载实际需要的列因此只取少数几列的查询性能大幅提升。同时Druid 会针对每列的数据类型做存储优化以支撑快速扫描与聚合。关于列式布局的细节可进一步阅读 Segment 文件结构 与 存储概览。2. 可扩展的分布式系统Scalable distributed system典型 Druid 部署横跨数十到数百台服务器可以每秒摄入数百万条记录同时保有万亿级记录并将查询延迟维持在亚秒到数秒之间。分布式能力的基础是下文详述的多类服务角色Coordinator、Overlord、Broker、Historical 等可参见 架构文档。3. 大规模并行处理Massively parallel processingDruid 能够在整个集群范围内并行处理每一条查询。Broker 服务负责将查询路由到持有相关 Segment 的多个服务上并行执行再合并结果返回参见 Broker 服务。4. 实时或批量摄入Realtime or batch ingestionDruid 支持实时流式摄入如 Kafka、Kinesis与批量摄入Hadoop 批量摄入、Native 批量摄入。已摄入的数据立即可查——实时任务构建中的 Segment 在发布前即可被查询。5. 自愈、自均衡、易运维Self-healing, self-balancing, easy to operate运维人员通过增减服务器即可横向扩缩容集群会在后台自动再均衡且无需停机。若某台 Druid 服务器故障系统会自动绕开故障路由数据直到服务器被替换。Druid 被设计为可无计划停机地持续运行配置变更与软件升级同样如此。6. 云原生、容错、不丢数据的架构摄入完成后Druid 会将数据副本安全地存入 Deep Storage——通常是云对象存储、HDFS 或共享文件系统。即使所有 Druid 服务器全部宕机也可以从 Deep Storage 恢复数据对于只影响少数服务器的局部故障副本机制replication保证恢复期间查询依然可用。7. 支持快速过滤的索引Indexes for quick filteringDruid 使用 Roaring 或 CONCISE 压缩位图索引实现跨多列的快速过滤与检索。位图索引在源码中以独立的位图抽象呈现例如 BitmapFactory、RoaringBitmapFactory、WrappedRoaringBitmap 与 WrappedImmutableRoaringBitmap 等位图的序列化逻辑集中在 BitmapSerde。默认使用 Roaring 压缩也可在 IndexSpec 中配置为 Concise。8. 基于时间的分区Time-based partitioningDruid 首先按时间对数据分区可选地再按其他字段做二次分区。时间范围查询只会访问与查询时间区间匹配的分区从而带来显著的性能提升。时间分区的粒度由摄入时的segmentGranularity参数位于granularitySpec决定。9. 近似算法Approximate algorithmsDruid 内置了近似 count-distinct、近似排名、近似直方图与分位数等算法内存占用有界、且通常远快于精确计算当精度比速度更重要时Druid 也提供精确的 count-distinct 与精确排名。10. 摄入时自动汇总Automatic summarization at ingest timeDruid 可选地在摄入阶段对数据做部分预聚合即 Rollup可以显著节省存储成本并提升查询性能。参见 Rollup 文档。何时该使用 Druid以及何时不该适合使用 Druid 的场景官方文档列出的匹配清单包括写入速率非常高但更新较少。大部分查询是聚合与报表类查询如 group by也可能包含搜索与扫描查询。目标查询延迟在 100ms 到数秒之间。数据带有时间维度——Druid 针对时间做了专门的优化与设计取舍。可能有多张表但每条查询只命中一张大的分布式事实表可额外命中若干较小的 lookup 维表。存在高基数high cardinality数据列如 URL、用户 ID需要对其做快速计数与排名。希望从 Kafka、HDFS、扁平文件或 Amazon S3 等对象存储加载数据。不适合使用 Druid 的场景以下情况通常不建议选择 Druid需要按主键对既有记录做低延迟更新。Druid 支持流式插入streaming inserts但不支持流式更新更新只能通过后台批量作业完成。离线报表系统对查询延迟不敏感。大 join即一张大事实表 join 另一张大事实表且能接受这类查询耗时较长。分布式架构六类服务与三种服务器形态Druid 采用云友好的分布式架构各服务可独立配置与伸缩以获得最大的集群运维灵活性。其容错设计保证某一组件故障不会立即影响其他组件。服务角色总览Druid 包含以下服务类型详见 架构文档Coordinator管理集群中数据的可用性Segment 的加载、均衡与淘汰。Overlord控制数据摄入工作负载的任务分配。Broker处理来自外部客户端的查询。Router将请求路由到 Broker、Coordinator 与 Overlord。Historical存储可查询的数据。MiddleManager 与 Peon执行数据摄入。Indexer作为 MiddleManager Peon 任务执行系统的替代方案。所有服务都可以在 Web Console 的Services页签中查看三种服务器类型与推荐部署为便于部署官方建议将服务组织为三种服务器类型Master、Query与Data。服务器类型承载服务职责Master serverCoordinator Overlord管理数据摄入与数据可用性启动新的摄入作业、协调 Data server 上数据的可用性Query serverBroker Router提供用户与客户端应用交互的端点将查询路由到 Data server或可选地代理 Master server 请求Data serverHistorical MiddleManager可选 Indexer执行摄入作业并存储可查询数据Master serverCoordinator监视 Data server 上的 Historical 服务负责把 Segment 分配给具体服务器并确保 Segment 在 Historical 之间均衡分布。Overlord监视 Data server 上的 MiddleManager 服务是数据摄入的控制器负责将摄入任务分配给 MiddleManager 并协调 Segment 发布。Query serverBroker接收外部客户端的查询并转发给 Data server收到各子查询结果后合并返回给调用方。通常应查询 Broker而不是直接查询 Historical 或 MiddleManager。Router提供位于 Broker、Overlord 与 Coordinator 之前的统一 API 网关。Router 服务还运行 Web Console——一个用于加载数据、管理 datasource 与任务、查看服务器状态和 Segment 信息的 UI。Data serverHistorical负责历史数据的存储与查询包括已在系统中提交的流式数据。Historical 从 Deep Storage 下载 Segment 并响应针对这些 Segment 的查询但不接受写入。MiddleManager负责将新数据摄入集群从外部数据源读取数据并发布新的 Druid Segment。Peon由 MiddleManager 派生的任务执行引擎。每个 Peon 运行在独立的 JVM 中负责执行单个任务且始终与派生出它的 MiddleManager 位于同一台主机上。Druid 使用独立 JVM 来隔离任务资源与日志每个 Peon 同时只能运行一个任务而一个 MiddleManager 可以管理多个 Peon参见 MiddleManager 服务。Indexer可选MiddleManager Peon 的替代方案。与每个任务 fork 独立 JVM 进程不同Indexer 在单个 JVM 进程内以线程方式运行任务。它更易配置部署并支持任务间资源共享但作为较新的功能目前仍标记为 实验性。通常二选一部署要么 MiddleManager要么 Indexer而不是两者同时。服务合设Colocation建议按服务器类型合设服务通常能更好地利用硬件资源超大规模集群则建议拆分到独立服务器以避免资源竞争Coordinator 与 Overlord两者的负载都随集群 Segment 数量增长其中 Coordinator 增长更明显。Segment 数量极大的集群可考虑拆分两者为 Coordinator 的均衡负载留出资源。也可以通过设置druid.coordinator.asOverlord.enabled属性将两者合并为单一服务运行参见 Coordinator 运维配置。Historical 与 MiddleManager在摄入或查询负载较高时建议将两者部署在不同主机上以避免 CPU 与内存竞争。Historical 需要空闲内存用于内存映射 Segment这也是分开部署的另一个理由。三大外部依赖Deep Storage、元数据存储与 ZooKeeper除了内置服务Druid 还依赖三个外部组件它们被设计为尽量复用已有的基础设施。Deep StorageDeep Storage 是 Segment 的存放地是一种不由 Druid 提供的存储机制它直接决定数据持久性durability只要 Druid 进程能访问到该存储上的 Segment无论丢失多少个 Druid 节点都不会丢数据若 Segment 从该存储层消失则其代表的数据即告丢失。配置的 load rules 决定 Segment 主要存在于 Deep Storage还是 Deep Storage 与 Historical 进程的组合。Druid 支持多种 Deep Storage 选项选项说明本地存储Local适用于单机或多台服务器共享文件系统如 NFS的场景。生产多服务器集群建议改用云对象存储或 HDFSAmazon S3 或 S3 兼容存储经druid-s3-extensions扩展支持Google Cloud Storage经druid-google-extensions扩展支持Azure Blob Storage经druid-azure-extensions扩展支持HDFS经druid-hdfs-storage扩展支持本地存储的配置项如下写入common.runtime.properties属性可选值说明默认值druid.storage.typelocal存储类型必须设置druid.storage.storageDirectory任意本地目录存放 Segment 的目录必须与druid.segmentCache.locations和druid.segmentCache.infoDir不同/tmp/druid/localStoragedruid.storage.ziptrue、falseSegment 以目录false还是 zip 文件true写入false配置示例druid.storage.typelocal druid.storage.storageDirectory/tmp/druid/localStorageDeep Storage 的用途包括存放全部已摄入数据加载到 Historical 的低延迟查询 Segment 也会保留在 Deep Storage 作为备份仅存于 Deep Storage 的 Segment 可用于 从 Deep Storage 查询以及在服务之间后台传递数据即 Segment 文件。Historical 在本地磁盘缓存 Segment 并提供查询磁盘上的 Segment 是 Druid 低延迟查询性能的来源。你也可以直接查询仅存在于 Deep Storage 的 Segment用一部分性能换取无需扩容 Historical 即可查询更多数据的能力。容量规划时注意两点Deep Storage 需能容纳全部已摄入数据Historical 磁盘需能容纳你希望加载到其上、需要低延迟查询的热数据。元数据存储Metadata storage元数据存储保存各类共享系统元数据如 Segment 用量信息与任务信息但不保存实际数据参见 元数据存储文档。集群部署通常使用 PostgreSQL 或 MySQL 这类传统 RDBMS单机部署通常使用本地 Apache Derby 数据库。Derby 是默认元数据存储但不适合生产环境生产建议使用 MySQL 或 PostgreSQL注意元数据存储必须 ACID 兼容否则可能引发任务偶发失败等问题。元数据存储包含Segment 记录、规则记录Segment 应落在何处、配置记录、任务相关表由 Overlord 与 MiddleManager 管理任务时使用与审计记录规则等配置变更的审计历史。其中 Segment 表由druid.metadata.storage.tables.segments属性指定由 Coordinator 轮询以确定集群中应可查询的 Segment 集合即used segments。used列值为 1 表示应被集群加载使用为 0 表示不应加载保留元数据以支持回滚payload列存储 Segment 元数据的 JSON blob例如{ dataSource:wikipedia, interval:2012-05-23T00:00:00.000Z/2012-05-24T00:00:00.000Z, version:2012-05-24T00:10:00.046Z, loadSpec:{ type:s3_zip, bucket:bucket_for_segment, key:path/to/segment/on/s3 }, dimensions:comma-delimited-list-of-dimension-names, metrics:comma-delimited-list-of-metric-names, shardSpec:{type:none}, binaryVersion:9, size:size_of_segment, identifier:wikipedia_2012-05-23T00:00:00.000Z_2012-05-24T00:00:00.000Z_2012-05-23T00:10:00.046Z }Derby 的配置方式写入 Druid 配置文件druid.metadata.storage.typederby druid.metadata.storage.connector.connectURIjdbc:derby://localhost:1527//opt/var/druid_state/derby;createtrue自定义数据库连接池DBCP属性时使用druid.metadata.storage.connector.dbcp.前缀例如druid.metadata.storage.connector.dbcp.maxConnLifetimeMillis1200000 druid.metadata.storage.connector.dbcp.defaultQueryTimeout30000注意username、password、connectURI、validationQuery、testOnBorrow这几个属性必须使用druid.metadata.storage.connector.前缀设置。只有以下进程会访问元数据存储Indexing service 进程如有、Realtime 进程如有、Coordinator 进程。因此只需为这些机器授予访问权限例如在 AWS 安全组中。由于丢失的元数据无法恢复官方还建议为元数据存储搭建高可用环境相关清理操作可参考 元数据记录自动清理。ZooKeeper用于内部服务发现、协调与领导者选举leader election详见 ZooKeeper 文档。Broker 依赖 ZooKeeper 中的 Segment 分布元数据来路由查询Historical 通过 ZooKeeper 的 load queue 路径获知加载指令、通过 served segments 路径对外宣告可用 SegmentCoordinator 也通过 ZooKeeper 向 Historical 下发加载/卸载指令。存储模型Datasource、时间块与 SegmentDruid 将数据存储在datasource中类似传统 RDBMS 的表。每个 datasource 按时间分区并可再按其他属性分区。每个时间范围称为一个chunk时间块例如按天分区时的一天。chunk 内的数据再划分为一个或多个 Segment每个 Segment 是单个文件通常包含数百万行数据。由于 Segment 组织在时间块中可以将其理解为排在一条时间线上一个 datasource 可能只有几个 Segment也可能有几十万甚至上百万个 Segment。每个 Segment 由 MiddleManager 创建创建时是可变的mutable且未提交uncommitted——数据一旦写入未提交的 Segment 即可查询。Segment 构建过程通过以下方式为后续查询加速转换为列式格式建立位图索引压缩String 列做字典编码并最小化 ID 存储位图索引做位图压缩所有列做类型感知压缩Segment 定期被提交并发布到 Deep Storage变为不可变并从 MiddleManager 移交到 Historical 服务同时关于该 Segment 的元数据记录自描述的元数据包含 schema、大小、在 Deep Storage 中的位置等被写入元数据存储Coordinator 据此了解集群中有哪些数据可用。索引与移交Indexing and handoff索引indexing是创建新 Segment 的机制移交handoff是 Segment 被发布并由 Historical 服务的机制。摄入侧流程摄入任务启动并构建新 Segment必须先确定 Segment 标识符追加型任务如 Kafka 任务、append 模式的 index 任务通过 Overlord 的 allocate API 为已有 Segment 集合追加新分区覆盖型任务如 Hadoop 任务、非 append 模式的 index 任务则锁定时间区间并创建新版本号与新 Segment 集合。若为实时任务如 Kafka 任务此时 Segment 立即可查询可用但未发布。任务读完后将 Segment 推送到 Deep Storage并通过向元数据存储写入记录完成发布。实时任务为确保数据持续可查会等待 Historical 加载该 Segment 后再退出非实时任务则立即退出。Coordinator / Historical 侧流程Coordinator 周期性默认每 1 分钟轮询元数据存储发现新发布的 Segment。发现已发布、标记使用但尚不可用的 Segment 后选择一个 Historical 并指示其加载。Historical 加载 Segment 并开始提供服务。若摄入任务正在等待移交此刻退出。Segment 标识符Segment 采用四部分标识符Datasource 名称时间区间时间块对应区间即摄入时指定的segmentGranularity版本号通常为 Segment 集合开始创建时的 ISO8601 时间戳分区号datasourceintervalversion 内唯一的整数未必连续示例datasourceclarity-cloud0时间块2018-05-21T16:00:00.000Z/2018-05-21T17:00:00.000Z版本2018-05-21T15:56:09.909Z分区号 1clarity-cloud0_2018-05-21T16:00:00.000Z_2018-05-21T17:00:00.000Z_2018-05-21T15:56:09.909Z_1分区号为 0chunk 内第一个分区的 Segment 会省略分区号clarity-cloud0_2018-05-21T16:00:00.000Z_2018-05-21T17:00:00.000Z_2018-05-21T15:56:09.909ZSegment 版本与 MVCC版本号提供了一种多版本并发控制MVCC机制以支持批量覆盖写入。纯追加场景下每个时间块只有一个版本而覆盖写入时Druid 会创建一组具有相同 datasource、相同时间区间但更高版本号的新 Segment——这对系统其余部分是一个信号旧版本应从集群移除由新版本替换。切换对用户几乎是瞬时完成的Druid 先加载新数据暂不允许查询待全部加载完成后将所有新查询切换到新 Segment几分钟后再丢弃旧 Segment。Segment 生命周期与可用性状态每个 Segment 的生命周期涉及三个区域元数据存储Segment 构建完成后其元数据通常只有几 KB 的小 JSON写入元数据存储该动作称为发布publishing。元数据记录中的布尔标志used控制 Segment 是否应可查询。Deep StorageSegment 构建完成后即被推送至 Deep Storage紧接在发布元数据之前。查询可用性Segment 可在某些数据服务器上被查询——实时任务上、直接从 Deep Storage、或 Historical 服务上。可以通过 Druid SQL 的sys.segments表 检查活跃 Segment 的状态标志is_publishedSegment 元数据已发布到元数据存储且used为 true。is_availableSegment 当前可查询在实时任务或 Historical 上。is_realtimeSegment 仅在实时任务上可用。实时摄入的 datasource 通常先为true发布移交后变为false。is_overshadowedSegment 已发布used为 true且被其他已发布 Segment 完全遮蔽。通常为瞬态此后used会被自动置为 false。可用性与一致性Druid 在架构上分离了摄入与查询。摄入侧主要摄入方式均为拉取式pull-based且提供事务性保证全有或全无地发布受监管的 seekable-stream 摄入Kafka、Kinesis流偏移与 Segment 元数据在同一事务中提交到元数据存储保证精确一次exactly-once发布未发布数据可回滚失败后从最后提交的偏移处继续摄入。Hadoop 批量摄入每个任务在单个事务中发布全部 Segment 元数据。Native 批量摄入并行模式下子任务完成后由 supervisor 任务在单事务中发布全部元数据简单单任务模式下由单个任务在完成后单事务发布。部分摄入方式还提供幂等性保证Kafka/Kinesis 因偏移与元数据同步更新而幂等Hadoop 批量摄入在输入源不是正在写入的 datasource时幂等Native 批量摄入在appendToExisting为 false 且输入源不是同一 datasource 时幂等。查询侧Broker 负责保证单次查询涉及一致的 Segment 集合查询开始时根据当前可用情况选择恰当的 Segment 版本集合并通过**原子替换atomic replacement**让用户视角的查询从旧数据瞬时切换到新数据无一致性或性能影响。原子替换逐时间块进行其基础是 core set 概念时间块被覆盖时创建更高版本号的新 core set且必须全部可用后 Broker 才会用其替换旧集合每个时间块每个版本只有一个 core set每个时间块同时只使用一个版本。此外实验性的 segment 锁定模式将任务 context 中的forceTimeChunkLock设为 false允许在同一版本下创建多个原子更新组实现子集原子替换与替换追加并行。若多个 Historical 同时离线超过副本因子查询会只包含仍可用的 Segment并在后台尽快在其他 Historical 上重载。Segment 文件结构字典、值列表与位图Segment 文件是列式的每列的数据布局在独立的数据结构中。通过单独存储每列Druid 只扫描查询实际需要的列从而降低查询延迟。列分为三种基本类型时间戳timestamp、维度dimension与指标metric时间戳与指标列是使用 LZ4 压缩的整数或浮点值数组。查询确定要选择哪些行后解压这些列、取出相关行并应用聚合算子若查询不需要某列Druid 直接跳过该列数据。维度列支持过滤与 group-by 操作因此每个维度需要三种数据结构字典Dictionary将值始终按字符串处理映射为整数 ID使列表与位图值能够紧凑表示。值列表List使用字典编码的列值供 GroupBy 与 TopN 查询使用仅基于过滤器聚合指标的查询可以不用访问值列表。位图Bitmap列中每个不同值对应一个位图指示哪些行包含该值。位图便于快速做 AND/OR 运算因此能实现快速过滤即倒排索引inverted index。以示例数据中的 Page 列为例1: Dictionary { Justin Bieber: 0, Ke$ha: 1 } 2: List of column data [0, 0, 1, 1] 3: Bitmaps valueJustin Bieber: [1,1,0,0] valueKe$ha: [0,0,1,1]注意位图与字典、列表的不同字典与列表随数据量线性增长而位图区的大小是数据量 × 列基数的乘积——每个不同的列值都有一个位图。列数据列表中的每一行只有一个位图具有非零项这意味着高基数列的位图极其稀疏、因而高度可压缩。Druid 利用这一点采用专为位图设计的压缩算法如 Roaring 位图压缩。Null 值处理默认情况下Druid 以 SQL 兼容的 null 处理模式存储 SegmentString 列始终将 null 存为 ID 0字典首位并在位图索引中关联条目用于过滤 null数值列也存储 null 值位图索引用于聚合的 null 检查与过滤 null 匹配。Druid 还保留了一种使用默认值代替 null 的传统模式Druid 28.0.0 之前的默认行为现已弃用并将在未来版本移除可通过设置druid.generic.useDefaultValueForNulltrue启用。传统模式下摄入时创建的 Segment 具有如下特征String 列无法区分与null二者等价数值列无法表示 null 行而是存储0。传统模式下数值列没有 null 值位图Segment 尺寸可能略小某些涉及数值列的查询因无需检查 null 位图而略有性能提升。不同 schema 的 Segment 共存同一 datasource 的不同 Segment 可以有不同的 schema。若某 String 列维度存在于一个 Segment 而不存在于另一个涉及两者的查询仍然可用默认模式下缺少该维度的 Segment 表现得像该维度只含空白值SQL 兼容模式下表现得像只含 null 值。同理若某数值列指标缺失对该指标聚合时表现得像指标不存在。列格式与多值列每列存储为两部分Jackson 序列化的ColumnDescriptor以及该列的二进制数据。ColumnDescriptor是 Druid 内部 ColumnDescriptor 类的 Jackson 序列化实例借助 Jackson 的多态反序列化可以以最小代码影响引入新的序列化方式它包含列的一些元数据如类型、是否多值以及一组可反序列化其余二进制的序列化/反序列化逻辑。多值列multi-value column允许单行在某一列包含多个字符串可视为字符串数组。此时数据结构发生变化某行的值列表项可能是一个数组如[0,1]且一个包含 n 个值的行在位图中对应 n 个非零项。压缩Druid 默认对 String、long、float、double 列的值块使用LZ4压缩对 String 列与数值 null 的位图使用Roaring压缩。官方建议除非针对自身数据与查询模式做过实验、且证据表明非默认选项更优否则使用默认值。Druid 也支持 Concise 位图压缩对 String 列位图而言Roaring 与 Concise 的差异在高基数列上最明显——Roaring 在匹配大量值的过滤上明显更快但某些情况下 Concise 因 Roaring 格式开销而占用更小匹配大量值时仍更慢。压缩在Segment 级别配置而非逐列参见 IndexSpec。Segment 组件一个 Segment 包含以下文件文件说明version.bin4 字节整数表示当前 Segment 格式版本。例如 v9 Segment 的值为 0x0, 0x0, 0x0, 0x9meta.smoosh关于其他 smoosh 文件内容的元数据文件名与偏移量XXXXX.smoosh拼接的二进制数据。文件合并减少了访问数据时必须打开的文件描述符数量单文件不超过 2 GB以保持在 Java 内存映射ByteBuffer的限制内。其中包含每列各自的文件含指向 Segment 时间戳的__time列以及存放附加 Segment 元数据的index.drd文件在代码层面Segment 具有内部格式版本当前为v9。Segment 大小建议为了在高查询负载下运行良好Segment 文件大小应保持在推荐区间300–700 MB。若 Segment 文件超出该范围可考虑调整 Segment 时间区间的粒度segmentGranularity或对数据做分区并/或调整partitionsSpec中的targetRowsPerSegment该参数的合理起点约为 500 万行。若同一时间区间存在多个 Segment来自不同摄入作业可使用 Compaction 将其合并为每个时间区间一个 Segment 以获得最佳性能更多分区指导参见 Batch ingestion 文档的 Partitioning specification。分片Sharding与查询完整性同一时间区间与 datasource 可以有多个 Segment它们构成该时间区间的一个block。根据分片所用的shardSpec类型Druid 查询可能要求 block 完整才能完成。例如若一个 block 由以下三个 Segment 组成sampleData_2011-01-01T02:00:00:00Z_2011-01-01T03:00:00:00Z_v1_0 sampleData_2011-01-01T02:00:00:00Z_2011-01-01T03:00:00:00Z_v1_1 sampleData_2011-01-01T02:00:00:00Z_2011-01-01T03:00:00:00Z_v1_2则三个 Segment 必须全部加载后针对2011-01-01T02:00:00:00Z_2011-01-01T03:00:00:00Z区间的查询才能完成。Linear shard spec 是例外它不强制完整性即使分片未完全加载查询也可以完成。例如实时摄入用 linear shard spec 创建了三个 Segment若只加载了两个查询会返回这两个 Segment 的结果。Segment 更新与替换的影响Druid 用版本化实现 MVCC注意与 Segment 格式版本相区分。跨多个 Segment 区间的更新只在各自区间内原子而非整个更新原子。例如foo_2015-01-01/2015-01-02_v1_0 foo_2015-01-02/2015-01-03_v1_1 foo_2015-01-03/2015-01-04_v1_2v2Segment 构建完成后即加载进集群并在与v1重叠的时间段内替换v1。在v2完全加载前集群可能混合存在v1与v2foo_2015-01-01/2015-01-02_v1_0 foo_2015-01-02/2015-01-03_v2_1 foo_2015-01-03/2015-01-04_v1_2此时查询可能命中v1与v2的混合集合。若以新 schema 重新索引数据Druid 会为新 Segment 分配新版本 ID。核心服务的工作原理CoordinatorSegment 管理与均衡Coordinator 主要负责 Segment 管理与分发向 Historical 下发加载/卸载指令、加载新 Segment、淘汰过期 Segment、确保 Segment 按配置的副本数复制到多个 Historical 节点并通过在节点间移动 Segment 保持负载均衡参见 Coordinator 文档。运行周期Coordinator 周期性运行间隔可配置每次运行先评估集群当前状态再决定行动。它连接 ZooKeeper 获取集群信息连接数据库获取 used Segment 信息与加载规则。分配策略在分配未指派 Segment 前会按容量对每个 tier 的 Historical 排序容量最小的服务器优先级最高未指派 Segment 总被分配给容量最小的服务以维持均衡。Coordinator 不直接与 Historical 通信而是在 Historical 的 load queue 路径下创建临时信息Historical 看到请求后加载 Segment 并开始服务。清理遮蔽 Segment每次运行对比数据库中的 used Segment 与集群实际服务的 Segment向 Historical 发送卸载请求被遮蔽版本过旧、数据已被新 Segment 替换的 Segment 被标记为 unused在下一次运行中从 Historical 卸载。此外还会清理满足特定条件以 -INF 或 INF 结尾的 tombstone 段、不与任何被遮蔽 Segment 重叠、0 个 core 分区的非遮蔽永恒 tombstone Segment。Segment 可用性若某 Historical 重启或不可用Coordinator 会将其服务的所有 Segment 视为已丢弃每个被丢弃的 Segment 会有一个带生命周期lifetime的过渡数据结构在该生命周期内 Coordinator 不会重新指派它从而避免短暂离线的服务数据被集群重新分布。均衡负载每次运行计算每个 Historical 服务的 Segment 总大小对每个 tier 找出利用率最高与最低的服务计算两者利用率百分比差若超过阈值则将若干 Segment 从最高利用率服务移动到最低利用率服务每次运行的移动数量可配置上限被移动 Segment 随机选择且仅当移动后高低差确实降低时才移动。FAQ 要点客户端从不直接联系 CoordinatorHistorical 与 Broker 也完全不感知 Coordinator分别通过 ZooKeeper 路径与元数据通信。Coordinator 是否先于其他服务启动无关紧要——即使所有 Coordinator 全部宕机集群仍可继续工作只是数据拓扑不再变化。Overlord任务协调与黑名单Overlord 负责接受任务、协调任务分发、围绕任务创建锁并向调用方返回状态。它有两种运行模式默认 locallocal 模式Overlord 同时负责创建执行任务的 Peon因此必须同时提供全部 MiddleManager 与 Peon 配置适合简单工作流。remote 模式Overlord 与 MiddleManager 作为独立服务运行可部署在不同服务器若打算将 indexing service 作为全部 Druid 索引的单一入口官方推荐此模式。黑名单机制若某 MiddleManager 的任务失败数超过阈值Overlord 会将其列入黑名单被黑名单的 MiddleManager 不超过 20%并会周期性移出黑名单。相关配置druid.indexer.runner.maxRetriesBeforeBlacklist druid.indexer.runner.workerBlackListBackoffTime druid.indexer.runner.workerBlackListCleanupPeriod druid.indexer.runner.maxPercentageBlacklistWorkers自动扩缩容若启用 autoscaling任务在 pending 状态过久时可新增 MiddleManager一段时间内未运行任何任务的 MiddleManager 可被终止。Broker查询路由与缓存Broker 在分布式集群中路由查询它解析 ZooKeeper 中发布的 Segment 分布元数据据此路由查询并合并各服务的返回结果参见 Broker 文档。路由原理大多数查询包含表示时间范围的 interval 对象Segment 也按时间区间组织并分布在集群中。Broker 首先基于 ZooKeeper 信息构建世界视图——为每个 datasource 建立 Segment 及其服务者的时间线timeline收到针对某 datasource 与区间查询时在时间线中查找该区间对应的服务并转发查询。ZooKeeper 维护 Historical 与流式摄入 Peon 及其服务的 Segment 信息。缓存Broker 采用 LRU 失效策略的缓存按 Segment 粒度缓存结果。缓存可本地化也可用 memcached 等外部分布式缓存共享。每次收到查询Broker 先映射到 Segment 集合命中缓存的 Segment 结果直接取出未命中的转发给 Historical返回后写入缓存。实时 Segment 从不缓存——实时数据持续变化缓存结果不可靠。HistoricalSegment 存储与内存映射Historical 负责历史数据含已在系统中提交的流式数据的存储与查询。它在本地磁盘segment cache缓存 Segment并从该缓存与内存缓存提供查询。加载流程Historical 从 Deep Storage 拉取 Segment 到本地 segment cache位置与大小由druid.segmentCache.locations配置。它不与其他 Historical 或 Coordinator 直接通信Coordinator 在 ZooKeeper 的 load queue 路径创建临时条目Historical 监听该路径发现新条目后检查自身缓存若无则从 ZooKeeper 获取 Segment 元数据Deep Storage 位置、解压处理方式等拉取处理后通过 ZooKeeper 的 served segments 路径宣告 Segment 可查Broker 据此获知可用数据。启动时 Historical 会扫描本地缓存并立即宣告其中 Segment以尽快提供查询。内存映射缓存segment cache 使用内存映射mmap从操作系统底层消耗内存使 Historical 可将部分 Segment 文件驻留内存以提升查询性能受 JVM 堆、堆外/直接内存缓冲及其他服务影响。查询时若所需部分在内存映射缓存page cache中则直接复用否则从磁盘读取可能挤掉其他 Segment 数据。空闲系统内存越接近druid.server.maxSizeSegment 数据越可能驻留内存、查询越快。该缓存独立于 查询级缓存。服务启动命令各服务的启动入口统一为org.apache.druid.cli.Main server service例如org.apache.druid.cli.Main server coordinator org.apache.druid.cli.Main server broker org.apache.druid.cli.Main server historical org.apache.druid.cli.Main server middleManager如何进一步学习若想继续深入官方文档建议按以下路径展开通过 Quickstart 快速开始 亲手启动并体验 Druid阅读 存储组件 与 Segments 了解数据存储细节阅读 查询处理 获取 Druid 查询处理流程的高层概览针对各服务的配置与调优参见 Coordinator 配置、Overlord 配置、Broker 配置、Historical 配置、MiddleManager 与 Peons 配置以及 基础集群调优通过 服务状态 API 参考 查看各服务的 HTTP 端点在数据建模前阅读 schema design。总结来说Druid 的价值在于把列式存储、位图索引、时间分区、并行处理与预聚合这五件事在分布式架构下系统地组合起来它牺牲了主键级低延迟更新与大表 join两类能力换来了事件型数据上的高摄入吞吐、秒级聚合查询与近乎无停机的运维体验。理解了服务分工、Deep Storage/元数据存储/ZooKeeper 三大外部依赖以及 Segment 从创建、发布到移交的完整生命周期就掌握了 Druid 设计与排障的两条主线。赞分享数据库OLAP大数据后端【免费下载链接】druidApache Druid: a high performance real-time analytics database.项目地址https://gitcode.com/gh_mirrors/druid6/druid点击查看免费下载相关推荐Apache Druid设计模式解析如何构建高效的实时分析数据模型Apache Druid设计模式解析如何构建高效的实时分析数据模型 Apache Druid德鲁伊数据库作为高性能实时分析数据库其核心优势在于将实时数据数据库数据分析OLAP大数据实时分析数据仓库后端5分钟掌握Zotero Style插件让文献管理变得轻松高效的艺术5分钟掌握Zotero Style插件让文献管理变得轻松高效的艺术 在数字化科研时代文献管理是每个研究者都会面临的挑战。你是否曾为海量文献的分类整理而头疼桌面应用知识管理科研Apache Druid 全面指南实时分析数据库的架构设计、快速上手与源码构建Apache Druid 全面指南实时分析数据库的架构设计、快速上手与源码构建 Apache Druid 是一个面向实时分析的分布式列式数据库其核心价值在于数据库OLAP大数据后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考