Druid SQL和Security在美团点评的实践

大自然的搬运工

导读长久以来,对SQL和权限的支持一直是Druid的软肋。虽然社区早在0.9和0.12版本就分别添加了对SQL和Security的支持,但根据我们了解,考虑到功能的成熟度和稳定性,真正把SQL和Security用起来的用户是比较少的。本次分享将介绍社区SQL和Security方案的原理,以及美团点评在落地这两个功能的过程中所遇到的问题、做出的改进、和最终取得的效果。下面开始今天的分享:

我今天的分享内容包括四部分。首先,和大家介绍一下美团对Druid的使用现状,以及我们在构建Druid平台的过程中遇到的挑战。第二部分,介绍Druid SQL的基本原理和使用方式,以及我们在使用Druid SQL的过程中遇到的问题和做的一些改进。第三部分,介绍Druid在数据安全上提供的支持,以及我们结合自身业务需求在Druid Security上的实践经验。最后,对今天的分享内容做一个总结。

一、Druid在美团的现状和挑战

1.1 Druid应用现状

美团从16年开始使用Druid,集群版本从0.8发展到现在的0.12版本。线上有两个Druid集群, 总共大概有70多个数据节点。

数据规模上,目前有500多张表,100TB的存储,最大的表每天从Kafka摄入的消息量在百亿级别。查询方面,每天的查询量有1700多万次,这里包括了一些程序定时发起的查询,比如风控场景中定时触发的多维查询。性能方面,不同的应用场景会有不同的要求,但整体上TP99响应时间在一秒内的表占了80%,这和我们对Druid的定位——秒级实时OLAP引擎是一致的。

1.2 Druid平台化挑战

把Druid作为一个服务提供给业务使用的过程中,我们主要遇到了易用性、安全性、稳定性三方面的挑战。

易用性:业务会关心Druid的学习和使用成本有多高,是否能很快接入。大家知道,Druid本身对数据写入和查询只提供了基于JSON的API接口,你需要去学习接口的使用方法,了解各种字段的含义,使用成本是很高的。这是我们希望通过平台化去解决的问题。

安全性:数据是很多业务的核心资产之一,业务非常关心Druid服务能否保障他们的数据安全。Druid较早的版本对安全的支持较弱,因此这一块也是我们去年重点建设的部分。

稳定性:一方面需要解决开源系统落地过程中出现的各种稳定性问题,另一方面,如何在查询逻辑不可控的情况下,在一个多租户的环境中定位和解决问题,也是很大的挑战。

二、Druid SQL的应用和改进

在Druid SQL出现之前,Druid查询通过基于JSON的DSL来表达(下图)。这种查询语言首先学习成本很高,用户需要知道Druid提供了哪些queryType,每种queryType需要传哪些参数,如何选择合适的queryType等。其次是使用成本高,应用需要实现JSON请求的生成逻辑和响应JSON的解析逻辑。

通过Druid SQL,你可以将上面的复杂JSON写成下面的标准SQL。SQL带来的便利是显而易见的,一方面对于程序员和数据分析师没有额外的学习成本,另一方面可以使用类似JDBC的标准接口,大大降低了门槛。

2.1 Druid SQL简介

下面我简单地介绍一下Druid SQL。

首先,Druid SQL是0.10版本新增的一个核心模块,由Druid社区提供持续的支持和优化,因此不管是稳定性还是完善性,都会比其他给Druid添加SQL方言的项目更好。

从原理上看,Druid SQL主要实现了从SQL到原生JSON查询语言的翻译层。由于只是做了一层语言的翻译,好处是Druid SQL对集群的稳定性和性能不会有很大影响,缺点是受限于原生JSON查询的能力,Druid SQL只实现了SQL功能的一个子集。

调用方式上,Druid SQL提供了HTTP和JDBC两种方式来满足不同应用的需求。最后表达力上,Druid SQL几乎能表达所有JSON查询能实现的逻辑,并且它能自动帮你选择最合适的queryType。

下面是三个Druid SQL的例子。

第一个例子是近似TopN查询。对于根据某个指标分析单个维度TopN值的需求,原生的JSON查询提供了一种近似TopN算法的实现。Druid SQL能够识别出这种模式,生成对应的近似TopN查询。

