阿里云千亿规模实时日志分析的架构设计和实践

简介: 本文为阿里云SLS 执少 在《DataFunTalk技术交流会:阿里云实时查询分析专场》分享时的议题内容(文字版本)。首先,阿里云日志服务SLS是一个什么样的产品和服务呢? 我们用一句话来概括的话,那就是我们是一个云上的、一站式的、可观测日志服务平台。 首先呢,我们提供了强大的日志数据采集能力,支持...

本文为阿里云SLS 执少 在《DataFunTalk技术交流会:阿里云实时查询分析专场》分享时的议题内容(文字版本)。

1. SLS:云上一站式可观测日志服务平台

image.png

首先,阿里云日志服务SLS是一个什么样的产品和服务呢? 我们用一句话来概括的话,那就是我们是一个云上的、一站式的、可观测日志服务平台。 首先呢,我们提供了强大的日志数据采集能力,支持丰富多样的采集形式(包括Agent无感采集、多语言SDK、主流日志框架插件、WebTracking等),同时支持多种日志数据形态(包括Log、Metric、Trace、Event等),本次专题我们也会有相关的同学,会和大家去分享我们的日志采集利器ilogtail相关的设计和实现,这块目前我们也已经开源(链接),除此之外呢,我们还提供了相关的数据加工、消费投递等等一些管道化的能力。 那么数据采集上来之后呢,我们进行统一存储,同时我们提供冷热智能分层的存储能力,可以帮助用户尽可能的去节省成本;然后在这个基础之上呢,我们提供了日志相关的数据处理和分析的能力。那么,这两块构成了我们产品和服务的一个核心的基础能力。 在这个基础能力之上呢,我们还实现了丰富的工具(比如AIOps智能巡检以及智能告警等),以及不同维度和场景的应用(包括云产品可观测CloudLens、ITOps开发和运维监控、安全审计、成本分析等等),最终,将日志数据以不同的维度和视角,服务于我们的最终用户(包括开发人员、运维人员、产品运营,以及安全审计等等不同的角色)。 以上是整个SLS产品的一个业务版图,那么今天呢,我们主要是想和大家分享我们在存储和分析这块基础能力上面的建设,主要侧重去分享我们在日志分析系统中的架构设计,以及我们在面临一些核心问题时的思考以及解决思路,希望与大家一起交流和学习。

2. 日志分析服务的业务覆盖和服务能力

image.png

上图是我们的日志分析服务整体的业务覆盖和服务能力。我们主要还是面向日志这个场景去做相关的数据分析。那么大家应该都了解,日志数据,形态是丰富多变的,包括无结构、半结构以及结构化的数据。我们通过KV键值对的形式,统一收口存储在我们存储引擎中,同时通过SQL分析引擎,对上层提供我们的数据分析服务。 而在具体的业务层面,我们有很多业务基于日志数据分析去做实时监控、实时大屏,这一类业务,用户请求的刷新率非常高,无论是请求的频次还是并发都很高,系统会面临高并发的查询压力。其次,我们还有很多用户是做实时告警、链路分析、交互式的查询分析,以及AI异常检测等这类的业务,那这一块用户对于数据分析的时效性要求非常高,所以我们的查询分析能力要做到秒级实时。第三块呢,我们还有一些可视化、报表统计,以及ScheduleSQL等这类的业务,那这些业务主要是跑批,那么它的特点就是数据量可能非常大,所以我们会面临超大规模数据量的计算和分析。 在整体业务覆盖上,我们除了在阿里云上提供对外的云服务以外,在整个阿里集团内部,也被众多的BU所使用,同时也是经历了多年双十一的技术挑战。 就SQL分析引擎的整体能力而言,我们现在每天有数十亿的查询次数,千万亿级别的行扫描规模,以及数十PB级别的数据吞吐。同时,我们查询的平均延时控制在三百毫秒以内,在双十一这种业务高峰时刻,我们的峰值并发能够达到7.2w,峰值QPS能达到数十万这样一个压力。 所以整体来说,我们在面对上述这样的一个业务场景和需求时,我们所面临的最核心的问题,我想主要是包括下面这四个方面。

3. 4个核心问题

image.png

