使用IDEA时,自动生成的类注释中的用户名默认为操作系统当前的用户名。这样的用户名通常和实际要用的用户名是不一致的。 已知的有两种调整方式。 第一种方式在File -> Settings -> Editor -> File and Code Templates –> Class –>Includes –>File Header 添加固定的author注释即可。如下: 不过我并不喜欢这种固定写死的方式,有的时候需要自动生成文档的时候这里还是会出问题。 在idea中,对应用户名的变量是“${USER}”,这个变量对应的虚拟机参数是“-Duser.name”。要调整这个参数,在网上找到的多是修改idea安装目录下的“\bin\idea.exe.vmoptions”文件,在最后一行添加“-Duser.name=用户名”这种做法。 不过这种方式在较新版本的idea中并不适用。可用的方式是在IDEA的菜单中选择 Help –> Edit Custom VM Options, 然后点击确定,在idea的用户目录中创建一个新的idea.exe.vmoptions文件,在编辑器中为这个文件的最后一行添加“-Duser.name=用户名”。 再次调整File Header为如下格式: 然后保存重启即可。 #####
[阅读更多...]-
IDEA修改类注释自动生成的用户名
-
Gradle Refresh异常: Could not HEAD
在idea中使用Gradle的时候遇到了一个奇怪的异常:获取公司内部repository中的包是正常的,但是获取jar源文件的时候却遇到了一些异常。 异常信息可以通过执行“Refresh All Gradle Projects”操作,在Gradle View中看到。 异常信息简略如下: 完整异常信息如下,我做了折叠: 根据异常信息可以看出,是访问公司库发生了异常。检查过repository的配置,用户名和密码都是正确的。后来想到公司的repository是 需要通过https访问的,可能是证书的问题。 找到repository的证书,将证书放到JAVA_HOME/jre/lib/security目录下,重启idea,再次执行“Refresh All Gradle Projects”可以看到不再报异常了。 #######
[阅读更多...] -
spark java.lang.StackOverflowError
问题描述 在工作中使用spark的一个主要内容就是从多个路径下搜集数据并进行处理。常用的代码大致如下: 在readData方法中调用SparkContext的sequenceFile方法读取文件创建RDD集合。而后调用RDD的reduce和union(即“++”)方法将多个RDD集合进行合并。 这里的代码通常是不会报错的。但是执行合并后的RDD集合的Action算子(这里是collect方法)的时候偶尔会遇到StackOverflowError。异常信息如下: 从异常信息上来看,是在使用java.io.ObjectInputStream执行序列化的时候出现了递归或者死循环,因为栈空间不足导致的这个问题。 继续跟踪调试,发现主要是因为处理的文件过多导致的——一般处理的文件数量超过450(大致数值)以后就会遇到这个问题。查了一些资料,了解到根本原因是RDD Lineage过长:代码中每执行一次RDD.union操作就会增加一次RDD Lineage的步长。 解决问题 现在根据问题的特征和根源,找到的解决思路大致上有这么几个: 增加执行时的栈空间; 避免一次处理过多文件; 定期削减RDD Lineage的长度; 避免创建太长的Lineage。 接下来逐个解释下上面的思路。 增加栈空间 从异常信息中可以看到StatckOverflowError是在Executor中抛出的,所以要调整Executor栈空间,可以在job提交参数中添加如下内容: 这项配置将Executor的栈空间设置为80M。 然后我们测试一下,测试目标是一次处理720个文件。执行结果OK。 再次测试,仍然是80M栈空间,目标一次处理4320个文件。仍然能执行成功。 可知这个方案在一定程度上是可行的,至少可以用来做任务优化。 避免一次处理过多的文件 这个思路是最简单的:既然一次处理太多文件会报错,那么就分成多个批次来处理好了。 调整后的代码如下: 这种方式肯定是可行的,但是用起来多少有点儿麻烦:需要将中间结果集临时存储起来而后再一起使用。中间结果集要是比较小的话还好说,一个变量就足够了;中间结果集要是太大了就得先保存到HDFS上,而后再做二次处理。 削减RDD Lineage的长度 既然问题是因为RDD Lineage长度过长导致的,那么就需要在RDD Lineage变得太长之前,将之削减掉一部分。做法是对合并出的RDD结果集定期做checkpoint,并随意执行一个Action算子。 代码如下: 我一度依赖过这个方案。但是这个方案有一个很大的缺点:就是执行效率太低——比上一种方案效率还低,可以说是执行时间最慢的一种方式了。checkpoint操作实际上是将每个rdd都存储到了硬盘上,其效率可想而知。 避免创建太长的Lineage 前面说的第二种方式也可以说是这种思路的实现。对这个问题来说,推荐得比较多的还是使用SparkContext.union方法来替换RDD.union方法。代码大致如下: 这个方式也是我最初寄望最多的一个方案。 本来希望能通过这个方案一劳永逸地解决这个问题。可是在测试的时候遇到了些问题:这个方案也不能处理路径太多(800个以上)的问题,但是也没有立即报错,而是阻塞住了。执行两三个小时后提示任务执行失败。在日志中可以找到如下错误提示: 对于这个错误提示我目前并无头绪,只能先抛出来给大家看一下。以后如果有进展再继续补充。 就这样。 参考文档: http://apache-spark-user-list.1001560.n3.nabble.com/java-lang-StackOverflowError-when-calling-count-td5649.html https://stackoverflow.com/questions/30522564/spark-when-union-a-lot-of-rdd-throws-stack-overflow-error https://stackoverflow.com/questions/38206166/apache-spark-stackoverflowerror-when-trying-to-indexing-string-columns #####
[阅读更多...] -
Kafka java.nio.channels.ClosedChannelException
最近开始部署一个工程时遇到了Kafka消费的问题,报错信息如下: 相关的工程已经在测试环境测试过了,部署到新的环境上却报错了。所以这个问题应该是环境问题或配置问题。 进入Kafka源码可以发现这个问题是在连接Kafka Broker时出现的。所以这个问题应该是网络连接导致的。找运维部的同事看了一下,确认是相关的节点与Kafka集群的通信被屏蔽了。解除屏蔽后问题就得到修复了。 在StackOverflow上也查过相关的问题,多是类似的缘故,比如host的错误配置等。
[阅读更多...] -
Gradle访问需要用户名密码的仓库
公司私有的maven仓库在访问时是需要用户名密码的。访问这种仓库的时候需要在build.gradle中配置repository用户权限,如下面这样: 但是如果每个项目都要配置一次的话,多少会让人有些觉得不耐烦。所以可以这个配置也可以在init中完成。打开gradle安装目录->init.d目录,创建init配置文件“init.gradle”,配置详情如下: 这样配置以后,就可以去掉在build.gradle中的repository相关的配置了,算是简化了build.gradle的配置了。
[阅读更多...] -
使用Gradle构建scala多模块工程
前段时间终于无法忍受sbt慢如龟速的编译打包速度了。稍稍调研了一下,就果断切换到了gradle。由于调研得比较匆忙,在使用过程中遇到了各种问题。好在最后都能解决了。 我这里使用scala主要是用来编写spark job。由于我自己的一些需要,这些job中有几个是多模块的。在这里简单解释一下如何使用gradle构建scala多模块项目。 这里用我最近开发的项目来做说明。项目名称是consumer-portrait-job,有两个子模块:common和compute。 首先在项目根目录下创建一个settings.gradle文件,这个文件主要用来描述项目名称及子模块信息: 然后再创建一个build.gradle文件。这个文件描述了主项目及子项目的一些通用配置。配置如下: 在这个配置文件中包含两个大的模块:allprojects和subprojects。 allprojects中的配置是所有项目共享的(包含根项目)。在这里,我定义了项目的groupId和version等信息,并应用了gradle的idea插件。 subprojects的配置是所有子项目通用的。 在subprojects中的第一行声明了使用gradle的scala插件。 接下来的配置项“sourceCompatibility”声明了编译时使用的jdk版本;“targetCompatibility”确保了编译生成的class与指定版本的jdk兼容。 在ext中声明了子项目中可以使用的一些变量。我这里是声明了scala和spark的版本。 repositories项配置了当前项目可以使用的仓库。这里使用的第一个仓库是本机的maven库,第二库是ali提供的repository服务,第三个库是maven中央库。(曾经研究过如何让gradle和maven公用同一个本地仓库,不过最后也是不了了之)。 dependencies中声明了所有子模块都需要使用的依赖项。这里用到了scala库和spark库,这两个库只会在编译期用到,所以声明使用的依赖类型是compileOnly(这种依赖类型是gradle Java插件独有的,gradle scala插件继承自java插件,所以也可以使用)。 task mkdirs是一个自定义任务。在根项目配置完settings.gradle和build.gradle后,执行“gradle mkdirs”命令完成子模块目录的创建工作。 在两个子模块common和compute下创建build.gradle文件并做配置。 common模块的build.gradle配置详情: 这里只是声明了一下commons模块独有的依赖项。 compute模块是启动模块,在该模块中有spark任务的驱动类。该模块的build.gradle配置详情: 配置中的第一行dependencies仍然是配置compute模块的依赖项。其中略需注意的是对common模块的依赖。 接下来的jar声明指明了将该模块打成的jar包的名称。脚本中需要根据包名来调用模块生成的包,默认生成的包名会带上版本信息,不太合适。 最后是一个自定义任务。该任务的目标是将一些必要的jar和其他文件打成一个zip包,以便于上传任务到执行服务器。任务中的第一个部分是将一些运行时依赖打入zip包中的lib目录,使用include关键字提示包含运行时依赖中指定名称的包,也可以使用exclude关键字排除一些包。第二部分是将生成的jar和本地doc目录中的文件打入zip包的根目录。 就这样。有空再写个示例项目留着参考。 ——————–END———————-
[阅读更多...] -
理解AOP
说下我对AOP的理解:AOP是给程序添加统一功能的一种技术。在代码层面上来说,AOP就是在必要的业务代码中织入业务无关的、统一的代码的一种技术。在实现AOP的时候,通常努力争取的目标是对业务代码无侵入或是低侵入。 平时用得比较多的是Spring中的AOP实现,还有一些比如Spring中的shiro权限验证,@Cachable注解等在某种程度上也可以认为是AOP。在Spring AOP之外还有很多其他的实现方式。 在了解AOP实现方案之前有必要看一下java类编译加载的过程。 java类编译的过程如下: .java文件 –> 编译 –> .class文件 –> 载入虚拟机 在虚拟机中加载使用类的过程如下: 装载 –> 验证 –> 准备 –> 解析 –> 初始化 –> 使用 –>卸载 上面所述的过程中,通常可以在编译和装载这两个环节向原始代码中织入AOP代码。 编译环节织入代码往往需要定制的编译器,如使用AspectJ的时候,就可以通过AspectJ的编译器ajc来完成AOP代码的织入。这个环节织入的代码class文件中就有体现。 装载环节实现代码织入的方式比较多,大致有这么几种: 基于JVMTI Agent方式; 替换默认的系统类加载器; 使用自定义类加载器; 使用动态代理。 jvmtgi方式是一种侵入度很低的形式,只需要在启动时添加一些特定的启动参数,如javaagent。我曾经写过一个java方法运行时间统计程序:jspy-agent,就是用这种方式在在java方法中织入计时代码。这种方式需要对字节码文件进行操作,目前的字节码操作框架有asm、bcel、javassist等。 使用自定义类加载器替换默认系统类加载器也只需要添加一个启动参数:-Djava.system.class.loader=com.your.CustomClassLoader。前提是需要自定义一个类加载器。当然自定义类加载器也不只是有这一种使用方式。 关于动态代理,目前用的比较多的就是JDK动态代理和CGlib了,相关的资料一抓一大把,不说了。 大体上就是这些了。不过还有一个问题,想和大家一起想想:java设计模式中的代理模式、装饰模式是不是也算是AOP的一种实现呢? 参考文档: SpringAOP织入:https://blog.csdn.net/wuliusir/article/details/32916419 JVMTI Agent:https://www.ibm.com/developerworks/cn/java/j-lo-jpda2/ #############
[阅读更多...] -
使用ThreadLocal
之前和同事讨论过一个日期格式化的问题,程序比较简单,大体是这样: 同事并不喜欢这个代码。原因是每调用一次format方法都会创建一个SimpleDateFormat对象。虽然我一再强调SimpleDateFormat对象是方法内的,即生即灭,不会导致明显的内存或性能上的问题。但是同事还是打算尝试一下。一段时间后我看到了下面的代码: 他使用了ThreadLocal。关于ThreadLocal,官方文档的说法如下: 该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。 文档的说法有些令人费解,所以这里把英文原文也贴出来: This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID). 简而言之,ThreadLocal的作用就是为每个线程提供一个线程内的局部变量来让该线程使用。这个局部变量的状态不应该受到其他线程的影响,从而在某种程度上实现了线程隔离。 也许我们需要换一个更简单直观的程序来看一下: 在这个ThreadLocalTest类中,我们创建了一个ThreadLocal变量,通过这个变量为每个线程创建了一个值为当前时间戳的字符串。在程序中启动了3个线程来测试每个是否线程都会得到一个唯一的字符串变量。另外,在每个线程中都会重复调用线程内部的字符串变量来测试该变量是否发生了变化。 执行结果如下图: 执行结果中的第一列是线程ID,第二列则是在不同时间输出的线程内部字符串变量。可以看到不同线程对应的字符串变量是不同的,一个线程在不同时间使用的内部字符串变量是相同的。 回到一开始讨论的日期工具类上。通过使用ThreadLocal我的同事避免了每次调用format方法都要创建一个SimpleDateFormat对象的问题,减少了对象创建回收的过程。同时因为每个线程内的SimpleDateFormat实例都是线程隔离的,也就是说这个线程内部的SimpleDateFormat实例每次只会被这一个线程所使用,所以虽然SimpleDateFormat类是非线程安全的,在使用中也不会出现问题。 了解了使用ThreadLocal的优势后,又想到了另一个问题:在使用ThreadLocal时每创建一个线程就会为这个线程创建一个内部实例,那么在无限次创建线程的时候,是否会因为ThreadLocal创建的内部实例过多导致内存泄漏?或者换个角度考虑:线程运行停止后,ThreadLocal是如何回收该线程的内部实例的。 这个问题我需要想一想。想明白了换篇文儿继续说。 #####
[阅读更多...] -
jar包中的jar
前两天在整理一个工程的时候突发奇想,能不能把需要的jar都打到一个类似于war包的jar包里面去,但是不解开原始jar包,而是将之统一放到外面jar包中的一个lib目录下。 按这个思路试了好几次,都遇到了NoClassDefFoundError。一开始以为是manifest中配置有误,在几次尝试调整manifest文件仍然失败之后,觉得有必要查看一些类似的案例。 类似的案例比如SpringBoot工程打成的jar包,又比如jenkins的可以直接用jar命令调用的war包。找到相关的包分析了许久,仍然找不到端倪。 后来在Oracle的官方文档中找到了解释: To load classes in JAR files within a JAR file into the class path, you must write custom code to load those classes. For example, if MyJar.jar contains another JAR file called MyUtils.jar, you cannot use the Class-Path header in MyJar.jar’s manifest to load classes in MyUtils.jar into the class path. 懒得翻译了,大意就是如果要引用jar中的jar,就需要编写额外的代码,如自定义的类加载器。SpringBoot打成的包就是这样做的。 参考文档:https://docs.oracle.com/javase/tutorial/deployment/jar/downman.html #######
[阅读更多...] -
Gradle中的“provided”
maven中值为provided的scope,可以让我们声明一个只在编译时使用的非传递性的依赖。在gradle中我们可以声明compileOnly依赖来实现类似的效果(需要java插件)。示例如下: compileOnly声明的使用场景可以一言蔽之:声明的依赖只在compile阶段使用,但是不会在runtime阶段用到,并且这种依赖也是非传递性的。 ######
[阅读更多...]