第二个例子是半连接。我们知道Druid是不支持灵活JOIN的,但业务经常会有这样的需求,就是以第一个查询的结果作为第二个查询的过滤条件,用SQL表达的话就是in subquery,或者半连接。Druid SQL对这种场景做了特殊支持,用户不需要在应用层发起多个查询,而是写成in subquery的形式就行了。Druid SQL会先执行子查询,将结果物化成外层查询过滤条件,然后再执行外层的查询。

最后是一个嵌套GroupBy的例子。Druid SQL能够识别出这种多层的GroupBy结构,生成对应的原生嵌套GroupBy JSON 。

2.2 Druid SQL架构

下面介绍Druid SQL的整体架构。

Druid SQL是在查询代理节点Broker中实现的功能,主要包含Server和SQL Layer两个模块。

Server模块负责接收和解析请求,包括HTTP和JDBC两类 。对于普通的HTTP请求,新增相应的REST Endpoint即可。对于JDBC,Druid复用了Avatica项目的JDBC Driver和RPC定义,因此只需要实现Avatica的SPI就行了。由于Avatica的RPC也是基于HTTP的,因此两者可以使用同一个Jetty Server。

SQL Layer负责将SQL翻译成原生的JSON查询,是基于Calcite项目实现的。Calcite是一个通用的SQL优化器框架,能够将标准SQL解析、分析、优化成具体的执行计划,在大数据领域得到了广泛的使用。图中浅绿色的组件是Calcite提供的,浅蓝色的组件是Druid实现的,主要包括三个。

首先,DruidSchema组件为Calcite提供查询解析和验证需要的元数据,例如集群中包含哪些表,每张表各个字段的名称和类型等信息。RulesSet组件定义了优化器使用的转换规则。由于Druid SQL只做语言翻译,因此这里都是一些逻辑优化规则(例如投影消除、常量折叠等),不包含物理优化。通过RulesSet,Calcite会将逻辑计划转成DruidRel节点,DruidRel包含了查询的所有信息。最后,QueryMaker组件会尝试将DruidRel转成一个或多个原生JSON查询,这些JSON查询最终提交到Druid的QueryExecution模块执行。

2.3 API选择: HTTP or JDBC

Druid SQL提供了HTTP和JDBC两种接口,我应该用哪个?我们的经验是,HTTP适用于所有编程语言,Broker无状态,运维较简单;缺点是客户端处理逻辑相对较多。JDBC对于Java应用更友好,但是会导致Broker变成有状态节点,这点在做复杂均衡时需要格外注意。另外JDBC还有一些没有解决的BUG,如果你使用JDBC接口,需要额外关注。

2.4 改进

下面介绍我们对Druid SQL做的一些改进。

第一个改进是关于Schema推导的性能优化。我们知道Druid是一个schema-less系统,它不要求所有的数据的schema相同,那如何定义Druid表的schema呢?社区的实现方式是:先通过SegmentMetadataQuery计算每个segment的schema,然后合并segment schema得到表的segment,最后在segment发生变化时重新计算整个表的schema。

社区的实现在我们的场景下遇到了三个问题。第一是Broker启动时间过长。我们一个集群有60万个segment,测试发现光计算这些segment的schema就需要半小时。这会导致Broker启动后,需要等半个小时才能提供服务。第二个问题是Broker需要在内存中缓存所有Segment的元数据,导致常驻内存增加,另外schema刷新会带来很大的GC压力。第三个问题是,社区方案提交的元数据查询量级与Broker和Segment个数的乘积成正比的,因此扩展性不好。

针对这个问题,我们分析业务需求和用法后发现:首先schema变更是一个相对低频的操作,也就是说大部分segment的schema是相同的,不需要去重复计算。另外,绝大数情况业务都只需要用最新的schema来查询。因此,我们的解决方案是,只使用最近一段时间,而不是所有的segment来推导schema。改造后,broker计算schema的时间从半小时降低到了20秒,GC压力也显著降低了。

第二个优化是关于日志和监控。请求日志和监控指标是我们在运维过程中重度依赖的两个工具,比如慢查询的定位、SLA指标的计算、流量回放测试等都依赖日志和监控。但是0.12版本的SQL既没有请求日志,也没有监控指标,这是在上线前必须要解决的问题。我们的目标有两个:首先能记录所有SQL请求的基本信息,例如请求时间、用户、SQL内容,耗时等;其次能将SQL请求和原生的JSON查询关联起来。因为执行层面的指标都是JSON查询粒度的,我们需要找到JSON查询对应的原始SQL查询。

