news 2026/9/28 6:55:20

Hive去重优化:distinct与group by性能对比及调优实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Hive去重优化:distinct与group by性能对比及调优实战

做数据的人,大概都写过这类SQL:统计今日UV、统计独立用户数、统计某维度组合的去重数量。distinct和group by在SQL语义上经常可以互相替代,所以网上总有人争论哪个更快。我早年也以为这俩差不多,直到有一次线上一个统计任务用distinct跑了快两个小时还卡在最后的Reduce阶段,换成group by写法半小时就出结果了。从那以后,我对这个问题的态度就变成了:看场景、看引擎、看数据分布,没有绝对的好,但有明确的取舍逻辑。

这篇文章我会从Hive的执行原理讲起,再用实际场景对比两者的性能差异,接着给出一套可落地的调优方案,最后聊聊Hive 3.x、Tez/Spark这些新引擎下局面发生了什么变化。不管你是刚接触Hive的新人,还是已经在生产环境里踩过坑的老手,希望能帮你把group by和distinct的选择逻辑彻底理顺。

1. 先搞懂执行原理:distinct和group by在Hive里到底怎么跑

1.1 从MapReduce模型看两者的本质

要判断谁快谁慢,得先知道Hive底层怎么执行这两类SQL。在Hive 2.x及更早版本,默认执行引擎是MapReduce,整个计算过程分为Map、Shuffle、Reduce三个阶段。

distinct本质上是全局去重。在Map阶段,Hive会把去重字段作为key输出,但Shuffle之后,为了确保“同一字段值只能出现一次”,数据必须汇到同一个Reduce任务上做全局去重。换句话说,经典执行计划下,select distinct user_id from t这条SQL无论数据量多大,Reducer数量基本就是1个。单Reduce意味着所有Map输出的数据全部压向一台机器,网络IO、磁盘读写、单节点计算压力全部集中在这一个点上,这为数据倾斜埋下了很大的隐患。

group by则不一样。它天然支持分治:Shuffle阶段按分组key进行分区,每个分组key会路由到同一个Reducer,但多个分组可以并行分布在不同Reducer上。更关键的是,Hive默认开启hive.map.aggr=true,Map阶段会对相同key先做一次局部聚合,也就是Map端聚合。这个过程会把大量重复数据在Map端就提前削减掉,Shuffle的数据量往往是原始数据的一个零头。

我用一个生活化的类比来帮你理解。distinct就像全班同学把作业统一交到班主任一个人手里,班主任在讲台上一份一份检查有没有重复的名字,全校几千人的作业只由一个人处理;group by则是先按班级分组,各班班长在自己教室先整理一遍名单,只把“班级+唯一姓名”汇总到年级组,年级组再合并,压力分散、流量减少,自然更快。

1.2 必须正视的“单Reduce魔咒”与数据倾斜

上面说的“单Reduce”是Hive早期版本中distinct最致命的问题。实际生产环境里,数据分布几乎不可能是均匀的。比如统计按来源渠道分组后的去重用户数,某些大渠道的用户量可能是小渠道的几百倍。使用distinct时,所有渠道的数据全都进入同一个Reducer,热点key直接被放到一台机器上处理,OOM、任务失败、长时间卡顿都很常见。

即使改用group by source_channel,如果某个渠道的key特别大,也会出现单个Reducer倾斜。但区别在于,group by可以对倾斜的key进行加盐拆分、Salted Shuffle等处理后,把大key打散成多份并行处理,而distinct因为是全量全局去重,加盐处理的逻辑必须额外小心,不能直接套用,否则去重结果会出错。

还有一个很容易被忽视的点:distinct在Shuffle阶段没有Map端聚合可以依赖。Map端聚合依赖“相同key在本地就有足够多的重复”这个前提,distinct虽然也是按字段分组,但map端做局部去重后,如果原数据中某个字段值出现次数只有1次,局部去重几乎没有削减效果,全部数据照样原封不动发给单个Reducer。

1.3 group by的Map端聚合优势到底有多大

group by的Map端局部聚合在很多场景下效果惊人。假设你有一个10亿条的日志表,要统计每天的UV,即select day, count(distinct user_id) from log group by day。先别管这段SQL里的distinct,单看group by day,Map阶段每个MapTask会先在本地算出一份(day, 部分聚合结果),然后只需要把少量Map端结果Shuffle给Reduce。

