转载请在页首明显处注明作者与出处

一:说明

此为大数据系列的一些博文,有空的话会陆续更新,包含大数据的一些内容,如hadoop,spark,storm,机器学习等。

当前使用的hadoop版本为2.6.4

此为mapreducer的第二章节

这一章节中有着 计算共同好友,推荐可能认识的人

上一篇:hadoop系列三:mapreduce的使用(一)

  1. 一:说明
  2. 二:在开发工具在运行mapreducer
  3. 2.1:本地模式运行mapreducer
  4. 2.2:在开发工具中运行在yarn
  5. 三:mapreduce实现join
  6. 3.1:sql数据库中的示例
  7. 3.2:mapreduce的实现思路
  8. 3.3:创建相应的javabean
  9. 3.4:创建mapper
  10. 3.5:创建reduce
  11. 3.6:完整代码
  12. 3.7:数据倾斜的问题
  13. 四:查找共同好友,计算可能认识的人
  14. 4.1:准备数据
  15. 4.2:计算指定用户是哪些人的好友
  16. 4.3:计算共同好友
  17. 五:使用GroupingComparator分组计算最大值
  18. 5.1:定义一个javabean
  19. 5.2:定义一个GroupingComparator
  20. 5.3:map代码
  21. 5.4:reduce的代码
  22. 5.5:启动类
  23. 六:自定义输出位置
  24. 6.1:自定义FileOutputFormat
  25. 七:自定义输入数据
  26. 八:全局计数器
  27. 九:多个job串联,定义执行顺序
  28. 十:mapreduce的参数优化
  29. 10.1:资源相关参数
  30. 10.2:容错相关参数
  31. 10.3:本地运行mapreduce作业
  32. 10.4:效率和稳定性相关参数

二:在开发工具在运行mapreducer

之前我们一直是在开发工具中写好了代码,然后打包成jar包在服务器中以hadoop jar的形式运行,当然这个极其麻烦,毕竟上传这个部署还是很麻烦的,其次就是每改一次代码,都需要重新打包到服务器中。还有一个最大的缺点就是没有办法打断点调试一些业务代码,这对于定位代码问题极其困难。这里也有两个办法。

2.1:本地模式运行mapreducer

何为本地模式,就是不是运行在yarn上面,仅仅是以运行在本地的一个模式。

首先既然是运行在本地,就需要有所有mapreducer的class文件,先在hadoop官网中下载hadoop的代码,然后编译成相应的操作系统版本,以笔者在windows中开发的环境,肯定是编译windows版本的,然后设置相应的环境变量

  1. HADOOP_HOME=E:\software\hadoop-2.6.2

然后增加path

  1. %HADOOP_HOME%\bin

然后看一下main方法,其实代码什么都不用改,conf的配置全部可以不写,直接运行就是本地模式,至于为什么在服务器根据hadoop jar运行时,会运行到jar中,因为hadoop jar命令加载了配置文件。

  1. Configuration conf = new Configuration();
  2. //这个默认值就是local,其实可以不写
  3. conf.set("mapreduce.framework.name", "local");
  4. //本地模式运行mr程序时,输入输出可以在本地,也可以在hdfs中,具体需要看如下的两行参数
  5. //这个默认值 就是本地,其实可以不配
  6. //conf.set("fs.defaultFS","file:///");
  7. //conf.set("fs.defaultFS","hdfs://server1:9000/");
  8.  
  9. Job job = Job.getInstance(conf);

那实际上,需要使用本地模式的时候,这里面的配置可以什么都不写,因为默认的参数就是本地模式,所以这个时候直接运行就行了,当然,在后面我们接收了两个参数,分别是数据的的来源和存储位置,所以我们运行的时候的时候,直接提交参数就行了,以idea为例

像在这里就传了两个参数,地址就在D盘中。

当然,其实也是支持挂在hdfs中的,如下配置

  1. Configuration conf = new Configuration();
  2. //这个默认值就是local,其实可以不写
  3. conf.set("mapreduce.framework.name", "local");
  4. //本地模式运行mr程序时,输入输出可以在本地,也可以在hdfs中,具体需要看如下的两行参数
  5. //其实是可以本地模式也可以使用hdfs中的数据的
  6. //conf.set("fs.defaultFS","file:///");
  7. conf.set("fs.defaultFS","hdfs://server1:9000/");

也就是说,即使是本地模式,不仅仅可以使用在硬盘中,也可以使用在hdfs中

其实我们还需要加上一个日志文件,不然等下出错了,也看不到错误信息,仅仅是一片空白,那就尴尬了

在src/main/resource中添加一个log4j.properties文件,内容如下

  1. log4j.rootLogger=info, stdout, R
  2. log4j.appender.stdout=org.apache.log4j.ConsoleAppender
  3. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
  4. log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
  5. log4j.appender.R=org.apache.log4j.RollingFileAppender
  6. log4j.appender.R.File=example.log
  7. log4j.appender.R.MaxFileSize=100KB
  8. log4j.appender.R.MaxBackupIndex=1
  9. log4j.appender.R.layout=org.apache.log4j.PatternLayout
  10. log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n

打印所有的info信息

2.2:在开发工具中运行在yarn中

上一部分中,我们是运行在本地模式,但是使用开发工具,可以更好的debug,这次我们在开发工具在,运行在服务器中的yarn上面。

想要运行在yarn上面,我们可以进行如下的配置

  1. Configuration conf = new Configuration();
  2. //运行在yarn的集群模式
  3. conf.set("mapreduce.framework.name","yarn");
  4. conf.set("yarn.resourcemanager.hostname","server1");//这行配置,使得该main方法会寻找该机器的mr环境
  5. conf.set("fs.defaultFS","hdfs://server1:9000/");

通过之前的代码,我们知道我们要设置一个参数,使得mr环境能找到该代码的jar包,然后复制到所有的mr机器中去运行,但是我们这里要换一种方式,因为开发工具运行的时候,是直接运行class文件,而不是jar包

  1. Job job = Job.getInstance(conf);
  2. //使得hadoop可以根据类包,找到jar包在哪里,如果是在开发工具中运行,那么则是找不到的
  3. //job.setJarByClass(WordCountDriver.class);
  4. job.setJar("c:/xx.jar");

所以,如果我们要执行如下的代码,我们还需要先对程序进行打包才行。

仅仅修改完如上的一点代码,我们开始运行。

同样的,先配置启动参数,因为我们没改别的代码,mr的输入与输出都是从启动参数中读取的

然后执行main方法,如果server1有配置在hosts文中的话,那么见证奇迹.....哦,见证错误吧

在这里会看到一个错误,啥,没权限,对的,而且我们看到一个Administrator的用户,这个其实是我windows系统的用户,说明mapreduce运行的时候,拿的用户是当前登陆的用户,而在服务器中,如果看过之前的文章,我们给的目录权限是hadoop用户,所以我们要设置hadoop的用户。

我们要怎么做呢?还有要怎么设置用户为hadoop呢?我们来看一段hadoop的核心代码

  1. if (!isSecurityEnabled() && (user == null)) {
  2. String envUser = System.getenv(HADOOP_USER_NAME);
  3. if (envUser == null) {
  4. envUser = System.getProperty(HADOOP_USER_NAME);
  5. }
  6. user = envUser == null ? null : new User(envUser);
  7. }

这段代码是获取用户的代码,这个时候我们就知道该怎么设置用户名了,常量名称为:HADOOP_USER_NAME

  1. System.setProperty("HADOOP_USER_NAME","hadoop");
  2. Configuration conf = new Configuration();
  3. //运行在yarn的集群模式
  4. conf.set("mapreduce.framework.name","yarn");
  5. conf.set("yarn.resourcemanager.hostname","server1");//这行配置,使得该main方法会寻找该机器的mr环境
  6. conf.set("fs.defaultFS","hdfs://server1:9000/");

可以看到红色区域,设置了hadoop的用户,此时,我们再运行一下代码,见证下一个错误,ps:一定要配置日志文件,不然看不到错误信息

从完整的日志中,其实是可以看到,它是运行在yarn中了,不过出错了,图中是错误信息

有点让我吃惊的这竟然是中文的日志哈,如果是英文的日志,则是这样的

意思差不多哈,看到这个错误,我们要怎么解决呢?

这是hadoop的一个bug,新版本中已经解决,并且这个bug只会在windwos系统中出现,也就是意味着,如果你用的是linux的图形化界面,在这里面使用开发工具运行,也是不会有问题的。

先看一下问题是怎么产生的吧。先关联源码。

我们先找到org.apache.hadoop.mapred.YARNRunner这个类,并且在492行打上注释,可能位置会不一样,不过只需要找到environment变量即可,然后查看这个变量的名称

经过debug后,进入断点,查看environment变量,把内容最长的一段复制出来到记事本中查看。

很明显,最后的代码是执行在linux中的,但是这段环境却有问题。

首先就是%HADOOP_CONF_DIR%这种环境变量,对linux熟悉的可能知道,linux的环境变量是$JAVA_HOME$的这种形式,这是一个问题。

其次就是斜杠windows与linux也是不同的。

最后,环境变量的相隔,在linux中是冒号,而在windows中是分号。

这下应该知道问题了,不过我们要怎么改呢?只能改源代码了,千万不要对改源代码抱有害怕的心里,如果认真想想,这种类型的代码,就算是一个刚学会java基础的人也会修改,并没有什么可怕的。当然,等会也会贴出改完后的完整代码,不想改的同学直接复制就行了。

