Doris 在蔚来汽车的应用
分享嘉宾:唐怀东 蔚来汽车 数据团队负责人
编辑整理:罗庆新 IBM
出品平台:DataFunTalk
导读: 本次分享的题目是Doris在蔚来汽车的应用,主要包括以下几大部分:
- OLAP在蔚来的发展
- Doris作为统一OLAP数仓
- Doris在运营平台上的实践
- 经验总结
01 OLAP在蔚来的发展
第一部分,介绍一下OLAP在蔚来汽车的发展。
1. 2017年引入Druid
在当时可选择的OLAP存储和查询引擎并不多,比较常见的有Druid、Kylin。
我们优先引入Druid的原因是以前有使用经验比较熟悉。
而Kylin预计算虽然具有极高的查询效率优势,但是:
- Kylin底层最合适和最优的存储是HBase,公司未引入作为基础选型,会额外增加运维的工作。
- Kylin对各种维度和指标进行预计算,如果维度和维度取值非常多,会有维度爆炸的问题,对存储造成非常大的压力。
2. 2019年引入TiDB
开始引入TiDB的场景是分布式OLTP。随着TiFlash分析引擎的引入,其OLAP能力逐渐增强,OLTP和OLAP两者结合具有非常大的优势。TiDB对MySQL的兼容性做得非常好,OLTP的数据进来后,直接进行OLAP分析和查询,相对来说路径会短很多,所以它可以在某些场景得到很大的应用。
3. 2021年引入Doris
这得益于最近几年国内在基础架构和基础技术上的发力,无论Doris还是其他一些存储,包括我们研究过的时序数据库TDengine,这是国内发展越来越好的地方。
下面详细说一下我们公司为什么会选择Doris,以及为什么我们是从这个路径过来。
产品的优点、缺点、技术使用成本、协议、性能和运维成本,这几个方向决定了我们的技术选型。
第一,Druid的优势很明显,支持实时和离线数据接入,列式存储,高并发,查询效率非常高。 其缺点也很明显:
- 未使用标准协议例如JDBC,使用门槛高
- Join的支持弱
- 精确去重的效率低,性能会下降。整体性能要分场景去考虑,这也是我们后期去选型其他OLAP的原因
- 运维成本高,不同的组件有不同的安装方式和不同的依赖;数据导入还要考虑和Hadoop集成以及JAR包的依赖
第二,TiDB是一个OLTP+OLAP的成熟引擎。
- 优势是更新非常友好,得益于其本质上优先是OLTP数据库。还有就是我们要支持明细和聚合,场景不仅有指标计算或dashbroad展示,有时还有明细数据查询
- 缺点是它不是一个独立的OLAP,TiFlash依赖于OLTP,会增加存储。其OLAP能力稍显不足
- 支持标准SQL,使用成本低
- 性能分场景
- 运维成本低
第三,我们重点关注的Doris,其优点完全满足我们的诉求。
- 高并发,我们比较看重这点
- 同时支持实时和离线
- 支持明细和聚合
- 支持一定程度的更新
- 物化视图的能力能极大的加速查询效率
- 兼容MySQL协议,所以开发和使用成本比较低
- 性能满足我们的要求
- 运维成本比较低
最后提一下Clickhouse,我们之前也调研过,也尝试想去使用它。其单机性能极强,但是:
- 我们明确需要的场景下,它的多表join支持的稍微差一些
- 并发度比较低
- 运维成本极高,运维过的人都会懂得为什么这一块的运维成本比较高
02 Doris作为统一OLAP数仓
这张图基本上就是从数据源到数据接入、数据计算、数据仓库、数据服务以及应用。
1. 数据源
蔚来的场景下,数据源不仅仅指业务系统的数据,还有埋点数据、设备数据、车辆数据等等。数据会通过一种接入方式接入到大数据平台。
2. 数据接入
我们所有的数据源并不都是采用这一种接入方式。对于一些业务系统的数据,可以开启CDC捕捉变化的数据,然后转换成一个数据流存储到Kafka,接续再进行流式的计算。
对于有些数据只能通过批量的方式直接进入到我们的分布式存储。
3. 数据计算
我们没有采用流批一体, 采用的是Lambda架构 ,而不是Kappa架构。
Lambda架构其实是离线和实时分两条路径,这个是和我们本身的业务场景有关系的:
- 不是所有的数据都是流式的。
- 不是所有的数据都能够存储到数据流里,一些历史数据不会存储到Kafka。
- 有些场景数据要极度的精准,为了保证数据的准确性,我们还是会有一个离线的场景或者离线的pipeline,把整个数据重新计算,然后重刷,然后保证这个数据最终是准确的。所以我们还是采用Lambda架构。
4. 数据仓库
数据计算到数仓,这两条线路我们没有采用Flink或Spark Doris Connector,Flink用Routine Load,而Spark用Broker Load并不是直接去写的,这有一定的使用场景和原因。像Spark这一块我们生成的数据,其实还是会留一份到Hive做一些其他场景的使用。这样计算一次,就可以同时在多个地方去使用。
Flink也类似。
5. 数据服务
Doris后面是一个One Service,业界在数据服务化这一块做的其实是非常多的。通过注册数据源或灵活配置的方式,自动生成API,对API进行流量的控制和权限的控制,这样它的灵活性会极大提高。有些公司的One Service还增加了编排能力,借助于k8s serverless方案,能把整个服务做得非常灵活和丰富。
6. 数据应用
应用可以是报表应用,也可以直接提供一些服务。
我们主要有两类使用场景:
- 面向用户 ,类似于互联网,我们有很多用户的场景,包括看板和指标
- 面向车 ,我们一些车的数据通过这种方式进入到Doris,一般不会非常明细的数据,而是通过一定的聚合,Doris数据体量在几十亿级别,但总体性能还是能满足我们的要求。
03 Doris在运营平台上的实践
1. CDP运营平台的架构
接下来介绍Doris在运营平台上的实践,这是我们一个真实的使用场景。互联网公司基本上都会做一个自己的CDP,它包括几个模块:
- 标签 ,这个是基石。
- 圈人 ,就是基于标签,我们怎么把人圈出来。
- 洞察 ,针对圈定的人群,了解它是什么样的分布?它长什么样子?它有什么特点,所以通常会有洞察这个步骤。
- 触达 ,就是通过不同的触达路径,例如短信、电话、声音、APP通知、IM等等都可以触达到用户,当然这中间还有一些流量的控制。
为了闭环,我们运营计划还有个效果分析,这样从闭环完整性的角度来说,做到有动作、有效果、有反馈。
Doris在这里面起到了 最重要的作用 ,包括: 标签存储 、 人群存储 、 效果分析 。
标签分为基础标签和用户行为的基础数据,在此基础之上,我们可以灵活自定义其他标签。换一个维度考虑,标签还分为实时的标签和离线的标签。
2. CDP存储选型的考量点
我们从5个维度去考量CDP存储的选型。
①离线和实时统一
如前所述标签有离线标签,有实时标签,我们这个实时其实还是一个准实时的场景。对于有些数据,准实时已足够满足我们的需求,大量的标签还是离线的标签,采用的方式就是Doris的Routine Load和Broker Load。
另外同一张表上,不同列更新的频率也是不一样的。例如用户的基础标签,我们可能对用户的身份需要实时的更新,因为用户的身份是时刻变化的,它的变化的频率,我们不可能做成T+1,否则整个使用场景就会极大的受限。但对于有些标签离线就可以,例如用户的性别、年龄等基础标签,T+1就足够。但我们为什么期望把这种基础用户的原子标签放在一张表,这是因为一张表,维护成本会很低。再者后期自定义标签时,表的数量会极大的减少,这样对于整体性能的提升有极大好处。
②高效圈选
用户运营有了标签,第二步就是圈人,圈选就是根据标签的不同组合,把符合标签条件的所有人筛选出来,这时会有不同标签条件组合的查询、这个查询在Doris引入向量化之后有比较明显的提升。
③高效聚合
前面提到的用户洞察或群体洞察以及效果分析统计,需要对数据做统计分析,并不是单一的按用户ID获取标签的这种简单场景,它读取的数据量和查询效率,对我们这个标签的分布、群体的分布、效果分析的统计都有很大的影响,Doris的功能特点:
- 第一是数据分片 ,我们按时间把数据分片,分析统计就会极大的减少数据量,可以极大的加速查询和分析的效率。
- 第二是节点聚合 ,然后再收集做统一的聚合。
- 第三是向量化加速 ,向量化引擎对性能提升非常显著。这也是为什么Doris最近在这一块发力的原因。
③多表关联
我们的CDP可能和业界一些场景的CDP不太一样,因为有些场景的CDP标签都是提前算好的,不存在自定义标签的场景。而我们只做原子标签,或者说用户基础行为数据的统计。把灵活性留给使用CDP的用户,他可以根据自己的业务场景去自定义标签,这时候会发现底层的数据是分散在不同的数据库表里,如果做自定义的标签的建设,势必需要做表的关联。
我们选择Doris一个非常非常重要的原因,就是多表关联的能力。我们通过性能测试,目前能够满足我们的要求,而这块确实给用户提供了非常强大的能力。因为标签是动态的且符合业务场景。
④联邦查询
用户触达成功与否我们会记录到TiDB。因为我们做用户运营,如果仅仅是一个通知,可能只影响用户体验,如果涉及到钱例如发放积分或优惠券,任务执行就要做到不重不漏,这种OLTP场景用TiDB比较合适。
然后我们做效果分析,我们要知道运营计划执行到什么程度,是否达成目标,其分布情况等等。所以需要把任务执行情况和人群圈选相结合才能进行分析,就会用到Doris和TiDB的关联,外表关联进行查询。
开始我们设想标签体量比较小,保存到es可能比较合适,通过我们的测试这块没有上线,后面会解释其原因。
04 经验总结
第一个是 bitmap ,我们还没有达到那么大的体量,无法发挥其效率。如果体量达到一定程度,用bitmap会有很好的性能提升。计算UV场景,Id全集大于5000万,可以考虑bitmap聚合。
第二个是 ES外表 ,单表查询下推效率还是很好的。但聚合查询效率比较低,ES不适合聚合查询,会把ES明细全读到Doris,然后聚合;ES也不适合多表关联,读取大量数据,会引发ES频繁GC。
第三个是 分批更新列 ,为了减少表的数量和提升join表的性能,设计表尽量精简尽量聚合,相同类型的事实都放在一起。但相同类型的字段可能更新频率不同,有些字段需要天级更新,有些字段可能需要小时级更新,单独更新某一列就是一个明显的诉求。Doris聚合模型单独更新某些列的解决方案是使用REPLACE_IF_NOT_NULL。注意:用null替换原来的非null值是做不到的,可以把所有的null替换成有意义的默认值,例如unknown。
第四个 在线服务 ,Doris同一份数据,同时服务在线离线场景,容易互相影响。尽量用另外单独的技术方案提供在线服务。
还有未来的展望,主要是用Manager更好的去管理Doris,还有资源隔离,避免互相影响。
05 问答环节
Q1 :实时和离线同时更新同一张表不同列,会有冲突问题吗?你们这边是怎么解决的?
A1 : 没有发现冲突问题,我们使用REPLACE_IF_NOT_NULL的方式更新
Q2 :运营平台的标签是如何存储的?
A2 : 标签存储在Doris,包括两类,一类是基础标签,没有任何逻辑判断的原子标签,一类是用户行为的分析统计,例如对用户的APP操作行为进行聚合,其本质上也是事实数据。不同的行为存储到不同的表中,更上层的定制标签用定时任务进行抽取、计算和再存储,提前定义好自定义标签的表结构,名字额外的映射,然后直接更新数据即可。
Q3 :运营平台存储类似于用户位置、用户性别等不同标签,是多表分开存储还是用大宽表,此外建表我们如何选择明细模型还是聚合模型,存储和查询上有什么建议?
A3 : 我们是根据场景分开存储的,前面提及的用户基础或原子标签,像用户信息可以是一张表。而不同的行为会存储到不同的地方,互不影响。选型我们非常看重Doris的多表关联能力,自定义标签需要多表关联生成。自定义标签的表和列会提前定义好,名字用额外的表做映射。
Q4 :标签表的结构是怎么设计的?怎么设计标签的自定义属性?
A4 : 基础数据我们是先算好的,有多少种行为,有多少基础的原子标签会提前定义好。自定义标签我们是先建一个大宽表,其本身默认是没有含义的,如果定制一个标签,占用一个column即可。
Q5 :你们公司的数仓全部基于Doris吗?是否分层?
A5: 并不是所有的数仓全用Doris,我们公司的车辆明细数据的体量非常大,用户的使用场景非常灵活,他们要经常自己去写udf查询数据,没有办法提前固化,所以一些离线场景是用Hive。Doris适用场景还是实时的OLAP场景,Web或APP仪表盘上展示指标,直接查询数仓不合适。我们对不同的场景的分层是不一样的,实时很少分很多层,基本上没有特殊需求,包括明细加上最终的聚合。离线主要是根据使用场景或查询要求的不同,我们也会分层。
分享嘉宾
唐怀东 蔚来汽车 数据团队负责人
本科毕业于北京邮电大学,硕士毕业于中国科学院。曾在Yahoo北京研发中心,负责新闻推荐效果分析指标看板开发,以及在猎豹移动,负责新闻推荐算法。目前在蔚来汽车,负责大数据平台&中台建设。