[记录点滴] 小心 Hadoop Speculative 调度策略

[记录点滴] 小心 Hadoop Speculative 调度策略

[0x00] 摘要

本文从一个bug入手,为大家展示Hadoop Speculative机制,以及编写mapreduce程序的注意点。

[0x01] 缘由

一个小弟来找我帮忙调试一个问题。

项目大概:用C++编写 map reduce程序,mapper会解析大文件为很多小文件。每个小文件名字是唯一确定的(小文件名字 = 大文件名字 + index),为了调试,文件名字又加入了生成文件的时间戳。

问题描述:程序运行成功,突然发现有很多“同名+不同时间戳”的文件。

这个小弟完全懵圈了,看代码没有任何异常,但就是同一个文件被生成多次。只能找我帮忙看看。

[0x02] 代码示例

map程序示例如下(仅仅是示例)

int main(int argc,char** argv) {
    string key;
    while(cin >> key) {
        key是文件名字,
        解析这个大文件为若干小文件,然后存储
        cout << key << "/t" << "1" << endl;
    }
    return 0;
}

reduce.cpp程序如下:

int main(int argc,char** argv) {
    string key,num;
    map<string,int> count; 
    map<string,int>::iterator it;
  
    while(cin >> key >> num) {
        it = count.find(key);
        if(it != count.end()) {
            it->second++;
        }
        else {
            count.insert(make_pair(key,1));
        }
    }

    for(it = count.begin(); it != count.end(); it++) {
        cout << it->first << "/t" << it->second << endl;
    }
    return 0;
}

看到这里,有的兄弟会提出疑问,这不就是wordcount的reduce代码嘛!

是的,这个就是wordcount的reduce代码。其实这个项目的reducer没有什么业务逻辑意义,业务完全在map程序中执行,或者说业务就是map的副产品而已。而这个就是最终问题所在

[0x03] 排查过程

其实从这个问题原因看,就是大致定位出问题点也没法找出最终原因。我们当时只能定位如下:

  • 从程序执行来看,map / reduce 没有任何问题。
  • 从文件名字和log看,同一个大文件被两个map node处理了,所以输出了“同名+不同时间戳”的文件。。
  • 但是从reduce结果看,"两个map node" 中有一个node没有输出(这里指的是输出到Reducer)

因为无法最终定位,所以只能从配置项,官方文档和网上资料中查找。

我们开始怀疑是Hadoop有备份或者高可用机制,这样就会启动多个task,但这样没法解释为啥有一个map node参与处理却没有参与最后输出。

最后觉得配置中有一个speculative.execution的字样平时没怎么关注,而且看起来和执行相关。于是就去查找,结果发现就是这家伙造成的。

[0x04] Speculative execution

4.1 掉队者

作业(job)提交时,会被map-reduce 框架的 JobTracker 拆成一系列的map任务、reduce任务在整个hadoop 集群的机器上执行。

由于一些原因,可能是硬件老化,软件层面的不恰当配置,程序Bug,负载不均或者其他的一些问题,导致在一个JOB下的多个TASK速度不一致,比如有的任务已经完成,但是有些任务可能只跑了10%,根据木桶原理,这些任务将成为整个JOB的短板。

Straggle(掉队者)是指那些跑的很慢但最终会成功完成的任务。一个掉队的Map任务会阻止Reduce任务开始执行。

4.2 推测执行

Hadoop不会尝试去诊断或者修复这些Straggle,但是可以识别那些跑的比较慢的任务。它会在集群的其他节点上去启动这些慢任务的多个实例作为备份,这就是hadoop的推测执行(speculative execution)。

如果集群启动了推测执行,这时为了最大限度的提高短板,Hadoop会为该task启动备份任务,让speculative task与原始task同时处理一份数据,哪个先运行完,则将谁的结果作为最终结果,并且在运行完成后Kill掉另外一个任务

推测执行(Speculative Execution)是通过利用更多的资源来换取时间的一种优化策略。

4.3 问题所在

我们这个bug就是因为推测执行造成的。