我们复制这样的一个类,包括代码,包名都要一样,直接建立在我们的工程中,java会优先读取本工程中的类

  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18.  
  19. package org.apache.hadoop.mapred;
  20.  
  21. import java.io.IOException;
  22. import java.nio.ByteBuffer;
  23. import java.util.ArrayList;
  24. import java.util.Collection;
  25. import java.util.HashMap;
  26. import java.util.HashSet;
  27. import java.util.List;
  28. import java.util.Map;
  29. import java.util.Vector;
  30.  
  31. import org.apache.commons.logging.Log;
  32. import org.apache.commons.logging.LogFactory;
  33. import org.apache.hadoop.classification.InterfaceAudience.Private;
  34. import org.apache.hadoop.conf.Configuration;
  35. import org.apache.hadoop.fs.FileContext;
  36. import org.apache.hadoop.fs.FileStatus;
  37. import org.apache.hadoop.fs.Path;
  38. import org.apache.hadoop.fs.UnsupportedFileSystemException;
  39. import org.apache.hadoop.io.DataOutputBuffer;
  40. import org.apache.hadoop.io.Text;
  41. import org.apache.hadoop.ipc.ProtocolSignature;
  42. import org.apache.hadoop.mapreduce.Cluster.JobTrackerStatus;
  43. import org.apache.hadoop.mapreduce.ClusterMetrics;
  44. import org.apache.hadoop.mapreduce.Counters;
  45. import org.apache.hadoop.mapreduce.JobContext;
  46. import org.apache.hadoop.mapreduce.JobID;
  47. import org.apache.hadoop.mapreduce.JobStatus;
  48. import org.apache.hadoop.mapreduce.MRJobConfig;
  49. import org.apache.hadoop.mapreduce.QueueAclsInfo;
  50. import org.apache.hadoop.mapreduce.QueueInfo;
  51. import org.apache.hadoop.mapreduce.TaskAttemptID;
  52. import org.apache.hadoop.mapreduce.TaskCompletionEvent;
  53. import org.apache.hadoop.mapreduce.TaskReport;
  54. import org.apache.hadoop.mapreduce.TaskTrackerInfo;
  55. import org.apache.hadoop.mapreduce.TaskType;
  56. import org.apache.hadoop.mapreduce.TypeConverter;
  57. import org.apache.hadoop.mapreduce.protocol.ClientProtocol;
  58. import org.apache.hadoop.mapreduce.security.token.delegation.DelegationTokenIdentifier;
  59. import org.apache.hadoop.mapreduce.v2.LogParams;
  60. import org.apache.hadoop.mapreduce.v2.api.MRClientProtocol;
  61. import org.apache.hadoop.mapreduce.v2.api.protocolrecords.GetDelegationTokenRequest;
  62. import org.apache.hadoop.mapreduce.v2.jobhistory.JobHistoryUtils;
  63. import org.apache.hadoop.mapreduce.v2.util.MRApps;
  64. import org.apache.hadoop.security.Credentials;
  65. import org.apache.hadoop.security.SecurityUtil;
  66. import org.apache.hadoop.security.UserGroupInformation;
  67. import org.apache.hadoop.security.authorize.AccessControlList;
  68. import org.apache.hadoop.security.token.Token;
  69. import org.apache.hadoop.yarn.api.ApplicationConstants;
  70. import org.apache.hadoop.yarn.api.ApplicationConstants.Environment;
  71. import org.apache.hadoop.yarn.api.records.ApplicationAccessType;
  72. import org.apache.hadoop.yarn.api.records.ApplicationId;
  73. import org.apache.hadoop.yarn.api.records.ApplicationReport;
  74. import org.apache.hadoop.yarn.api.records.ApplicationSubmissionContext;
  75. import org.apache.hadoop.yarn.api.records.ContainerLaunchContext;
  76. import org.apache.hadoop.yarn.api.records.LocalResource;
  77. import org.apache.hadoop.yarn.api.records.LocalResourceType;
  78. import org.apache.hadoop.yarn.api.records.LocalResourceVisibility;
  79. import org.apache.hadoop.yarn.api.records.ReservationId;
  80. import org.apache.hadoop.yarn.api.records.Resource;
  81. import org.apache.hadoop.yarn.api.records.URL;
  82. import org.apache.hadoop.yarn.api.records.YarnApplicationState;
  83. import org.apache.hadoop.yarn.conf.YarnConfiguration;
  84. import org.apache.hadoop.yarn.exceptions.YarnException;
  85. import org.apache.hadoop.yarn.factories.RecordFactory;
  86. import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider;
  87. import org.apache.hadoop.yarn.security.client.RMDelegationTokenSelector;
  88. import org.apache.hadoop.yarn.util.ConverterUtils;
  89.  
  90. import com.google.common.annotations.VisibleForTesting;
  91. import com.google.common.base.CaseFormat;
  92.  
  93. /**
  94. * This class enables the current JobClient (0.22 hadoop) to run on YARN.
  95. */
  96. @SuppressWarnings("unchecked")
  97. public class YARNRunner implements ClientProtocol {
  98.  
  99. private static final Log LOG = LogFactory.getLog(YARNRunner.class);
  100.  
  101. private final RecordFactory recordFactory = RecordFactoryProvider.getRecordFactory(null);
  102. private ResourceMgrDelegate resMgrDelegate;
  103. private ClientCache clientCache;
  104. private Configuration conf;
  105. private final FileContext defaultFileContext;
  106.  
  107. /**
  108. * Yarn runner incapsulates the client interface of yarn
  109. *
  110. * @param conf
  111. * the configuration object for the client
  112. */
  113. public YARNRunner(Configuration conf) {
  114. this(conf, new ResourceMgrDelegate(new YarnConfiguration(conf)));
  115. }
  116.  
  117. /**
  118. * Similar to {@link #YARNRunner(Configuration)} but allowing injecting
  119. * {@link ResourceMgrDelegate}. Enables mocking and testing.
  120. *
  121. * @param conf
  122. * the configuration object for the client
  123. * @param resMgrDelegate
  124. * the resourcemanager client handle.
  125. */
  126. public YARNRunner(Configuration conf, ResourceMgrDelegate resMgrDelegate) {
  127. this(conf, resMgrDelegate, new ClientCache(conf, resMgrDelegate));
  128. }
  129.  
  130. /**
  131. * Similar to
  132. * {@link YARNRunner#YARNRunner(Configuration, ResourceMgrDelegate)} but
  133. * allowing injecting {@link ClientCache}. Enable mocking and testing.
  134. *
  135. * @param conf
  136. * the configuration object
  137. * @param resMgrDelegate
  138. * the resource manager delegate
  139. * @param clientCache
  140. * the client cache object.
  141. */
  142. public YARNRunner(Configuration conf, ResourceMgrDelegate resMgrDelegate, ClientCache clientCache) {
  143. this.conf = conf;
  144. try {
  145. this.resMgrDelegate = resMgrDelegate;
  146. this.clientCache = clientCache;
  147. this.defaultFileContext = FileContext.getFileContext(this.conf);
  148. } catch (UnsupportedFileSystemException ufe) {
  149. throw new RuntimeException("Error in instantiating YarnClient", ufe);
  150. }
  151. }
  152.  
  153. @Private
  154. /**
  155. * Used for testing mostly.
  156. * @param resMgrDelegate the resource manager delegate to set to.
  157. */
  158. public void setResourceMgrDelegate(ResourceMgrDelegate resMgrDelegate) {
  159. this.resMgrDelegate = resMgrDelegate;
  160. }
  161.  
  162. @Override
  163. public void cancelDelegationToken(Token<DelegationTokenIdentifier> arg0) throws IOException, InterruptedException {
  164. throw new UnsupportedOperationException("Use Token.renew instead");
  165. }
  166.  
  167. @Override
  168. public TaskTrackerInfo[] getActiveTrackers() throws IOException, InterruptedException {
  169. return resMgrDelegate.getActiveTrackers();
  170. }
  171.  
  172. @Override
  173. public JobStatus[] getAllJobs() throws IOException, InterruptedException {
  174. return resMgrDelegate.getAllJobs();
  175. }
  176.  
  177. @Override
  178. public TaskTrackerInfo[] getBlacklistedTrackers() throws IOException, InterruptedException {
  179. return resMgrDelegate.getBlacklistedTrackers();
  180. }
  181.  
  182. @Override
  183. public ClusterMetrics getClusterMetrics() throws IOException, InterruptedException {
  184. return resMgrDelegate.getClusterMetrics();
  185. }
  186.  
  187. @VisibleForTesting
  188. void addHistoryToken(Credentials ts) throws IOException, InterruptedException {
  189. /* check if we have a hsproxy, if not, no need */
  190. MRClientProtocol hsProxy = clientCache.getInitializedHSProxy();
  191. if (UserGroupInformation.isSecurityEnabled() && (hsProxy != null)) {
  192. /*
  193. * note that get delegation token was called. Again this is hack for
  194. * oozie to make sure we add history server delegation tokens to the
  195. * credentials
  196. */
  197. RMDelegationTokenSelector tokenSelector = new RMDelegationTokenSelector();
  198. Text service = resMgrDelegate.getRMDelegationTokenService();
  199. if (tokenSelector.selectToken(service, ts.getAllTokens()) != null) {
  200. Text hsService = SecurityUtil.buildTokenService(hsProxy.getConnectAddress());
  201. if (ts.getToken(hsService) == null) {
  202. ts.addToken(hsService, getDelegationTokenFromHS(hsProxy));
  203. }
  204. }
  205. }
  206. }
  207.  
  208. @VisibleForTesting
  209. Token<?> getDelegationTokenFromHS(MRClientProtocol hsProxy) throws IOException, InterruptedException {
  210. GetDelegationTokenRequest request = recordFactory.newRecordInstance(GetDelegationTokenRequest.class);
  211. request.setRenewer(Master.getMasterPrincipal(conf));
  212. org.apache.hadoop.yarn.api.records.Token mrDelegationToken;
  213. mrDelegationToken = hsProxy.getDelegationToken(request).getDelegationToken();
  214. return ConverterUtils.convertFromYarn(mrDelegationToken, hsProxy.getConnectAddress());
  215. }
  216.  
  217. @Override
  218. public Token<DelegationTokenIdentifier> getDelegationToken(Text renewer) throws IOException, InterruptedException {
  219. // The token is only used for serialization. So the type information
  220. // mismatch should be fine.
  221. return resMgrDelegate.getDelegationToken(renewer);
  222. }
  223.  
  224. @Override
  225. public String getFilesystemName() throws IOException, InterruptedException {
  226. return resMgrDelegate.getFilesystemName();
  227. }
  228.  
  229. @Override
  230. public JobID getNewJobID() throws IOException, InterruptedException {
  231. return resMgrDelegate.getNewJobID();
  232. }
  233.  
  234. @Override
  235. public QueueInfo getQueue(String queueName) throws IOException, InterruptedException {
  236. return resMgrDelegate.getQueue(queueName);
  237. }
  238.  
  239. @Override
  240. public QueueAclsInfo[] getQueueAclsForCurrentUser() throws IOException, InterruptedException {
  241. return resMgrDelegate.getQueueAclsForCurrentUser();
  242. }
  243.  
  244. @Override
  245. public QueueInfo[] getQueues() throws IOException, InterruptedException {
  246. return resMgrDelegate.getQueues();
  247. }
  248.  
  249. @Override
  250. public QueueInfo[] getRootQueues() throws IOException, InterruptedException {
  251. return resMgrDelegate.getRootQueues();
  252. }
  253.  
  254. @Override
  255. public QueueInfo[] getChildQueues(String parent) throws IOException, InterruptedException {
  256. return resMgrDelegate.getChildQueues(parent);
  257. }
  258.  
  259. @Override
  260. public String getStagingAreaDir() throws IOException, InterruptedException {
  261. return resMgrDelegate.getStagingAreaDir();
  262. }
  263.  
  264. @Override
  265. public String getSystemDir() throws IOException, InterruptedException {
  266. return resMgrDelegate.getSystemDir();
  267. }
  268.  
  269. @Override
  270. public long getTaskTrackerExpiryInterval() throws IOException, InterruptedException {
  271. return resMgrDelegate.getTaskTrackerExpiryInterval();
  272. }
  273.  
  274. @Override
  275. public JobStatus submitJob(JobID jobId, String jobSubmitDir, Credentials ts) throws IOException, InterruptedException {
  276.  
  277. addHistoryToken(ts);
  278.  
  279. // Construct necessary information to start the MR AM
  280. ApplicationSubmissionContext appContext = createApplicationSubmissionContext(conf, jobSubmitDir, ts);
  281.  
  282. // Submit to ResourceManager
  283. try {
  284. ApplicationId applicationId = resMgrDelegate.submitApplication(appContext);
  285.  
  286. ApplicationReport appMaster = resMgrDelegate.getApplicationReport(applicationId);
  287. String diagnostics = (appMaster == null ? "application report is null" : appMaster.getDiagnostics());
  288. if (appMaster == null || appMaster.getYarnApplicationState() == YarnApplicationState.FAILED || appMaster.getYarnApplicationState() == YarnApplicationState.KILLED) {
  289. throw new IOException("Failed to run job : " + diagnostics);
  290. }
  291. return clientCache.getClient(jobId).getJobStatus(jobId);
  292. } catch (YarnException e) {
  293. throw new IOException(e);
  294. }
  295. }
  296.  
  297. private LocalResource createApplicationResource(FileContext fs, Path p, LocalResourceType type) throws IOException {
  298. LocalResource rsrc = recordFactory.newRecordInstance(LocalResource.class);
  299. FileStatus rsrcStat = fs.getFileStatus(p);
  300. rsrc.setResource(ConverterUtils.getYarnUrlFromPath(fs.getDefaultFileSystem().resolvePath(rsrcStat.getPath())));
  301. rsrc.setSize(rsrcStat.getLen());
  302. rsrc.setTimestamp(rsrcStat.getModificationTime());
  303. rsrc.setType(type);
  304. rsrc.setVisibility(LocalResourceVisibility.APPLICATION);
  305. return rsrc;
  306. }
  307.  
  308. public ApplicationSubmissionContext createApplicationSubmissionContext(Configuration jobConf, String jobSubmitDir, Credentials ts) throws IOException {
  309. ApplicationId applicationId = resMgrDelegate.getApplicationId();
  310.  
  311. // Setup resource requirements
  312. Resource capability = recordFactory.newRecordInstance(Resource.class);
  313. capability.setMemory(conf.getInt(MRJobConfig.MR_AM_VMEM_MB, MRJobConfig.DEFAULT_MR_AM_VMEM_MB));
  314. capability.setVirtualCores(conf.getInt(MRJobConfig.MR_AM_CPU_VCORES, MRJobConfig.DEFAULT_MR_AM_CPU_VCORES));
  315. LOG.debug("AppMaster capability = " + capability);
  316.  
  317. // Setup LocalResources
  318. Map<String, LocalResource> localResources = new HashMap<String, LocalResource>();
  319.  
  320. Path jobConfPath = new Path(jobSubmitDir, MRJobConfig.JOB_CONF_FILE);
  321.  
  322. URL yarnUrlForJobSubmitDir = ConverterUtils.getYarnUrlFromPath(defaultFileContext.getDefaultFileSystem().resolvePath(defaultFileContext.makeQualified(new Path(jobSubmitDir))));
  323. LOG.debug("Creating setup context, jobSubmitDir url is " + yarnUrlForJobSubmitDir);
  324.  
  325. localResources.put(MRJobConfig.JOB_CONF_FILE, createApplicationResource(defaultFileContext, jobConfPath, LocalResourceType.FILE));
  326. if (jobConf.get(MRJobConfig.JAR) != null) {
  327. Path jobJarPath = new Path(jobConf.get(MRJobConfig.JAR));
  328. LocalResource rc = createApplicationResource(FileContext.getFileContext(jobJarPath.toUri(), jobConf), jobJarPath, LocalResourceType.PATTERN);
  329. String pattern = conf.getPattern(JobContext.JAR_UNPACK_PATTERN, JobConf.UNPACK_JAR_PATTERN_DEFAULT).pattern();
  330. rc.setPattern(pattern);
  331. localResources.put(MRJobConfig.JOB_JAR, rc);
  332. } else {
  333. // Job jar may be null. For e.g, for pipes, the job jar is the
  334. // hadoop
  335. // mapreduce jar itself which is already on the classpath.
  336. LOG.info("Job jar is not present. " + "Not adding any jar to the list of resources.");
  337. }
  338.  
  339. // TODO gross hack
  340. for (String s : new String[] { MRJobConfig.JOB_SPLIT, MRJobConfig.JOB_SPLIT_METAINFO }) {
  341. localResources.put(MRJobConfig.JOB_SUBMIT_DIR + "/" + s, createApplicationResource(defaultFileContext, new Path(jobSubmitDir, s), LocalResourceType.FILE));
  342. }
  343.  
  344. // Setup security tokens
  345. DataOutputBuffer dob = new DataOutputBuffer();
  346. ts.writeTokenStorageToStream(dob);
  347. ByteBuffer securityTokens = ByteBuffer.wrap(dob.getData(), 0, dob.getLength());
  348.  
  349. // Setup the command to run the AM
  350. List<String> vargs = new ArrayList<String>(8);
  351. // vargs.add(MRApps.crossPlatformifyMREnv(jobConf,
  352. // Environment.JAVA_HOME)
  353. // + "/bin/java");
  354. // TODO 此处为修改处
  355. System.out.println(MRApps.crossPlatformifyMREnv(jobConf, Environment.JAVA_HOME) + "/bin/java");
  356. vargs.add("$JAVA_HOME/bin/java");
  357.  
  358. // TODO: why do we use 'conf' some places and 'jobConf' others?
  359. long logSize = jobConf.getLong(MRJobConfig.MR_AM_LOG_KB, MRJobConfig.DEFAULT_MR_AM_LOG_KB) << 10;
  360. String logLevel = jobConf.get(MRJobConfig.MR_AM_LOG_LEVEL, MRJobConfig.DEFAULT_MR_AM_LOG_LEVEL);
  361. int numBackups = jobConf.getInt(MRJobConfig.MR_AM_LOG_BACKUPS, MRJobConfig.DEFAULT_MR_AM_LOG_BACKUPS);
  362. MRApps.addLog4jSystemProperties(logLevel, logSize, numBackups, vargs, conf);
  363.  
  364. // Check for Java Lib Path usage in MAP and REDUCE configs
  365. warnForJavaLibPath(conf.get(MRJobConfig.MAP_JAVA_OPTS, ""), "map", MRJobConfig.MAP_JAVA_OPTS, MRJobConfig.MAP_ENV);
  366. warnForJavaLibPath(conf.get(MRJobConfig.MAPRED_MAP_ADMIN_JAVA_OPTS, ""), "map", MRJobConfig.MAPRED_MAP_ADMIN_JAVA_OPTS, MRJobConfig.MAPRED_ADMIN_USER_ENV);
  367. warnForJavaLibPath(conf.get(MRJobConfig.REDUCE_JAVA_OPTS, ""), "reduce", MRJobConfig.REDUCE_JAVA_OPTS, MRJobConfig.REDUCE_ENV);
  368. warnForJavaLibPath(conf.get(MRJobConfig.MAPRED_REDUCE_ADMIN_JAVA_OPTS, ""), "reduce", MRJobConfig.MAPRED_REDUCE_ADMIN_JAVA_OPTS, MRJobConfig.MAPRED_ADMIN_USER_ENV);
  369.  
  370. // Add AM admin command opts before user command opts
  371. // so that it can be overridden by user
  372. String mrAppMasterAdminOptions = conf.get(MRJobConfig.MR_AM_ADMIN_COMMAND_OPTS, MRJobConfig.DEFAULT_MR_AM_ADMIN_COMMAND_OPTS);
  373. warnForJavaLibPath(mrAppMasterAdminOptions, "app master", MRJobConfig.MR_AM_ADMIN_COMMAND_OPTS, MRJobConfig.MR_AM_ADMIN_USER_ENV);
  374. vargs.add(mrAppMasterAdminOptions);
  375.  
  376. // Add AM user command opts
  377. String mrAppMasterUserOptions = conf.get(MRJobConfig.MR_AM_COMMAND_OPTS, MRJobConfig.DEFAULT_MR_AM_COMMAND_OPTS);
  378. warnForJavaLibPath(mrAppMasterUserOptions, "app master", MRJobConfig.MR_AM_COMMAND_OPTS, MRJobConfig.MR_AM_ENV);
  379. vargs.add(mrAppMasterUserOptions);
  380.  
  381. if (jobConf.getBoolean(MRJobConfig.MR_AM_PROFILE, MRJobConfig.DEFAULT_MR_AM_PROFILE)) {
  382. final String profileParams = jobConf.get(MRJobConfig.MR_AM_PROFILE_PARAMS, MRJobConfig.DEFAULT_TASK_PROFILE_PARAMS);
  383. if (profileParams != null) {
  384. vargs.add(String.format(profileParams, ApplicationConstants.LOG_DIR_EXPANSION_VAR + Path.SEPARATOR + TaskLog.LogName.PROFILE));
  385. }
  386. }
  387.  
  388. vargs.add(MRJobConfig.APPLICATION_MASTER_CLASS);
  389. vargs.add("1>" + ApplicationConstants.LOG_DIR_EXPANSION_VAR + Path.SEPARATOR + ApplicationConstants.STDOUT);
  390. vargs.add("2>" + ApplicationConstants.LOG_DIR_EXPANSION_VAR + Path.SEPARATOR + ApplicationConstants.STDERR);
  391.  
  392. Vector<String> vargsFinal = new Vector<String>(8);
  393. // Final command
  394. StringBuilder mergedCommand = new StringBuilder();
  395. for (CharSequence str : vargs) {
  396. mergedCommand.append(str).append(" ");
  397. }
  398. vargsFinal.add(mergedCommand.toString());
  399.  
  400. LOG.debug("Command to launch container for ApplicationMaster is : " + mergedCommand);
  401.  
  402. // Setup the CLASSPATH in environment
  403. // i.e. add { Hadoop jars, job jar, CWD } to classpath.
  404. Map<String, String> environment = new HashMap<String, String>();
  405. MRApps.setClasspath(environment, conf);
  406.  
  407. // Shell
  408. environment.put(Environment.SHELL.name(), conf.get(MRJobConfig.MAPRED_ADMIN_USER_SHELL, MRJobConfig.DEFAULT_SHELL));
  409.  
  410. // Add the container working directory at the front of LD_LIBRARY_PATH
  411. MRApps.addToEnvironment(environment, Environment.LD_LIBRARY_PATH.name(), MRApps.crossPlatformifyMREnv(conf, Environment.PWD), conf);
  412.  
  413. // Setup the environment variables for Admin first
  414. MRApps.setEnvFromInputString(environment, conf.get(MRJobConfig.MR_AM_ADMIN_USER_ENV), conf);
  415. // Setup the environment variables (LD_LIBRARY_PATH, etc)
  416. MRApps.setEnvFromInputString(environment, conf.get(MRJobConfig.MR_AM_ENV), conf);
  417.  
  418. // Parse distributed cache
  419. MRApps.setupDistributedCache(jobConf, localResources);
  420.  
  421. Map<ApplicationAccessType, String> acls = new HashMap<ApplicationAccessType, String>(2);
  422. acls.put(ApplicationAccessType.VIEW_APP, jobConf.get(MRJobConfig.JOB_ACL_VIEW_JOB, MRJobConfig.DEFAULT_JOB_ACL_VIEW_JOB));
  423. acls.put(ApplicationAccessType.MODIFY_APP, jobConf.get(MRJobConfig.JOB_ACL_MODIFY_JOB, MRJobConfig.DEFAULT_JOB_ACL_MODIFY_JOB));
  424.  
  425. // TODO BY DHT
  426. for (String key : environment.keySet()) {
  427. String org = environment.get(key);
  428. String linux = getLinux(org);
  429. environment.put(key, linux);
  430. }
  431. // Setup ContainerLaunchContext for AM container
  432. ContainerLaunchContext amContainer = ContainerLaunchContext.newInstance(localResources, environment, vargsFinal, null, securityTokens, acls);
  433.  
  434. Collection<String> tagsFromConf = jobConf.getTrimmedStringCollection(MRJobConfig.JOB_TAGS);
  435.  
  436. // Set up the ApplicationSubmissionContext
  437. ApplicationSubmissionContext appContext = recordFactory.newRecordInstance(ApplicationSubmissionContext.class);
  438. appContext.setApplicationId(applicationId); // ApplicationId
  439. appContext.setQueue( // Queue name
  440. jobConf.get(JobContext.QUEUE_NAME, YarnConfiguration.DEFAULT_QUEUE_NAME));
  441. // add reservationID if present
  442. ReservationId reservationID = null;
  443. try {
  444. reservationID = ReservationId.parseReservationId(jobConf.get(JobContext.RESERVATION_ID));
  445. } catch (NumberFormatException e) {
  446. // throw exception as reservationid as is invalid
  447. String errMsg = "Invalid reservationId: " + jobConf.get(JobContext.RESERVATION_ID) + " specified for the app: " + applicationId;
  448. LOG.warn(errMsg);
  449. throw new IOException(errMsg);
  450. }
  451. if (reservationID != null) {
  452. appContext.setReservationID(reservationID);
  453. LOG.info("SUBMITTING ApplicationSubmissionContext app:" + applicationId + " to queue:" + appContext.getQueue() + " with reservationId:" + appContext.getReservationID());
  454. }
  455. appContext.setApplicationName( // Job name
  456. jobConf.get(JobContext.JOB_NAME, YarnConfiguration.DEFAULT_APPLICATION_NAME));
  457. appContext.setCancelTokensWhenComplete(conf.getBoolean(MRJobConfig.JOB_CANCEL_DELEGATION_TOKEN, true));
  458. appContext.setAMContainerSpec(amContainer); // AM Container
  459. appContext.setMaxAppAttempts(conf.getInt(MRJobConfig.MR_AM_MAX_ATTEMPTS, MRJobConfig.DEFAULT_MR_AM_MAX_ATTEMPTS));
  460. appContext.setResource(capability);
  461. appContext.setApplicationType(MRJobConfig.MR_APPLICATION_TYPE);
  462. if (tagsFromConf != null && !tagsFromConf.isEmpty()) {
  463. appContext.setApplicationTags(new HashSet<String>(tagsFromConf));
  464. }
  465.  
  466. return appContext;
  467. }
  468.  
  469. /**
  470. * 此处为修改处
  471. * @param org
  472. * @return
  473. */
  474. private String getLinux(String org) {
  475. StringBuilder sb = new StringBuilder();
  476. int c = 0;
  477. for (int i = 0; i < org.length(); i++) {
  478. if (org.charAt(i) == '%') {
  479. c++;
  480. if (c % 2 == 1) {
  481. sb.append("$");
  482. }
  483. } else {
  484. switch (org.charAt(i)) {
  485. case ';':
  486. sb.append(":");
  487. break;
  488.  
  489. case '\\':
  490. sb.append("/");
  491. break;
  492. default:
  493. sb.append(org.charAt(i));
  494. break;
  495. }
  496. }
  497. }
  498. return (sb.toString());
  499. }
  500.  
  501. @Override
  502. public void setJobPriority(JobID arg0, String arg1) throws IOException, InterruptedException {
  503. resMgrDelegate.setJobPriority(arg0, arg1);
  504. }
  505.  
  506. @Override
  507. public long getProtocolVersion(String arg0, long arg1) throws IOException {
  508. return resMgrDelegate.getProtocolVersion(arg0, arg1);
  509. }
  510.  
  511. @Override
  512. public long renewDelegationToken(Token<DelegationTokenIdentifier> arg0) throws IOException, InterruptedException {
  513. throw new UnsupportedOperationException("Use Token.renew instead");
  514. }
  515.  
  516. @Override
  517. public Counters getJobCounters(JobID arg0) throws IOException, InterruptedException {
  518. return clientCache.getClient(arg0).getJobCounters(arg0);
  519. }
  520.  
  521. @Override
  522. public String getJobHistoryDir() throws IOException, InterruptedException {
  523. return JobHistoryUtils.getConfiguredHistoryServerDoneDirPrefix(conf);
  524. }
  525.  
  526. @Override
  527. public JobStatus getJobStatus(JobID jobID) throws IOException, InterruptedException {
  528. JobStatus status = clientCache.getClient(jobID).getJobStatus(jobID);
  529. return status;
  530. }
  531.  
  532. @Override
  533. public TaskCompletionEvent[] getTaskCompletionEvents(JobID arg0, int arg1, int arg2) throws IOException, InterruptedException {
  534. return clientCache.getClient(arg0).getTaskCompletionEvents(arg0, arg1, arg2);
  535. }
  536.  
  537. @Override
  538. public String[] getTaskDiagnostics(TaskAttemptID arg0) throws IOException, InterruptedException {
  539. return clientCache.getClient(arg0.getJobID()).getTaskDiagnostics(arg0);
  540. }
  541.  
  542. @Override
  543. public TaskReport[] getTaskReports(JobID jobID, TaskType taskType) throws IOException, InterruptedException {
  544. return clientCache.getClient(jobID).getTaskReports(jobID, taskType);
  545. }
  546.  
  547. private void killUnFinishedApplication(ApplicationId appId) throws IOException {
  548. ApplicationReport application = null;
  549. try {
  550. application = resMgrDelegate.getApplicationReport(appId);
  551. } catch (YarnException e) {
  552. throw new IOException(e);
  553. }
  554. if (application.getYarnApplicationState() == YarnApplicationState.FINISHED || application.getYarnApplicationState() == YarnApplicationState.FAILED || application.getYarnApplicationState() == YarnApplicationState.KILLED) {
  555. return;
  556. }
  557. killApplication(appId);
  558. }
  559.  
  560. private void killApplication(ApplicationId appId) throws IOException {
  561. try {
  562. resMgrDelegate.killApplication(appId);
  563. } catch (YarnException e) {
  564. throw new IOException(e);
  565. }
  566. }
  567.  
  568. private boolean isJobInTerminalState(JobStatus status) {
  569. return status.getState() == JobStatus.State.KILLED || status.getState() == JobStatus.State.FAILED || status.getState() == JobStatus.State.SUCCEEDED;
  570. }
  571.  
  572. @Override
  573. public void killJob(JobID arg0) throws IOException, InterruptedException {
  574. /* check if the status is not running, if not send kill to RM */
  575. JobStatus status = clientCache.getClient(arg0).getJobStatus(arg0);
  576. ApplicationId appId = TypeConverter.toYarn(arg0).getAppId();
  577.  
  578. // get status from RM and return
  579. if (status == null) {
  580. killUnFinishedApplication(appId);
  581. return;
  582. }
  583.  
  584. if (status.getState() != JobStatus.State.RUNNING) {
  585. killApplication(appId);
  586. return;
  587. }
  588.  
  589. try {
  590. /* send a kill to the AM */
  591. clientCache.getClient(arg0).killJob(arg0);
  592. long currentTimeMillis = System.currentTimeMillis();
  593. long timeKillIssued = currentTimeMillis;
  594. while ((currentTimeMillis < timeKillIssued + 10000L) && !isJobInTerminalState(status)) {
  595. try {
  596. Thread.sleep(1000L);
  597. } catch (InterruptedException ie) {
  598. /** interrupted, just break */
  599. break;
  600. }
  601. currentTimeMillis = System.currentTimeMillis();
  602. status = clientCache.getClient(arg0).getJobStatus(arg0);
  603. if (status == null) {
  604. killUnFinishedApplication(appId);
  605. return;
  606. }
  607. }
  608. } catch (IOException io) {
  609. LOG.debug("Error when checking for application status", io);
  610. }
  611. if (status != null && !isJobInTerminalState(status)) {
  612. killApplication(appId);
  613. }
  614. }
  615.  
  616. @Override
  617. public boolean killTask(TaskAttemptID arg0, boolean arg1) throws IOException, InterruptedException {
  618. return clientCache.getClient(arg0.getJobID()).killTask(arg0, arg1);
  619. }
  620.  
  621. @Override
  622. public AccessControlList getQueueAdmins(String arg0) throws IOException {
  623. return new AccessControlList("*");
  624. }
  625.  
  626. @Override
  627. public JobTrackerStatus getJobTrackerStatus() throws IOException, InterruptedException {
  628. return JobTrackerStatus.RUNNING;
  629. }
  630.  
  631. @Override
  632. public ProtocolSignature getProtocolSignature(String protocol, long clientVersion, int clientMethodsHash) throws IOException {
  633. return ProtocolSignature.getProtocolSignature(this, protocol, clientVersion, clientMethodsHash);
  634. }
  635.  
  636. @Override
  637. public LogParams getLogFileParams(JobID jobID, TaskAttemptID taskAttemptID) throws IOException {
  638. return clientCache.getClient(jobID).getLogFilePath(jobID, taskAttemptID);
  639. }
  640.  
  641. private static void warnForJavaLibPath(String opts, String component, String javaConf, String envConf) {
  642. if (opts != null && opts.contains("-Djava.library.path")) {
  643. LOG.warn("Usage of -Djava.library.path in " + javaConf + " can cause " + "programs to no longer function if hadoop native libraries " + "are used. These values should be set as part of the " + "LD_LIBRARY_PATH in the " + component + " JVM env using " + envConf
  644. + " config settings.");
  645. }
  646. }
  647. }

