序列化框架选型

分布式应用中,序列化很关键,选择一个合适的序列化框架能给分布式应用带来性能红利,还能减少不必要的麻烦.本文仅仅从我遇到的一些实际问题来说明序列化的选型.更深入部分也可以参考java序列化

序列化框架的性能可以参考jvm-serializers,

1
Kryo
的性能还是很牛叉的.

除了性能,还要考虑兼容性/序列化后的大小.如果仅仅考虑性能,我们选择

1
Kryo
就足够了.但是Kryo有些用着不爽的地方,比如不是线程安全的/兼容性.

1.兼容性

字段增删不兼容,这个问题有时候很麻烦.比如用memache做缓存,把对象序列化后存入memcache,如果出现字段增删的情况,必须在服务重启的时候把缓存清空,不然就会导致灰常严重的BUG.但是如果应用服务器有多台,这个问题还是避免不了.总会有个时间窗口会出现不同服务器上的同一个应用有不同的类版本,仍然可能会出现灰常严重的BUG.

现在的

1
Kryo
提供了兼容性的支持,使用
1
CompatibleFieldSerializer.class
,在
1
kryo.writeClassAndObject
写入的信息如下:

1
class name|field lenght|field1 name|field2 name|field1 value| filed2 value

读入

1
kryo.readClassAndObject
时,会先读入
1
field names
.然后匹配当前反序列化类的field和顺序,构造结果.

子类和父类中有同名的字段时,kryo反序列化会丢失字段值,出现问题的原因和hessian出问题一样.

给kryo提交了一个improvement,在初始化类型信息时,去掉父类中重复名称的field.

2.线程安全

非线程安全也很好处理,每次都new对象出来,当然这样不是最佳的使用方式.通过线程变量来解决会比较合理,保证了性能还能提供很方便使用的工具类.

3.如何生成对象

对于没有无参构造器的类来说,生成新对象是个问题,可以使用java内部的机制来new一个对象。 可以参考下KryoReflectionFactorySupport的实现方式

4.性能

下面测试了java下的各种序列化实现方式的性能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
0 Serialisation    write=4,206ns read=16,945ns total=21,151ns
 	1 Serialisation    write=3,626ns read=18,205ns total=21,831ns
0 MemoryByteBuffer write=270ns read=324ns total=594ns
 	1 MemoryByteBuffer write=270ns read=330ns total=600ns
 	0 MemoryByteBufferWithNativeOrder  write=357ns read=360ns total=717ns
 	1 MemoryByteBufferWithNativeOrder  write=323ns read=359ns total=682ns
 	0 DirectByteBuffer write=236ns read=325ns total=561ns
 	1 DirectByteBuffer write=231ns read=301ns total=532ns
 	0 DirectByteBufferWithNativeOrder  write=261ns read=310ns total=571ns
 	1 DirectByteBufferWithNativeOrder  write=243ns read=290ns total=533ns
 	0 UnsafeMemory write=28ns read=82ns total=110ns
 	1 UnsafeMemory write=24ns read=75ns total=99ns
 	0 kryo write=373ns read=348ns total=721ns
 	1 kryo write=390ns read=386ns total=776ns
 	0 kryoWithCompatibleFields write=1,037ns read=1,625ns total=2,662ns
 	1 kryoWithCompatibleFields write=1,038ns read=1,657ns total=2,695ns
 	0 kryoWithCompatibleFieldsAndDuplicateFieldAccept  write=1,077ns read=1,560ns total=2,637ns
 	1 kryoWithCompatibleFieldsAndDuplicateFieldAccept  write=1,064ns read=1,583ns total=2,647ns
 	0 kryoWithUnsafe   write=164ns read=204ns total=368ns
 	1 kryoWithUnsafe   write=168ns read=210ns total=378ns
 	0 fastjson write=1,942ns read=5,834ns total=7,776ns
 	1 fastjson write=1,873ns read=5,879ns total=7,752ns

