1.JobTracker能否决定给当前的TaskTracker节点分配一个Job的具体的哪一个任务?
2.什么是map本地任务?
3.nonRunningMapCache的作用是什么?
4.从TaskTracker节点上分配挂载的本地任务时,如果以前发生过该TaskTracker节点执行某一Map任务失败了的情况,则应将该Map任务该如何处理?
分享到:  QQ好友和群 腾讯微博 QQ空间

收藏 转播 分享 淘帖 支持 反对

欢迎加入about云群371358502、39327136,云计算爱好者群,亦可关注about云腾讯认证空间||关注本站微信
 
回复

使用道具 举报

   
pig2

681

主题

1201

帖子

6384

积分

超级版主

积分
6384

  • 收听TA
  • 发消息
沙发

 楼主| 发表于 2014-4-6 23:51:53 | 只看该作者

众 所周知,JobTracker节点使用配置的任务调度器TaskScheduler来为某一个具体的TaskTracker节点分配任务,同时这个任务调 度器只能决定给该TaskTracker节点分配哪一个Job或者那些Job的任务以及分配多少个任务,但是它却不能决定给当前的TaskTracker 节点分配一个Job的具体的哪一个任务。

另外,针对一个具体的TaskTracker节点而言,任何一个作业都可以判断它的那些Map任务相对于该TaskTracker节点来说属于本地任务,那些Map任务是属于非本地任务的,当然,对于Reduce任务来说,是没有本地任务与非本地任务这一说法的。

因此,具体来讲就是,当任务调度器决定为一个TaskTracker节点分配一个Job的本地任务时,它会调用该JobInProgress对象的 obtainNewLocalMapTask()方法,分配一个非本地任务时,它会调用对应的obtainNewNonLocalMapTask()方 法,那么以这个TaskTracker节点在集群中的物理位置为参考,这个Job可能有多个本地任务和多个非本地任务,至于为该TaskTracker节 点分配哪一个本地或者非本地任务就由JobInProgress来决定了;当任务调度器为TaskTracker节点分配一个Job的Reduce任务 时,就会调用该Job对应的JobInProgress对象的obtainNewReduceTask()方法。至于JobInProgress对象究竟 是如何分配一个本地或非本地Map任务、Reduce任务的,那将是本文接下来要详细讲述的重点了。

1.分配作业的Map任务


     作业的Map任务之所以有本地和非本地之分,主要是因为该Map任务的输入数据和执行该Map任务的TaskTracker节点在集群中的位置有可 能同。本地与非本地Map任务是相对于执行或将要执行该任务的TaskTracker节点来说的,当任务调度器决定为一个TaskTracker节点分配 某一个Job的一个本地Map任务时,它(JobInProgress)会查找这个Job中的那些Map任务的输入数据合该TaskTracker节点在 同一台PC或机架上,那么这些Map任务对于TaskTracker节点来说就是本地任务了。这里要值得一提的是,在作业初始化的时候,就为每一个Map 任务做了一个本地化的预分配工作,即根据Map任务的输入数据的物理位置,将该Map任务挂载到对应的物理节点上,该过程的源代码为:

  1. private Map<Node, List<TaskInProgress>> createCache(JobClient.RawSplit[] splits, int maxLevel) {
  2. Map<Node, List<TaskInProgress>> cache = new IdentityHashMap<Node, List<TaskInProgress>>(maxLevel);
  3. for (int i = 0; i < splits.length; i++) {
  4. String[] splitLocations = splits[i].getLocations();//获取该数据切片坐在的物理位置(多个副本)
  5. if (splitLocations.length == 0) {
  6. nonLocalMaps.add(maps[i]);
  7. continue;
  8. }
  9. //针对每一个副本的物理位置
  10. for(String host: splitLocations) {
  11. //解析副本在集群中的哪一个节点上
  12. Node node = jobtracker.resolveAndAddToTopology(host);
  13. LOG.info("tip:" + maps[i].getTIPId() + " has split on node:" + node);
  14. for (int j = 0; j < maxLevel; j++) {
  15. List<TaskInProgress> hostMaps = cache.get(node);
  16. if (hostMaps == null) {
  17. hostMaps = new ArrayList<TaskInProgress>();
  18. cache.put(node, hostMaps);
  19. hostMaps.add(maps[i]);//将Map任务挂载到该节点上
  20. }
  21. //去重,避免一个节点挂载了两个相同的Map任务
  22. if (hostMaps.get(hostMaps.size() - 1) != maps[i]) {
  23. hostMaps.add(maps[i]);
  24. }
  25. node = node.getParent();//获取节点的父节点(由于maxLevel的值是2,所以父节点就是rack节点)
  26. }
  27. }
  28. }
  29. return cache;
  30. }