代码就是这样子,重新运行main方法,就会发现,已经是运行成功了,第一次这样运行会有点慢,也不会太慢,第二次就正常了。  

最后补充一些东西,其实conf的几行参数,也可以不写

  1. conf.set("mapreduce.framework.name","yarn");
  2. conf.set("yarn.resourcemanager.hostname","server1");//这行配置,使得该main方法会寻找该机器的mr环境
  3. conf.set("fs.defaultFS","hdfs://server1:9000/");

也就是这几行参数,其实是可以注释掉的。注释掉后会去读取配置文件,我们从服务器中把下面的几个配置文件下载下来

这里面的配置,是服务器中已经配置好的配置,再把它放到src/main/resource中,打包的时候,就会加载到classpath中,

如图,配置文件中也有着这些配置,所以如果不写conf参数,把配置文件放进去,也是可以的

三:mapreduce实现join

点我查看源码

3.1:sql数据库中的示例

先列举说明一下,以关系弄数据库来说明,假定我们现在有这样两个表,订单表和产品表。

订单表

  1. 订单Id,时间,产品编号,出售数量
  2. 1001,20170822,p1,3
  3. 1002,20170823,p2,9
  4. 1003,20170824,p3,11

产品表

  1. #产品编号,产品名称,种类,单价
  2. p1,防空火箭,1,20.2
  3. p2,迫击炮,1,50
  4. p3,法师塔,2,100