Hadoop发现有的任务执行慢,就启动了备份任务。这时候两个任务都对同样的文件进行解析,生成新的文件。这样文件就被重复生成了。但是因为推测执行机制,最终只有一个任务顺利完成,这就是为什么reducer只看到一个任务执行完成。

[0x05] 问题解决

MapReduce任务有两个参数可以控制Speculative Task:

  • mapred.map.tasks.speculative.execution: mapper阶段是否开启推测执行
  • mapred.reduce.tasks.speculative.execution: reducer阶段是否开启推测执行

这两个参数默认都为true。

所以我们直接修改mapred-site.xml,如下:

<property>
  <name>mapreduce.map.speculative</name>
  <value>false</value>
</property>

<property>
  <name>mapreduce.reduce.speculative</name>
  <value>false</value>
</property>

则没有重复文件输出。

[0x06] map编写注意点

map函数应该是幂等的,即同样的输入,如果map执行到一半退出,在另外一个节点重试这个map任务,则应该得到同样的业务逻辑和业务输出。

因此,编写map / reduce程序要特别小心,特别是和外部资源如数据库/文件相关时候。这时候如果误用了Speculative Task,则很容易发生重复读/写,产生异常。同时又额外消耗了节点资源。

[0x07] 源码分析

以下 hadoop 源码分析均摘录于 Hadoop2.6.0运行mapreduce之推断(speculative)执行(上)

这部分源码在 org.apache.hadoop.mapreduce.v2.app.speculate.DefaultSpeculator

maybeScheduleASpeculation用于计算map或者reduce任务推断调度的可能性。

  • maybeScheduleASpeculation方法首先根据当前Task的类型(map或reduce)获取相应类型任务的需要分配Container数量的缓存containerNeeds,然后遍历containerNeeds。
  • 遍历containerNeeds的执行步骤如下:
    1. 如果当前Job依然有未分配Container的Task,那么跳过当前循环,继续下一次循环。这说明如果当前Job的某一类型的Task依然存在未分配Container的,则不会进行任务推断;
    2. 从当前应用的上下文AppContext中获取Job,并获取此Job的所有的Task(map或者reduce);
    3. 计算允许执行推断的Task数量numberAllowedSpeculativeTasks(map或者reduce)。其中MINIMUM_ALLOWED_SPECULATIVE_TASKS的值是10,PROPORTION_TOTAL_TASKS_SPECULATABLE的值是0.01。numberAllowedSpeculativeTasks取MINIMUM_ALLOWED_SPECULATIVE_TASKS与PROPORTION_TOTAL_TASKS_SPECULATABLE*任务数量之积之间的最大值。因此我们知道,当Job的某一类型(map或者reduce)的Task的数量小于1100时,计算得到的numberAllowedSpeculativeTasks等于10,如果Job的某一类型(map或者reduce)的Task的数量大于等于1100时,numberAllowedSpeculativeTasks才会大于10。numberAllowedSpeculativeTasks变量可以有效防止大量任务同时启动备份任务所造成的资源浪费。
    4. 遍历Job对应的map任务或者reduce任务集合,调用speculationValue方法获取每一个Task的推断值。并在迭代完所有的map任务或者reduce任务后,获取这一任务集合中的推断值bestSpeculationValue最大的任务ID。
    5. 再次计算numberAllowedSpeculativeTasks,其中PROPORTION_RUNNING_TASKS_SPECULATABLE的值等于0.1,numberRunningTasks是处于运行中的Task。numberAllowedSpeculativeTasks取numberAllowedSpeculativeTasks与PROPORTION_RUNNING_TASKS_SPECULATABLE*numberRunningTasks之积之间的最大值。因此我们知道当Job的某一类型(map或者reduce)的正在运行中的Task的数量小于110时(假设第3步得到的numberAllowedSpeculativeTasks等于10),计算得到的numberAllowedSpeculativeTasks等于10,如果Job的某一类型(map或者reduce)的正在运行中的Task的数量大于等于110时,numberAllowedSpeculativeTasks才会大于10。
    6. 如果numberAllowedSpeculativeTasks大于numberSpeculationsAlready(已经推断执行过的Task数量),则调用addSpeculativeAttempt方法将第4步中选出的任务的任务ID添加到推断尝试中。
