博客

  • 在Spring Boot启动后执行指定代码

    在开发时有时候需要在整个应用开始运行时执行一些特定代码,比如初始化环境,准备测试数据等等。

    在Spring中可以通过ApplicationListener来实现相关的功能,不过在配合Spring Boot使用时就稍微有些区别了。

    创建ApplicationListener

    这里以填充部分测试数据为例子,首先实现ApplicationStartup类。

    publicclass ApplicationStartup implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    publicvoidonApplicationEvent(ContextRefreshedEvent event) {
    SourceRepository sourceRepository = event.getApplicationContext().getBean(SourceRepository.class);
    Source je =new Source("justice_eternal吧","http://tieba.baidu.com/f?kw=justice_eternal");
    sourceRepository.save(je);
    }
    }

    这类并不会自动执行,需要我们注册。

    硬编码注册

    Spring Boot有一个类SpringApplication,这个类是Spring Boot的入口,包含所有的配置。

    @Configuration
    @ComponentScan
    @EnableAutoConfiguration
    publicclass WebApplication {
    publicstaticvoidmain(String[] args) {
    SpringApplication springApplication =new SpringApplication(WebApplication.class);
    springApplication.addListeners(new ApplicationStartup());
    springApplication.run(args);
    }
    }

    硬编码的弊端在于无法区别环境,当我们需要部署应用到生产环境时需要修改代码。

    配置文件

    Spring Boot支持profiles模式,在application.properties中配置

    spring.profiles.active=dev

    然后在application-dev.properties中配置开发环境的参数。

    增加一个配置来注册自定义的监听器

    context.listener.classes=cn.acgmo.ApplicationStartup
  • 从Hadoop MapReduce到Spark

    Spark是一款通用集群计算框架,和Hadoop的MapReduce类似。由于其提供的抽象更简单,性能和功能上比Hadoop强不少。
    它已经越来越流行了。

    如果是新的项目,或者是为了学习,那么选择Spark完全没问题。

    不过对于一些使用了MapReduce的项目来说,迁移就稍微复杂一些了。

    Hadoop自身

    Hadoop的使用在不断扩大,但同时越来越多的实践证实了MapReduce并不是通用计算范例。

    Hadoop的架构本身为其他可能的替代方案提供了场所,比如Impala项目等等。

    而对于Hadoop来说,有一部分Hadoop的实现本身和MapReduce本身的抽象并不一致。

    • Mapper和Reducer总是使用键值对作为输入输出
    • Reducer处理的级别是键
    • Mapper和Reducer的对象的生命周期跨越了多个map()和reduce(),同时还支持了setup()和cleanup()

    Spark

    Spark也是众多替代方案中的一种。

    但是对于已经部署在生产环境的项目而言,一句替代方案是不够的,对于一些实时计算的系统更是这样。

    好在利用Spark实现类似MapReduce的模型是完全可行的。同时实现本身还可以更简单,并在大部分情况下更快。

    对于MapReduce模型本身,使用Spark来实现反而显得更亲近,毕竟Scala的编码风格和API对于本身源于LISP的MapReduce抽象更接近。

    键值对和元组

    从最基础的例子来看,如果需要计算一个大型文本文件每一行的长度,对于Hadoop MapReduce来说由于输出是键值对,那么就会使用长度作为键,以1为值。

    publicclass LineLengthMapper extends Mapper<LongWritable,Text,IntWritable,IntWritable> {
    protectedvoidmap(LongWritable lineNumber, Text line, Context context)
    throws IOException, InterruptedException {
    context.write(new IntWritable(line.getLength()),new IntWritable(1));
    }
    }

    LineLengthMapperTextInputFormat提供了输入,对于每一个键值对而言,一行为值,而位置为键。

    当然,虽然我很少用到,但是我相信有些任务还是需要用到这个键的。

    Spark中,对应的功能看起来就是这样的

    lines.map(line => (line.length,1))

    Spark的核心抽象是RDD,这里的输入仅仅是字符串,而不是键值对。而Scala中的元组(Tuple)和上面的键值对很类似。

    map()的输出是一个(int,int)的元组。当RDD包含元组的时候,可以调用一些额外的方法,比如reduceByKey(),这也是实现MapReduce的一个重要部分。

    Spark的reduceByKey

    为了得到长度的输出,Hadoop MapReduce还需要一个Reducer来处理。

    publicclass LineLengthReducer extends Reducer<IntWritable,IntWritable,IntWritable,IntWritable> {
    protectedvoidreduce(IntWritable length, Iterable<IntWritable> counts, Context context)
    throws IOException, InterruptedException {
    int sum =0;
    for (IntWritable count : counts) {
    sum += count.get();
    }
    context.write(length,new IntWritable(sum));
    }
    }

    而在Spark中对应的是

    val counts =lines.map(line => (line.length,1)).reduceByKey(_ + _)

    Spark的API中包含了reduce()方法,不过这个方式是将键值对处理为一个单值,这不是Hadoop MapReduce的行为。相比之下
    reduceByKey更接近。

    setup() and cleanup()

    在MapReduce中,mapper和reducer都可以包含一个setup和cleanup方法。

    比如处理数据库的连接

    publicclass SetupCleanupMapper extends Mapper<LongWritable,Text,Text,IntWritable> {
    private Connection dbConnection;
    
    protectedvoidsetup(Context context) {
    dbConnection = ...;
    }
    
    protectedvoidcleanup(Context context) {
    dbConnection.close();
    }
    }

    而Spark的map()和flatMap()并没有这种东西,我暂时采用的办法是在调用map()前后处理,比如

    lines.mapPartitions { valueIterator =>
    if (valueIterator.isEmpty) {
    Iterator[...]()
    }else {
    val dbConnection = ...
    valueIterator.map { item =>
    val transformedItem = ...
    if (!valueIterator.hasNext) {
    dbConnection.close()
    }
    transformedItem
    }
    }
    }

    其他概念

    Hadoop MapReduce中还有一个隐含的概念Combiner,并且还可以一次触发多个输出。

    另外一点是MapReduce的Writeable序列化是独有的。

    Spark中有一些替代品,比如accumulatorsgroupBy,而序列化可以考虑Java自身的或者使用Kryo等等。

    MapReduce抽象本身是强力的,只不过在Hadoop的实现中有了一些特殊化,同时函数式语言更加胜任这种工作。

    参考

    RDD
    accumulators

  • 新的一个cn域名备案中

    昨天万网搞活动,送了一张20元代金券,算下来com和cn域名首年购买都是29元。

    之前有个网站做的很简陋,但是流量出奇的不错。

    最近也在用Grails做东西,正好整理一下,做个新网站出来。

    选来选去还是买了cn域名,cn域名需要上传身份证做实名认证,昨天上传,今天就审核完成了。

    备案自然是在阿里云上,流程走过了一次自然觉得简单了不少,不过没想到的是cn备案需要邮寄资料…

    所以域名的选择还是com为好呀。

  • Grails生成war包过大的问题

    最近在用Grails,确实体验了一把快速开发的乐趣。

    Grails从名字上看就有点Rails的感觉,使用的很多细节也是。

    快速创建项目,完整的测试框架,完善的插件体系,大部分东西都能够快速实现。

    完成开发以后生成war包,部署到容器上就行了。

    因为要把项目放到生产环境了,看了一下war包的大小,居然有40M+。

    为什么包这么大

    一个war包如此之大,确实惊悚了。因为项目本身并没有太多东西,实在不应该这么大。

    直接拆开war包,主要的集中在assets目录和WEB-INF目录。

    前者是资源目录,包括css,js和png等等。后者主要包含了lib。

    前者的问题好解决,因为用了bower,所以下载的目录中有很多不是必须的东西。

    而后者问题就比较麻烦了。Grails很方便了,集成了很多东西,比如GORM作为数据持久层上依赖了Hibernate,光lib的大小就是5M。

    而且还有一些包明显不应该打在包里面,比如tomcat-embed.jar。

    加快发布

    自己机器上开发当然没有问题,大就大呗,但是这么大一个war包放在服务器上就有点吓人了。

    首先是发布上的问题,这么大的一个包,远程部署过去实在耽误时间。

    对于第一个asserts目录,直接在打包时exclude不需要的东西就是了,也节约不了多少东西。

    对于第二个WEB-INF目录,可以操作的地方就多了。首先移除不需要的东西,可以节约10M左右。这样打包下来还是有30M左右。

    首先在Config.groovy中配置

    defdeps = [
    "hibernate3.jar",
    "groovy-all-*.jar",
    "standard-${servletVersion}.jar",
    "jstl-${servletVersion}.jar",
    "oscache-*.jar",
    "commons-logging-*.jar",
    "sitemesh-*.jar",
    "spring-*.jar",
    "log4j-*.jar",
    "ognl-*.jar",
    "commons-*.jar",
    "xstream-1.2.1.jar",
    "xpp3_min-1.1.3.4.O.jar" ]
    grails.war.dependencies = {
    fileset(dir:"libs") {
    for (patternin deps) {
    include(name: pattern)
    }
    }
    }

    移除不需要的东西,可以节约10M左右。这样打包下来还是有30M左右。

    对于那些不能移除的,比如hibernate,spring等等,直接放到容器的lib目录中。不随war一起了。

    直接运行命令

    grails war --nojars

    这样的体积只有6M左右。

    Proguard瘦身

    即使是必须的lib,其中也必然有不少是我们不需要的东西,比如Guava,我常用的其实就那么几个类,其他的不需要。

    这个时候使用Proguard的效果就格外好了。

    具体的操作是一样的,虽然使用了Grails和Groovy,但是最后得到的还是标准的war包和字节码。

    我推荐的是Hibernate和Spring的混淆参考现有方案,这两块实在太容易出问题了,而且收获也不大。

    其他的工具库就可以放手干了。

    瘦身以后体积为18m(包含lib目录)。

    war包大的问题

    war包的大小会在一定程度上影响性能,但是不得不说这个影响很小。

    如果你的JVM不能完整的加在整个包,那么确实会有一些问题。

    另外实际的系统可用内存也是一个问题。

    特别是对于我而言,云服务器的资源是算钱,大量的无意义的大包会吃掉很多资源。

    参考资料

    Does the size of a jar file affect the performance of the JVM

  • 备案完成

    备案完成

    今天上午十点的时候收到的管所的短信通知,提示备案通过。

    大约一个小时以后博客就可以正常访问了。

    备案从8号开始,一直到今天24号,总共耗时16天。

    这其中不得不赞一下阿里云的服务,备案的审核只要在阿里云那边的都是一天过。

    其中照相的时候还邮寄了幕布,中午发出,第二天就到了,照了相,当天下午就提交管所了。

    备案时间

    这十几天的闭站的损失没有想象中那么大。主要是DNS那边支持针对搜索引擎做专门的处理,所以对于蜘蛛来说网站还是原来的地址,原来的内容。

    只是正常的用户访问受到了影响。

    当然,现在的速度那是极快的,360监控给出的数据如下:

    网站响应速度

    而之前的速度一直是2000毫秒以上。

  • 准备开始备案

    前思后想,还是决定给博客备个案,重新移动到国内服务器上。

    选用的阿里云,售前客服也很靠谱的感觉,唯一不爽的就是首次备案需要闭站。

    我觉得今天晚些时候就会暂时关闭博客,希望备案不会花太久时间。

  • 简洁的Formula

    之前为Homebrew添加了一个Moco的Formula,提交PR以后一直没有回应,还以为项目组不接受这个提交呢。

    今天收到提示已经merge了,代码也作为一些修改,感觉代码好了不少。

    其实这个Formula是我第一个写ruby,基本是边看边学,感觉需要了解的还很多。

    require

    Ruby中的require和java的import很像,可以加在标准类库里面的文件,当然也可以是已安装的gems文件。

    因为测试中使用了ruby的http库,所以我把require写在文件开头

    require"formula"
    require'net/http'

    而McQuaid修改后将http的引用移动到测试中

    testdo
    require"net/http"

    字符串

    因为moco的启动会有一个端口,我用一个变量表示,然后直接拼接

    actualResponse=Net::HTTP.get(URI('http://localhost:'+port.to_s))

    修改以后直接用占位符表示

    response =Net::HTTP.getURI"http://localhost:#{port}"

    断言

    因为返回的结果需要判断,我使用的是if判断,不相符直接报错

    if(actualResponse!='Hello, Moco')
    onoe"Error! The response is not right."
    end

    可以直接用一句话操作

    assert_equal"Hello, Moco", response

    参考

    修改前
    修改后

  • Intelli IDEA 14隐藏被排除的文件夹

    Intelli IDEA 14隐藏被排除的文件夹

    花了99刀升级到Idea最新版,14.0.1,然后发现了一个问题。

    被排除的文件和文件夹以红色显示了。

    看着这东西,人一下子就不好了。

    还好设置可以改回来。

    右上角,关闭“Show Excluded Files”即可。

    不显示被排除的文件

  • Java中支持深浅拷贝的第三方库

    最近项目上用到了一些Deep Clone的库,但是原来使用的库速度略慢,直接拖累了应用。

    今天有空看了一下相关的可用的第三方库。

    深拷贝

    Apache旗下的Commons-Lang3包有一个序列化的工具SerializationUtils,可以做深拷贝。

    当然前提是你的类实现了序列化接口。

    Java Deep Cloning Library是我觉得最好用的一个。它的深拷贝通过反射实现,适合用于你
    不能控制的第三方类或者没有实现序列化的类。

    浅拷贝

    还是Apache旗下,不过不是Commons-Lang3,而是Commons-Beanutils。其中BeanUtils提供了一个cloneBean方法。
    可以直接简单的操作。

    如果项目依赖了Spring的库,那么直接使用BeanUtils即可。

    参考文献

    Common-Lang3
    Java Clone
    Spring BeanUtls

  • 使用moco的global设置功能拆分配置文件

    Moco是一个很方便的小工具,可以作为开发时临时的虚假服务端。

    Moco的响应方式主要通过config.json来控制。有新的需要时直接在文件中添加删除就行了。

    不过长此以往这个文件就越来越大了,比如1000+行。

    还好的是moco提供了global setting这个功能可以做到配置文件拆分。

    配置修改

    之前的配置文件看起来应该是这样的:

    [
    {
    "request":{
    "uri":"/foo"
    },
    "response":{
    "text":"foo"
    }
    },
    {
    "request":{
    "uri":"/bar"
    },
    "response":{
    "text":"bar"
    }
    }
    ]

    我希望将这两个请求拆分到两个不同的文件中。

    [
    {
    "include":"foo.json"
    },
    {
    "include":"bar.json"
    }
    ]

    然后在foo.json文件中添加:

    [
    {
    "request":{
    "uri":"/foo"
    },
    "response":{
    "text":"foo"
    }
    }
    ]

    bar.json类似。

    这样通过moco的-g参数启动就可以了。

    java -jar moco-runner-<version>-standalone.jar start -p 12306 -g settings.json

    moco-maven-plugin

    一般项目中还是使用插件来启动moco的,而GarrettHeel的这款插件可以满足我们的需要。

    <plugin>
    <groupId>com.garrettheel</groupId>
    <artifactId>moco-maven-plugin</artifactId>
    <version>0.9.3</version>
    <configuration>
    <port>8081</port>
    <globalFile>settings.json</globalFile>
    </configuration>
    </plugin>

    因为moco一般用于回归测试或者功能性测试,所以在maven的生命周期中一般配置为

    <plugin>
    <groupId>com.garrettheel</groupId>
    <!-- ... -->
    <executions>
    <execution>
    <id>start-moco</id>
    <phase>pre-integration-test</phase>
    <goals>
    <goal>start</goal>
    </goals>
    </execution>
    <execution>
    <id>stop-moco</id>
    <phase>post-integration-test</phase>
    <goals>
    <goal>stop</goal>
    </goals>
    </execution>
    </executions>
    </plugin>

    参考资料

    moco-maven-plugin