开发实战角度:distinct实现原理及具体优化总结

1.背景

Distinct是一种常用的操作,在所有数据库的SQl语言中都是一个非常重要的操作,在Hive中,Distinct去重原理是通过MapReduce来实现的,Distinct操作可以应用于单个列,亦可以应用于多个列。基本原理是将输入的数据集按照指定的列进行分组,在每个分组内部去除重复的值,最后将每个分组的唯一值合并成一个结果集。

最近一位好学的小伙伴在学习的过程中,学习到count distinct 的这块内容的时候,基于单个count  distinct 的原理,可以理解的不错,但是对于多字段的count distinct的时候讲的就很难让人理解,今天就来给大家总结一下。

2.Group By

group by的操作适合我们的聚合时distinct息息相关的,所以在我们总结distinct 之前 ,我们不得不先来看一下group by 操作的具体实现原理。

  1. map阶段,将group by后的字段组合作为一个key,如果group by单个字段,那么key就一个。将group by之后要进行的聚合操作字段作为值,如果要进行count,则value是赋1;如要sum另一个字段,那么value就是该字段。

  2. shuffle阶段,按照key的不同分发到不同的reducer。注意此时可能因为key分布不均匀而出现数据倾斜的问题。这个问题是我们处理数据倾斜比较常规的查找原因的方法之一,也是我们解决数据倾斜的处理阶段。

  3. reduce阶段,如果是count将相同key的值累加,如果是其他操作,按需要的聚合操作,得到结果。

实例如下图,对应语句是:

with tmp1 as (select'a' as pro,'1' as cityunion allselect'a','1'union allselect'a','1'union allselect'b','0'
)
selectpro,city,count(*)
fromtmp1
group bypro,city