复制代码

通过这样的一个预处理过程,最终Node与Map任务之间的映射关系被保存在它的一个属性nonRunningMapCache中了。当 JobInProgress为一个TaskTracker节点分配一个本地Map任务时,它可以只需要解析该TaskTracker节点在集群中的哪一个 节点node上,根据该node就可以从nonRunningMapCache中获取一个Map任务,该Map任务相对于当前这个TaskTracker 来说就是本地任务了;当JobInProgress为一个TaskTracker节点分配一个非本地Map任务时,它可以获取集群中所有的rack节点 (除它自己所在的rack外),通过这些rack节点node,就可以从nonRunningMapCache中获取一个Map任务,该Map任务相对于 当前这个TaskTracker来说就是非本地任务了。

根据上面的源代码可以看出,所谓的本地任务之分就是由maxLevel来确定的,即Map任务的输入数据与TaskTracker节点在集群中的物理距 离,在目前的版本中(Hadoop-0.20.2.0),maxLevel的默认值是2,也可由JobTracker节点的配置文件来设置,对应的配置项 为:mapred.task.cache.levels。另外,从上面的源代码可以看出,这个预处理过程也明确地定义了非本地Map任务,即map操作的 输入数据的位置为null的Map任务,这并不代表说该Map任务没有输入数据。因为,Hadoop为用户提供了自定义数据切片的API(用户自己实现 InputSplit),这里的RawSplit并没有直接保存map操作所需的输入数据的位置信息,而是对真正的InputSplit进行了封装,这告 诉我们两个很重要的情况,

一:用户在自定义Map任务的InputSplit时,应考虑这个Map任务是否可以作为某些TaskTracker节点的本地任务(比如某一个Map任务的输入数据在跨越多个节点,那么这个Map任务永远也不可能是本地任务);

二:Map任务的InputSplit实现可以为map操作带入少量的输入数据(例如,某一个Map任务需要两个输入数据,一个数据很大,另一个数据很 小,只有几百或上千Bytes,那么,用户就可以自定义一个InputSplit来保存这个小数据,很明显,用HDFS保存这样小的数据根本不划算)。这 个非本地Map任务保存在nonLocalMaps属性中。

1.1 分配本地Map任务

JobInProgress给某一个TaskTracker节点分配一个本地Map任务的操作比较的简单,不过,这其中有一个异常情况,就是当这个 TaskTracker节点无法被解析成集群中的一个Node时,那么,本次的本地Map任务分配会被当做一次分配非本地Map任务来操作。这个过程的源 代码如下:

  1. public synchronized Task obtainNewLocalMapTask(TaskTrackerStatus tts,int clusterSize, int numUniqueHosts) throws IOException {
  2. if (!tasksInited.get()) {
  3. return null;
  4. }
  5. //为当前的计算节点获取一个本地map任务
  6. int target = findNewMapTask(tts, clusterSize, numUniqueHosts, maxLevel, status.mapProgress());
  7. if (target == -1) {
  8. return null;
  9. }
  10. Task result = maps[target].getTaskToRun(tts.getTrackerName());
  11. if (result != null) {
  12. addRunningTaskToTIP(maps[target], result.getTaskID(), tts, true);
  13. }
  14. return result;
  15. }