private int maybeScheduleASpeculation(TaskType type) {
    int successes = 0;
    long now = clock.getTime();
    //根据当前Task的类型(map或reduce)获取相应类型任务的需要分配Container数量的缓存containerNeeds
    ConcurrentMap<JobId,AtomicInteger> containerNeeds
        = type == TaskType.MAP ? mapContainerNeeds : reduceContainerNeeds;

    //遍历containerNeeds
    for (ConcurrentMap.Entry<JobId,AtomicInteger> jobEntry : containerNeeds.entrySet()) {
      // This race conditon is okay.  If we skip a speculation attempt we
      //  should have tried because the event that lowers the number of
      //  containers needed to zero hasn't come through,it will next time.
      // Also,if we miss the fact that the number of containers needed was
      //  zero but increased due to a failure it's not too bad to launch one
      //  container prematurely.
      //如果当前Job依然有未分配Container的Task,那么跳过当前循环,继续下一次循环。这说明如果当前Job的某一类型的Task依然存在未分配Container的,则不会进行任务推断;
      if (jobEntry.getValue().get() > 0) {
        continue;
      }

      int numberSpeculationsAlready = 0;
      int numberRunningTasks = 0;

      // loop through the tasks of the kind
      //从当前应用的上下文AppContext中获取Job,并获取此Job的所有的Task(map或者reduce)
      Job job = context.getJob(jobEntry.getKey());
      Map<TaskId,Task> tasks = job.getTasks(type);

      //计算允许执行推断的Task数量numberAllowedSpeculativeTasks(map或者reduce)
      int numberAllowedSpeculativeTasks
          = (int) Math.max(minimumAllowedSpeculativeTasks,proportionTotalTasksSpeculatable * tasks.size());

      TaskId bestTaskID = null;
      long bestSpeculationValue = -1L;

      // this loop is potentially pricey.
      // TODO track the tasks that are potentially worth looking at
      //遍历Job对应的map任务或者reduce任务集合,在迭代完所有的map任务或者reduce任务后,获取这一任务集合中的推断值bestSpeculationValue最大的任务ID。
      for (Map.Entry<TaskId,Task> taskEntry : tasks.entrySet()) {
        //调用speculationValue方法获取每一个Task的推断值。
        long mySpeculationValue = speculationValue(taskEntry.getKey(),now);

        if (mySpeculationValue == ALREADY_SPECULATING) {
          ++numberSpeculationsAlready;
        }

        if (mySpeculationValue != NOT_RUNNING) {
          ++numberRunningTasks;
        }

        //获取这一任务集合中的推断值bestSpeculationValue最大的任务ID
        if (mySpeculationValue > bestSpeculationValue) {
          bestTaskID = taskEntry.getKey();
          bestSpeculationValue = mySpeculationValue;
        }
      }
      //再次计算numberAllowedSpeculativeTasks
      numberAllowedSpeculativeTasks
          = (int) Math.max(numberAllowedSpeculativeTasks,proportionRunningTasksSpeculatable * numberRunningTasks);

      // If we found a speculation target,fire it off
      //如果numberAllowedSpeculativeTasks大于numberSpeculationsAlready(已经推断执行过的Task数量),则调用addSpeculativeAttempt方法将第4步中选出的任务的任务ID添加到推断尝试中。
      if (bestTaskID != null
          && numberAllowedSpeculativeTasks > numberSpeculationsAlready) {
        addSpeculativeAttempt(bestTaskID);
        ++successes;
      }
    }

    return successes;
}

speculationValue方法主要用于估算每个任务的推断值。主要是“备份任务实例运行”的结束时间 和 “原任务实例的结束时间” 的差值,越大则调度执行的价值越大。