在这个阶段如果出现数据倾斜,我们也可以进行相应的处理,常见的就是把key单独拉出来计算,也可以替换随机数,当然除去替换key为随机数、提前挑出大数量级key值等通用调优方法,适用于group by的特殊方法有以下几种:

  1. set hive.map.aggr=true,就是开启map端的combiner,减少传到reducer的数量级,同时需要设置参数hive.groupby.mapaggr.checkinterval, 规定在 map 端进行聚合操作的条目数目。

  2. 设置mapred.reduce.tasks为较大数量,用来降低每个reducer处理的数据量。

  3. set hive.groupby.skewindata=true,该参数也是比较常规的设置,该参数可自动进行负载均衡。生成的查询计划会有两个 MR Job。第一个 MR Job 中,Map 的输出结果集合会随机分布到Reduce 中,每个 Reduce 做部分聚合操作,并输出结果,这样处理的结果是相同的 Group By Key有可能被分发到不同的 Reduce 中,从而达到负载均衡的目的;第二个 MR Job 再根据预处理的数据结果按照 Group ByKey 分布到 Reduce 中(这个过程可以保证相同的 Group By Key 被分布到同一个 Reduce中,最后完成最终的聚合操作。

3.Distinct 单字段

原理

上面我们可以清晰的明白group  by的流程,那么接下来我们再来回顾一下distinct 的具体实现,当执行Distinct操作时,Hive会将操作转化为一个MapReduce作业,并按照指定的列进行分组。在Map阶段,每个Mapper会读取输入数据,并将指定的列作为输出的key,然后,通过Shuffle过程将具有相同key的数据发送到同一个Reducer中。

我们可以看出,由于使用了distinct,这就导致在map端的combine无法合并重复数据,所以必须将id作为Key输出,在Reduce阶段再对来自于不同Map Task、相同Key的结果进行消重,计入最终统计值。

对于这种count(distinct)全聚合操作的时候,即使我们设定了reduce task的具体个数,例如set mapred.reduce.tasks=100;hive最终也只会启动一个reducer。这就造成了所有map端传来的数据都在一个tasks中执行,这唯一的Reduce Task需要Shuffle大量的数据,并且进行排序聚合等处理,这使得这个操作成为整个作业的IO和运算瓶颈。

当distinct一个字段时,这里会将group by的字段和distinct的字段组合在一起作为map输出的key,value设置为1,同时将group by的字段定为分区键,这一步非常重要,这样就可以将GroupBy字段作为reduce的key,在reduce阶段,利用mapreduce的排序,输入天然就是按照组合key排好序的。根据分区键将记录分发到reduce端后,按顺序取出组合键中的distinct字段,这时distinct字段也是排好序的。依次遍历distinct字段,每找到一个不同值,计数器就自增1,即可得到count distinct结果。例如下面的SQL语句,过程可以下图示意。

with tmp1 as (select'a' as pro,'1' as city,'张三' as useridunion allselect'a','1','张三'union allselect'a','1','张三'union allselect'b','0','张三'
)
selectpro,count(distinct userid)
fromtmp1
group bypro

优化

对于单distinct 的优化,我们的课程也提到过很多次,利用Hive对嵌套语句的支持,将原来一个MapReduce作业转换为两个作业,在第一阶段选出全部的非重复的字段id,在第二阶段再对这些已消重的id进行计数;这样在第一阶段我们可以通过增大Reduce的并发数,并发处理Map输出。在第二阶段,由于id已经消重,因此COUNT(*)操作在Map阶段不需要输出原id数据,只输出一个合并后的计数即可。这样即使第二阶段Hive强制指定一个Reduce Task的时候,极少量的Map输出数据也不会使单一的Reduce Task成为瓶颈。

其实在实际运行时,Hive还对这两阶段的作业做了额外的优化。它将第二个MapReduce作业Map中的Count过程移到了第一个作业的Reduce阶段。这样在第一阶Reduce就可以输出计数值,而不是消重的全部id。这一优化大幅地减少了第一个作业的Reduce输出IO以及第二个作业Map的输入数据量。最终在同样的运行环境下优化后的语句可以说是大大提升了执行效率。sql优化模板如下:

SELECTCOUNT(*)
FROM(SELECTDISTINCT idFROMTABLE_NAMEWHERE…) t;

4.Distinct 多字段(mult-distinct)

原理

对于mult-distinct,如果我们仍然按照上面一个distinct字段的方法,即下图这种实现方式,无法根据uid和date分别排序,也就无法通过LastKey去重,仍然需要在reduce阶段在内存中通过Hash去重。

with tmp1 as (select'a' as pro,'1' as city,'张三' as useridunion allselect'a','1','张三'union allselect'a','1','张三'union allselect'b','0','张三'
)
selectpro,count(distinct userid),count(distinct city)
fromtmp1
group bypro

所以hive会使用另外一种处理方式,如果查询中有多个 distinct-expression,同一条record,会生成多条记录进行Shuffle,增大Shuffle量;假设有N个distinct,处置数据有M条,那么这布处置后的输出是N*M条数据(这里产生数据膨胀,也是数据倾斜产生的阶段),所以尽量在MAP端过滤尽可能多的数据生。处理方法是对所有的distinct字段编号,那么相同字段就会分别排序,这时只需要在reduce阶段记录LastKey即可去重。这种实现方式很好的利用了MapReduce的排序,节省了reduce阶段去重的内存消耗,但是缺点是增加了shuffle的数据量。需要注意的是,在生成reduce value时,除第一个distinct字段所在行需要保留value值,其余distinct数据行value字段均可为空。

当然我们有读源码的能力的话,而也可以从hive源码里具体代码可参考ReduceSinkOperator.process方法,代码片段如下图所示。

优化

selectsum(case when d.pv_flag = 1 then 1 else 0 end) as pv,count(distinct id) as uv,count(distinct ip) as ip,sum(distinct otime),count(distinct cookie)
fromaccess_dap d
wherelog_date = '$YESTERDAY'

对于这种多重distinct的操作,我们也会经常遇见,就比如上面的的sql,这个sql我们可以按照三步走的步骤进行优化。

  • 1.预处理,去重汇总,将能去重的重复的数据先去重,并且尽量过滤数据,收敛数据。

  • 2.以空间换时间,我们的磁盘空间是非常容易获取到,我们可以用union all的把数据根据distinct的字段扩充起来,假如有4个distinct,相当于数据扩充4倍,用rownumber=1来达到间接去重的目的,这里的union all只走一个job。

  • 3.嵌套查询,得到最终结果,这里没有一个distinct,全部走的是普通sum,可以在mapper端提前聚合,同样可以调节reduce数,这个会快很多。

----1
create table tmp1 as
selectid,ip,cookie,idis_zero,sum(case when pv_flag = 1 then 1 else 0 end) as pv,sum(otime) as onlinetime
fromlogin_log
group byid,ip,cookie,idis_zero;---2create table tmp2 as
selecttype,type_value,rownumber(type, type_value) as rn,pv,onlinetime
from(selecttype,type_value,pv,onlinetimefrom(select'id' as type,cast(id as string) as type_value,pv,onlinetimefromtmp1whereidis_zero = 0union allselect'ip' as type,ip as type_value,pv,onlinetimefromtmp1union allselect'cookie' as type,case when cookie = 'null' then 'acorn_cookie' else cookie end as type_value,pv,onlinetimefromtmp1) t1 cluster by type,type_value) t2;
-----------------------------------------------------------------------------------------------------
selectsum(case when type = 'ip' then pv else cast(0 as bigint) end) as pv,sum(case when type = 'id'and rn = 1 then 1 else 0 end) as uv,sum(case when type = 'ip'and rn = 1 then 1 else 0 end) as ip,sum(case when type = 'ip' then onlinetime else cast('0' as bigint) end) as onlinetime,sum(case when type = 'cookie'and rn = 1 then 1 else 0 end) as cookie,'$STA_TYPE','$STA_TYPE'
fromtmp2

开发实战角度:distinct实现原理及具体优化总结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/643130.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习期末复习总结笔记(李航统计学习方法)

文章目录 模型复杂度高---过拟合分类与回归有监督、无监督、半监督正则化生成模型和判别模型感知机KNN朴素贝叶斯决策树SVMAdaboost聚类风险PCA深度学习范数计算梯度下降与随机梯度下降SGD线性回归逻辑回归最大熵模型适用性讨论 模型复杂度高—过拟合 是什么:当模型…

golang通过go-git下载gitlab源码

1 申请令牌 方法1:具体项目下申请: 方法2:全局申请 2 获取token 3 下载代码 package mainimport ("fmt""os""github.com/go-git/go-git/v5" )func main() {_, err : git.PlainClone("/tmp/foo",…

java web mvc-07-Vaadin 入门介绍

拓展阅读 Spring Web MVC-00-重学 mvc mvc-01-Model-View-Controller 概览 web mvc-03-JFinal web mvc-04-Apache Wicket web mvc-05-JSF JavaServer Faces web mvc-06-play framework intro web mvc-07-Vaadin web mvc-08-Grails 开源 The jdbc pool for java.(java …

MySQL函数—数值函数,随机数验证码生成

MySQL函数—日期函数 函数功能CEIL(x)向上取整FLOOR(x)向下取整MOD(x,y)返回x/y的模(取余)RAND()返回0-1的随机数ROUND(x,y)求参数x的四舍五入,保留y位小数 1、向上取整:CEIL。只要小数点后的数字大于0就取整。 select CEIL(1.2…

Qt解析含颜色的QString字符串显示到控件

1、需求 开发接收含颜色字符串显示到窗口,可解析字符串颜色配置窗口属性,且分割字符串显示。 mprintf(“xxxxxx”);打印的xxxxxx含有颜色配置。 2、实现方法 2.1、条件 选用Qt的PlainTextEdit控件显示字符串,配置为只读模式 …

pytestallure分析redis的数据并动态生成testCase报告

1.pytest.mark.parametrize pytest.mark.parametrize 是一个pytest的装饰器,它可以用于将参数传递给测试函数。使用 pytest.mark.parametrize 装饰器时,需要在装饰器中指定参数名称和参数值。对于多个参数,可以使用多个装饰器。 下面是一些…

数据链路层——笔记·续

使用集线器的星形拓扑 传统以太网传输媒体:粗同轴电缆 -> 细同轴电缆 -> 双绞线。 采用双绞线的以太网采用星形拓扑。 在星形的中心则增加了一种可靠性非常高的设备,叫做集线器 (hub)。 传统以太网使用同轴电缆,采用总线形拓扑结构&am…

嵌入式软件工程师面试题——2025校招社招通用(计算机网络篇)(二十八)

说明: 面试群,群号: 228447240面试题来源于网络书籍,公司题目以及博主原创或修改(题目大部分来源于各种公司);文中很多题目,或许大家直接编译器写完,1分钟就出结果了。但…

计算机网络 第3章(数据链路层)

系列文章目录 计算机网络 第1章(概述) 计算机网络 第2章(物理层) 计算机网络 第3章(数据链路层) 文章目录 系列文章目录1. 数据链路层概述1.1 概述1.2 三个重要问题 2. 封装成帧2.1 介绍2.2 透明传输2.3 总…

归一化是是什么意思,为什么要归一化

归一化 归一化是指将数据转换为标准尺度或相对比例的过程。在数据处理中,归一化的目标是使数据具有统一的尺度,以便更好地适应模型的训练和提高模型性能。归一化通常是通过线性变换将数据映射到一个特定的范围或分布。 为什么要进行归一化? …

RK3399平台开发系列讲解(USB篇)USB2.0 包格式分类

🚀返回专栏总目录 文章目录 一、令牌包格式二、数据包格式三、握手包格式沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 USB协议定了多种类型的包,有令牌包、数据包和握手包。 根据PID可将USB2.0的包分为四种包类型。 令牌包(Token):01B数据包(Data):11B握手包…

QT下载、安装详细教程[Qt5.15及Qt6在线安装,附带下载链接]

QT5.15及QT6的下载和安装 1.下载1.1官网下载1.2国内镜像网站下载 2.安装3.软件启动及测试程序运行3.1Qt Creator(Community) 1.下载 QT自Qt5.15版本后不在支持离线安装包下载(非商业版本,开源),故Qt5.15及Qt6需要使用在线安装程序…

云原生安全:风险挑战与安全架构设计策略

概述 数字化转型已经成为当今最流行的话题之一,大部分企业已经开启自身的数字化转型之旅,在未来企业只有数字化企业和非数字化企业之分。通过数字经济的加速发展,可以有效推动企业数字化转型的步伐。云计算作为数字化转型的底座和重要的载体…

Redis 面试题 | 07.精选Redis高频面试题

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【思路合集】talking head generation+stable diffusion

1 以DiffusionVideoEditing为baseline: 改进方向 针对于自回归训练方式可能导致的漂移问题: 训练时,在前一帧上引入小量的面部扭曲,模拟在生成过程中自然发生的扭曲。促使模型查看身份帧以进行修正。在像VoxCeleb或LRS这样的具…

webpack如何把dist.js中某个模块js打包成一个全局变量,使得在html引入dist.js后可以直接访问

webpack可以通过使用expose-loader来将模块中的一个js文件暴露为全局可以访问的变量。下面是一个示例代码: 1、安装expose-loader npm install expose-loader --save-dev 2、webpack.config.js配置文件 值得注意的是:我在本地使用16.14.2版本的node打包…

【深度学习】初识深度学习

初识深度学习 什么是深度学习 关系: #mermaid-svg-7QyNQ1BBaD6vmMVi {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-7QyNQ1BBaD6vmMVi .error-icon{fill:#552222;}#mermaid-svg-7QyNQ1BBaD6vmMVi .err…

【AI大模型】WikiChat超越GPT-4:在模拟对话中事实准确率提升55%终极秘密

WikiChat,这个名字仿佛蕴含了无尽的智慧和奥秘。它不仅是一个基于人工智能和自然语言处理技术的聊天机器人,更是一个能够与用户进行深度交流的智能伙伴。它的五个突出特点:高度准确、减少幻觉、对话性强、适应性强和高效性能,使得…

Airtest自动化测试工具

一开始知道Airtest大概是在年初的时候,当时,看了一下官方的文档,大概是类似Sikuli的一个工具,主要用来做游戏自动化的,通过截图的方式用来解决游戏自动化测试的难题。最近,移动端测试的同事尝试用它的poco库…

GPT5?OpenAI 创始人:GPT5 已在训练中,需要更多数据

OpenAI 最近发出征集大规模数据集的呼吁,特别是“今天在互联网上尚未公开轻松获取”的数据集,尤其是长篇写作或任何格式的对话。 GPT-5丨AI浪潮席卷全球,OpenAI 推出GPT-4 后,又于上月26日宣布今年9月、10月将推出GPT-4.5&#xf…