我在实际测试里遇到过这样的情况:一张日增量5亿条的曝光日志,按用户维度用group by做预聚合,Shuffle的数据量从原始的上百GB直接降到几GB,原因是单个用户一天的曝光记录可能有几十上百条,Map端聚合能把重复记录拍掉一大半。后续Reduce阶段的压力也成倍下降,整个任务的耗时自然大幅缩短。

但注意,group by的Map端聚合不是万能的。如果分组字段基数特别大,比如直接用毫秒时间戳或者完整URL做分组维度,Map端聚合基本起不到削减作用。另外,如果聚合函数本身依赖全局状态,Map端聚合也可能失效,这类问题牵扯到UDAF和Combiner的关联,我后面会单独展开。

2. 实测对比:不同场景下谁更划算

2.1 单列去重场景:group by真的稳赢吗

我做了很多次对比测试,场景尽量贴近生产:一张模拟电商订单表,字段包括order_id, user_id, order_date, amount, channel。测试SQL非常简单,一个算总去重用户数,一个算按渠道分组的去重用户数。

单列去重总量这个场景,我拿1亿条订单数据测,select count(distinct user_id)和select count(*) from (select user_id from t group by user_id) tmp,前者默认落在一个Reducer上,跑完大概需要18分钟;后者因为Map端聚合把重复的user_id压掉了一大批,Shuffle数据量小了很多,最终耗时大概12分钟。两个SQL如果加上order_date做为时间过滤条件,数据量降到几千万条时,差异会缩小到一两分钟以内。

这意味着什么?如果数据量不大,或者去重字段的基数远小于总记录数,group by的优势没有被完全激发,两者耗时的差距就没那么明显,甚至可以互相替换。但一旦数据量上亿,且去重字段有大量重复值,group by的Map端聚合优势会被放大,差距能到30%以上。

所以我的结论不是“无脑用group by”,而是“在数据量大、重复率高的场景,group by更稳;在数据量小、字段基数又接近总行数的场景,两者基本差不多”。

2.2 多列去重与多个count(distinct)的经典组合

现实业务里,只去重一列的情况太少见了。更常见的写法是:

select channel, count(distinct user_id) as uv, count(distinct order_id) as order_cnt, count(distinct product_id) as product_cnt from orders where day = '2025-01-01' group by channel;

这类SQL有三个count(distinct),在Hive 2.x的MapReduce引擎下,会触发多次MapReduce Job。原因很简单:Hive的优化器在应对多个不同字段的distinct时,很难在单个Reduce阶段同时完成三种不同的全局去重。最终表现就是Job数量翻倍,每一轮都要全量扫描数据,整个任务耗时甚至比三个单独SQL加起来还要夸张。

另外,还需要区分count(distinct a, b)和count(distinct a), count(distinct b)。前者是对(a, b)组合去重,后者是分别对a和b去重,两者统计口径完全不同,但在执行计划里都叫“Distinct”。组合去重在部分Hive版本中同样只能走单Reduce,性能更差。

针对这种多列去重场景,我更推荐先group by去重,再做外层聚合:

select channel, count(*) as uv, count(order_id) as order_cnt, count(product_id) as product_cnt from ( select channel, user_id, order_id, product_id from orders where day = '2025-01-01' group by channel, user_id, order_id, product_id ) t group by channel;

内层group by把多列一起分组,天然形成了“组合去重”的中间结果;外层再按渠道做纯count(*)聚合,每一步都不涉及全局单Reduce,执行计划更均衡,Job数量也稳定。

2.3 group by + 再聚合的组合拳能解决什么

“先group by去重,再二次聚合”这招,几乎可以覆盖所有去重统计口径,而且正确性非常好理解。它尤其适用于三类场景。

第一类是多列去重计数,上面已经演示过了。第二类是不同时间窗口的UV计算,比如要同时看当日UV、7日UV、30日UV。如果只用count(distinct user_id)加case when,写出来的SQL不仅冗长,性能还差。更好的方式是先按user_id聚合出活跃日期列表,然后在外层按时间窗口条件计数。第三类是留存分析里的多日用户去重,比如同时看昨天和今天的活跃用户交集,用group by结合多个sum(case when ...)可以清晰表达。

我用一个留存口径的例子来说明:

select a.day, count(distinct case when b.day = '2025-01-02' then a.user_id end) as active_retain_uv from ( select user_id, day from user_active_daily where day = '2025-01-01' group by user_id, day ) a left join ( select user_id, day from user_active_daily where day = '2025-01-02' group by user_id, day ) b on a.user_id = b.user_id group by a.day;