第一,区别于传统的离线OLAP数仓系统,我们是一个在线的数据分析服务。所以对于查询的实时性、低延时这一块的要求是非常高的。业务要求我们做到秒级的查询延时,同时,日志数据一旦采集和存储上来,要求可见即可得,可得即可算。 第二,主要是超大的数据规模。我们在线上实际业务中,单次查询就可能达到TB级别的数据量、千亿级别的数据行规模。同时,行扫描可能从几条到百万到千亿级别不等,而且这个规模是根据用户需求弹性多变的。所以,如何解决超大数据规模且规模多变是我们系统架构上面临的一个核心的挑战。 第三,由于我们是一个在线的面向云上多租户的服务,所以系统会经受高并发的查询压力。比如说像我们在双十一的业务高峰时刻,系统能够达到7.2w的峰值并发。同时,单点上可以有上千的并发查询,上万的并发任务。高并发对于系统的核心节点上的负载压力巨大,是我们面临的又一个重要挑战。 最后,也是我们最核心的一个挑战:如何去保障服务的高可用,如何做好租户之间的隔离。由于多租户其实是共享云上资源的,不可避免会有各种各样的热点资源的争用,我们需要从系统架构层面,去思考如何做好服务的治理以及压力的防控。 所以今天,我主要就是想针对上面这四个核心问题,和大家交流和分享一下我们在系统架构上面的一些具体的设计和思考。

4. 查询分析范式

image.png

首先,需要介绍一下SLS日志查询分析的范式。主要包括三个关键因素: 首先是查询语句,类似于搜索引擎(如ES),用户根据指定的过滤条件(关键词、数值范围、模糊查询等),将需要的特征日志数据检索出来。 其次是分析语句,遵从标准SQL92语法,针对检索出来的日志数据可以进行多维度灵活的统计和分析。 第三是时间范围,我们知道日志数据的分析,离不开时间这个维度。所以我们对任何的查询和分析,都会指定一个时间范围。 这三个要素,就构成了我们SLS查询和分析的范式。通过前面的查询,我们可以检索出需要的特征数据,然后再通过后面的分析语句,我们可以进行各种灵活的数据分析和计算。

5. 实时低延迟的设计关键

image.png

以上面的查询分析为例,上图是一个类似Nginx访问接入的日志数据形态。那么如上面这个查询分析,我们其实是过滤出来符合前面查询条件的这些特征数据,同时,我们要去分析status的整体分布,所以从日志的数据里面将这个status的数据进行一个聚合计算,最后得到我们的结果。 那么,结合上面这样示例,我们思考,怎么实现查询分析的实时、低延迟呢? 这里我们考虑到日志数据有其自身的特点:

  • 时间是日志数据的天然属性。

  • 日志数据的分析,往往都是面向特征的。比如上面的例子,其实是要检索出cn-shanghai并且host是指定域名的这类日志数据。

  • 数据分析的维度,往往是局部的,一般是日志行中的特定字段,所以其实并不需要将整行的日志数据全部加载上来。

所以,基于日志数据的这些特点呢,我认为实时、低延迟的关键在于:如何快速地定位出数据,并且高效地将数据加载上来,然后进行高速地计算。

6. 索引和列存

image.png

我们用到的第一个技术,是索引和列存。 首先介绍下我们的存储模型,分成三个维度: 首先,最外面是Project级别,在此层面实现了用户级别的隔离。 其实,Project内可以由多个日志库组成,我们叫LogStore,在此层面实现了生命周期的管理。 然后,在LogStore内,我们以Shard为粒度将数据进行分片,日志数据根据hashkey路由到指定key-range范围的Shard上。 最后,在单个Shard内,日志数据将以队列的方式去追加写,通过LSM写入模型最终存储到我们分布式存储中。

前面提到日志数据的一个天然属性是时间。所以在单个Shard内,数据其实都是按照时间进行分布的。 所以这里第一个核心要素,我们实现了基于时间的检索模型。让我们来回顾一下前面的查询分析范式。我们通过from_time和to_time,可以快速定位到我们在这一段时间范围内的一个数据分布。 其次,我们要根据查询语句检索出特征数据,为了实现近似O(1)的高效检索,这里我们使用反向索引技术。通过将查询条件下推到存储,在存储层面通过反向索引,快速定位和检索出符合查询条件的相关数据。 同时,前面提到,日志分析的数据往往是局部的,其实并不需要将整行日志的数据全部读取上来,所以这里还实现了列存,将需要的指定列数据加载上来进行分析即可。 所以整体上,除了LSM追加写以外,我们还会去进行异步的索引和列存的构建,最终存储到盘古分布式存储上面。 从本质上来说,这里是以空间来换时间。通过减少IO次数来提升检索效率,通过列存来减少无效数据的扫描。以此来提升数据读取和计算的效率。 当然,仅仅索引和列存,还远不够。