speculationValue方法的执行步骤如下:

  1. 如果任务还没有被推断执行,那么调用estimator的thresholdRuntime方法获取任务可以接受的运行时长acceptableRuntime。如果acceptableRuntime等于Long.MAX_VALUE,则将ON_SCHEDULE作为返回值,ON_SCHEDULE的值是Long.MIN_VALUE,以此表示当前任务的推断值很小,即被推断尝试的可能最小。
  2. 如果任务的运行实例数大于1,则说明此任务已经发生了推断执行,因此返回ALREADY_SPECULATING。ALREADY_SPECULATING等于Long.MIN_VALUE + 1。
  3. 调用estimator的estimatedRuntime方法获取任务运行实例的估算运行时长estimatedRunTime。
  4. 调用estimator的attemptEnrolledTime方法获取任务实例开始运行的时间,此时间即为startTimes中缓存的start。这个值是在任务实例启动时导致DefaultSpeculator的processSpeculatorEvent方法处理Speculator.EventType.ATTEMPT_START类型的SpeculatorEvent事件时保存的。
  5. estimatedEndTime表示估算任务实例的运行结束时间,estimatedEndTime = estimatedRunTime + taskAttemptStartTime。
  6. 调用estimator的estimatedNewAttemptRuntime方法估算如果此时重新为任务启动一个实例,此实例运行结束的时间estimatedReplacementEndTime。
  7. 如果缓存中没有任务实例的历史统计信息,那么将estimatedRunTime、任务实例进度progress,当前时间封装为历史统计信息缓存起来。
  8. 如果缓存中存在任务实例的历史统计信息,如果缓存的estimatedRunTime和本次估算的estimatedRunTime一样并且缓存的实例进度progress和本次获取的任务实例进度progress一样,说明有一段时间没有收到心跳了,则模拟一次心跳。如果缓存的estimatedRunTime和本次估算的estimatedRunTime不一样或者缓存的实例进度progress和本次获取的任务实例进度progress不一样,那么将estimatedRunTime、任务实例进度progress,当前时间更新到任务实例的历史统计信息中。
  9. 如果estimatedEndTime小于当前时间,则说明任务实例的进度良好,返回PROGRESS_IS_GOOD,PROGRESS_IS_GOOD等于Long.MIN_VALUE + 3。
  10. 如果estimatedReplacementEndTime大于等于estimatedEndTime,则说明即便启动备份任务实例也无济于事,因为它的结束时间达不到节省作业总运行时长的作用。
  11. 计算本次估算的结果值result,它等于estimatedEndTime - estimatedReplacementEndTime,当这个差值越大表示备份任务实例运行后比原任务实例的结束时间就越早,因此调度执行的价值越大。
  12. 如果numberRunningAttempts等于0,则表示当前任务还没有启动任务实例,返回NOT_RUNNING,NOT_RUNNING等于Long.MIN_VALUE + 4。
  13. 重新计算acceptableRuntime,处理方式与第1步相同。
  14. 返回result。