这个SQL先用双层group by把两天的活跃明细各自去重,再通过join关联,最外层计数。count(distinct case when ...)在这里只在结果集很小的外层使用,内层完全不碰全局distinct,整个执行计划不容易出倾斜问题。

3. 调优实战:数据倾斜、小文件与UDAF

3.1 数据倾斜的经典解法:加盐与两阶段聚合

数据倾斜是group by和distinct都会遇到的问题。distinct的倾斜体现在单Reduce压垮一台机器,group by的倾斜体现在某个大key霸占一个Reducer。针对group by倾斜,最稳妥的方案是两阶段聚合。

第一步,给key加一个随机扰动,比如concat(user_id, '_', floor(rand() * 10)),让原来的大key被拆成10份;第二步,在每个小分组内做局部聚合;第三步,去掉盐分再按原key聚合一次。这个过程其实对应了很多数据计算引擎中的“Partial Aggregation + Final Aggregation”。

用SQL写出来大致是:

select channel, sum(partial_cnt) as uv from ( select channel, concat(user_id, '_', floor(rand() * 10)) as salted_key, count(*) as partial_cnt from orders group by channel, concat(user_id, '_', floor(rand() * 10)) ) t group by channel;

注意,这段SQL处理的是“用户出现多次只算一次”的UV口径。加盐后,同一个用户会被随机拆到10个不同的小桶里,每个小桶里的count(*)都只是该用户被分到这个桶的记录数。外层把partial_cnt全部加起来,恰好等于该渠道下所有用户的出现次数和。如果每个用户只出现一次,算出来的就是真正的UV;如果用户有重复出现,这个方案就不适用了,需要先对(channel, salted_key, user_id)做去重再计数。

不过Hive本身的count(distinct user_id)更习惯于直接使用:

select channel, count(distinct user_id) from orders group by channel;

这种写法里distinct和group by共存,Map端可以把大部分重复值先处理掉,Reduce端再对每个channel做去重。如果channel本身倾斜严重,再考虑加盐方案,但加盐去重比较麻烦,需要确保同一个用户始终进入同一个盐分桶,比如改成concat(user_id, '_', hash(user_id) % 10),不能完全随机。

Hive还提供了一个现成参数hive.groupby.skewindata=true,开启后Hive会尝试把倾斜的group by拆成两个MapReduce Job:第一个Job对加盐后的key做部分聚合,第二个Job对结果做最终聚合。这个参数在常规业务场景已经能解决大部分group by倾斜,但代价是额外多一轮计算,任务时间可能变长,所以不要无脑开启,只在确认倾斜时使用。

3.2 小文件问题与group by的关联治理

很多团队在做Hive优化时,痛点不是SQL性能本身,而是小文件问题。有段时间我排查一个离线数仓任务,发现输出目录下有上千个几十KB的小文件,每次下游加载都慢得离谱,后来定位到根因就是group by的Reduce数量太多,而且每个Reducer的数据输入量差异巨大,输出被切割得很碎。

Reduce数量主要由hive.exec.reducers.bytes.per.reducer控制,默认是256MB或者1GB,具体看版本。在实际调优中,我会按这个公式估算:

reducer数量 = min(hive.exec.reducers.max, 输入数据量 / hive.exec.reducers.bytes.per.reducer)

如果每个Reducer处理的数据量远小于阈值,比如只有几十MB,Reduce数量就会很多,输出的小文件自然也多。解决办法是调大hive.exec.reducers.bytes.per.reducer,让每个Reducer分配到更多数据,减少Reduce数量。但要注意,Reduce数量太少也不行,比如一个Reduce扛10GB数据,单个任务会慢到怀疑人生。

另一个治理方式是强制合并小文件。hive.merge.mapfiles=true和hive.merge.mapredfiles=true可以分别控制Map Only任务和MapReduce任务的输出合并。还有hive.merge.smallfiles.avgsize,可以设置一个平均大小阈值,小于这个阈值就触发合并。这些参数在动态分区插入场景尤为重要,因为动态分区本身就容易产生大量小文件,group by只是加剧了问题。