如果是用关系形数据库的SQL来表达,将会是如下的SQL

  1. select * from 订单表 a left join 产品表 b on a.产品编号=b.产品编号

3.2:mapreduce的实现思路

首先找到链接的字符串,就是产品编号,可以看到,无论是订单表,还是产品表,都有个订单编号,sql中是根据这个关联,我们在mapreduce中也需要根据它来关联。

实现思路就是把产品编号,作为key当成reduce的输入。

这个时候,reduce中,全部是同一个产品的数据,其中有多个订单表的数据,这些订单是对应着同一个产品,也会有一条产品的表数据,然后把这些数据综合起来就行。

3.3:创建相应的javabean

以上是在sql数据库中的写法,假定我们有多个文件存在于hdfs中,我们要关联其中的数据,而数据格式就是这样的一个格式,我们要怎么处理呢?它就是mapreduce的一个join写法,我们这次使用本地模式运行。

首先在创建D:\mr\join\input目录,创建两个文件,分别为order_01.txt和product_01.txt里面分别把上面的订单数据和产品数据存放进去。

然后我们定义一个javabean,来存放这些信息,并且让其实现hadoop的序列化

  1. /**
  2. * 这个类的信息,包含了两个表的信息记录
  3. */
  4. static class Info implements Writable,Cloneable{
  5. /**
  6. * 订单号
  7. */
  8. private int orderId;
  9. /**
  10. * 时间
  11. */
  12. private String dateString;
  13. /**
  14. * 产品编号
  15. */
  16. private String pid;
  17. /**
  18. * 数量
  19. */
  20. private int amount;
  21. /**
  22. * 产品名称
  23. */
  24. private String pname;
  25. /**
  26. * 种类
  27. */
  28. private int categoryId;
  29. /**
  30. * 价格
  31. */
  32. private float price;
  33. /**
  34. * 这个字段需要理解<br>
  35. * 因为这个对象,包含了订单与产品的两个文件的内容,当我们加载一个文件的时候,肯定只能加载一部分的信息,另一部分是加载不到的,需要在join的时候,加进去,这个字段就代表着这个对象存的是哪些信息
  36. * 如果为0 则是存了订单信息
  37. * 如果为1 则是存了产品信息
  38. */
  39. private String flag;
  40.  
  41. @Override
  42. protected Object clone() throws CloneNotSupportedException {
  43. return super.clone();
  44. }
  45.  
  46. @Override
  47. public void write(DataOutput out) throws IOException {
  48. out.writeInt(orderId);
  49. out.writeUTF(dateString);
  50. out.writeUTF(pid);
  51. out.writeInt(amount);
  52. out.writeUTF(pname);
  53. out.writeInt(categoryId);
  54. out.writeFloat(price);
  55. out.writeUTF(flag);
  56. }
  57.  
  58. @Override
  59. public void readFields(DataInput in) throws IOException {
  60. orderId = in.readInt();
  61. dateString = in.readUTF();
  62. pid = in.readUTF();
  63. amount = in.readInt();
  64. pname = in.readUTF();
  65. categoryId = in.readInt();
  66. price = in.readFloat();
  67. flag = in.readUTF();
  68. }
  69.  
  70. public Info() {
  71. }
  72.  
  73. public void set(int orderId, String dateString, String pid, int amount, String pname, int categoryId, float price,String flag) {
  74. this.orderId = orderId;
  75. this.dateString = dateString;
  76. this.pid = pid;
  77. this.amount = amount;
  78. this.pname = pname;
  79. this.categoryId = categoryId;
  80. this.price = price;
  81. this.flag = flag;
  82. }
  83.  
  84. public int getOrderId() {
  85. return orderId;
  86. }
  87.  
  88. public void setOrderId(int orderId) {
  89. this.orderId = orderId;
  90. }
  91.  
  92. public String getDateString() {
  93. return dateString;
  94. }
  95.  
  96. public String getFlag() {
  97. return flag;
  98. }
  99.  
  100. public void setFlag(String flag) {
  101. this.flag = flag;
  102. }
  103.  
  104. public void setDateString(String dateString) {
  105. this.dateString = dateString;
  106. }
  107.  
  108. public String getPid() {
  109. return pid;
  110. }
  111.  
  112. public void setPid(String pid) {
  113. this.pid = pid;
  114. }
  115.  
  116. public int getAmount() {
  117. return amount;
  118. }
  119.  
  120. public void setAmount(int amount) {
  121. this.amount = amount;
  122. }
  123.  
  124. public String getPname() {
  125. return pname;
  126. }
  127.  
  128. public void setPname(String pname) {
  129. this.pname = pname;
  130. }
  131.  
  132. public int getCategoryId() {
  133. return categoryId;
  134. }
  135.  
  136. public void setCategoryId(int categoryId) {
  137. this.categoryId = categoryId;
  138. }
  139.  
  140. public float getPrice() {
  141. return price;
  142. }
  143.  
  144. public void setPrice(float price) {
  145. this.price = price;
  146. }
  147.  
  148. @Override
  149. public String toString() {
  150. final StringBuilder sb = new StringBuilder("{");
  151. sb.append("\"orderId\":")
  152. .append(orderId);
  153. sb.append(",\"dateString\":\"")
  154. .append(dateString).append('\"');
  155. sb.append(",\"pid\":")
  156. .append(pid);
  157. sb.append(",\"amount\":")
  158. .append(amount);
  159. sb.append(",\"pname\":\"")
  160. .append(pname).append('\"');
  161. sb.append(",\"categoryId\":")
  162. .append(categoryId);
  163. sb.append(",\"price\":")
  164. .append(price);
  165. sb.append(",\"flag\":\"")
  166. .append(flag).append('\"');
  167. sb.append('}');
  168. return sb.toString();
  169. }
  170. }