7. 数据本地性

image.png

我们来看一下,在整个计算和存储架构上,其实整体来说都是分布式的架构。 日志数据写入,通过存储节点(DataNode),以LSM模型最终写入到分布式存储。一部分热数据在存储节点的memory中,另一部分已经归档下去的数据,其实就存到了分布式存储上面。 日志数据分析,通过计算节点(Worker),从存储层将数据加载上来,完成相关的计算和分析。 那这里就有个很有意思的点:究竟选择哪些计算节点来参与本次计算任务呢?那比如选择上图左3Worker节点,而数据呢,是在左2DataNode节点上,那这样的话,将会涉及跨网络的通信以及数据传输。 所以,这里我们用到的第二个技术:数据本地性。尽可能地将计算节点和存储节点放在同一个机器上面,充分利用数据本地性优势。同时。我们还面临另一个问题:计算服务和存储服务是不一样的进程,涉及到跨进程间的数据交换,这里通过domain socket进行控制面的通信,然后通过share memory进行数据交换。 通过数据本地性原理,我们充分利用了LSM中的本地MemCache,分布式存储节点本地PageCache,以此来提升IO效率。同时通过数据本地性的架构设计,尽量避免跨网络通信和数据交换的网络开销。

8. 多级缓存

image.png

要实现秒级实时查询,仅上面两点技术,仍然不够。 这里想到计算机科学领域大佬Butler Lampson的一句话:“任何计算机领域的问题,都可以通过另外一层抽象得以解决。” 我们也是基于这样的思想,在系统中架设了多级缓存:

  • 在数据层, 通过数据本地性,充分利用Shard写入节点上的MemCache,分布式存储节点上的PageCache等缓存能力。

  • 在索引层, 通过将倒排、数值、字典等索引块的信息进行缓存,来减少索引数据的反复加载以及编解码的开销。

  • 在元数据层,我们在分析引擎层面把元数据进行缓存,将相关的索引字段信息、Shard分片信息、数据分布等进行了相关的缓存。通过这些缓存信息,可以有效加速SQL语义解析以及物理执行计划的过程。

  • 在SQL语义解析层, 针对逻辑执行计划是相同的SQL,我们对SQL Plan也进行了缓存,来减少分析引擎核心节点coordinator上面的重复SQL解析的负载开销。

  • 在调度层,我们其实有一些数据节点,之前它已经把数据加载过,那么可能本地还有相关的缓存数据,所以在调度层面我们把数据分片以及任务执行的调度历史进行了相关缓存。通过亲和力调度,可以将相同的数据分片或任务调度到原来节点上,充分利用节点上的数据本地性,以及下层缓存所带来的收益。

  • 在计算层,我们实现了一个PartialAggOperator算子,对相同数据且执行相同算子的部分中间计算结果进行缓存,可以减少反复的数据加载和计算开销。

  • 在最终结果层,由于日志数据的显著特点(没有update),我们可以把完全相同(查询|分析|时间完全一样)的查询的最终计算结果进行缓存,减少重复查询的计算压力。

整体来说,通过这样一些技术手段,我们可以实现一个较低延时(秒级)的查询和分析的能力。

9. 超大数据规模

image.png

第二个核心问题,是我们会面临超大数据规模的挑战。我们来看这样的一个情况:随着用户数据的不断写入增长,单个Shard上的数据可能会越来越多,我们在线上实际业务中也遇到单次查询可以有TB级的数据量和千亿级的行规模。那么按照我们之前的数据本地性的设计呢,如果所有的数据都要从这个存储节点上面进行加载并计算,显然能力上面是不够的。所以这里我们通过水平扩展、存算分离的思路,将落到分布式存储上面的这些Block数据块,水平散列到更多的存储节点上面,同时分配给更多的计算节点,最终交由上层进行计算汇聚。这样一来,在存储层面,我们对于IO的压力实现了水平的散列,同时在计算层面,由于MAP阶段有更多的计算节点参与计算,提升了计算的并行度,包括内存/CPU等资源得以水平扩展。 但是存算分离也并非万能膏药,架构的调整,也给我们带来了新的挑战。是什么呢? 首先是数据本地性丢失,我们前面为什么要去做数据本地性,其实就是利用本地缓存、减少网络开销。而现在存储计算分离架构之上的话,我们会丢失这种数据的本地性。因为可能分配新的存储节点加载数据,那它可能之前没有这部分数据,需要重新去加载,因此数据本地的丢失会导致一定的延时增加。 另外一点是负载热点,虽然我们进行了水平扩展,但是一旦发生数据倾斜或者一些数据热点,就会出现负载不均或者局部热点的情况。 我们通过下面的方法应对上述挑战:

  • 第一个就是做了相关的亲和力调度。对于已经分配过某些调度任务的节点,基于亲和力调度,下次尽量还分配到这个节点,继续利用这个节点上的数据本地性。

  • 第二个就是对负载进行实时感知,通过多目标的调度策略来实现系统整体更均衡的调度。

