昨天和澎湃聊了这个事情,当时的想法是数据库中有表记录版本,项目代码中存储变更脚本.无意中看到数据库版本控制工具liquibase.最开始还是有点担心,怕这东西把数据库玩坏了.看了看官方文档,再粗略看了下主流程的源代码,发掘下我想要的功能,这个工具已经足够强大了,我们用好就行.

下面以cs为例,讲讲整个过程.

1. 在cs-dal中添加maven依赖.

    <build>
        <plugin>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-maven-plugin</artifactId>
            <version>3.2.2</version>
            <configuration>
                <!--数据库变更主文件-->
                <changeLogFile>src/main/resources/db/changelog/db.master.xml</changeLogFile>
                <!--数据库相关配置文件-->
                <propertyFile>src/main/resources/db/config/dal-${spring.profiles.active}.properties</propertyFile>
            </configuration>
            <executions>
                <execution>
                    <phase>process-resources</phase>
                    <goals>
                        <goal>update</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

2. 编写数据库变更脚本

2.1 数据库变更主文件

db.master.xml:

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
    xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
     http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.2.xsd">
	<!--数据库变更文件.注意:保证顺序,脚本执行顺序和include的先后有关系.includeAll可以加载所有脚本,加载顺序和文件命名有关系,容易犯错误,建议不使用.-->
	<include relativeToChangelogFile="true" file="db.1.0.sql"/>
   		<include relativeToChangelogFile="true" file="db.2.0.sql"/>
</databaseChangeLog>

2.2 变更脚本

db.1.0.sql:

--liquibase formatted sql

--changeset qiubo:1
create table test1 (
	id int primary key,
	name varchar(255)
);
--rollback drop table test1;

db.2.0.sql:

--liquibase formatted sql

--changeset qiubo:2
insert into test1 (id, name) values (1, 'name 1');
insert into test1 (id, name) values (2, 'name 2');

语法参考:http://www.liquibase.org/documentation/sql_format.html

2.3 数据库配置文件

dal-local.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/yjf_cs?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull
username=root
password=root
#数据库schema名
changelogSchemaName=yjf_cs

建议大家把原来的数据库配置文件中的key改为和这个一致,没必要搞多份配置.

3.路径结构

确保xxx-dal如下的路径结构,请大家统一:

|---pom.xml
|---src
|    |---main
|    |    |---java
|    |    |---resources
|    |    |        |---db
|    |    |        |    |---changelog
|    |    |        |    |        |---db.1.0.sql
|    |    |        |    |        |---db.2.0.sql
|    |    |        |    |        |---db.master.xml
|    |    |        |    |---config
|    |    |        |    |     |---dal-dev.properties
|    |    |        |    |     |---dal-local.properties
|    |    |        |    |     |---dal-net.properties
|    |    |        |    |     |---dal-online.properties
|    |    |        |    |     |---dal-pnet.properties
|    |    |        |    |     |---dal-sdev.properties
|    |    |        |    |     |---dal-snet.properties
|    |    |        |    |     |---dal-stest.properties
|    |    |        |    |     |---dal-test.properties

4.执行

在cs-dal目录执行:

mvn liquibase:update -Dspring.profiles.active=local

上面的脚本会以local环境执行,读取dal-local.properties中的数据库配置,执行数据库变更脚本.执行成功后,会在数据库中新建两个表.DATABASECHANGELOG会记录数据库变更信息,DATABASECHANGELOGLOCK用于防止多台服务器同时部署时的并发问题.