我们的解决方案已经合并到0.14版本。首先,我们会给每个SQL请求分配唯一的sqlQueryId。然后我们扩展了RequestLogger接口,添加了输出SQL日志的方法。下图是一个例子,对于每个SQL请求,除了输出SQL内容外,也会输出它的sqlQueryId,可以用来与客户端的日志做关联。还会输出SQL对应的每个JSON查询的queryId,可以用来和JSON查询做关联分析。

第三个改进虽然比较小,但是对服务的稳定性很重要。我们知道,JSON查询要求用户指定查询的时间范围,Druid会利用这个范围去做分区裁剪,这对提高性能非常重要。但是Druid SQL并没有这方面的限制。用户写SQL经常会忘记加时间范围的限定,从而导致全表扫描,占用大量的集群资源,是一个很大的风险。所以我们添加了对where条件的检查,如果用户没有指定时间戳字段的过滤条件,查询会直接报错。

三、Druid Security的实践经验

首先介绍我们在数据安全上面临的问题。当时使用的是0.10版本,这个版本在数据安全上没有任何的支持,所有的API都没有访问控制,任何人都能访问甚至删除所有的数据,这对业务的数据安全来说是一个非常大的隐患。

我们希望实现的目标有五点:所有API都经过认证、实现DB粒度的权限控制、所有数据访问都有审计日志、业务能平滑升级到安全集群、对代码的改动侵入性小。

为了实现这些目标,我们首先调研了Druid在后续版本中新增的安全功能。

3.1 Druid Security 功能和原理

0.11版本支持了端到端的传输层加密(TLS),能够实现客户端到集群,以及集群各个节点之间的传输层安全。0.12版本引入了可扩展的认证和鉴权框架,并且基于这个框架,提供了BA和Kerberos等认证方式,以及一个基于角色的鉴权模块。

下面这张图介绍认证鉴权框架的原理和配置。

3.2 Druid Security社区方案缺点

社区方案能满足我们大部分的需求,但还存在一些问题。

第一个问题是我们发现浏览器对BA认证的支持很差。因此对于Web控制台,我们希望走统一的SSO认证。

第二个问题是为了支持业务平滑过渡到安全集群,上线初期必须兼容非认证的请求,当时我们使用的0.12版本没有该功能。

第三个问题是社区基于角色的鉴权模块只提供了底层的管理API,用户直接使用这些API非常不方便。

最后一个问题是社区还不支持审计日志。

针对这些问题,我们做了三个主要的改进。

3.3 改进

改进一:基于DB的访问控制

首先,为了简化权限的管理,我们引入了DB的概念,并实现了DB粒度的访问控制。业务通过DB的读写账号访问DB中的表。

改进二:自动管理权限DB

通过任务接入平台维护DB和DataSource的映射关系,并在DB和DataSource发生变化时,调用鉴权模块接口更新权限DB。

改进三:支持SSO认证和非认证访问

自定义认证链条,通过SSO认证Filter实现Web控制台的SSO认证,通过非安全访问Filter兜底,兼容非认证的请求。

注意事项

1. 使用0.13以上版本 (或者cherrypick高版本的bugfix)

2. 上线流程

  • 启用basic-security功能,用allowAll兜底

  • 初始化权限DB,创建匿名用户并授权

  • 将allowAll替换为anonymous

  • 逐步回收匿名用户的权限

3. 上线顺序:coordinator->overlord->broker->historical->middleManager

四、总结

4.1 关于SQL

1. 如果还在用原生的JSON查询语言,强烈建议试一试

2. 社区在不断改进SQL模块,建议使用最新版本

3. Druid SQL本质上是一个语言翻译层

  • 对查询性能和稳定性没有太大影响

  • 受限于Druid本身的查询处理能力,支持的SQL功能有限

4. 要留意的坑

  • 大集群的schema推导效率

  • Broker需要等schema初始化后再提供服务(#6742)

4.2 关于Security

1. Druid包含一下Security特性,建议升级到最新版本使用

  • 传输层加密

  • 认证鉴权框架

  • BA和Kerberos认证

  • RBAC鉴权

2. 认证鉴权框架足够灵活,可根据自身需求扩展

3. 经历生产环境考验,完成度和稳定性足够好

4. 上线前应充分考虑兼容性和节点更新顺序

  

未经允许不得转载:大自然的搬运工 » Druid SQL和Security在美团点评的实践

赞 (0)

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址