这一块整体而言,我们其实是在速度和规模之间进行一个权衡。通过水平扩展、存算分离的架构改造,我们将IO、内存以及CPU这些资源实现横向扩展。同时计算和存储层面架构上的分离,使得我们不需要将计算和存储绑定到同一个节点,以此提升了存储和计算上面的并行度。但是我们也遇到了新的挑战。通过亲和力调度,以及负载均衡,来应对这样的一些新的挑战。

10. 高并发——关键节点性能提升

image.png

第三个核心问题,我们是一个在线的多租户的数据分析服务,系统会经受高并发的压力挑战。 整体来说,我们的分析引擎架构其实是非常简单的。前面有一个核心节点(叫Coordinator),后面是一些工作节点(叫Worker)节点。由Coordinator节点来总控和协调整体的分析计算过程,各个Worker节点负责具体计算任务的执行,最终仍然由Coordinator节点汇总计算结果并返回给客户端。 随着用户并发查询请求量的不断增长,我们看到Coordinator作为核心节点,其上承受的负载压力其实是比较重的。一方面它要负责承接用户的并发请求,其次还要对用户SQL进行解析,同时还要负责查询整体的任务调度。我们在线上实际采样发现,在SQL解析阶段,其CPU开销是非常重的。这里包括词法分析、语法分析,还有Plan生成以及优化改写,尤其是后两步CPU开销尤为繁重。所以整个SQL解析阶段,是Coordinator节点负载的一个关键开销所在。 同时,我们发现我们在业务上面有很大一部分业务来源,是来自仪表盘、智能告警以及ScheduleSQL这些业务,那么这些业务有一个共同的特点,他们的查询SQL相对是固化不变的,基本上只变动时间,因此它们生成的SQL逻辑执行计划其实也是固定不变的。基于此,我们实现了PlanCache,缓存相同SQL的Plan,避免了重复SQL解析过程,减少核心节点Coordinator上面的关键负载开销。 整体效果来说,PlanCache缓存命中率达到75%。CPU消耗降低了20~30%。同时JVM GC压力和次数有了明显的降低。

11. 高并发——网络优化

image.png

高并发的另一个挑战,来自于网络。由于分析引擎是一个扁平化的系统架构,Coordinator节点作为系统的协调节点,要负责整体的任务调度,因此要和整个集群中所有Worker节点进行通信。随着整个集群中的节点数规模越来越大,并发查询数越来越多,单个Coordinator节点面临了网络连接数爆炸式增长。每秒有十万以上的并发任务请求,由于原来的通信模式是HTTP,单个Coordinator和Worker之间的通信,其实Coordinator是作为HTTP客户端,因此受连接端口数的限制。一旦端口耗尽,那么整体服务的稳定性都会受到影响。 我们的应对之道,是复用网络信道,将HTTP短连通信模式改造成RPC长连模式。这样做有几方面考虑:

  • 一方面,可以复用信道,减少反复建联的开销。

  • 另一方面,可以有效控制连接数的规模,在集群内将连接数做到恒定可控。

12. 租户隔离

image.png