我遇到过最典型的组合坑是:一张大表动态分区按天写入,分区键的基数虽有1000多个,但每个分区数据量很小,结果写出来的文件全是几十KB的小块。我当时的处理方式是先把原SQL拆成“先聚合中间结果,再动态分区写入”两步,并且给中间结果的外层查询单独设置更合理的Reduce数量,再配合distribute by和文件合并参数,最终把小文件数量降了一个数量级。distribute by这里的作用是控制数据如何分布给Reducer,比如distribute by day可以保证同一天的数据进同一个Reducer,每个Reducer输出一个针对该分区的文件,避免每个Reducer都朝所有分区写碎片。

3.3 为什么count(distinct)不能用Combiner,以及自定义UDAF的用武之地

Hive的Map端聚合,底层依赖一个能力叫Combiner。Combiner对聚合函数有一个隐藏要求:函数必须是“结合律”的,中间结果可以任意合并而不影响最终结果。count(distinct x)不满足这一要求,因为distinct要求全局去重,Map端把部分数据合并成“部分去重后的集合”后,这个集合之间再做合并时,还需要感知全局的重复情况,这个过程没法用简单的计数规约来完成。所以count(distinct)在很多版本里不能走Combiner优化,部分数据照样全量穿透到Reduce。

这个限制也给自定义UDAF留出了空间。比如Hive提供了approx_count_distinct这个函数,底层基于HyperLogLog算法,不精确但速度快。它允许在Map端记录一个固定大小的HLL位图,Combine阶段直接合并位图,Reduce阶段估算基数。实测下来,在百亿级数据上做近似去重,approx_count_distinct比count(distinct)快非常多,误差通常能控制在1%以内,很多大厂做UV实时统计用的就是这类方案。

如果业务要求精确去重,又不想忍受count(distinct)的单Reduce问题,一般会自己写一个UDAF。例如实现一个基于Roaring Bitmap的UDAF,把字段值映射成整数并压入Bitmap,Map端局部构造Bitmap,Combine阶段合并Bitmap,Reduce阶段统计BitMap的基数。我见过不少团队把这种方案用在数亿规模的ID去重上,效果比直接count(distinct)稳定很多。

写自定义UDAF时有几个细节要特别注意。第一,initialize、iterate、merge、terminatePartial、terminate五个方法必须完整实现,尤其不能漏掉terminatePartial,没有它Map端聚合不会生效。第二,如果UDAF需要维护大量中间状态,比如Bitmap占内存很大,要考虑在Map端控制单个key的状态大小,必要时可以结合加盐进一步拆分。第三,UDAF的返回类型如果是复杂结构,要记得实现resolve方法让Hive能正确解析类型。

4. 新引擎与新技术:Hive 3.x、Tez/Spark与Doris的对比

4.1 Hive 3.x + Tez/Spark下,局面发生了哪些变化

很多朋友的印象还停留在“Hive用MapReduce,distinct一定慢”。Hive 3.x以后,事情有了变化。执行引擎换成Tez或Spark后,distinct不再只能走单Reduce。Spark SQL里的distinct会先做一次局部去重,再做一次全局去重,类似于两阶段聚合。Tez引擎下,Hive优化器也会尝试对去重查询做更多改写,尽量分散压力。

我分别在CDH 6.x的Hive 2.x + MapReduce环境和一个Hive 3.x + Tez环境上跑同样的SQL:10亿条记录,统计唯一order_id数量。Hive 3.x + Tez环境下,count(distinct order_id)和“内层group by order_id、外层count(*)”的耗时差距已经缩小到百分之十几,不像MapReduce时代动辄翻倍。但即使如此,group by在重复率高的大数据集上依然更稳,因为它的Map端聚合优势依然存在。

Hive 3.x还引入了LLAP这个概念,全称是Live Long and Process。LLAP让一部分常驻Executor留在节点上,缓存热数据,执行Short-lived查询时不需要重新启动Container。这个能力对交互式单条查询比如select count(distinct user_id) from table where condition帮助很大,因为瓶颈从“容器启动+调度开销”转移到了实际计算过程。但LLAP对内存要求高,集群压力原本已经很大时,开启LLAP反而可能把节点整崩,需要谨慎评估。

4.2 当你手边同时有Hive和Doris,SQL去重该交给谁

相关热搜词里有个hive与doris,这个组合其实代表了批处理和MPP数据库的分界问题。Doris是一个MPP架构的OLAP数据库,数据以列存为主,查询走向量化执行,MPP天然支持并行shuffle,去重、分组、join这些操作都分散到多个BE节点同时执行。在同等数据量下,Doris执行select count(distinct user_id)这类去重查询,速度往往比Hive快出一个量级,因为它不需要启动一堆Container,节点本身常驻,内存表结构也在。