每种序列化执行1000000次,并且有预热. 各组数据相对比较,可以得出一些结论:

  • 直接调用unsafe,最快,但是最麻烦
  • java自带的序列化很慢,最好不要用
  • kryo2.22提供的unsafe支持,性能非常卓越
  • kryo兼容性序列化器,开销挺大.写需要写入字段名,读的时候还需要做匹配撮合,读比写慢
  • fastjson也挺快的,兼容性\跨语言互操性俱佳.

序列化后的字节大小可以参考jvm-serializers

LINUX上MYSQL优化三板斧

http://www.woqutech.com/?p=1200

讲了如何优化linux mysql服务器,主要讲了操作系统层面的优化

  • CPU方面

    关闭电源保护模式(充分利用cpu资源)

  • 内存:

    vm.swappiness = 0 (尽量少swap)

    关闭numa (在NUMA架构下,本地内存的访问和非本地内存的访问代价是不一样的)

  • 文件系统:

    用noatime(不用更新文件的access time),nobarrier(不用文件系统强制底层设备刷新缓存,由RAID卡或者Flash卡来保证)挂载系统

    IO调度策略修改为deadline。(IO调度策略采用deadline,它是非公平的调度策略,但是能兼顾一个请求不会在队列中等待太久导致饿死)

Java Multi-Threading and Concurrency Interview Questions with Answers

http://www.journaldev.com/1162/java-multi-threading-concurrency-interview-questions-with-answers

虽然是面试题,但是讲了很多java线程中的基础概念

禁用Spotlight后,Alfred找不到app

个人感觉Alfred要比spotlight好用得多,特别是装了某些workflows后,操作效率明显提高.本着不要浪费性能的原则,参考MAC OS X 关闭 spotlight 降温的大法把spotlight服务给全禁用掉了.

用了两天感觉不对,一些常用软件在Alfred中找不到,官方回答了Can Alfred work without Spotlight enabled?,看来还得启用spotlight才行.悲剧的是降温大法里面提到的启用办法老是报错.下面的命令能解决这个问题:

1
2
3
sudo mv Search2.bundle/ Search.bundle/
sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.metadata.mds.plist
sudo mdutil -a -i on 

markdown中出现了特殊字符导致blog一直不更新

前几天写了一篇博文,提交到github后,一直主页都刷不出来,以为是github出问题了.专门去twiter看了下,没有发现风吹草动,这下必须得把测试预览环境搭建起来.

1
2
3
4
5
6
7
8
9
10
11
12
13
sudo gem install jekyll
#安装markdwon解析插件
sudo gem install rdiscount
#切换到项目路径
cd bohrqiu.github.com
#启动jekyll服务并监听文件变动
jekyll serve --watch 启动起来后,居然看到markdown解析失败了