复制代码

  1. /**
  2. * 为当前的计算节点从作业的map任务集中选取一个合适的任务;
  3. * 参数maxCacheLevel决定了当前分配的是本地任务还是非本地任务
  4. */
  5. private synchronized int findNewMapTask(final TaskTrackerStatus tts, final int clusterSize, final int numUniqueHosts, final int maxCacheLevel, final double avgProgress) {
  6. ...
  7. Node node = jobtracker.getNode(tts.getHost());  //根据当前计算节点的主机/IP来获取其在集群拓扑结构中对应的位置节点
  8. //
  9. // I) Non-running TIP :
  10. // 1. check from local node to the root [bottom up cache lookup]
  11. //    i.e if the cache is available and the host has been resolved
  12. //    (node!=null)
  13. if (node != null) {
  14. Node key = node;    //当前待分配的map任务的输入数据所在的节点
  15. int level = 0;
  16. // maxCacheLevel might be greater than this.maxLevel if findNewMapTask is
  17. // called to schedule any task (local, rack-local, off-switch or speculative)
  18. // tasks or it might be NON_LOCAL_CACHE_LEVEL (i.e. -1) if findNewMapTask is
  19. //  (i.e. -1) if findNewMapTask is to only schedule off-switch/speculative
  20. // tasks
  21. int maxLevelToSchedule = Math.min(maxCacheLevel, maxLevel);
  22. for (level = 0;level < maxLevelToSchedule; ++level) {
  23. List <TaskInProgress> cacheForLevel = nonRunningMapCache.get(key);    //获取节点key上还未分配的map任务
  24. if (cacheForLevel != null) {
  25. tip = findTaskFromList(cacheForLevel, tts, numUniqueHosts,level == 0);   //从一个map任务集中为当前的计算节点找到一个合适的任务
  26. if (tip != null) {
  27. // Add to running cache
  28. scheduleMap(tip);
  29. // remove the cache if its empty
  30. if (cacheForLevel.size() == 0) {
  31. nonRunningMapCache.remove(key);
  32. }
  33. return tip.getIdWithinJob();
  34. }
  35. }
  36. key = key.getParent();
  37. }
  38. // Check if we need to only schedule a local task (node-local/rack-local)
  39. if (level == maxCacheLevel) {
  40. return -1;
  41. }
  42. }
  43. ...
  44. }

复制代码

还有一个值得注意的问题就是,如果该TaskTracker节点所在的Node上有Map任务时,当从该Node上分配挂载的本地任务时,如果以前发生过 该TaskTracker节点执行某一Map任务失败了的情况,则应将该Map任务从Node上删除,同时,对于无法执行或正在执行的Map任务也应该从 Node上删除,对应的源码为:

  1. private synchronized TaskInProgress findTaskFromList(Collection<TaskInProgress> tips, TaskTrackerStatus ttStatus, int numUniqueHosts, boolean removeFailedTip) {
  2. Iterator<TaskInProgress> iter = tips.iterator();
  3. while (iter.hasNext()) {
  4. TaskInProgress tip = iter.next();
  5. // Select a tip if
  6. //   1. runnable   : still needs to be run and is not completed
  7. //   2. ~running   : no other node is running it
  8. //   3. earlier attempt failed : has not failed on this host
  9. //                               and has failed on all the other hosts
  10. // A TIP is removed from the list if
  11. // (1) this tip is scheduled
  12. // (2) if the passed list is a level 0 (host) cache
  13. // (3) when the TIP is non-schedulable (running, killed, complete)
  14. if (tip.isRunnable() && !tip.isRunning()) {
  15. // check if the tip has failed on this host
  16. if (!tip.hasFailedOnMachine(ttStatus.getHost()) || tip.getNumberOfFailedMachines() >= numUniqueHosts) {
  17. // check if the tip has failed on all the nodes
  18. iter.remove();
  19. return tip;
  20. }
  21. else if (removeFailedTip) {
  22. // the case where we want to remove a failed tip from the host cache
  23. // point#3 in the TIP removal logic above
  24. iter.remove();
  25. }
  26. } else {
  27. // see point#3 in the comment above for TIP removal logic
  28. iter.remove();
  29. }
  30. }
  31. return null;
  32. }

复制代码

1.2 分配非本地Map任务