但Doris能替代Hive吗?不能。Hive的价值在于海量离线数据的批处理、复杂的ETL流程、与HDFS生态的深度绑定,以及数据分布在超大集群时的稳定性。Doris更适合数据已经加工好之后,提供给业务方做交互式分析和报表查询。

所以我的建议非常明确:如果瓶颈出现在Hive的离线加工链路里,比如每天凌晨跑数,耗时在小时级别,那么优先优化Hive本身的SQL、参数和UDAF;如果需要给业务方提供一个秒级查询的入口,让他们自己对UV、去重用户做灵活分析,那把数据同步到Doris这类MPP仓库,让Hive做批处理,让Doris做查询,分工明确。

实际项目里,我见过太多团队试图用Hive扛交互式查询,最后把离线集群资源吃干榨净,业务方还是嫌慢。反过来,也有人试图用Doris跑几百亿规模的全量历史数据加工,结果导入超时、内存爆炸。正确思路是画清楚两者的边界:Hive负责“算出来”,Doris负责“查得快”。

5. 常见问题与排查实录

5.1 一句话排掉“列不存在”“group by having错误”这些坑

关于去重和分组相关的报错,我收集了几个高频问题,列成表方便你排查:

报错或问题现场可能原因解决思路
查询提示某个列不存在,比如漏掉了表别名列名拼写错误或表别名没生效用DESCRIBE表名确认字段,检查SQL里的名称和别名
group by之后在select里取了非分组字段语义限制,Hive默认不开启hive.groupby.orderby.position.alias等宽松选项要么把该字段加进group by,要么用聚合函数包裹
having里使用了select别名做过滤Hive的having不能直接引用部分版本的别名把过滤条件改写为对聚合函数结果的比较
SQL执行时报groups: cannot find name for group id运行任务的Linux用户不在预期用户组中,常见于容器环境检查任务运行用户和用户组,调整资源队列权限
任务输出文件个数暴涨,大量小文件Reduce数量过多或动态分区碎片化调整hive.exec.reducers.bytes.per.reducer,开启文件合并参数

这里特别说一下group by和having的配合。having是对分组后的结果进行过滤,所以having里引用的字段只能是分组字段或聚合函数的结果,不能引用原始明细字段。比如select day, count(distinct user_id) as uv from log group by day having uv > 100,这条在多数Hive版本会报错,因为having里不能直接用别名uv,要写成having count(distinct user_id) > 100。习惯了这个规则,写完SQL先自查一遍,能省掉很多无效调试。

5.2 一个真实案例:按日去重用户数的排查全过程

最后分享一个我记忆很深的真实问题。某个业务需要统计最近7天每天的去重用户数,SQL大概长这样:

select day, count(distinct user_id) as uv from user_action_log where day >= date_sub(current_date, 7) group by day;

线上集群是Hive 2.x + MapReduce。第一次跑,任务在Reduce阶段卡了将近40分钟,然后报OOM。我当时先看了两个东西:一是Reduce数量,结果日志显示只有1个Reduce,符合count(distinct)的单Reduce特征;二是数据分布,发现最后一天的数据量占了7天总量的70%左右,全部压向一个Reduce。

我给的优化分两步。第一步,先把瓶颈切掉,改用内层group by day, user_id去重,外层再group by day计数,这样Reduce可以按天分布,理论上7个Reducer并行,每个Reducer只处理一部分数据。

select day, count(*) as uv from ( select day, user_id from user_action_log where day >= date_sub(current_date, 7) group by day, user_id ) t group by day;

第二步是对这个方案的数据倾斜做预防。因为最后一天数据量异常大,仅仅按天分组还不够,大概率出现某一天的单Reducer还是吃不消。我就在内层group by时给user_id加了一个盐值,比如substr(hash(user_id), 1, 3),拆成更多局部桶,外层再按天汇总,相当于绕开了单天倾斜。

为了验证SQL改动后结果是否正确,我把旧SQL和新SQL各抽了100万条数据做测试对比,两个结果的UV完全一致。正式环境跑下来,整个任务从40分钟降到15分钟左右。后来我又把引擎切到Tez,时间进一步压缩到七八分钟。这个案例其实就把前面讲的原理都串联起来了:单Reduce带来的压力、group by的并行优势、加盐处理大key、新引擎对性能的改变。