| Maruku tells you:
+-----
| Malformed HTML starting at "<v2,则打包失败)"
| -----
| gin`来规范传递依赖,要求当前依赖的版本和传递依赖版本一样或者比传递依赖版本高(比如A->Cv1 ,A->D->Cv2,如果v1<v2,则打包失败)EOF 没想到`<`符号还不能直接使用~~!

git api 总结

git很久没有用了,加上现在有github的客户端,基本上把命令都忘完了,偶然看到 @heiniuhaha 妹纸总结的git api,太详细了,收了.

打造环境感知的应用

这里说的

1
环境感知的应用
是指,应用放在不同的环境,就可以使用不同环境的配置,不需要重新打包.

1.我眼中理想的环境和应用

  • 应用不用管环境配置
  • 参数配置在配置管理系统中的
  • 框架从配置管理中加载配置
  • 只有操作系统或者应用服务器知道环境的概念

理想很丰满,现实很惨…,揉醒了,继续面对现实.要实现这个目标,还有很多需要做的.现在我们尽可能要做的是,开发童鞋自己把环境搞定,不给其他童鞋添堵.

2.适合我们的
1
环境感知
的应用

我们的系统大多数用的是tomcat作为应用服务器,最好是让应用服务器来提供环境标识,在应用中结合spring profile机制来实现环境感知

3.如何操作

3.1在操作系统中添加环境变量

比如在10测试环境,在

1
/etc/profile
中增加如下东东:

1
export CATALINA_OPTS=' -Dspring.profiles.active=dev '

然后执行

1
. /etc/profile
解析

3.2配置日志

并不是所有系统都会针对不同环境启用不同的日志配置文件,即便有logback可以很方便的来解决这些问题.

3.2.1 在logback配置内区分环境

比如我们在本地测试的时候,把日志输出到console,便于我们查问题. 可以在logback.xml中加入:

1
2
3
4
5
6
7
8
9
10
11
12
<if condition='property("os.name").toUpperCase().contains("WINDOWS")||property("os.name").toUpperCase().contains("MAC")'>
	<then>
		<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
			<encoder>
				<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{0}:%L-%X{ID}- %msg%n</pattern>
			</encoder>
		</appender>
		<root>
			<appender-ref ref="STDOUT" />
		</root>
	</then>
</if>

上面的配置会在windows和mac中启用console日志输出

在logback中引入外部配置文件,也可以区分不同的环境

1
<property resource="spring/log/log-${spring.profiles.active}.properties" />

上面会根据

1
spring.profiles.active
的配置读取不同的配置文件

3.2.2 不同环境区分不同的日志配置文件

如果您有针对不同环境不同的日志文件,

1
com.yjf.common.log.LogbackConfigListener
提供了支持.

1
2
3
4
<context-param>
  		<param-name>logbackConfigLocation</param-name>
  		<param-value>WEB-INF/logback-${spring.profiles.active}.xml</param-value>
  	</context-param>

3.3 配置应用

数据库和外部资源配置,一般会放入到单独的配置文件中,我们可以使用spring提供的能力来实现环境感知

3.3.1 使用properties文件
1
2
3
4
5
6
7
<bean id="propertyConfigurerForJDBC"
	class="com.yjf.common.dal.EncryptablePropertyPlaceholderConfigurer">
	<property name="order" value="1" />
	<property name="ignoreUnresolvablePlaceholders" value="true" />
	<property name="location"
		value="classpath:jdbc-${spring.profiles.active}.properties" />
</bean>

上面会根据

1
spring.profiles.active
的配置读取不同jdbc配置文件

3.3.2 使用spring profile
1
2
3
4
5
6
<beans profile="production">
 	<bean id="xxx" class="xxxxBEAN" />  
</beans>
<beans profile="test">
 	<bean id="xxx" class="xxxxBEAN" />  
</beans>

spring profile通过读取系统或者环境变量

1
spring.profiles.active
来启用不同的bean.

3.3.2 硬编码实现

1
com.yjf.common.env.Env
提供了在编写java code时,区分不同的环境

1
2
3
4
5
    private void doIt() {
    	if (Env.isOnline()) {
    		//do anything you like.
        }
	}

上面的代码只在生成环境运行,

1
com.yjf.common.env.Env
通过读取
1
spring.profiles.active
来判断环境

3.4 配置测试

通过上面的一些列配置,环境都由

1
spring.profiles.active
控制.在本地测试时,也需要启用此环境变量.

3.4.1 tomcat/jetty 启动类

1
TomcatBootstrapHelper
启动时,默认会在系统变量里增加
1
spring.profiles.active=dev

1
new TomcatBootstrapHelper(11111).start(); 上面的代码会使用`dev`环境配置.如果您按照3.3.1配置,此时会读取`jdbc-dev.properties`

我没有写jetty的启动帮助类,主要原因是为了和线上保持一致,减少一些不可预知的问题.如果要使用jetty,请增加如下代码:

1
2
3
static{
	System.setProperty("spring.profiles.active", "dev");
}
3.4.1 单元测试

在测试类或者测试父类中增加:

1
2
3
static{
	System.setProperty("spring.profiles.active", "dev");
}