private long speculationValue(TaskId taskID,long now) {
    Job job = context.getJob(taskID.getJobId());
    Task task = job.getTask(taskID);
    Map<TaskAttemptId,TaskAttempt> attempts = task.getAttempts();
    long acceptableRuntime = Long.MIN_VALUE;
    long result = Long.MIN_VALUE;

    //如果任务还没有被推断执行,那么调用estimator的thresholdRuntime方法获取任务可以接受的运行时长acceptableRuntime
    if (!mayHaveSpeculated.contains(taskID)) {
      acceptableRuntime = estimator.thresholdRuntime(taskID);
      if (acceptableRuntime == Long.MAX_VALUE) {
        return ON_SCHEDULE;
      }
    }

    TaskAttemptId runningTaskAttemptID = null;

    int numberRunningAttempts = 0;

    for (TaskAttempt taskAttempt : attempts.values()) {
      if (taskAttempt.getState() == TaskAttemptState.RUNNING
          || taskAttempt.getState() == TaskAttemptState.STARTING) {
        //如果任务的运行实例数大于1,则说明此任务已经发生了推断执行,因此返回ALREADY_SPECULATING。
        if (++numberRunningAttempts > 1) {
          return ALREADY_SPECULATING;
        }
        runningTaskAttemptID = taskAttempt.getID();

        //调用estimator的estimatedRuntime方法获取任务运行实例的估算运行时长estimatedRunTime。
        long estimatedRunTime = estimator.estimatedRuntime(runningTaskAttemptID);

        //调用estimator的attemptEnrolledTime方法获取任务实例开始运行的时间,此时间即为startTimes中缓存的start。
        long taskAttemptStartTime
            = estimator.attemptEnrolledTime(runningTaskAttemptID);
        if (taskAttemptStartTime > now) {
          // This background process ran before we could process the task
          //  attempt status change that chronicles the attempt start
          return TOO_NEW;
        }

        //estimatedEndTime表示估算任务实例的运行结束时间
        long estimatedEndTime = estimatedRunTime + taskAttemptStartTime;

        //调用estimator的estimatedNewAttemptRuntime方法估算如果此时重新为任务启动一个实例,此实例运行结束的时间estimatedReplacementEndTime。
        long estimatedReplacementEndTime
            = now + estimator.estimatedNewAttemptRuntime(taskID);

        float progress = taskAttempt.getProgress();
        TaskAttemptHistoryStatistics data =
            runningTaskAttemptStatistics.get(runningTaskAttemptID);
        if (data == null) {
          //如果缓存中没有任务实例的历史统计信息,那么将estimatedRunTime、任务实例进度progress,当前时间封装为历史统计信息缓存起来。
          runningTaskAttemptStatistics.put(runningTaskAttemptID,new TaskAttemptHistoryStatistics(estimatedRunTime,progress,now));
        } else {
          //如果缓存中存在任务实例的历史统计信息
          if (estimatedRunTime == data.getEstimatedRunTime()
              && progress == data.getProgress()) {
            //如果缓存的estimatedRunTime和本次估算的estimatedRunTime一样并且缓存的实例进度progress和本次获取的任务实例进度progress一样
            // Previous stats are same as same stats
            if (data.notHeartbeatedInAWhile(now)
                || estimator.hasStagnatedProgress(runningTaskAttemptID,now)) {
              // Stats have stagnated for a while,simulate heart-beat.
              TaskAttemptStatus taskAttemptStatus = new TaskAttemptStatus();
              taskAttemptStatus.id = runningTaskAttemptID;
              taskAttemptStatus.progress = progress;
              taskAttemptStatus.taskState = taskAttempt.getState();
              // Now simulate the heart-beat
              //说明有一段时间没有收到心跳了,则模拟一次心跳。
              handleAttempt(taskAttemptStatus);
            }
          } else {
            //如果缓存的estimatedRunTime和本次估算的estimatedRunTime不一样或者缓存的实例进度progress和本次获取的任务实例进度progress不一样
            // Stats have changed - update our data structure
            //将estimatedRunTime、任务实例进度progress,当前时间更新到任务实例的历史统计信息中。
            data.setEstimatedRunTime(estimatedRunTime);
            data.setProgress(progress);
            data.resetHeartBeatTime(now);
          }
        }

        //如果estimatedEndTime小于当前时间,则说明任务实例的进度良好,返回PROGRESS_IS_GOOD,PROGRESS_IS_GOOD等于Long.MIN_VALUE + 3。
        if (estimatedEndTime < now) {
          return PROGRESS_IS_GOOD;
        }

        //如果estimatedReplacementEndTime大于等于estimatedEndTime,则说明即便启动备份任务实例也无济于事,因为它的结束时间达不到节省作业总运行时长的作用。
        if (estimatedReplacementEndTime >= estimatedEndTime) {
          return TOO_LATE_TO_SPECULATE;
        }

        //计算本次估算的结果值result,它等于estimatedEndTime - estimatedReplacementEndTime,当这个差值越大表示备份任务实例运行后比原任务实例的结束时间就越早,因此调度执行的价值越大。
        result = estimatedEndTime - estimatedReplacementEndTime;
      }
    }

    // If we are here,there's at most one task attempt.
    if (numberRunningAttempts == 0) {
      //如果numberRunningAttempts等于0,则表示当前任务还没有启动任务实例,返回NOT_RUNNING,NOT_RUNNING等于Long.MIN_VALUE + 4。
      return NOT_RUNNING;
    }

    //重新计算acceptableRuntime,处理方式与第1步相同。
    if (acceptableRuntime == Long.MIN_VALUE) {
      acceptableRuntime = estimator.thresholdRuntime(taskID);
      if (acceptableRuntime == Long.MAX_VALUE) {
        return ON_SCHEDULE;
      }
    }

    return result;
}

