年度归档: 2015 年

  • Spring Boot 日志管理和收集

    Spring Boot 日志管理和收集

    日志对于应用程序的重要性不言而喻,社区也有各种各样的日志框架。

    Spring Boot从实现上不一依赖于任何日志框架的实现,只有对于common-logging API的依赖,对应的抽象实现是LoggingSystem

    依靠这一层的抽象,对于日志等级的配置可以完全在Spring Boot中实现,比如application.properties

    logging.level.org.springframework.web=INFO
    logging.level.org.hibernate=DEBUG

     

    对于具体的日志框架实现可以选择 Java Util Logging, Log4J, Log4J2 或者 Logback。从mvn库的依赖和下载数量来看Logback的使用数量确实有较大的优势。

    对于具体实现部分的配置还是由实现框架本身管理,比如Logback对应的logback.xml文件。

    大部分日志的输出一般会对应两部分,一部分是stdout,一部分是日志文件。

    对于大规模部署的云原生应用,直接输出到stdout,然后从docker中收集日志也是一个不错的选择的。

    市面上日志聚合和搜索平台实在太多,有部分需要从代码层面做出修改,但是最简单的办法还是从日志实现本身入手。比如
    loggly提供了对于logback的支持。

    只需要在配置加上对应的appender即可。

    <configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
    <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
    
  • Maven Wrapper

    Maven Wrapper

    Maven是一个常用的构建工具,但是Maven的版本和插件的配合并不是那么完美,有时候你不得不切换到一个稍微旧一些的版本,以保证所有东西正常工作。

    而Gradle提供了一个Wrapper,可以很好解决版本切换的问题,当然更重要的是不需要预安装Gradle。

    Maven虽然没有官方的Wrapper,但是有一个第三方的Wrapper可以使用。

    安装很简单mvn -N io.takari:maven:wrapper,安装完成如下

    安装

    使用的时候直接./mvnw clean install即可,它会自动下载最新版本来执行。

    运行

    如果需要指定版本,重新生成mvnw文件在运行即可

    mvn -N io.takari:maven:wrapper -Dmaven=3.1.0
    ./mvnw clean install

    切换版本

  • Spring Boot结合FluentLenium做集成测试

    对于Web应用,一般为了测试功能性,都会结合selenium做功能性测试。

    Selenium会启动浏览器访问网站,然后对网站进行交互性操作,并判断结果。

    一般的操作流程是,打war包,部署war包,等待Web应用启动,开始测试,整理测试结果,关闭Web应用。

    而Selenium只是一个测试工具,并不会管理其他问题,需要自己处理web应用的启动和关闭。

    同时Selenium暴露的API有些底层,对于WebDriver的管理需要自己控制,对于元素的查找等也比较繁碎。

    Spring Boot在1.2.X版本中增强了对于Integration测试的集成,当然也包括了WebIntegration的集成,可以使用这部分功能来管理除具体测试以外的管理问题,
    Spring Boot会在测试开始前启动Web应用,并在测试后关闭它们。

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = WebApplication.class)
    @WebIntegrationTest({"server.port=0","spring.profiles.active=test"})

    在测试类加上这段注解,server.port=0表示随机选取端口,而spring.profiles.active=test指定了当前profile为test。

    修改profile是很实用的,比如以我个人的习惯,我会在dev和prod中使用真实的数据库和其他组件,而在CI服务器上一般使用内存数据库,其他依赖组件尽可能使用内嵌式的,或者由docker提供,这样的话相关配置的区别就需要在不同的profile中区分了。

    随机化的端口可以保证测试不会由于端口占用失败,但是测试中我们需要用

    @Value("${local.server.port}")
    int port;

    把具体的端口号取到,以便在测试中使用。

    FluentLenium是一个写selenium测试的工具库,它可以帮你管理webdriver,并提供了更简单的API来加快测试的开发速度和可读性。

    FluentLenium默认使用的是Firefox Driver,可以自己修改。

    比如有一个测试去测试首页的标题

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = WebApplication.class)
    @WebIntegrationTest({"server.port=0","spring.profiles.active=test"})
    publicclass HomeTest extends FluentTest {
    @Value("${local.server.port}")
    
  • 使用Java DSL配置Spring Integration

    Spring Integration是Spring下的一个项目,主要为了扩展Spring现有的模型已支持Enterprise Integration Patterns。

    Spring Integration还和其他项目结合紧密,比如Spring XD就可以使用其作为输入来源。

    Spring Integration可以完全依赖于xml工作,即意味着一个核心系统完成后,可以只单单更改xml配置本身来完成相关功能变更。

     

    但是xml配置也有不便利的地方,特别是在项目开发初期,对于开发人员而言,xml的表达能力自然不如java,在需要功能扩展的时候也必须需要java代码才行。

    而Spring的一个新的子项目spring-integration-java-dsl就可以实现使用java dsl来配置相关功能。

    以最简单的消费atom源为例,xml的配置如下

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:int="http://www.springframework.org/schema/integration"
    xmlns:file="http://www.springframework.org/schema/integration/file"
    xmlns:feed="http://www.springframework.org/schema/integration/feed"
    xsi:schemaLocation="http://www.springframework.org/schema/integration/feed http://www.springframework.org/schema/integration/feed/spring-integration-feed.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/integration/file http://www.springframework.org/schema/integration/file/spring-integration-file.xsd
    http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
    <feed:inbound-channel-adapter id="news" url="https://spring.io/blog.atom">
    <int:poller fixed-rate="5000"/>
    </feed:inbound-channel-adapter>
    <int:transformer
    
  • 使用git@osc的WebHook自动部署

    有一个小项目代码托管在git@osc上,因为oschina支持免费的私有库,代码就放在上面了。

    因为毕竟是实验性的东西,部署比较频繁,就写了一小段shell去做部署,也就是每次需要部署的时候登陆到主机执行这段shell即可。

    后面看到了deploybot,感觉挺有意思的,但是最后的部署需要登录到主机上,总感觉不安全。

    看了一下,利用WebHook也可以自己做到相关功能。

    git@osc的WebHook是可以配置密码的,所以安全性还可以。

    因为很简单的功能,监听一个端口,获取到post到的数据,然后判断密码,在判断commit信息,如果包含[deploy]就调用部署脚本。

    图方便直接使用nodejs来写,虽然对nodejs很不熟,但是毕竟快。用到的库是express,日志用的log4js

    var express=require('express');
    var app= express();
    var bodyParser=require('body-parser');
    var spawn=require('child_process').spawn;
    var log4js=require('log4js');
    log4js.configure({
    appenders:[
    {type:'console' },
    {type:'file', filename:'logs.log' }
    ]
    });
    var logger = log4js.getLogger();
    app.use(bodyParser.urlencoded({ extended:false }));
    
  • 用jitpack直接引入github项目

    用jitpack直接引入github项目

    Github是一个汇聚了很多有趣东西的地方。如果你使用java作为主要开发语言,并配合gradle,maven等构建工具,那么项目的依赖大部分应该取自maven仓库。

    但有些时候总有有些意外,有些项目的作者并没有将项目发布到maven中央仓库,有些分布在自己的一些第三方的仓库中。

    没有发布的还好,自己寻找方案,如果发布到第三方仓库的,很有可能出现一些问题。

    一直正常运行的项目突然CI挂了,去看看结果发现对方的第三方库挂了,而且这个依赖还是一个snapshot的依赖。

    虽然在github上有源码,但是使用时我并不需要修改源码,所以并不希望直接使用源码级的依赖,采用jar的依赖是最好的。

    无意之间发现了jitpack,解决了这个问题。

    以这个项目为例modular ,项目基于maven,有pom.xml配置。

    直接jitpack中填入这个地址,可以看到几个选项。

    jitpack

     

    我需要最新版本,选择第一个,然后在gradle配置中添加

    repositories {
      maven {url"https://jitpack.io" }
    }
    dependencies {
      compile'com.github.mountainblade:modular:4bf87e0a75'
    }

    一切就搞定了。

    jitpack会自动打包并生成jar包,第一次请求会慢一些,后面就正常了。还可以查看打包的日志和项目文档。

    比如https://jitpack.io/com/github/mountainblade/modular/4bf87e0a75/build.log.…

  • 构建可测试的libGDX应用

    构建可测试的libGDX应用

    Libgdx应用由于其特殊性,测试本身还是有一定难度的。好在Libgdx自身可以跨平台,在桌面平台调试/测试自然比在设备上方便不少。

    但是一个庞大的项目光看调试/测试是很难保证高可靠性的,特别是很多情况并不好测试,比如触发特定事件后的抽奖模块。

    如果可以使用一定数量的单元测试来保证应用的可靠性那自然是最好的了。

    分离绘制

    Libgdx应用测试的一个困难在于UI层面,基于opengl绘制出的界面并没有特定的自动化工具来完成测试,最简单的办法就是分离绘制和其他模块。

    你现有的Libgdx应用可能是这样的

    现有架构

    在render中放置了大量的代码,并在其中直接处理输入和其他逻辑,那么这种代码确实很难测试。

    如果你的应用是这个样子的

    实体系统

    那么大部分代码都可以直接被单元测试覆盖。

    Libgdx提供了ashley作为实体系统。

    利用Backend测试

    即使剥离了绘制,还是有大部分代码需要Libgdx环境。Libgdx环境是指除绘制以外其他模块,比如Gdx.files。

    这种情况下最简单的就是使用headless backend测试。

    这个backend有关gl部分是mock出来的,但是其他部分都是真实的,基本可以视为单元测试环境。

    在Web开发中我们喜欢提functional测试,即控制浏览器在页面上操作,并作出断言检测。

    其实利用Jglfw,我们依然可以做出类似的测试。

    我自己定义了一个Junit的Runner作为测试的辅助。这样的测试会打开一个界面,然后在测试完成后关闭。

    publicclass LibgdxRunner extends BlockJUnit4ClassRunner {
    private Random random =new Random();
    publicLibgdxRunner(Class<?> klass)throws InitializationError {
    super(klass);
    initApplication();
    }
    privatevoidinitApplication() {
    try 
  • 使用Docker构造Jenkins集群

    使用Docker构造Jenkins集群

    Jenkins作为常用的CI功能自然是不用说了,如果运行在上面的项目有很多,而且对于环境的要求各不相同就稍微有点复杂了。

    比如一个Java项目,使用maven构建,Java语言等级为8,但是有部分代码又希望保有java7的语言等级,那么环境上的配置就稍微复杂了一点。

    如果还有其他比如Python,NodeJs项目,那么这个机器的环境就极为复杂了。

     

    Jenkins本身是支持主从节点的,也就是说具体的构建是可以指定由某个节点完成的。

    也就意味着我们只需要提供一个由所需环境的从机即可。当然,由一个独立的主机来提供是最好的,但是很多时候并没有那么多资源,而且环境需求也多变。

    这种情况下Docker就是一个很好的选择了,我们来尝试用Docker提供一个Java8带maven的环境。

    Jenkins主从模式可以有四种方式实现,我们选择最简单的,从ssh登录。

    整体配置如下

    FROM maven:3-jdk-8
    RUN apt-getupdate && apt-get install -y openssh-server
    RUN mkdir /var/run/sshd
    RUN mkdir /var/jenkins
    
    RUN echo'root:jenkins' | chpasswd
    RUN sed -i's/PermitRootLogin without-password/PermitRootLogin yes/' /etc/ssh/sshd_config
    
    RUN sed's@sessions*requireds*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
    
  • 云应用与Lattice

    快,是现在一个很喜欢提的概念。快速发现用户需求,快速的产品原因,快速的提供服务,快速的迭代,快速的部署,快速的试错。

    为了满足这些,软件架构和基础服务也在发生改变。

    从独立机房到云主机,再到所谓的容器,快速的部署一个具有极大扩展性的应用变得越来越容易,当然费用也是越来越低。

    为了适应这些变化,软件架构也在向云应用转变。

    Cloud Native Application

    Cloud Native Application,说实话,我不知道中文名字叫什么,先称为云应用(我相信应该有一个更正式的说法)。

    从中衍生的还有所谓的 Cloud Native Application Architectures,即云应用体系结构。

    简单说来无非是一下几个问题:

    • 以一个或多个无状态进程运行应用
    • 通过端口绑定提供服务
    • 快速启动和优雅终止可最大化健壮性
    • 把日志当作事件流
    • 其他。。。

    应用由小的模块组成,每个模块尽可能无状态,模块能够快速的终止和启动。

    更多的相关概念和理论可以参考文木末的免费电子书。

    Spring-Boot和Spring-Cloud

    以Web应用说明,如果构建成war包然后发布到容器中,那么就很难做到以端口绑定提供服务,多个无状态进程等等。

    Spring-Boot提供了一个很简单的思路,如果发布到容器不行,那么就把容器包含在自身,使用内置的jetty或者tomcat来作为服务的承载。

    当然Spring-Boot还提供了快速的开发的若干便利,从配置到依赖管理,进而到调试和发布。

    Spring-Cloud提供了功能更进一步的“云特性”。

    • 配置管理
    • 路由
    • 负载均衡
    • 服务注册和发现
    • 集群决策和状态管理

    可以说在Spring的大旗之下有超过10个项目或者子项目再为云应用这个理念服务。

    Lattice

    有了理念,有了工具和框架,自然需要一个平台。

    Lattice就是一个为云应用而生的管理平台,主要的功能包括:

    • Http负载均衡
    • 日志
  • Spring boot中的Info Endpoint

    为了监控应用,Spring Boot提供了EndPoint的支持。

    目前提供了十一种,其中大部分都包含默认实现,只有其中的Info需要用户自己提供。

    Info的存在很大程度上可以替代AppCheck功能。

    最简单添加Info信息的方式就是自己在配置中写入

    info.name=app

    这样访问/info就可以获得类似这样的结果

    {
    name: 'app'
    }

    但是有一些信息是不固定的,比如版本号。

    这种可以使用打包工具去完成信息的填充。比如写成

    info.version=${version}

    在Gradle配置中加上

    processResources{
    expand(project.properties)
    }

    当然这样写有一些问题,如果你是用了Flyway等数据库版本管理工具,那么你原声的SQL文件也会被处理,视情况而定,有很高几率信息填充会失败。

    所以先过滤一下

    processResources {
    filesMatching('**/*.properties') { expand(project.properties) }
    }

    当然,有时候我们还需要一些版本库的信息,比如Git相关信息。

    可以Gradle的Git插件来获取数据,然后提供给Info。

    importorg.ajoberstar.grgit.Grgit
    
    Grgit repo = Grgit.open(project.file('.'))
    ext.git = [
    name