3.4:创建mapper

mapper的代码可以直接看注释

  1. static class JoinMapper extends Mapper<LongWritable,Text,Text,Info>{
  2. private Info info = new Info();
  3. private Text text = new Text();
  4. @Override
  5. protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
  6. String line = value.toString();
  7. if(line.startsWith("#")){//跳转带#的注释
  8. return;
  9. }
  10. //获取当前任务的输入切片,这个InputSplit是一个最上层抽象类,可以转换成FileSplit
  11. InputSplit inputSplit = context.getInputSplit();
  12. FileSplit fileSplit = (FileSplit) inputSplit;
  13. String name = fileSplit.getPath().getName();//得到的是文件名,这里根据文件名来判断是哪一种类型的数据
  14. //我们这里通过文件名判断是哪种数据
  15. String pid = "";
  16. String[] split = line.split(",");
  17. if(name.startsWith("order")){//加载订单内容,订单数据里面有 订单号,时间,产品ID,数量
  18. //orderId,date,pid,amount
  19. pid = split[2];
  20. info.set(Integer.parseInt(split[0]),split[1],pid,Integer.parseInt(split[3]),"",0,0,"0");
  21.  
  22. }else{//加载产品内容,产品数据有 产品编号,产品名称,种类,价格
  23. //pid,pname,categoryId,price
  24. pid = split[0];
  25. info.set(0,"",pid,0,split[1],Integer.parseInt(split[2]),Float.parseFloat(split[3]),"1");
  26. }
  27. text.set(pid);
  28. context.write(text,info);
  29. }
  30. }

3.5:创建reduce

直接看注释即可

  1. static class JoinReduce extends Reducer<Text,Info,Info,NullWritable>{
  2.  
  3. @Override
  4. protected void reduce(Text key, Iterable<Info> values, Context context) throws IOException, InterruptedException {
  5. Info product = new Info();//这个对象用来存放产品的数据,一个产品所以只有一个对象
  6. List<Info> infos = new ArrayList<>();//这个list用来存放所有的订单数据,订单肯定是有多个的
  7. for(Info info : values){
  8. if("1".equals(info.getFlag())){
  9. //产品表的数据
  10. try {
  11. product = (Info) info.clone();
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. }
  15. }else{//代表着是订单表的数据
  16. Info order = new Info();
  17. try {
  18. order = (Info) info.clone();
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. infos.add(order);
  23. }
  24. }
  25. //经过上面的操作,就把订单与产品完全分离出来了,订单在list集合中,产品在单独的一个对象中
  26. //然后可以分别综合设置进去
  27. for(Info tmp : infos){
  28. tmp.setPname(product.getPname());
  29. tmp.setCategoryId(product.getCategoryId());
  30. tmp.setPrice(product.getPrice());
  31. //最后进行输出,就会得到结果文件
  32. context.write(tmp,NullWritable.get());
  33. }
  34. }
  35. }

3.6:完整代码

上面贴了map与reduce,就差启动的main方法了,不过main方法是普通的main方法,和上一篇文中的启动方法一样,这里直接把join的所有代码全部贴了出来,包含main方法,全部写在一个文件里面

  1. package com.zxj.hadoop.demo.mapreduce.join;
  2.  
  3. import org.apache.commons.beanutils.BeanUtils;
  4. import org.apache.hadoop.conf.Configuration;
  5. import org.apache.hadoop.fs.Path;
  6. import org.apache.hadoop.io.LongWritable;
  7. import org.apache.hadoop.io.NullWritable;
  8. import org.apache.hadoop.io.Text;
  9. import org.apache.hadoop.io.Writable;
  10. import org.apache.hadoop.mapreduce.InputSplit;
  11. import org.apache.hadoop.mapreduce.Job;
  12. import org.apache.hadoop.mapreduce.Mapper;
  13. import org.apache.hadoop.mapreduce.Reducer;
  14. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
  15. import org.apache.hadoop.mapreduce.lib.input.FileSplit;
  16. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
  17.  
  18. import java.io.DataInput;
  19. import java.io.DataOutput;
  20. import java.io.IOException;
  21. import java.util.ArrayList;
  22. import java.util.List;
  23.  
  24. /**
  25. * @Author 朱小杰
  26. * 时间 2017-08-22 .22:10
  27. * 说明 ...
  28. */
  29. public class MRJoin {
  30. /**
  31. * 这个类的信息,包含了两个表的信息记录
  32. */
  33. static class Info implements Writable,Cloneable{
  34. /**
  35. * 订单号
  36. */
  37. private int orderId;
  38. /**
  39. * 时间
  40. */
  41. private String dateString;
  42. /**
  43. * 产品编号
  44. */
  45. private String pid;
  46. /**
  47. * 数量
  48. */
  49. private int amount;
  50. /**
  51. * 产品名称
  52. */
  53. private String pname;
  54. /**
  55. * 种类
  56. */
  57. private int categoryId;
  58. /**
  59. * 价格
  60. */
  61. private float price;
  62. /**
  63. * 这个字段需要理解<br>
  64. * 因为这个对象,包含了订单与产品的两个文件的内容,当我们加载一个文件的时候,肯定只能加载一部分的信息,另一部分是加载不到的,需要在join的时候,加进去,这个字段就代表着这个对象存的是哪些信息
  65. * 如果为0 则是存了订单信息
  66. * 如果为1 则是存了产品信息
  67. */
  68. private String flag;
  69.  
  70. @Override
  71. protected Object clone() throws CloneNotSupportedException {
  72. return super.clone();
  73. }
  74.  
  75. @Override
  76. public void write(DataOutput out) throws IOException {
  77. out.writeInt(orderId);
  78. out.writeUTF(dateString);
  79. out.writeUTF(pid);
  80. out.writeInt(amount);
  81. out.writeUTF(pname);
  82. out.writeInt(categoryId);
  83. out.writeFloat(price);
  84. out.writeUTF(flag);
  85. }
  86.  
  87. @Override
  88. public void readFields(DataInput in) throws IOException {
  89. orderId = in.readInt();
  90. dateString = in.readUTF();
  91. pid = in.readUTF();
  92. amount = in.readInt();
  93. pname = in.readUTF();
  94. categoryId = in.readInt();
  95. price = in.readFloat();
  96. flag = in.readUTF();
  97. }
  98.  
  99. public Info() {
  100. }
  101.  
  102. public void set(int orderId, String dateString, String pid, int amount, String pname, int categoryId, float price,String flag) {
  103. this.orderId = orderId;
  104. this.dateString = dateString;
  105. this.pid = pid;
  106. this.amount = amount;
  107. this.pname = pname;
  108. this.categoryId = categoryId;
  109. this.price = price;
  110. this.flag = flag;
  111. }
  112.  
  113. public int getOrderId() {
  114. return orderId;
  115. }
  116.  
  117. public void setOrderId(int orderId) {
  118. this.orderId = orderId;
  119. }
  120.  
  121. public String getDateString() {
  122. return dateString;
  123. }
  124.  
  125. public String getFlag() {
  126. return flag;
  127. }
  128.  
  129. public void setFlag(String flag) {
  130. this.flag = flag;
  131. }
  132.  
  133. public void setDateString(String dateString) {
  134. this.dateString = dateString;
  135. }
  136.  
  137. public String getPid() {
  138. return pid;
  139. }
  140.  
  141. public void setPid(String pid) {
  142. this.pid = pid;
  143. }
  144.  
  145. public int getAmount() {
  146. return amount;
  147. }
  148.  
  149. public void setAmount(int amount) {
  150. this.amount = amount;
  151. }
  152.  
  153. public String getPname() {
  154. return pname;
  155. }
  156.  
  157. public void setPname(String pname) {
  158. this.pname = pname;
  159. }
  160.  
  161. public int getCategoryId() {
  162. return categoryId;
  163. }
  164.  
  165. public void setCategoryId(int categoryId) {
  166. this.categoryId = categoryId;
  167. }
  168.  
  169. public float getPrice() {
  170. return price;
  171. }
  172.  
  173. public void setPrice(float price) {
  174. this.price = price;
  175. }
  176.  
  177. @Override
  178. public String toString() {
  179. final StringBuilder sb = new StringBuilder("{");
  180. sb.append("\"orderId\":")
  181. .append(orderId);
  182. sb.append(",\"dateString\":\"")
  183. .append(dateString).append('\"');
  184. sb.append(",\"pid\":")
  185. .append(pid);
  186. sb.append(",\"amount\":")
  187. .append(amount);
  188. sb.append(",\"pname\":\"")
  189. .append(pname).append('\"');
  190. sb.append(",\"categoryId\":")
  191. .append(categoryId);
  192. sb.append(",\"price\":")
  193. .append(price);
  194. sb.append(",\"flag\":\"")
  195. .append(flag).append('\"');
  196. sb.append('}');
  197. return sb.toString();
  198. }
  199. }
  200.  
  201. static class JoinMapper extends Mapper<LongWritable,Text,Text,Info>{
  202. private Info info = new Info();
  203. private Text text = new Text();
  204. @Override
  205. protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
  206. String line = value.toString();
  207. if(line.startsWith("#")){//跳转带#的注释
  208. return;
  209. }
  210. //获取当前任务的输入切片,这个InputSplit是一个最上层抽象类,可以转换成FileSplit
  211. InputSplit inputSplit = context.getInputSplit();
  212. FileSplit fileSplit = (FileSplit) inputSplit;
  213. String name = fileSplit.getPath().getName();//得到的是文件名,这里根据文件名来判断是哪一种类型的数据
  214. //我们这里通过文件名判断是哪种数据
  215. String pid = "";
  216. String[] split = line.split(",");
  217. if(name.startsWith("order")){//加载订单内容,订单数据里面有 订单号,时间,产品ID,数量
  218. //orderId,date,pid,amount
  219. pid = split[2];
  220. info.set(Integer.parseInt(split[0]),split[1],pid,Integer.parseInt(split[3]),"",0,0,"0");
  221.  
  222. }else{//加载产品内容,产品数据有 产品编号,产品名称,种类,价格
  223. //pid,pname,categoryId,price
  224. pid = split[0];
  225. info.set(0,"",pid,0,split[1],Integer.parseInt(split[2]),Float.parseFloat(split[3]),"1");
  226. }
  227. text.set(pid);
  228. context.write(text,info);
  229. }
  230. }
  231.  
  232. static class JoinReduce extends Reducer<Text,Info,Info,NullWritable>{
  233.  
  234. @Override
  235. protected void reduce(Text key, Iterable<Info> values, Context context) throws IOException, InterruptedException {
  236. Info product = new Info();//这个对象用来存放产品的数据,一个产品所以只有一个对象
  237. List<Info> infos = new ArrayList<>();//这个list用来存放所有的订单数据,订单肯定是有多个的
  238. for(Info info : values){
  239. if("1".equals(info.getFlag())){
  240. //产品表的数据
  241. try {
  242. product = (Info) info.clone();
  243. } catch (Exception e) {
  244. e.printStackTrace();
  245. }
  246. }else{//代表着是订单表的数据
  247. Info order = new Info();
  248. try {
  249. order = (Info) info.clone();
  250. } catch (Exception e) {
  251. e.printStackTrace();
  252. }
  253. infos.add(order);
  254. }
  255. }
  256. //经过上面的操作,就把订单与产品完全分离出来了,订单在list集合中,产品在单独的一个对象中
  257. //然后可以分别综合设置进去
  258. for(Info tmp : infos){
  259. tmp.setPname(product.getPname());
  260. tmp.setCategoryId(product.getCategoryId());
  261. tmp.setPrice(product.getPrice());
  262. //最后进行输出,就会得到结果文件
  263. context.write(tmp,NullWritable.get());
  264. }
  265. }
  266. }
  267.  
  268. static class JoinMain{
  269. public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
  270. Configuration conf = new Configuration();
  271. Job job = Job.getInstance(conf);
  272.  
  273. job.setJarByClass(JoinMain.class);
  274.  
  275. job.setMapperClass(JoinMapper.class);
  276. job.setReducerClass(JoinReduce.class);
  277.  
  278. job.setMapOutputKeyClass(Text.class);
  279. job.setMapOutputValueClass(Info.class);
  280.  
  281. job.setOutputKeyClass(Info.class);
  282. job.setOutputValueClass(NullWritable.class);
  283.  
  284. FileInputFormat.setInputPaths(job,new Path(args[0]));
  285. FileOutputFormat.setOutputPath(job,new Path(args[1]));
  286.  
  287. boolean b = job.waitForCompletion(true);
  288. if(b){
  289. System.out.println("OK");
  290. }
  291.  
  292. }
  293. }
  294.  
  295. }

