• Gradle依赖排除

    在引用依赖时经常会有这样的问题:某些间接引用的依赖项是不需要的;产生了依赖冲突。此时需要排除一些依赖。 下面的内容介绍了几种在gradle中排除依赖的方式。 在dependency中排除 这种方式是粒度最细的,也是最为繁琐的。此时可以考虑全局设置。 在全局配置中排除 全局配置是在configuration中完成的。 禁用传递依赖 禁用传递依赖需要将属性transitive设置为false。可以在dependency中禁用传递依赖,也可以在configuration中全局禁用: 还可以在单个依赖项中使用@jar标识符忽略传递依赖: 强制使用某个版本 如果某个依赖项是必需的,而又存在依赖冲突时,此时没必要逐个进行排除,可以使用force属性标识需要进行依赖统一。当然这也是可以全局配置的: 在打包时排除依赖 先看一个示例: 代码表示在打zip包的时候会过滤掉名称中包含“unwanted”和“log”的jar包。这里调用的exclude方法的参数和前面的例子不太一样,前面的参数多是map结构,这里则是一个正则表达式字符串。 也可以使用在打包时调用include方法选择只打包某些需要的依赖项: 始终选择最新的依赖的项 最后这个特性和排除依赖没有什么关系,只是顺带说一下。 在依赖项中使用加号+,可以在构建时检查远程仓库是否存在该依赖的新版本,如果存在新版本则下载选用最新版本。更有用的是可以指定依赖某个大版本下的最新子版本,如1.+表示始终采用依赖的1.x序列的最新版本。 示例如下: 就这样。 #####

    [阅读更多...]
  • spark使用kafka报NoSuchMethodError

    运行spark任务消费kafka时,报了如下的异常: 使用的spark版本是1.6.1,kafka版本是0.8.2.1。 根据异常信息猜测应该是scala版本导致的问题。 查看了一下依赖的spark和kafka的配置为: 依赖项直接copy自mvnrepository网站。 使用gradle dependencies指令查看了依赖详情:kafka依赖的scala的版本是2.11.5,spark依赖的scala版本是2.11.6。而spark集群服务器部署的scala版本是2.11.8。 照说都是在2.11.*范围内,不应该报错的。 反复测试了几次之后忽然想到spark集群使用的scala版本也许不是2.11.8,也就是说spark集群的scala版本和服务器scala环境的版本也许不一致,我之前的意识是错误的。 需要测试一下,依稀有些印象可以使用2.10.4版本的scala进行编译,所以得先把依赖调整一下: kafka和spark依赖的name后面的数字,如“kafka_2.10”中的2.10指的是采用scala 2.10.*编译打包的。 打包上传后,测试通过,不再报那个异常了。 该如何确认spark集群使用的scala的版本呢。第一个思路是看spark集群lib目录下的依赖是不是会有相关的提示。看了一下比较失望: 就这么几个包,一点儿信息都没有。 不死心,用vi查看了一下。在spark-1.6.1-yarn-shuffle.jar的DEPENDENCIES文件中找到些许证明: 提示了spark-network-common_2.10是用scala 2.10编译的。 又查了一些资料,可以简单进行判断: 如果是spark1,用的scala版本通常是2.10,spark2则通常对应2.11版的scala。 就这样。 ########

    [阅读更多...]
  • gradle: No cached version of org.scala-lang

    在idea中使用gradle构建项目时总是提示失败,发现报错信息如下: 看了一下,关键是最后一行: 印象中并没有设置过离线模式。看了一下idea中gradle的设置,发现存在相关的默认设置“Offline work”。取消勾选“Offline work”就可以了。 依次打开如下菜单: Settings -> Build.Execution,Deployment -> Build Tools –> Gradle 在Globle Gradle settings中有“Offline work”项,取消掉勾选就可以了。 #######

    [阅读更多...]
  • 清理idea缓存

    使用idea的时候,有时调整了文件结构或pom文件就会报一些莫名其妙的错误。之前的解决方式是:打开应用目录,删除掉一系列idea生成的工程文件,而后再用idea重新打开就行了。 现在发现了一个新的解决方式:选择 菜单 -> File –>Invalidate Cache / Restart,打开缓存清理功能,点击“Invalidate and Restart”完成清理并重启就可以修复这类问题。 IntelliJ IDEA 的缓存和索引主要是用来加快文件查询,从而加快各种查找、代码提示等操作的速度。但是,IntelliJ IDEA 的索引和缓存并不是一直会良好地支持 IntelliJ IDEA 的,这某些特殊条件下,IntelliJ IDEA 的缓存和索引文件也是会损坏的,比如:断电、蓝屏引起的强制关机,当你重新打开 IntelliJ IDEA,IntelliJ IDEA 会有很大可能报各种莫名其妙的错误。

    [阅读更多...]
  • 从一段计时代码开始

    下面这段代码估计是我们见过(或写过)最多的代码了: 要统计某些逻辑块的执行时间,这段代码可以说是非常简单且有效。不过如果要统计的方法很多时,为那些方法都填上这段代码就有些让人头疼了。主要有这样几个问题: 太多的重复工作; 计时内容散乱不好整理; 在业务代码中插入了业务无关的内容。 前两个缺点忍一忍多费些力气也不是不能克服。关键是最后一个问题,一旦意识到以后,再这样写就总是觉得怪怪的。 这些问题该怎么解决呢。最初的思路是用模板方法模式或装饰器模式来解决,不过很快就否定了这个思路:代码中的业务逻辑通常是各种各样的,只为了解决计时这样一个边缘需求就强行引入一些方法模式很容易造成代码结构的混乱,而且这样做也可以说是一种更深的代码侵入。发现此路不通后很自然就转到AOP方案上了——AOP生来就是为了解决这种问题的。 具体该怎么使用AOP呢。之前在使用Spring框架的时候曾经写过一个基于spring-aspect完成的方法计时的程序: 代码中的StopWatch是一个自定义注解。这段代码会计算所有在包com.zhyea下的以StopWatch注解标记的方法的执行时间。 但是工作中很多应用是不依赖Spring的,这时就应该考虑一下AOP的实现了。简单来说,AOP的实现就是在某些方法执行前后运行特定的代码(类似于装饰器模式)。这些代码可以直接插入到类中,也可以在方法执行前后引用。至于插入AOP代码的时机,大体上有如下几个: 类编译时; 类加载前; 调用方法时。 对应三种插入代码的时机有着不同的AOP的实现方案。在类编译时插入AOP代码的如AspectJ,在使用时需要特别定制的编译器;调用方法时实现AOP,就如同前面的SpringAspect的例子,需要框架的支持,否则还是有一些代码的侵入;至于在类加载时实现AOP,Spring也有提供方案,是依赖cglib实现,不过这里不展开了。 对于我们要解决的方法计时的问题,我们可以考虑采用在类加载时插入计时代码来实现。这里我们会用到java.lang.instrument包和asm。 java.lang.instrument 是 Java SE 5 的新特性。使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。 asm是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。asm可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。 下面我们就用asm和instrument来实现下计时统计的需求。如下是一个测试类: 在类中的operation方法中插入计时代码后: 我们的目标就是将测试类转换成这段代码的样子。代码中的TimerClerk是一个计时统计工具类,可以忽略。 首先,我们先使用asm来完成插入计时代码的功能: 在代码中使用了AdviceAdapter这个类。通过这个类可以很方便地在进入方法和退出方法时插入代码。现在我们可以使用TimerClassAdapter在任意其他类中插入计时代码了。有心的话可以写一个main方法来试一下,也可以用反编译工具来查看生成的类看看是否和预期一致。 关于asm的更多内容有需要请参考官方文档。(在csdn也有一份中文文档可以参考,点击此处下载)。 然后是在instrument中使用上面定义的TimerClassAdapter类: 通过instrument可以在类加载时将原始类的字节码装饰成我们需要的样子。并返回新的类到类加载器中。 将代码打包为agent.jar,可以使用如下的命令执行程序: 就这样,使用-javaagent可以在启动应用前调用我们的计时代码插入程序,这样在应用运行时就能同步计算每个方法的执行时间。 另:上面的代码部分摘自我的一个开源项目jspy中,项目目前代码量还比较小,如有兴趣可以参考下。 ########

    [阅读更多...]
  • 关于Zero Copy

    概述 很多web应用都会有大量的静态文件。我们通常是从硬盘读取这些静态文件,并将完全相同的文件数据写到response socket。这样的操作需要较少的CPU,但是效率有些低,它需要经过如下的过程:kernel从硬盘读取数据,越过kernel-user边界将数据传递给用户空间的web应用;用户空间的web应用再次越过kernel-user边界将完全相同的数据写回到kernel空间的socket。在将数据从硬盘传递到socket的过程中,用户空间web应用的角色相当于一个中介,并且有些低效。 数据每次经过kernel-user边界的时候,都需要被copy一次,这样会消耗CPU资源及内存带宽。幸运的是,我们可以使用一种被称为zero copy的技术来消除这些copy操作。使用zero copy技术的应用会请求kernel直接将数据从硬盘拷贝到socket,而无需再经过应用。zero copy极大地提升了应用的性能,并减少了内核态和用户态上下文切换的次数。 在Linux和UNIX系统上,Java类库通过java.nio.channels.FileChannel的transferTo()方法实现了对zero copy的支持。我们可以使用transferTo()方法将读取到的字节数组直接从被调用的channel传输到另一个可写的channel上,这个过程中数据流转不需要通过应用。 接下来我们会先讲解一下如何使用传统的多次copy的机制实现数据的传输,而后再演示下使用transferTo()方法实现的zero copy技术是如何提升性能的。 传统数据传输方案 思考一下如下的场景:从一个文件读取数据,通过网络将数据传递给另一个应用程序(这个场景描述了大部分服务器应用的行为,包括处理静态文件的WEB服务器,FTP服务器,Mail服务器等)。这个操作的核心步骤只有两步,我们看下代码: 我们的代码只有两行,看起来很简单,但是服务器完成这个过程却需要在用户态和内核态之间进行4次上下文切换,也就是说在这个操作完成之前数据需要被copy 4次。下面的图片展示了服务器是如何将数据从文件传输到socket的。 图一:传统模式下数据拷贝过程: 图二:传统模式下内核态和用户态之间的上下文切换 涉及到的步骤包括: 调用read()方法导致了用户态到内核态的切换(参看图二)。在系统内部是通过sys_read()(或类似的其他方法)从文件读取数据。第一次copy(参看图一)是通过直接内存访问(DMA)引擎实现的,这次copy从硬盘上读取了文件内容并将之保存在内核空间的缓冲区中。 第二次copy发生在数据从内核缓冲区被copy到用户缓冲区时,此时read()方法也返回了。read()方法的返回导致了从内核态到用户态一次切换。现在数据是保存在用户空间的缓冲区中。 socket调用send()方法再次引起了用户态到内核态的切换。第三次copy再次将数据放回到内核缓冲区。不过这次的内核缓冲区和上次的不同,这次的缓冲区和目标socket相关。 调用的send()方法返回时,产生了内核态到用户态的上下文切换。这次DMA引擎将数据从内核缓冲区发送到protocol引擎,也就是第四次copy,这是一个独立异步的操作。 使用内核缓冲区作为中间层(而不是直接将数据传送到用户缓冲区)可能看起来有些低效。但是最初将内核缓冲区作为中间层引入进程的目的就是提升性能。在读取数据的时候,作为中间层的内核缓冲区的角色相当于“预读取缓存”,也就是说如果应用请求的数据量比内核缓冲区空间小,就会将一部分数据预读取到作为中间层的内核缓冲区中以供下一次请求使用。很显然,在请求的数据量比内核缓冲区空间小时,这样做可以显著地提升应用性能。在写数据的时候,多个中间层有助于更好地实现异步写(先将数据写到中间缓存,中间层快满时再批量写出)。 不幸的是,在请求的数据量大过内核缓冲区很多时,这种方法本身也会成为性能瓶颈:因为数据会在硬盘、内核缓冲区和用户缓冲区之间多次拷贝。 zero copy可以排除这些多余的copy来提升性能。 zero copy方案 重新思考一下传统的数据传输方案,将会发现第二次和第三次的copy行为实际上是不必要的。在传统方案里,应用做的事情只不过是缓存数据并将之转发到socket缓冲区,我们可以考虑直接将数据从读缓存发送到socket缓冲区中。transferTo()方法能让我们实现这种操作。 transferTo()方法的定义如下: transferTo()方法可以将数据从FileChannel发送到指定的WritableByteChannel中。transferTo()方法需要依赖底层操作系统的支持才能实现zero copy。在UNIX系统和各种Linux系统中,支持zero copy的系统方法是sendfile(),这个方法可以将数据从一个文件描述符转发到另一个文件描述符中。 sendfile()方法定义: 在概述中,我们写过两行代码演示传统数据传输的方法,演示代码中的file.read()和socket.send()两个方法的调用可以替换为调用transferTo()方法,示例如下: 下图演示了调用transferTo()方法时数据传输的路径: 下图演示了调用transferTo()方法时用户态和内核态上下文切换的过程: 调用transferTo()方法涉及到的步骤为: 调用transferTo()方法产生了第一次copy:DMA引擎将文件内容copy到了读缓存中。 然后系统内核将数据copy到与输出socket相关的内核缓冲区中。 第三次copy发生在DMA引擎将数据从内核socket缓冲区发送到protocol引擎时。 看看效果: 将用户态-内核态上下文切换由四次减少到了两次; 将数据的copy由四次减少到了三次(其中只有一次涉及到CPU)。 不过这样子还没有达到使用zero copy的目标。如果底层网卡支持收集操作的话,我们还可以去掉由内核完成的copy(即第二次copy)。在Linux Kernel2.4及以后的版本中,socket缓冲区描述符已经被调整到满足这种需求了。这样这个方案不仅仅是减少了上下文切换的次数,也消除了copy过程中对CPU依赖的部分。尽管用户还是在用transferTo()方法,但是其底层行为已经发生了变化: 调用transferTo()方法时,DMA引擎将文件内容copy到内核缓冲区中; 不再将数据copy到socket缓冲区中,只是将数据描述符(包含地址信息和长度信息)追加到socket缓冲区。DMA引擎直接将数据从内核缓冲区传递到protocol引擎,从而消除了仅剩的CPU copy。 下图展示了使用transferTo()方法和收集操作时copy的详情: 构建文件服务器 现在我们练习使用一下zero copy,就演示一下文件在客户端和服务器之间的传递(示例代码下载地址见文末)。TraditionalClient.java以及TraditionalServer.java是基于传统方案的实现,和新方法是File.read()和Socket.send()。TraditionalServer.java是一个Server端程序,它监听着一个特定的端口以让Client连接,每次会从socket读取4KB数据。TraditionalClient.java连接到Server上,从一个文件中读取(使用File.read()方法)4KB数据并通过socket将数据发送(使用Socket.send()方法)给Server。 类似的,TransferToServer.java和TransferToClient.java实现了相同的功能,不过使用的是transferTo()方法(调用了系统的sendfile()方法),将文件数据从Server端发送到了Client端。 性能比较 我们在一台Linux Kernel版本2.6的机器上执行了示例代码,以毫秒级的时间尺度比较了传统方案和transferTo()方案传输不同大小的数据文件的速度。下表为测试结果: File size Normal file transfer (ms) transferTo (ms) 7MB 156 45 21MB 337 128 63MB 843 387 98MB 1320 617 200MB 2124 1150 350MB 3631 1762 700MB 13498 4422 1GB 18399 8537 可以看到,较之传统方案,transferTo() API降低了大约65%的时间消耗。对于需要在IO channel间进行大量数据copy和传输的应用(比如WebServer),transferTo()可以显著地提升性能。 总结 我们演示了使用transferTo()的性能优势,可以看到中间缓冲区copy(即使是发生在内核中)会有一定的性能损失。对于需要进行channel间大量数据copy的应用,zero copy技术可以显著地提升性能。 其他 本文译自:Efficient data transfer through zero copy 实例代码下载地址:原始地址   CSDN下载 ###################

    [阅读更多...]
  • SpringBoot集成Jersey的单元测试方案

    使用SpringBoot集成Jersey做单元测试时遇到了application.xml找不到的提示。详情如下: 测试使用的代码大致如下: 原因是SpringBoot的Context和Jersey的Context是不同的。要修复这个问题可以基于SpringBoot的Context来构建测试时Jersey的Context,但是这样做也会遇到一些问题。具体什么问题懒得说了。直接说解决方案:跳过Jersey的Context,不使用JerseyTest抽象类,直接自行创建WebTarget实例。 看一个测试超类代码示例好了: 就这样。 #############

    [阅读更多...]
  • hbase hbck用法

    在这里简单整理下hbase hbck的用法。 用法: opts通用可选项 -help 展示help信息; -detail 展示所有Region的详情; -timelag <秒级时间>  处理在过去的指定时间内没有发生过元数据更新的region; -sleepBeforeRerun <秒级时间>  在执行-fix指令后时睡眠指定的时间后再检查fix是否生效; -summary 只打印表和状态的概要信息; -metaonly 只检查hbase:meta表的状态; -sidelineDir <hdfs://> 备份当前的元数据到HDFS上; -boundaries  校验META表和StoreFiles的Region边界是否一致; 元数据修复选项 在不确定的情况下,慎用以下指令。 -fix 尝试修复Region的分配,通常用于向后兼容; -fixAssignments 尝试修复Region的分配,用来替换-fix指令; -fixMeta  尝试修复元数据问题;这里假设HDFS上的region信息是正确的; -noHdfsChecking  不从HDFS加载/检查Region信息;这里假设hbase:meta表中的Region信息是正确的,不会在检查或修复任何HDFS相关的问题,如黑洞(hole)、孤岛(orphan)或是重叠(overlap); -fixHdfsHoles  尝试修复HDFS中的Region黑洞; -fixHdfsOrphans  尝试修复hdfs中没有.regioninfo文件的region目录; -fixTableOrphans  尝试修复hdfs中没有.tableinfo文件的table目录(只支持在线模式); -fixHdfsOverlaps  尝试修复hdfs中region重叠的现象; -fixVersionFile  尝试修复hdfs中hbase.version文件缺失的问题; -maxMerge <n>  在修复region重叠的现时,允许merge最多<n>个region(默认n等于5); -sidelineBigOverlaps  在修复region重叠问题时,允许暂时搁置重叠量较大的部分; -maxOverlapsToSideline <n>  在修复region重叠问题时,允许一组里暂时搁置最多n个region不处理(默认n等于2); -fixSplitParents 尝试强制将下线的split parents上线; -ignorePreCheckPermission  在执行检查时忽略文件系统权限; -fixReferencesFiles 尝试下线引用断开(lingering reference)的StoreFile; -fixEmptyMetaCells  尝试修复hbase:meta表中没有引用到任何region的entry(REGIONINFO_QUALIFIER为空的行)。 Datafile修复选项 专业命令,慎用。 -checkCorruptHFiles  检查所有HFile —— 通过逐一打开所有的HFile来确定其是否可用; -sidelineCorruptHFiles  隔离损坏的HFile。该指令中包含-checkCorruptHFiles操作。 Meta修复快捷指令 -repair  是以下指令的简写:-fixAssignments -fixMeta -fixHdfsHoles -fixHdfsOrphans -fixHdfsOverlaps -fixVersionFile -sidelineBigOverlaps -fixReferenceFiles -fixTableLocks -fixOrphanedTableZnodes; -repairHoles  是以下指令的简写:-fixAssignments -fixMeta -fixHdfsHoles。 Table lock选项 -fixTableLocks 删除已持有超过很长时间的table lock((hbase.table.lock.expire.ms配置项,默认值为10分钟)。 Table Znode选项 -fixOrphanedTableZnodes  如果表不存在,则将其在zookeeper中ZNode状态设置为disabled。 其他 之前遇到过一篇文,整理了一些HBase的维护案例:http://blackproof.iteye.com/blog/2052898 ########

    [阅读更多...]
  • 在Spark上通过自定义RDD访问HBase

    这里介绍一个在Spark上使用自定义RDD获取HBase数据的方案。 这个方案的基础是我们的HBase表的行键设计。行键设计大概是这样子的:标签ID+时间戳+随机码。平时的需求主要是导出指定标签在某个时间范围内的全部记录。根据需求和行键设计确定下实现的大方向:使用行键中的时间戳进行partition并界定startRow和stopRow来缩小查询范围,使用HBase API创建RDD获取数据,在获取的数据的基础上使用SparkSQL来执行灵活查询。 创建Partition 这里我们要自定义的RDD主要的功能就是获取源数据,所以需要自定义实现Partition类: 前面我们说过,主要是依赖行键中的时间戳来进行partition的,所以在自定义的QueryPartition类中保留了两个长整型构造参数start和stop来表示起止时间。剩下的构造参数idx作用是标记分区的索引。 这样,自定义RDD中的getPartitions()方法该如何实现也就很清楚了: 在上面代码中的第六行可以看到getPartitions()方法是按每小时一个区间进行partition的。 代码中的unit是一个查询单元,封装了一些必要的查询参数,包括存储数据的表、要查询的标签ID以及起止时间。大致是这样的: 注意,QueryUnit这个类需要实现Serializable接口。 查询HBase 因为要实现灵活查询的需求,所以需要将HBase表中符合需求的数据的所有列都取出来。我们可以考虑使用List[Map[String, String]]这样一种结构来暂时保存数据。根据这个需求实现的自定义HBaseClient的代码如下: 在代码中使用了typesafe的Config类来记录一些信息,比如zookeeper连接信息。 查询HBase这块儿没什么好说的。继续好了。 自定义RDD 前面的两节为自定义实现RDD类做了一些铺垫,包括进行partition的方式,以及一个查询hbase的工具类HBaseClient。实际上我们已经完成实现了自定义RDD的一个抽象方法getPartitions。 自定义RDD需要继承Spark的一个抽象类RDD。 在继承抽象类RDD的同时,需要为它提供两个构造参数,一个是SparkContext实例,一个是父RDD列表。我们要自定义的RDD的是用来获取源数据的,没有父RDD,所以父RDD列表可以直接设置为Nil。 抽象类RDD还有两个抽象方法需要实现,分别是getPartitions()和compute()。getPartitions()方法用来对原始的任务进行分片,可以将原始任务切割成不同的partition,以便进行分布式处理。compute()方法则是实现了对切割出的partition进行处理的逻辑。 getPartitions()方法的实现前面已经提过了,现在看看compute()方法的实现: 可以看到,compute()方法就是在getPartitions()方法创建的时间区间QueryPartition上对HBase中的表进行查询,并将查询出的结果封装成json字符串列表。 编写驱动类 至此,工作已经完成了大半,可以看看驱动类是怎么写的了: 驱动类的任务是在创建的QueryRDD上使用SparkSQL执行查询,并将查询结果保存到HDFS上。 代码中的SparkUtil只是将经常使用的初始化SparkContext以及执行Spark任务的行为封装了一下,是这样实现的: 好了,就这样!!

    [阅读更多...]
  • Unsupported major.minor version

    又遇到了Unsupported major.minor version 52.0这个问题。很清楚这是编译打包时使用的jdk版本与运行环境jre版本不一致导致的。但是总是记不住具体的jdk版本与major version的对应关系。在这里记一下: Java SE 9 = 53 (0x35 hex),[3]Java SE 8 = 52 (0x34 hex),Java SE 7 = 51 (0x33 hex),Java SE 6.0 = 50 (0x32 hex),Java SE 5.0 = 49 (0x31 hex),JDK 1.4 = 48 (0x30 hex),JDK 1.3 = 47 (0x2F hex),JDK 1.2 = 46 (0x2E hex),JDK 1.1 = 45 (0x2D hex). 信息来自Wikipedia。 #######

    [阅读更多...]