5.注意事项

  1. 先不要搞线上.
  2. 变更脚本命名:我现在做得简单db.1.0.sql,最好版本号为项目版本号,便于跟踪.
  3. 新项目建议采用此方案,跟踪数据库所有的开发变动.
  4. 老项目可以采用全量的方式使用.全量,先根据数据库的基础数据生成变更脚本generating_changelogs,然后同步版本(changeLogSync)到数据库中.这样做的好处是,以后可以从无到有的创建当前版本的数据库了.参考Adding Liquibase on an Existing project
  5. 老项目也可以采用增量的方式使用,增量的方式不会管以前的数据版本.如果采用这种方式,在新环境搭建数据库,你需要先用数据库工具还原到没有版本之前的状态,然后再执行变更脚本.参考Adding Liquibase on an Existing project
  6. 请不要修改(脚本内容/脚本路径)之前的数据库变更脚本,liquibase会对每个Changesets生成摘要,执行时会去对比,如果你修改了以前的Changesets,会报错(所有的变更在事务中执行,出错了会回滚,不用担心会影响到数据库).
  7. 官方文档很全,想深入的同学请阅读FAQ/BEST PRACTICES/Maven Liquibase Plugin.遇到问题之前先检查配置是否正确,有bug可以找我^_^.

很早之前写的一篇文字,一直没有搬上blog,以后会慢慢把有些东西放到blog上来.

webservice的性能实在是敢恭维。曾经因为webservice吞吐量上不去,对webservice进行了一些性能方面的优化:

1.分析

1.1 FastInfoset

采用了FastInfoset(简称FI),效果很明显,极端条件下的大数据量传输,性能提高60%,他可以减少传输成本,序列化成本和xml解析成本。cxf基于http协商机制(检查请求header中Accept: application/fastinfoset)来启用FI。

1.2 Gzip

客户端和服务器端是否使用Gzip压缩,也是基于http协议协商的(检查请求header 中是否有Accept-encoding:gzip)。但是这里需要仔细权衡下。对于小数据量,启用gzip压缩支持是吃力不讨好的行为,数据量很小的时候,gzip压缩结果不明显,还浪费cpu。

1.3 unexpected element异常

见:http://bohr.me/cxf-unexpected-element/

1.4 处理过程分析

cxf 中通过一些列interceptor来完成数据解析处理操作,每个interceptor绑定到特定的阶段,下面是GZIP 和FI interceptor所处的阶段

类型 Direction Phase
Gzip IN Phase.RECEIVE
  Out Phase.PREPARE_SEND
FI IN Phase.POST_STREAM
  Out Phase.PRE_STREAM

数据进来时,先RECEIVE阶段适配InputStream对象为GZIPInputStream,然后在POST_STREAM阶段解析数据。完成gzip解压缩,FI解析数据过程。

数据出去时,在PREPARE_SEND阶段适配OutputStream对象为GZipThresholdOutputStream,在PRE_STREAM阶段再序列化为二进制数据传输出去。完成FI序列化数据,GZIP压缩数据过程。

测试发送20250byte数据,仅仅启用FI时,发送数据量为20181byte,再启用Gzip压缩后,发送数据量为258byte。

2.操作步骤

2.1添加依赖

cxf版本修改为2.7.0并加入FastInfoset

<dependency>
	<groupId>com.sun.xml.fastinfoset</groupId>
	<artifactId>FastInfoset</artifactId>
	<version>1.2.9</version>
</dependency>

2.2 修改cxf配置

2.2.1 删除引入的cxf配置

<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

我们项目中很多spring配置文件都加入了上面的东东,这个不是必须的,不删除这东东会导致配置不生效。

2.2.2 配置gzip和FI

Spring配置文件中引入cxf namespace xmlns:cxf=http://cxf.apache.org/core和xsi:schemaLocation http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd

然后加入配置

<cxf:bus>
	<cxf:features>
		<cxf:fastinfoset force="false" />
		<bean class="org.apache.cxf.transport.common.gzip.GZIPFeature">
			<property name="threshold">
				<value>2048</value>
			</property>
		</bean>
	</cxf:features>
</cxf:bus>

注意这些特性client和server端都要配置。

3.写在最后

启用gzipFastInfoset,性能基本上也到达webservice的极致了.通过IgnoreUnexpectedElementValidationEventHandler再解决易用性问题,基本完美.

先总结下常用的一些监控工具:

##linux命令

  • w

系统负载

  • lsof -p pid

进程打开的文件

  • lsof -i:port

端口的运行情况

  • free -m

内存情况

  • vmstat