JobInProgress为某一个TaskTracker节点分配一个非本地Map任务相对于分配一个本地任务来说要复杂的多,它首先会先从 nonRunningMapCache中选择一个非本地任务,如果没有找到再从nonLocalMaps中选择一个任务,如果还没有找到,则判断这个作业 是否设置了hasSpeculativeMaps,如果没有设置,则不再为该TaskTracker节点分配非本地Map任务了;如果设置了,则从正在被 其它TaskTracker节点执行的本地或非本地Map任务中选一个,不过这是有优先顺序的,首先从正在运行的runningMapCache中寻找一 个本地Map任务,如果没有找到再从runningMapCache中寻找一个非本地Map任务,最后再从nonLocalRunningMaps中寻找 一个非本地Map任务,此时还没有找到的话,就不再为该TaskTracker节点分配Map任务了。这个过程的源代码如下:

  1. public synchronized Task obtainNewNonLocalMapTask(TaskTrackerStatus tts, int clusterSize, int numUniqueHosts)
  2. throws IOException {
  3. if (!tasksInited.get()) {
  4. return null;
  5. }
  6. int target = findNewMapTask(tts, clusterSize, numUniqueHosts, NON_LOCAL_CACHE_LEVEL, status.mapProgress());
  7. if (target == -1) {
  8. return null;
  9. }
  10. Task result = maps[target].getTaskToRun(tts.getTrackerName());
  11. if (result != null) {
  12. addRunningTaskToTIP(maps[target], result.getTaskID(), tts, true);
  13. }
  14. return result;
  15. }
  16. private synchronized int findNewMapTask(final TaskTrackerStatus tts, final int clusterSize, final int numUniqueHosts, final int maxCacheLevel, final double avgProgress) {
  17. ...
  18. Collection<Node> nodesAtMaxLevel = jobtracker.getNodesAtMaxLevel();
  19. // get the node parent at max level
  20. Node nodeParentAtMaxLevel = (node == null) ? null : JobTracker.getParentNode(node, maxLevel - 1);
  21. for (Node parent : nodesAtMaxLevel) {
  22. // skip the parent that has already been scanned
  23. if (parent == nodeParentAtMaxLevel) {
  24. continue;
  25. }
  26. List<TaskInProgress> cache = nonRunningMapCache.get(parent);
  27. if (cache != null) {
  28. tip = findTaskFromList(cache, tts, numUniqueHosts, false);
  29. if (tip != null) {
  30. // Add to the running cache
  31. scheduleMap(tip);
  32. // remove the cache if empty
  33. if (cache.size() == 0) {
  34. nonRunningMapCache.remove(parent);
  35. }
  36. LOG.info("Choosing a non-local task " + tip.getTIPId());
  37. return tip.getIdWithinJob();
  38. }
  39. }
  40. }
  41. // 3. Search non-local tips for a new task
  42. tip = findTaskFromList(nonLocalMaps, tts, numUniqueHosts, false);
  43. if (tip != null) {
  44. // Add to the running list
  45. scheduleMap(tip);
  46. LOG.info("Choosing a non-local task " + tip.getTIPId());
  47. return tip.getIdWithinJob();
  48. }
  49. // II) Running TIP :
  50. if (hasSpeculativeMaps) {
  51. long currentTime = System.currentTimeMillis();
  52. // 1. Check bottom up for speculative tasks from the running cache
  53. if (node != null) {
  54. Node key = node;
  55. for (int level = 0; level < maxLevel; ++level) {
  56. Set<TaskInProgress> cacheForLevel = runningMapCache.get(key);
  57. if (cacheForLevel != null) {
  58. tip = findSpeculativeTask(cacheForLevel, tts, avgProgress, currentTime, level == 0);
  59. if (tip != null) {
  60. if (cacheForLevel.size() == 0) {
  61. runningMapCache.remove(key);
  62. }
  63. return tip.getIdWithinJob();
  64. }
  65. }
  66. key = key.getParent();
  67. }
  68. }
  69. // 2. Check breadth-wise for speculative tasks
  70. for (Node parent : nodesAtMaxLevel) {
  71. // ignore the parent which is already scanned
  72. if (parent == nodeParentAtMaxLevel) {
  73. continue;
  74. }
  75. Set<TaskInProgress> cache = runningMapCache.get(parent);
  76. if (cache != null) {
  77. tip = findSpeculativeTask(cache, tts, avgProgress, currentTime, false);
  78. if (tip != null) {
  79. // remove empty cache entries
  80. if (cache.size() == 0) {
  81. runningMapCache.remove(parent);
  82. }
  83. LOG.info("Choosing a non-local task " + tip.getTIPId() + " for speculation");
  84. return tip.getIdWithinJob();
  85. }
  86. }
  87. }
  88. // 3. Check non-local tips for speculation
  89. tip = findSpeculativeTask(nonLocalRunningMaps, tts, avgProgress, currentTime, false);
  90. if (tip != null) {
  91. LOG.info("Choosing a non-local task " + tip.getTIPId() + " for speculation");
  92. return tip.getIdWithinJob();
  93. }
  94. }
  95. return -1;
  96. }

复制代码

2. 分配作业的Reduce任务


      由于 Reduce任务的输入数据来源于该作业所有的Map任务的输出,而执行Map任务的TaskTracker节点将map的输出保存在自己本地,所以 Reduce任务的输入数据在绝大多数情况下不可能都在某一个TaskTracker节点上,因此对于任何一个TaskTracker节点来说没有本地和 非本地的Reduce任务之分。JobInProgress为某一个TaskTracker节点分配一个Reduce任务的操作就相当的简单了,这个过程 类似于分配非本地Map任务。