最后配置启动参数,以本地开发模式运行

运行成功后,得到如下结果

这就完成了

3.7:数据倾斜的问题

上面我们虽然解决了join的问题,但是也会陷入另一个问题,那就是数据倾斜。

假如果说a产品有10万张订单,b产品只有10个订单,那么就会导致每个reduce分配的数据不一致,个别速度很快,个别速度很慢,达不到快速的效果,性能低下。

解决这个问题,就是在map端实现数据的合并,在每个map中,单独加载产品表的信息,因为产品表的数据,肯定相对小一些,然后在map中实现数据的合并。

四:查找共同好友,计算可能认识的人

点我下载源码

假定我们现在有一个社交软件,它的好友是单向好友,我们现在要计算用户之间的共同好友,然后向它推荐可能认识的人。

它需要经过两次mapreducer

4.1:准备数据

  1. A:B,C,D,F,E,O
  2. B:A,C,E,K
  3. C:F,A,D,I
  4. D:A,E,F,L
  5. E:B,C,D,M,L
  6. F:A,B,C,D,E,O,M
  7. G:A,C,D,E,F
  8. H:A,C,D,E,O
  9. I:A,O
  10. J:B,O
  11. K:A,C,D
  12. L:D,E,F
  13. M:E,F,G
  14. O:A,H,I,J

如上,冒号前面的是用户,冒号后面的是好友列表。

然后保存为文件,作为第一次mapreduce的输入

4.2:计算指定用户是哪些人的好友

  1. package com.zxj.hadoop.demo.mapreduce.findfriend;
  2.  
  3. import org.apache.hadoop.conf.Configuration;
  4. import org.apache.hadoop.fs.Path;
  5. import org.apache.hadoop.io.LongWritable;
  6. import org.apache.hadoop.io.Text;
  7. import org.apache.hadoop.mapreduce.Job;
  8. import org.apache.hadoop.mapreduce.Mapper;
  9. import org.apache.hadoop.mapreduce.Reducer;
  10. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
  11. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
  12.  
  13. import java.io.IOException;
  14.  
  15. /**
  16. * @Author 朱小杰
  17. * 时间 2017-08-24 .22:59
  18. * 说明 先算出某个用户是哪些人的好友
  19. */
  20. public class Friend1 {
  21.  
  22. static class FriendMapper1 extends Mapper<LongWritable, Text, Text, Text> {
  23. private Text k = new Text();
  24. private Text v = new Text();
  25. @Override
  26. protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
  27. String line = value.toString();
  28. String[] personFriends = line.split(":");
  29. String person = personFriends[0];//用户
  30. String friends = personFriends[1];//好友
  31. for (String friend : friends.split(",")) {
  32. //输出<好友,人>
  33. k.set(friend);
  34. v.set(person);
  35. context.write(k,v);
  36. }
  37. }
  38. }
  39.  
  40. /**
  41. * 输入 好友,用户
  42. */
  43. static class FriendReduce1 extends Reducer<Text,Text,Text,Text>{
  44. private Text k = new Text();
  45. private Text v = new Text();
  46. @Override
  47. protected void reduce(Text friend, Iterable<Text> persons, Context context) throws IOException, InterruptedException {
  48. StringBuffer sb = new StringBuffer();
  49. for(Text person : persons){
  50. sb.append(person).append(",");
  51. }
  52. k.set(friend);
  53. v.set(sb.toString());
  54. context.write(k,v);
  55. }
  56. }
  57.  
  58. public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
  59. String input = "D:\\mr\\qq\\input";
  60. String output = "D:\\mr\\qq\\out1";
  61. Configuration conf = new Configuration();
  62. Job job = Job.getInstance(conf);
  63.  
  64. job.setJarByClass(Friend1.class);
  65.  
  66. job.setMapperClass(FriendMapper1.class);
  67. job.setReducerClass(FriendReduce1.class);
  68.  
  69. job.setMapOutputKeyClass(Text.class);
  70. job.setMapOutputValueClass(Text.class);
  71.  
  72. job.setOutputKeyClass(Text.class);
  73. job.setOutputValueClass(Text.class);
  74.  
  75. FileInputFormat.setInputPaths(job,new Path(input));
  76. FileOutputFormat.setOutputPath(job,new Path(output));
  77.  
  78. boolean b = job.waitForCompletion(true);
  79. if(b){}
  80.  
  81. }
  82. }

这里计算后的结果就是,某个用户分别是哪些人的好友,得到结果如下

4.3:计算共同好友

  1. package com.zxj.hadoop.demo.mapreduce.findfriend;
  2.  
  3. import org.apache.hadoop.conf.Configuration;
  4. import org.apache.hadoop.fs.Path;
  5. import org.apache.hadoop.io.LongWritable;
  6. import org.apache.hadoop.io.Text;
  7. import org.apache.hadoop.mapreduce.Job;
  8. import org.apache.hadoop.mapreduce.Mapper;
  9. import org.apache.hadoop.mapreduce.Reducer;
  10. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
  11. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
  12.  
  13. import java.io.IOException;
  14. import java.util.Arrays;
  15.  
  16. /**
  17. * @Author 朱小杰
  18. * 时间 2017-08-24 .22:59
  19. * 说明 继续第第二步操作
  20. */
  21. public class Friend2 {
  22.  
  23. static class FriendMapper2 extends Mapper<LongWritable, Text, Text, Text> {
  24. /**
  25. * 这里拿到的是上一次计算的数据 A I,K,C,B,G,F,H,O,D,
  26. * A是哪些用户的好友
  27. * @param key
  28. * @param value
  29. * @param context
  30. * @throws IOException
  31. * @throws InterruptedException
  32. */
  33. @Override
  34. protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
  35. String line = value.toString();
  36. String[] split = line.split("\t");
  37. String friend = split[0];
  38. String[] persions = split[1].split(",");
  39. Arrays.sort(persions);
  40.  
  41. for(int i = 0 ; i < persions.length -2 ; i ++){
  42. for(int j = i+1 ; j < persions.length -1 ; j ++){
  43. //发送出 人-人 好友的数据,就是这两个人有哪个共同好友,会进入到同一个reducer中
  44. context.write(new Text(persions[i] + "-" + persions[j]),new Text(friend));
  45. }
  46. }
  47. }
  48. }
  49.  
  50. /**
  51. * 输入 好友,用户
  52. */
  53. static class FriendReduce2 extends Reducer<Text,Text,Text,Text>{
  54. private Text k = new Text();
  55. private Text v = new Text();
  56. @Override
  57. protected void reduce(Text person_person, Iterable<Text> friends, Context context) throws IOException, InterruptedException {
  58. StringBuffer sb = new StringBuffer();
  59. for(Text f : friends){
  60. sb.append(f.toString()).append(" ");
  61. }
  62. context.write(person_person,new Text(sb.toString()));
  63. }
  64. }
  65.  
  66. public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
  67. String input = "D:\\mr\\qq\\out1";
  68. String output = "D:\\mr\\qq\\out2";
  69. Configuration conf = new Configuration();
  70. Job job = Job.getInstance(conf);
  71.  
  72. job.setJarByClass(Friend2.class);
  73.  
  74. job.setMapperClass(FriendMapper2.class);
  75. job.setReducerClass(FriendReduce2.class);
  76.  
  77. job.setMapOutputKeyClass(Text.class);
  78. job.setMapOutputValueClass(Text.class);
  79.  
  80. job.setOutputKeyClass(Text.class);
  81. job.setOutputValueClass(Text.class);
  82.  
  83. FileInputFormat.setInputPaths(job,new Path(input));
  84. FileOutputFormat.setOutputPath(job,new Path(output));
  85.  
  86. boolean b = job.waitForCompletion(true);
  87. if(b){}
  88.  
  89. }
  90. }

经过这次计算,就能得到共同的好友了,因为是共同好友,所以他们也是有可能认识的人。

五:使用GroupingComparator分组计算最大值

点我下载源码

我们准备一些订单数据

  1. 1号订单,200
  2. 1号订单,300
  3. 2号订单,1000
  4. 2号订单,300
  5. 2号订单,900
  6. 3号订单,9000
  7. 3号订单,200
  8. 3号订单,1000

这是每一号订单,分别售出多少钱,这里要求计算出每一号订单中的最大金额。

5.1:定义一个javabean