第四个核心问题,是我们要去解决系统的高可用和租户的隔离。这块的挑战其实是非常大非常多的。同时也是我们云服务不得不面临并解决的问题。 那么我们云上多租户的一个核心挑战在于什么地方呢?就是在于我们在共享资源的前提下,怎么去做好租户之间的隔离(包括资源、性能、稳定性),怎么去做好服务的稳定性。 从本质上来讲,我们和Linux操作系统多租户的分时复用原理是一致的。通过对资源(存储、IO、网络、内存、CPU等)的分时复用实现云上共享资源在多租户之间的隔离使用。关键在于怎么做好隔离?做好资源的使用防护,避免某个用户的滥用、误用,避免因此给系统造成的不稳定。 所以,这里我们实现了分层的限流防护机制:

  • 首先,在接入层,把用户的请求下发下来,我们实现了分布式的用户查询队列,基于一致性Hash将同一个用户的请求落到相同的Coordinator节点上,然后进行统一的资源管控和限流,包括限制用户的并发查询数。

  • 其次,在执行层面,对内存使用以及查询执行时间进行限定,以此来控制用户对资源的有限使用,避免滥用我们的底层资源。

  • 然后,在具体底层执行层面,我们对于任意Task时间片进行了相关的限定。这包括计算的任务,查询检索的任务,以及IO任务,都会有相应的执行时间片进行限定。以此实现更细粒度的分时复用。

  • 最后,在数据存储层面,我们还对数据的扫描量进行相关的限定,实现存储IO和网络带宽的有限复用。

通过这样的分层限流,我们做好系统之间的分层防护,同时也保障好各个用户之间的资源隔离。 同时这里我们还不得不面临一个问题:通过这些限制之后,用户分析的数据可能并没有加载完整,那么就会造成查询的不精确。我们知道系统层面有著名的CAP理论,其实我们面临类似的问题:我们需要在速度、规模以及稳定性上去做一个平衡。针对查询不精确这种问题,我们目前的解决方案或者思路是,我并不会直接返回查询失败,而是会返回这个查询已经计算完成的部分计算结果,并且标记这个查询是不精确的。然后,通过用户渐进式的查询来逼近最终精确的结果。为什么能做到这一点呢?因为我们前面所述的多级缓存,查询将复用已经计算完成的部分结果,并在底层以一种渐进方式将全量数据加载上来,从而最终实现精确计算的结果。这个是我们对于这个问题目前的一个权衡和解决的思路。 所以最终,我们通过分层保护和限流实现云上多租户的资源共享和隔离,同时在速度、规模以及稳定性上做了相关的权衡和取舍。

13. 高可用

image.png

另一个挑战在于系统稳定性,业务上的一些大查询(大内存、乱序大IO等),它们会给我们系统的计算或者存储节点造成持续的压力。局部节点会成为热点,长时间Full GC会影响到整个服务的可用性。 在这一块我们实现了精细化的调度和隔离。基于内存水位、CPU使用率、数据分片和任务数,以及节点GC压力情况等多种策略实现集群整体的压力调度,优先分配压力负载低的节点参与新的工作任务。同时,对于高压力节点,比如它长时间发生FullGC,我们会对相关节点进行自动隔离。将这个节点从集群中offload下来,相关的计算任务不会再打到该节点上面,服务可用性也不会受该节点影响,等该节点的压力分散开了,比如内存压力释放下来后,我们再可以将该隔离节点onload归还到集群当中,让它继续参与工作。这是我们在集群管理和调度上的基本设计思路。

14. 总结

image.png


让我们小结一下。

  • 通过索引和列存、数据本地性,还有缓存,我们重点去解决了查询低延时的核心诉求,目前基本实现了秒级查询能力,当前平均延时<300ms,P90延时<415ms,P95延时<760ms。

  • 通过水平扩展、存算分离的架构改造,我们可以做到超大数据规模且规模弹性多变的数据处理和分析能力。同时可以支持从几条,到千万,到千亿规模不等的日志数据的查询分析。对于超大数据规模,比如说两千亿行的一个简单聚合计算,可以在二十秒内完成。

  • 通过关键节点的性能提升,以及网络优化,我们解决了高并发的问题。支持云上海量用户在线并发查询的实时分析能力。同时也挺过了多年双十一大促业务高峰时刻的并发峰值挑战。目前来看,单点能够有上千并发查询、上万并发任务能力,业务高峰时刻能达到7.2w的并发峰值。

  • 通过分层限流、调度隔离这样的一些设计和策略,实现系统整体的高可用和租户之间的隔离。稳定支撑阿里集团数十个BU数千条业务线的日志分析需求,同时也基本满足阿里云1-5-10的稳定性要求。

最后,凡事有“道”、“术”之分,以上也只是我们从具体问题出发,和大家一起分享和交流我们看到的问题视角和解决思路(一些“术”),希望大家以“术”获“道”,如果能够在平时工作和场景中有所启发和获益,我们将非常开心。

作者介绍
目录