[0x0FF] 参考

Hadoop中Speculative Task调度策略

Hadoop之推测执行

Hadoop map-reduce的 speculative execution(推测执行)

Hadoop2.6.0运行mapreduce之推断(speculative)执行(上)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读5.3k次,点赞10次,收藏39次。本章详细写了mysql的安装,环境的搭建以及安装时常见的问题和解决办法。_mysql安装及配置超详细教程
文章浏览阅读1.8k次,点赞50次,收藏31次。本篇文章讲解Spark编程基础这门课程的期末大作业,主要围绕Hadoop基本操作、RDD编程、SparkSQL和SparkStreaming编程展开。_直接将第4题的计算结果保存到/user/root/lisi目录中lisipi文件里。
文章浏览阅读7.8k次,点赞9次,收藏34次。ES查询常用语法目录1. ElasticSearch之查询返回结果各字段含义2. match 查询3. term查询4. terms 查询5. range 范围6. 布尔查询6.1 filter加快查询效率的原因7. boosting query(提高查询)8. dis_max(最佳匹配查询)9. 分页10. 聚合查询【内含实际的demo】_es查询语法
文章浏览阅读928次,点赞27次,收藏18次。
文章浏览阅读1.1k次,点赞24次,收藏24次。作用描述分布式协调和一致性协调多个节点的活动,确保一致性和顺序。实现一致性、领导选举、集群管理等功能,确保系统的稳定和可靠性。高可用性和容错性Zookeeper是高可用的分布式系统,通过多个节点提供服务,容忍节点故障并自动进行主从切换。作为其他分布式系统的高可用组件,提供稳定的分布式协调和管理服务,保证系统的连续可用性。配置管理和动态更新作为配置中心,集中管理和分发配置信息。通过订阅机制,实现对配置的动态更新,以适应系统的变化和需求的变化。分布式锁和并发控制。
文章浏览阅读1.5k次,点赞26次,收藏29次。为贯彻执行集团数字化转型的需要,该知识库将公示集团组织内各产研团队不同角色成员的职务“职级”岗位的评定标准;
文章浏览阅读1.2k次,点赞26次,收藏28次。在安装Hadoop之前,需要进行以下准备工作:确认操作系统:Hadoop可以运行在多种操作系统上,包括Linux、Windows和Mac OS等。选择适合你的操作系统,并确保操作系统版本符合Hadoop的要求。安装Java环境:Hadoop是基于Java开发的,因此需要先安装和配置Java环境。确保已经安装了符合Hadoop版本要求的Java Development Kit (JDK),并设置好JAVA_HOME环境变量。确认硬件要求:Hadoop是一个分布式系统,因此需要多台计算机组成集群。
文章浏览阅读974次,点赞19次,收藏24次。# 基于大数据的K-means广告效果分析毕业设计 基于大数据的K-means广告效果分析。
文章浏览阅读1.7k次,点赞6次,收藏10次。Hadoop入门理论
文章浏览阅读1.3w次,点赞28次,收藏232次。通过博客和文献调研整理的一些农业病虫害数据集与算法。_病虫害数据集
文章浏览阅读699次,点赞22次,收藏7次。ZooKeeper使用的是Zab(ZooKeeper Atomic Broadcast)协议,其选举过程基于一种名为Fast Leader Election(FLE)的算法进行。:每个参与选举的ZooKeeper服务器称为一个“Follower”或“Candidate”,它们都有一个唯一的标识ID(通常是一个整数),并且都知道集群中其他服务器的ID。总之,ZooKeeper的选举机制确保了在任何时刻集群中只有一个Leader存在,并通过过半原则保证了即使部分服务器宕机也能维持高可用性和一致性。
文章浏览阅读10w+次,点赞62次,收藏73次。informatica 9.x是一款好用且功能强大的数据集成平台,主要进行各类数据库的管理操作,是使用相当广泛的一款ETL工具(注: ETL就是用来描述将数据从源端经过抽取(extract)、转换(transform)、加载(load)到目的端的过程)。本文主要为大家图文详细介绍Windows10下informatica powercenter 9.6.1安装与配置步骤。文章到这里就结束了,本人是在虚拟机中装了一套win10然后在此基础上测试安装的这些软件,因为工作学习要分开嘛哈哈哈。!!!!!_informatica客户端安装教程
文章浏览阅读7.8w次,点赞245次,收藏2.9k次。111个Python数据分析实战项目,代码已跑通,数据可下载_python数据分析项目案例
文章浏览阅读1.9k次,点赞61次,收藏64次。TDH企业级一站式大数据基础平台致力于帮助企业更全面、更便捷、更智能、更安全的加速数字化转型。通过数年时间的打磨创新,已帮助数千家行业客户利用大数据平台构建核心商业系统,加速商业创新。为了让大数据技术得到更广泛的使用与应用从而创造更高的价值,依托于TDH强大的技术底座,星环科技推出TDH社区版(Transwarp Data Hub Community Edition)版本,致力于为企业用户、高校师生、科研机构以及其他专业开发人员提供更轻量、更简单、更易用的数据分析开发环境,轻松应对各类人员数据分析需求。_星环tdh没有hive
文章浏览阅读836次,点赞21次,收藏19次。
文章浏览阅读1k次,点赞21次,收藏15次。主要介绍ETL相关工作的一些概念和需求点
文章浏览阅读1.4k次。本文以Android、java为开发技术,实现了一个基于Android的博物馆线上导览系统 app。基于Android的博物馆线上导览系统 app的主要使用者分为管理员和用户,app端:首页、菜谱信息、甜品信息、交流论坛、我的,管理员:首页、个人中心、用户管理、菜谱信息管理、菜谱分类管理、甜品信息管理、甜品分类管理、宣传广告管理、交流论坛、系统管理等功能。通过这些功能模块的设计,基本上实现了整个博物馆线上导览的过程。
文章浏览阅读897次,点赞19次,收藏26次。1.背景介绍在当今的数字时代,数据已经成为企业和组织中最宝贵的资源之一。随着互联网、移动互联网和物联网等技术的发展,数据的产生和收集速度也急剧增加。这些数据包括结构化数据(如数据库、 spreadsheet 等)和非结构化数据(如文本、图像、音频、视频等)。这些数据为企业和组织提供了更多的信息和见解,从而帮助他们做出更明智的决策。业务智能(Business Intelligence,BI)...
文章浏览阅读932次,点赞22次,收藏16次。也就是说,一个类应该对自己需要耦合或调用的类知道的最少,类与类之间的关系越密切,耦合度越大,那么类的变化对其耦合的类的影响也会越大,这也是我们面向对象设计的核心原则:低耦合,高内聚。优秀的架构和产品都是一步一步迭代出来的,用户量的不断增大,业务的扩展进行不断地迭代升级,最终演化成优秀的架构。其根本思想是强调了类的松耦合,类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会波及有关系的类。缓存,从操作系统到浏览器,从数据库到消息队列,从应用软件到操作系统,从操作系统到CPU,无处不在。
文章浏览阅读937次,点赞22次,收藏23次。大数据可视化是关于数据视觉表现形式的科学技术研究[9],将数据转换为图形或图像在屏幕上显示出来,并进行各种交互处理的理论、方法和技术。将数据直观地展现出来,以帮助人们理解数据,同时找出包含在海量数据中的规律或者信息,更多的为态势监控和综合决策服务。数据可视化是大数据生态链的最后一公里,也是用户最直接感知数据的环节。数据可视化系统并不是为了展示用户的已知的数据之间的规律,而是为了帮助用户通过认知数据,有新的发现,发现这些数据所反映的实质。大数据可视化的实施是一系列数据的转换过程。