它首先直接从nonRunningReduces中寻找一个任务,如果没有找到则在看这个作业设置了hasSpeculativeReduces没有,若 没有则不分配了;若设置了,则从runningReduces中寻找一个正在被其它TaskTracker节点执行的Reduce任务分配给该 TaskTracker节点。该过程对应的源代码如下:

  1. public synchronized Task obtainNewReduceTask(TaskTrackerStatus tts, int clusterSize, int numUniqueHosts) throws IOException {
  2. if (status.getRunState() != JobStatus.RUNNING) {
  3. return null;
  4. }
  5. // Ensure we have sufficient map outputs ready to shuffle before
  6. // scheduling reduces
  7. if (!scheduleReduces()) {
  8. return null;
  9. }
  10. int  target = findNewReduceTask(tts, clusterSize, numUniqueHosts, status.reduceProgress());
  11. if (target == -1) {
  12. return null;
  13. }
  14. Task result = reduces[target].getTaskToRun(tts.getTrackerName());
  15. if (result != null) {
  16. addRunningTaskToTIP(reduces[target], result.getTaskID(), tts, true);
  17. }
  18. return result;
  19. }
  20. private synchronized int findNewReduceTask(TaskTrackerStatus tts, int clusterSize, int numUniqueHosts, double avgProgress) {
  21. if (numReduceTasks == 0) {
  22. return -1;
  23. }
  24. String taskTracker = tts.getTrackerName();
  25. TaskInProgress tip = null;
  26. // Update the last-known clusterSize
  27. this.clusterSize = clusterSize;
  28. if (!shouldRunOnTaskTracker(taskTracker)) {
  29. return -1;
  30. }
  31. long outSize = resourceEstimator.getEstimatedReduceInputSize();
  32. long availSpace = tts.getResourceStatus().getAvailableSpace();
  33. if(availSpace < outSize) {
  34. LOG.warn("No local disk space for reduce task. TaskTracker[" + taskTracker + "] has " + availSpace + " bytes free; but we expect reduce input to take " + outSize);
  35. return -1; //see if a different TIP might work better.
  36. }
  37. // 1. check for a never-executed reduce tip
  38. // reducers don't have a cache and so pass -1 to explicitly call that out
  39. tip = findTaskFromList(nonRunningReduces, tts, numUniqueHosts, false);
  40. if (tip != null) {
  41. scheduleReduce(tip);
  42. return tip.getIdWithinJob();
  43. }
  44. // 2. check for a reduce tip to be speculated
  45. if (hasSpeculativeReduces) {
  46. tip = findSpeculativeTask(runningReduces, tts, avgProgress, System.currentTimeMillis(), false);
  47. if (tip != null) {
  48. scheduleReduce(tip);
  49. return tip.getIdWithinJob();
  50. }
  51. }
  52. return -1;
  53. }
  54. private synchronized TaskInProgress findSpeculativeTask(Collection<TaskInProgress> list, TaskTrackerStatus ttStatus, double avgProgress, long currentTime, boolean shouldRemove) {
  55. Iterator<TaskInProgress> iter = list.iterator();
  56. while (iter.hasNext()) {
  57. TaskInProgress tip = iter.next();
  58. // should never be true! (since we delete completed/failed tasks)
  59. if (!tip.isRunning()) {
  60. iter.remove();
  61. continue;
  62. }
  63. //当前TaskTracker节点没有运行该任务
  64. if (!tip.hasRunOnMachine(ttStatus.getHost(), ttStatus.getTrackerName())) {
  65. if (tip.hasSpeculativeTask(currentTime, avgProgress)) {
  66. // In case of shared list we don't remove it. Since the TIP failed
  67. // on this tracker can be scheduled on some other tracker.
  68. if (shouldRemove) {
  69. iter.remove(); //this tracker is never going to run it again
  70. }
  71. return tip;
  72. }
  73. } else {
  74. // Check if this tip can be removed from the list.
  75. // If the list is shared then we should not remove.
  76. if (shouldRemove) {
  77. // This tracker will never speculate this tip
  78. iter.remove();
  79. }
  80. }
  81. }
  82. return null;
  83. }

复制代码

在目前的Hadoop版本设计中,作业中任务的调度细节被封装到了JobInProgress中,使得作业调度器TaskScheduler可完全控制的 调度粒度限制在Job级,同时JobInProgress为上层的TaskScheduler实现的任务调度提供API,这样做就大大地降低了用户自行设 计TaskScheduler的门槛,即可以很容易的根据自己的应用场景集中在作业级别上实现合适的调度策略。