5.3 关于hive引擎升级后SQL兼容性的一点点提醒

切引擎或者升级Hive大版本时,distinct和group by的语义基本不变,但执行计划和资源使用会变化。比如Hive 2.x里跑得好好的SQL,切到Spark后可能因为Spark默认的并行度、Shuffle分区数不同而变慢;切到Tez后,部分场景下Tez的容器复用机制会让任务启动变快,但如果你之前手动调了非常细致的Reduce参数,新引擎有可能不按你的预期执行。升级前一定要做一轮全量SQL回归验证,重点对比“数据量接近生产级的测试表上,去重结果是否一致、耗时是否正常”,不要只看一两条SQL。

写自定义UDAF或依赖某个UDAF时,同样要做引擎兼容性验证。Hive 3.x对UDAF的接口有了更严格的检查,老版本里能跑的UDAF在新的GenericUDAFResolver2体系下可能直接加载失败,这类坑在跨版本升级时防不胜防。

写在最后的一个选择建议

我自己在做技术选型时的判断标准已经比较固定了:数据量不大、去重字段又有索引类条件过滤时,怎么写都行,按业务可读性来;数据量大且字段重复率高,优先group by;多列去重统计,优先“内层group by + 外层聚合”;命中数据倾斜,加盐两阶段聚合;追求极速且可以接受近似值,直接上approx_count_distinct或自研HLL/Bitmap类UDAF;集群已经是Hive 3.x或Spark引擎,distinct的劣势缩小了,但仍要多观察执行计划,而不是听网上某一个版本的结论用到死。

如果非要说一条最有价值的经验,那就是:不要在写SQL之前就争论孰优孰劣,先看一眼执行计划,再拿真实数据量跑一次对比,最后根据Reduce数量和Shuffle量级做决定。数据工程师的直觉很重要,但生产环境的数据分布才是最终裁判。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 6:54:50

Matlab中实现Mask R-CNN实例分割:从数据准备到训练调参全攻略

简介:面向本硕博教研场景,一套基于Mask-RCNN的高精度目标检测与识别MATLAB仿真代码及配套操作视频,适用于希望在深度学习目标检测方向系统学习算法实现与代码调试的读者。资源共13个文件,rar压缩包约194.12MB,包含6个m…

作者头像 李华
网站建设 2026/9/28 6:52:59

嵌入式机械臂CAN控制与EEPROM参数管理实战

1. 这不是一份“代码阅读笔记”,而是一次嵌入式系统级的机械臂控制解剖如果你在B站刷到稚晖君的dummy机械臂视频,被那套流畅、紧凑、带着工业设计美感的五自由度结构吸引,又在GitHub上翻开源代码时一头雾水——CAN指令怎么发?EEPR…

作者头像 李华
网站建设 2026/9/28 6:52:39

基于Dify的hindsight机制:让AI应用学会自我复盘与持续进化

如果你像我一样在Dify上搭过几个AI应用,大概率逃不过这种场景:应用上线了,演示时候效果惊艳,可真落到用户手里,各种匪夷所思的回答就冒出来了。翻日志一看,问题明明很清晰——要么没检索到关键知识、要么上…

作者头像 李华
网站建设 2026/9/28 6:52:02

MapReduce分区器Partitioner详解:从原理到数据倾斜实战

MapReduce 里有一个问题,很多初学者做实训或者面试准备的时候都会碰到:明明已经写好了 Mapper 和 Reducer,程序也能跑通,但输出结果总是跟预期对不上。要么某个 key 的数据跑到了"错误"的 reduce 任务里,要么…

作者头像 李华
网站建设 2026/9/28 6:51:49

Springboot + Hyperledger Fabric 慈善救助上链系统

简介:一套面向高校计算机相关专业(人工智能、通信工程、自动化、电子信息、物联网等)课程设计与毕业设计的Springboot与Hyperledger Fabric整合项目,聚焦慈善救助场景下的信用区块链系统。资源共206个文件,压缩包约3.3…

作者头像 李华
网站建设 2026/9/28 6:51:07

非侵入式负荷分解Python实践:从总功率到电器级用电曲线

简介:一套基于Python实现的非侵入式负荷分解源码包,面向计算机、信息安全、物联网、自动化等相关专业的毕业设计、课程设计或期末大作业场景。项目选用UK-DALE数据集中house_2住户2013年2月至10月的数据,从数据导入、训练与测试集分割、模型构…

作者头像 李华