定义一个bean,并且实现序列化与排序比较接口

  1. package com.zxj.hadoop.demo.mapreduce.groupingcomporator;
  2.  
  3. import java.io.DataInput;
  4. import java.io.DataOutput;
  5. import java.io.IOException;
  6.  
  7. import org.apache.hadoop.io.DoubleWritable;
  8. import org.apache.hadoop.io.Text;
  9. import org.apache.hadoop.io.WritableComparable;
  10.  
  11. /**
  12. *
  13. *
  14. */
  15. public class OrderBean implements WritableComparable<OrderBean>{
  16.  
  17. private Text itemid;
  18. private DoubleWritable amount;
  19.  
  20. public OrderBean() {
  21. }
  22.  
  23. public OrderBean(Text itemid, DoubleWritable amount) {
  24. set(itemid, amount);
  25.  
  26. }
  27.  
  28. public void set(Text itemid, DoubleWritable amount) {
  29.  
  30. this.itemid = itemid;
  31. this.amount = amount;
  32.  
  33. }
  34.  
  35. public Text getItemid() {
  36. return itemid;
  37. }
  38.  
  39. public DoubleWritable getAmount() {
  40. return amount;
  41. }
  42.  
  43. @Override
  44. public int compareTo(OrderBean o) {
  45. int cmp = this.itemid.compareTo(o.getItemid());
  46. if (cmp == 0) {
  47. cmp = -this.amount.compareTo(o.getAmount());
  48. }
  49. return cmp;
  50. }
  51.  
  52. @Override
  53. public void write(DataOutput out) throws IOException {
  54. out.writeUTF(itemid.toString());
  55. out.writeDouble(amount.get());
  56.  
  57. }
  58.  
  59. @Override
  60. public void readFields(DataInput in) throws IOException {
  61. String readUTF = in.readUTF();
  62. double readDouble = in.readDouble();
  63.  
  64. this.itemid = new Text(readUTF);
  65. this.amount= new DoubleWritable(readDouble);
  66. }
  67.  
  68. @Override
  69. public String toString() {
  70.  
  71. return itemid.toString() + "\t" + amount.get();
  72.  
  73. }
  74.  
  75. }

5.2:定义一个GroupingComparator

我们都知道,reducer中,是把同一个key,以其所有的value放到了同一个reudce中计算,如果我们要把一个有着多属性的javabean当作key,那么同一个订单的bean就无法进入到同一个reduce中,我们需要通过这个分组,让所有同一个订单的bean全部进到同一个reduce中。

  1. package com.zxj.hadoop.demo.mapreduce.groupingcomporator;
  2.  
  3. import org.apache.hadoop.io.WritableComparable;
  4. import org.apache.hadoop.io.WritableComparator;
  5.  
  6. /**
  7. * @Author 朱小杰
  8. * 时间 2017-08-26 .17:31
  9. * 说明 利用reduce端的GroupingComparator来实现将一组bean看成相同的key
  10. * 用来分组
  11. * @author
  12. */
  13. public class ItemidGroupingComparator extends WritableComparator {
  14.  
  15. /**
  16. * 这个类必须写,因为mapreduce需要知道反射成为哪个类
  17. */
  18. protected ItemidGroupingComparator() {
  19. super(OrderBean.class, true);
  20. }
  21.  
  22. @Override
  23. public int compare(WritableComparable a, WritableComparable b) {
  24. OrderBean b1 = (OrderBean) a;
  25. OrderBean b2 = (OrderBean) b;
  26. //比较两个bean时,只比较这里面的一个字段,如果这里是相等的,那么mapreduce就会认为这两个对象是同一个key
  27. return b1.getItemid().compareTo(b2.getItemid());
  28. }
  29. }

我们也知道,mapredce是根据key来进行排序的,所以我们可以想象,在把同一个订单的所有的bean当作一个key时,一个订单,只会有一个数据进入到reduce中,而因为我们实现的排序接口,数据最大的会最先进入到reduce中。

5.3:map代码

map的代码很简单

  1. static class SecondarySortMapper extends Mapper<LongWritable, Text, OrderBean, NullWritable>{
  2.  
  3. OrderBean bean = new OrderBean();
  4.  
  5. @Override
  6. protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
  7.  
  8. String line = value.toString();
  9. String[] fields = StringUtils.split(line, ",");
  10.  
  11. bean.set(new Text(fields[0]), new DoubleWritable(Double.parseDouble(fields[1])));
  12.  
  13. context.write(bean, NullWritable.get());
  14.  
  15. }
  16.  
  17. }

这里很直接的把一个bean和一个null输出

5.4:reduce的代码

  1. static class SecondarySortReducer extends Reducer<OrderBean, NullWritable, OrderBean, NullWritable>{
  2.  
  3. //到达reduce时,相同id的所有bean已经被看成一组,且金额最大的那个一排在第一位,所以后面的key也就不存在了
  4. @Override
  5. protected void reduce(OrderBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
  6. context.write(key, NullWritable.get());
  7. }
  8. }

因为前面有解释到,一个订单,只会有一个bean进来,并且进来的这个bean,肯定是最大值的一个金额,所以我们直接输出就行了

5.5:启动类

启动类和以往有点不同

  1. public static void main(String[] args) throws Exception {
  2.  
  3. Configuration conf = new Configuration();
  4. Job job = Job.getInstance(conf);
  5.  
  6. job.setJarByClass(SecondarySort.class);
  7.  
  8. job.setMapperClass(SecondarySortMapper.class);
  9. job.setReducerClass(SecondarySortReducer.class);
  10.  
  11. job.setOutputKeyClass(OrderBean.class);
  12. job.setOutputValueClass(NullWritable.class);
  13.  
  14. FileInputFormat.setInputPaths(job, new Path("D:\\mr\\groupcompatrator\\input"));
  15. FileOutputFormat.setOutputPath(job, new Path("D:\\mr\\groupcompatrator\\out1"));
  16.  
  17. //在此设置自定义的Groupingcomparator类
  18. job.setGroupingComparatorClass(ItemidGroupingComparator.class);
  19.  
  20. job.waitForCompletion(true);
  21.  
  22. }

运行之后查看效果如下

六:自定义输出位置

点我下载源码

之前我们保存数据一直都是保存在文件系统中的,而且都是mapreduce代劳的,我们有没有可能把它输出到其它地方呢,比如关系型数据库,或者输出到缓存?hive等等这些地方?答案是可以的。

6.1:自定义FileOutputFormat

我们之前的启动类main方法中,一直有一行代码是这样子的

  1. FileOutputFormat.setOutputPath(job, new Path("D:\\mr\\wordcount\\out1"));

这行代码是指定输出的位置,可以猜一下,我们使用的应该是FileOutputFormat或者是它的子类,答案是对的。所以我们来继承它,它是一个抽象类

  1. package com.zxj.hadoop.demo.mapreduce.outputformat;
  2.  
  3. import org.apache.hadoop.mapreduce.RecordWriter;
  4. import org.apache.hadoop.mapreduce.TaskAttemptContext;
  5. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
  6.  
  7. import java.io.BufferedWriter;
  8. import java.io.FileWriter;
  9. import java.io.IOException;
  10.  
  11. /**
  12. * @Author 朱小杰
  13. * 时间 2017-08-26 .19:08
  14. * 说明 mapreduce写数据时,会先调用这个类的getRecordWriter()方法,拿到一个RecordWriter对象,再调这个对象的写数据方法
  15. */
  16. public class MyOutputFormat<Text, LongWritable> extends FileOutputFormat<Text, LongWritable> {
  17. @Override
  18. public RecordWriter<Text, LongWritable> getRecordWriter(TaskAttemptContext job) throws IOException, InterruptedException {
  19. return new MyRecordWriter<>();
  20. }
  21.  
  22. /**
  23. * 自定义的RecordWriter
  24. *
  25. * @param <Text>
  26. */
  27. static class MyRecordWriter<Text, LongWritable> extends RecordWriter<Text, LongWritable> {
  28. private BufferedWriter writer;
  29. public MyRecordWriter() {
  30. try {
  31. writer = new BufferedWriter(new FileWriter("d:/myFileFormat"));
  32. } catch (Exception e) {
  33. e.printStackTrace();
  34. }
  35. }
  36.  
  37. @Override
  38. public void write(Text key, LongWritable value) throws IOException, InterruptedException {
  39. writer.write(key.toString() + " " + value.toString());
  40. writer.newLine();
  41. writer.flush();
  42. }
  43.  
  44. @Override
  45. public void close(TaskAttemptContext context) throws IOException, InterruptedException {
  46. writer.close();
  47. }
  48. }
  49. }

如上的代码中,我们自定义了一个OutputFormat,并且把文件输出到了D盘,可以想象,假如说我们要输出到一些关系型数据库,或者一些缓存,或者其它的存储位置,我们都可以灵活的去通过这个类去扩展它,而并不仅仅是受限于文件系统。

这个类配置使用的代码也只有一行

  1. Job job = Job.getInstance(conf);
  2.  
  3. //设置自定义的OutputFormat
  4. job.setOutputFormatClass(MyOutputFormat.class);

我们可以看到,这里我们设置了输出的Format。虽然我们在这个自定义的format中指定了输出的位置为D盘的根目录,但是输入和输出的两个参数还是要传的,也就是这两行代码

  1. //指定输入文件的位置,这里为了灵活,接收外部参数
  2. FileInputFormat.setInputPaths(job, new Path("D:\\mr\\wordcount\\input"));
  3. //指定输入文件的位置,这里接收启动参数
  4. FileOutputFormat.setOutputPath(job, new Path("D:\\mr\\wordcount\\out1"));

或许有人会觉得,输入需要指定可以理解,输出为什么要指定呢?这是因为我们继承的是FileOutputFormat,所以我们就必须要有一个输出目录,这个目录也会输出文件,但是输出的不是数据文件,而是一个结果文件,代表着成功或者失败,而自定义中指定的format的位置,才是真正数据输出的位置

这里贴上完整的启动类的代码,自定义输出format不会影响到map与reduce,所以这里就不贴

  1. public static void main(String[] args) throws IOException {
  2. Configuration conf = new Configuration();
  3. //这个默认值就是local,其实可以不写
  4. conf.set("mapreduce.framework.name", "local");
  5. //本地模式运行mr程序时,输入输出可以在本地,也可以在hdfs中,具体需要看如下的两行参数
  6. //这个默认值 就是本地,其实可以不配
  7. //conf.set("fs.defaultFS","file:///");
  8. //conf.set("fs.defaultFS","hdfs://server1:9000/");
  9.  
  10. Job job = Job.getInstance(conf);
  11.  
  12. //使得hadoop可以根据类包,找到jar包在哪里
  13. job.setJarByClass(Driver.class);
  14.  
  15. //设置自定义的OutputFormat
  16. job.setOutputFormatClass(MyOutputFormat.class);
  17.  
  18. //指定Mapper的类
  19. job.setMapperClass(WordCountMapper.class);
  20. //指定reduce的类
  21. job.setReducerClass(WordCountReduce.class);
  22.  
  23. //设置Mapper输出的类型
  24. job.setMapOutputKeyClass(Text.class);
  25. job.setMapOutputValueClass(LongWritable.class);
  26.  
  27. //设置最终输出的类型
  28. job.setOutputKeyClass(Text.class);
  29. job.setOutputValueClass(LongWritable.class);
  30.  
  31. //指定输入文件的位置,这里为了灵活,接收外部参数
  32. FileInputFormat.setInputPaths(job, new Path("D:\\mr\\wordcount\\input"));
  33. //指定输入文件的位置,这里接收启动参数
  34. FileOutputFormat.setOutputPath(job, new Path("D:\\mr\\wordcount\\out1"));
  35.  
  36. //将job中的参数,提交到yarn中运行
  37. //job.submit();
  38. try {
  39. job.waitForCompletion(true);
  40. //这里的为true,会打印执行结果
  41. } catch (ClassNotFoundException | InterruptedException e) {
  42. e.printStackTrace();
  43. }
  44. }

影响到的位置也仅仅是红色代码区域。然后随便写一个wordcount的代码,执行结果如下,我们先看FileOutputFormat.setOutputPath()中参数目录的内容

很明显,这是mapreduce运行完成后,代表运行结果的文件

我们再看D盘的目录

打开可以看到输出的最终结果

自定义输出就完了,利用这个类的实现,我们可以自由实现存储的位置

七:自定义输入数据

待补充...

八:全局计数器

在运行mapreduce中,我们可能会遇到计数器的需求,比如说我们要知道计算了多少条数据,剔除了多少条不合法的数据。

  1. public class MultiOutputs {
  2. //通过枚举形式定义自定义计数器
  3. enum MyCounter{MALFORORMED,NORMAL}
  4.  
  5. static class CommaMapper extends Mapper<LongWritable, Text, Text, LongWritable> {
  6.  
  7. @Override
  8. protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
  9.  
  10. String[] words = value.toString().split(",");
  11.  
  12. for (String word : words) {
  13. context.write(new Text(word), new LongWritable(1));
  14. }
  15. //对枚举定义的自定义计数器加1
  16. context.getCounter(MyCounter.MALFORORMED).increment(1);
  17. //通过动态设置自定义计数器加1
  18. context.getCounter("counterGroupa", "countera").increment(1);
  19. //直接设定数值
  20. context.getCounter("","").setValue(10);
  21. }
  22.  
  23. }