分析JobInProgress中Map/Reduce任务分配相关推荐

  1. python中sorted函数的用法_Python中map,reduce,filter和sorted函数的使用方法

    map map(funcname, list) python的map 函数使得函数能直接以list的每个元素作为参数传递到funcname中, 并返回响应的新的list 如下: def sq(x): ...

  2. 对云计算中几种基础设施(Dynamo,Bigtable,Map/Reduce等)的朴素看法

    前言 云计算的概念近期可谓如火如荼,备受关注.我先前听到"云"这个名词时,很是觉得太过玄乎--也不知道它用在哪里,更不了解它如何实现,总有雾里看花的感觉! 好在近期工作需要的缘故, ...

  3. 使用Mongo Shell和Java驱动程序的MongoDB Map Reduce示例

    Map Reduce is a data processing technique that condenses large volumes of data into aggregated resul ...

  4. MapReduce剖析笔记之五:Map与Reduce任务分配过程

    转载:https://www.cnblogs.com/esingchan/p/3940565.html 在上一节分析了TaskTracker和JobTracker之间通过周期的心跳消息获取任务分配结果 ...

  5. python中all函数的用法_python中map、any、all函数用法分析

    这篇文章主要介绍了 python 中 map . any . all 函数用法 , 实例分析了 map . any . all 函数 的相关使用技巧 , 具有一定参考借鉴价值 , 需要的朋友可以参考下 ...

  6. python3函数中lambda/filter/map/reduce的用法

    lambda/filter/map/reduce这几个函数面试中很肯定会用到,本篇主要介绍这几个函数的用法. 1.lambda 匿名函数,用法如下: # lambada 参数,参数,参数 : 返回的表 ...

  7. MapReduce框架中map、reduce方法的运行机制

    MapReduce框架中map.reduce方法的运行机制 Hadoop的API中提供了Mapper和Reducer抽象类,分别有个抽象map()方法和reduce()方法,使用时只需实现该抽象类和抽 ...

  8. Python函数式编程中map()、reduce()和filter()函数的用法

    Python中map().reduce()和filter()三个函数均是应用于序列的内置函数,分别对序列进行遍历.递归计算以及过滤操作.这三个内置函数在实际使用过程中常常和"行内函数&quo ...

  9. hadoop中map和reduce的数量设置问题

    转载http://my.oschina.net/Chanthon/blog/150500 map和reduce是hadoop的核心功能,hadoop正是通过多个map和reduce的并行运行来实现任务 ...

最新文章

  1. 触发transition的几种方式--转
  2. c++学习笔记之基础篇
  3. php 一片空白,解决运行PHP一片空白
  4. idea mybatis generator插件_SpringBoot+MyBatis+Druid整合demo
  5. Docker+Jenkins+Git+GitLab实现DevOps
  6. 当学术大家遇到技术大拿,如何攻克数据库应用头号难题?数位产学研大咖这样解读
  7. mysql启多_MySQL启多个实例
  8. python获取天气数据_python获取天气数据
  9. 语言怎么解决扭魔方_吧台高度没留够,怎么坐都变扭,直接加玻璃框没想到也能解决难题...
  10. 吴恩达机器学习week2
  11. 冒泡排序最佳情况的时间复杂度,为什么是O(n)
  12. 二广高速公路4标段道路设计--武汉理工大学本科生毕业设计
  13. 微信小程序轮播图点击跳转页面
  14. 维基百科怎么做_维基百科创建修改技巧分享!
  15. STM32蓝牙小车以及PWM调速
  16. Java毕业设计_基于javaweb的网上预约实验室管理系统的设计与实现
  17. 淘宝电商项目落地,从零开始搭建亿级系统架构笔记
  18. 【ThreeJS基础教程-初识Threejs】1.ThreeJS的HelloWorld
  19. 简单的银行管理系统(功能齐备)
  20. 统计建模与R软件-第三章习题答案

热门文章

  1. Rabbitmq专题:springboot如何整合Rabbitmq?Rabbitmq有哪些工作模式?
  2. 配置配置DruidDataSource
  3. Netty入门笔记-Linux网络I/O模型介绍
  4. Android Retrofit框架请求复杂json数据
  5. 当CNI遇上Kata-KataNative的CNI扩展
  6. linux下C语言简单实现线程池
  7. java 时间序列预测_基于spark的时间序列预测包Sparkts._的使用
  8. Zookeeper的一些Bugs
  9. 我来重新学习 javascript 的面向对象(part 1)
  10. Android WebView与JS交互入门