进程、内存、内存分页、堵塞IO、traps及CPU活动的信息

  • iostat

磁盘io情况

  • top -n 1

cpu/负载/内存等使用情况.

  • iotop

磁盘io

  • ps aux | sort -k6nr | head -n 10

查看linux 实际内存占用最多的10个

  • ps aux | sort -k5nr | head -n 10

查看linux 虚拟内存占用最多的10个

  • dstat -lamps

查看系统整体状况

  • pstree -al pid|head -n 1

查看进程启动命令

  • strace -T -p pid

查看进程系统调用.开销很大,使用时要小心.

  • netstat

netstat -an |grep port 查看端口连接情况

netstat -alnp |grep pid 通过pid查看进程所有端口情况

  • ss -lntp |grep port

通过端口查看进程

  • nmon

强大的监控工具.也可以方便的出报表.我一般用来在压力测试时监控系统性能.

  • latencytop

用于查看系统内部慢.以前做mysql性能优化,多亏有这东东.

  • cat /proc/pid/status |grep Threads

查看进程内线程个数

##java工具

  • jvisualvm

jvm的运行情况/各种dump的分析都可以干,没有JRMC牛.oracle承诺会把JRockit的特性迁移到HotSpot上面来.现在jdk下已经有jmc了.

  • jps -lv

查看所有java进程.

  • jinfo -sysprops pid

查看java进程系统参数

  • jinfo -flag jvmflag pid

查看jvm flag.比如查看xss,jinfo -flag ThreadStackSize pid

  • jstack pid

查看线程栈信息

  • jmap -dump:live,format=b,file=xxx.hprof pid

生成heap dump

  • jmap -histo pid

查看java堆中对象统计信息

  • java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal

查看jvm flag

	The first column appears to reflect the data type of the option (intx, uintx, uint64_t, bool, double, ccstr, ccstrlist). 
	The second column is the name of the flag and the third column is the value, if any, that the flag is set to.
	The fourth column appears to indicate the type of flag and has values such as {product},{pd product}, {C1 product} for client or {C2 product} for server, {C1 pd product} for client or {C2 pd product} for server, {product rw}, {diagnostic} 
	(only if -XX:+UnlockDiagnosticVMOptions was specified), {experimental}, and {manageable}. See Eugene Kuleshov's The most complete list of -XX options for Java 6 JVM for a brief description of most of these categories as well as a listing of most of these options themselves.

线程栈分析器,这个是jvisualvm的插件.

基于eclipse的heap dump分析工具,这个工具是比jvisualvm在heap分析这块专业.不过jvisualvm能cover住大多数场景,基本上我都只用jvisualvm了.

  • jmap -heap pid

检查heap情况

GC日志分析

  • jstat -gcutil pid

查看gc总体情况

	S0  — Heap上的 Survivor space 0 区已使用空间的百分比
	S1  — Heap上的 Survivor space 1 区已使用空间的百分比
	E   — Heap上的 Eden space 区已使用空间的百分比
	O   — Heap上的 Old space 区已使用空间的百分比
	P   — Perm space 区已使用空间的百分比
	YGC — 从应用程序启动到采样时发生 Young GC 的次数
	YGCT– 从应用程序启动到采样时 Young GC 所用的时间(单位秒)
	FGC — 从应用程序启动到采样时发生 Full GC 的次数
	FGCT– 从应用程序启动到采样时 Full GC 所用的时间(单位秒)
	GCT — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒)
  • btrace

神器,线上出问题了,想知道某个方法的调用情况,入参之类的,就靠btrace了. 此工具大致原理如下:

  1. btrace-client attach 目标进程(com.sun.tools.attach.VirtualMachine#attach)
  2. 加载agent btrace-agent (com.sun.tools.attach.VirtualMachine#loadAgent)
  3. agent启动服务端,开启监听端口
  4. brace-client 把编译好的用户btrace代码发送到服务端,并等待服务端响应
  5. btrace-agent 通过asm修改运行时代码,织入用户btrace代码逻辑.监控到信息后,发给btrace-client