九:多个job串联,定义执行顺序

还记得之前我们写的mr程序中有计算qq好友,以及计算一本小说中,出现的哪个词最多的程序吗?我们分别是使用了两个mapreduce来计算这些数据,第二个mapreduce是基于第一个mapreduce的。

但是那个时候,我们是等待第一个程序执行完成后,手动执行第二个程序,其实这一步操作是可以自动的。我们可以把多个job关联起来

  1. Job job1 = 创建第一个job;
  2. Job job2 = 创建第二个job;
  3. Job job3 = 创建第三个job;
  4. ControlledJob cJob1 = new ControlledJob(job1.getConfiguration());
  5. ControlledJob cJob2 = new ControlledJob(job2.getConfiguration());
  6. ControlledJob cJob3 = new ControlledJob(job3.getConfiguration());
  7.  
  8. cJob1.setJob(job1);
  9. cJob2.setJob(job2);
  10. cJob3.setJob(job3);
  11.  
  12. // 设置作业依赖关系
  13. cJob2.addDependingJob(cJob1);//第二个依赖于第一个
  14. cJob3.addDependingJob(cJob2);//第三个依赖于第二个
  15.  
  16. JobControl jobControl = new JobControl("RecommendationJob");
  17. jobControl.addJob(cJob1);
  18. jobControl.addJob(cJob2);
  19. jobControl.addJob(cJob3);
  20.  
  21. // 新建一个线程来运行已加入JobControl中的作业,开始进程并等待结束
  22. Thread jobControlThread = new Thread(jobControl);
  23. jobControlThread.start();
  24. while (!jobControl.allFinished()) {
  25. Thread.sleep(500);
  26. }
  27. jobControl.stop();

十:mapreduce的参数优化

10.1:资源相关参数

  1. //以下参数是在用户自己的mr应用程序中配置就可以生效
  2. (1) mapreduce.map.memory.mb: 一个Map Task可使用的资源上限(单位:MB),默认为1024。如果Map Task实际使用的资源量超过该值,则会被强制杀死。
  3. (2) mapreduce.reduce.memory.mb: 一个Reduce Task可使用的资源上限(单位:MB),默认为1024。如果Reduce Task实际使用的资源量超过该值,则会被强制杀死。
  4. (3) mapreduce.map.java.opts: Map TaskJVM参数,你可以在此配置默认的java heap size等参数, e.g.
  5. “-Xmx1024m -verbose:gc -Xloggc:/tmp/@taskid@.gc @taskid@会被Hadoop框架自动换为相应的taskid), 默认值: “”
  6. (4) mapreduce.reduce.java.opts: Reduce TaskJVM参数,你可以在此配置默认的java heap size等参数, e.g.
  7. “-Xmx1024m -verbose:gc -Xloggc:/tmp/@taskid@.gc”, 默认值: “”
  8. (5) mapreduce.map.cpu.vcores: 每个Map task可使用的最多cpu core数目, 默认值: 1
  9. (6) mapreduce.reduce.cpu.vcores: 每个Reduce task可使用的最多cpu core数目, 默认值: 1
  10.  
  11. //应该在yarn启动之前就配置在服务器的配置文件中才能生效
  12. (7) yarn.scheduler.minimum-allocation-mb 1024 给应用程序container分配的最小内存
  13. (8) yarn.scheduler.maximum-allocation-mb 8192 给应用程序container分配的最大内存
  14. (9) yarn.scheduler.minimum-allocation-vcores 1
  15. (10)yarn.scheduler.maximum-allocation-vcores 32
  16. (11)yarn.nodemanager.resource.memory-mb 8192
  17.  
  18. //shuffle性能优化的关键参数,应在yarn启动之前就配置好
  19. (12) mapreduce.task.io.sort.mb 100 //shuffle的环形缓冲区大小,默认100m
  20. (13) mapreduce.map.sort.spill.percent 0.8 //环形缓冲区溢出的阈值,默认80%

10.2:容错相关参数

  1. (1) mapreduce.map.maxattempts: 每个Map Task最大重试次数,一旦重试参数超过该值,则认为Map Task运行失败,默认值:4
  2. (2) mapreduce.reduce.maxattempts: 每个Reduce Task最大重试次数,一旦重试参数超过该值,则认为Map Task运行失败,默认值:4
  3. (3) mapreduce.map.failures.maxpercent: 当失败的Map Task失败比例超过该值为,整个作业则失败,默认值为0. 如果你的应用程序允许丢弃部分输入数据,则该该值设为一个大于0的值,比如5,表示如果有低于5%的Map Task失败(如果一个Map Task重试次数超过mapreduce.map.maxattempts,则认为这个Map Task失败,其对应的输入数据将不会产生任何结果),整个作业扔认为成功。
  4. (4) mapreduce.reduce.failures.maxpercent: 当失败的Reduce Task失败比例超过该值为,整个作业则失败,默认值为0.
  5. (5) mapreduce.task.timeout: Task超时时间,经常需要设置的一个参数,该参数表达的意思为:如果一个task在一定时间内没有任何进入,即不会读取新的数据,也没有输出数据,则认为该task处于block状态,可能是卡住了,也许永远会卡主,为了防止因为用户程序永远block住不退出,则强制设置了一个该超时时间(单位毫秒),默认是300000。如果你的程序对每条输入数据的处理时间过长(比如会访问数据库,通过网络拉取数据等),建议将该参数调大,该参数过小常出现的错误提示是“AttemptID:attempt_14267829456721_123456_m_000224_0 Timed out after 300 secsContainer killed by the ApplicationMaster.”。

10.3:本地运行mapreduce作业

  1. mapreduce.framework.name=local
  2. mapreduce.jobtracker.address=local
  3. fs.defaultFS=local

10.4:效率和稳定性相关参数

  1. (1) mapreduce.map.speculative: 是否为Map Task打开推测执行机制,默认为false
  2. (2) mapreduce.reduce.speculative: 是否为Reduce Task打开推测执行机制,默认为false
  3. (3) mapreduce.job.user.classpath.first & mapreduce.task.classpath.user.precedence:当同一个class同时出现在用户jar包和hadoop jar中时,优先使用哪个jar包中的class,默认为false,表示优先使用hadoop jar中的class
  4. (4) mapreduce.input.fileinputformat.split.minsize: FileInputFormat做切片时的最小切片大小,
    (5)mapreduce.input.fileinputformat.split.maxsize: FileInputFormat做切片时的最大切片大小(切片的默认大小就等于blocksize,即 134217728)

hadoop系列四:mapreduce的使用(二)的更多相关文章

  1. hadoop系列三:mapreduce的使用(一)

    转载请在页首明显处注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/7224772.html 一:说明 此为大数据系列的一些博文,有空的话会陆续更新,包含大数据的 ...

  2. 安装Hadoop系列 — 新建MapReduce项目

    1.新建MR工程 依次点击 File → New → Ohter…  选择 “Map/Reduce Project”,然后输入项目名称:mrdemo,创建新项目:     2.(这步在以后的开发中可能 ...

  3. flask系列四之SQLAlchemy(二)表关系

    一.SQLAlchemy外键约束 1.创建外键约束表结构 目标:建立两个表“用户表(user)”和“问题表( question)”,其中问题表中的作者id是是用户表的id即外键的关系.(一个用户可以有 ...

  4. hadoop系列二:HDFS文件系统的命令及JAVA客户端API

    转载请在页首明显处注明作者与出处 一:说明 此为大数据系列的一些博文,有空的话会陆续更新,包含大数据的一些内容,如hadoop,spark,storm,机器学习等. 当前使用的hadoop版本为2.6 ...

  5. Hadoop MapReduce编程 API入门系列之网页排序(二十八)

    不多说,直接上代码. Map output bytes=247 Map output materialized bytes=275 Input split bytes=139 Combine inpu ...

  6. Hadoop 系列(二)安装配置

    Hadoop 系列(二)安装配置 Hadoop 官网:http://hadoop.apache.or 一.Hadoop 安装 1.1 Hadoop 依赖的组件 JDK :从 Oracle 官网下载,设 ...

  7. Hadoop系列之(二):Hadoop集群部署

    1. Hadoop集群介绍 Hadoop集群部署,就是以Cluster mode方式进行部署. Hadoop的节点构成如下: HDFS daemon:  NameNode, SecondaryName ...

  8. C#中的函数式编程:递归与纯函数(二) 学习ASP.NET Core Razor 编程系列四——Asp.Net Core Razor列表模板页面

    C#中的函数式编程:递归与纯函数(二)   在序言中,我们提到函数式编程的两大特征:无副作用.函数是第一公民.现在,我们先来深入第一个特征:无副作用. 无副作用是通过引用透明(Referential ...

  9. Istio的流量管理(实操二)(istio 系列四)

    Istio的流量管理(实操二)(istio 系列四) 涵盖官方文档Traffic Management章节中的inrgess部分. 目录 Istio的流量管理(实操二)(istio 系列四) Ingr ...

随机推荐

  1. jmeter-命令行执行脚本

    日常测试过程中发现,在大数量并发时,jmeterGUI界面时长宕机.卡死,在这种情况下我们就需要使用命令行来执行脚本了(非GUI), 命令行执行首先就必须要配置环境变量,如同JAVA-HOME一样,这 ...

  2. Argparse4j

    argparse4j 是 Python argparse 命令行解析器的 Java 语言移植版.这个要比spring AspectJ 更简单,更方便实现. <dependency> < ...

  3. Codeforces_617E: XOR and Favorite Number(莫队算法)

    题目链接 题意大致是说,给出一个长为n(n<=1e5)的数组,给定一个k(k<=1e6),给出m(m<=1e5)个询问,每组询问中回答 从a_l到a_r有多少个连续的子序列满足异或和 ...

  4. 【NOIP模拟】Grid(字符串哈希)

    题目背景 SOURCE:NOIP2016-RZZ-1 T3 题目描述 有一个 2×N 的矩阵,矩阵的每个位置上都是一个英文小写字符. 现在需要从某一个位置开始,每次可以移动到一个没有到过的相邻位置,即 ...

  5. 使用Dubbo、JSF等RPC框架时,对于异常的处理

    无论是Dubbo还是JSF等RPC框架,一般都会把接口分为2部分: 1,服务端(provider) 2,客户端(consumer) 由于,客户端与服务端可能不在同一个应用中,所以客户端一般在调用服务端 ...

  6. (转)Synchronized(对象锁)和Static Synchronized(类锁)的区别

    场景:面试的时候经常用得到! 1 综述 Synchronized和Static Synchronized区别 一个是实例锁(锁在某一个实例对象上,如果该类是单例,那么该锁也具有全局锁的概念),一个是全 ...

  7. 【javascript】回调函数

    1. 定义 回调函数,即当条件满足时执行的函数.有三种方法实现调用回调函数 call 1)call 用法:call(thisObj, Obj) 主要区别:call 方法会将函数对象上下文修改为this ...

  8. MyBatis+PageHelper实现分页

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7256105.html 前面讲到Spring+SpringMVC+MyBatis深入学习及搭建(十七)--Sp ...

  9. 使用Fiddler调试手机端页面请求/抓包

    简介 Fiddler作为一个强大的抓包工具,也是非常强大的http(s)协议分析工具,我们通常用它跟踪请求,PC端使用这里暂不做介绍(这里前提是熟悉PC端的使用),使用很简单. 那么我们如何来用它来跟 ...

  10. nodejs01--什么是nodejs,nodejs的基本使用

    nodejs使用范围 -直接在cmd命令行运行,在你的电脑上直接运行 -可以搭建一个web服务器(express,koa) -一些基本的使用 -modules是如何工作的 -npm管理modules ...