Archive

Author Archive

Linux Command & shell tips (2)

August 11th, 2011 西坪 No comments

1, locale
默认情况下 Ubuntu 是 utf8 的 locale, 如:

liu@dog:$ locale -a
C
en_AG
en_AU.utf8
... ...
zh_CN.utf8
zh_SG.utf8

但某些情况下程序以GBK的Locale运行,则需要启用 zh_CN.GBK 这样的locale。 此时会报错:

$: export LC_ALL=zh_CN.gbk
warning: setlocale: LC_ALL: cannot change locale (zh_CN.gbk): No such file or directory

解决方法:
1) 修改/var/lib/locales/supported.d/local文件,在文件中添加zh_CN.GBK GBK
2) sudo dpkg-reconfigure locales
然后locale -a:

liuzhr@dog:/data/mq_tcp$ locale -a
C
en_AG
en_AU.utf8
en_BW.utf8
... ...
POSIX
zh_CN.gbk
zh_CN.utf8
zh_SG.utf8

2, 不重启服务清空日志

xxx@dog:/data/logs # echo "" > apache-access_2011101110.log

3, MySQL 没有权限的情况 (系统是Ubuntu 10.04 LTS):

The error message in /var/log/mysql/error.log:

/usr/sbin/mysqld: Can't find file: './mysql/plugin.frm' (errno: 13)
110821  9:46:07 [ERROR] Can't open the mysql.plugin table. Please run mysql_upgrade to create it.
110821  9:46:07  InnoDB: Operating system error number 13 in a file operation.
InnoDB: The error means mysqld does not have the access rights to
InnoDB: the directory.
InnoDB: File name ./ibdata1
InnoDB: File operation call: 'open'.
InnoDB: Cannot continue operation.
110821  9:46:39 [Note] Plugin 'FEDERATED' is disabled.

Fix resolution: Edit /etc/apparmor.d/usr.sbin.mysqld , 将mysql-datadir 加入进去,如:

  /data/mysqldata/ r,
  /data/mysqldata/** rwk,

4, 如果 Ubuntu 不能启动怎么办? 答案是试试 boot-repair (Ubuntu 9.10 or later)。 这个问题与 grub2 有关,参考 Ubuntu 社区的 grub2 文档

5, 使用LVM: 参考 这里 还有 这篇

6, 如何在分区之间复制,还保持文件权限不变?

su root;
find / -xdev -print0 | cpio -pa0V /mnt/temp

参考:How to move your filesystem to a new hard drive

7, 如何压缩Linux分区的大小? 参考:Reducing VMDK Size 其中包括使用Gparted做分区复制。

8, Install Eclipse IDE:
http://colinrrobinson.com/technology/install-eclipse-ubuntu/

9, proxy through ssh tunnel:
ssh -CnfND 8080 your-ssh-server.com

Categories: server & system Tags:

系统优化经验分享

May 12th, 2011 西坪 No comments

将要做一个有关系统优化的分享,写了个提纲。
(没什么独创内容,都是老调重弹)

Categories: $Performance Tags:

2011, 掌握一门新语言:Erlang

April 17th, 2011 西坪 1 comment

我们已经进入了多核时代。 做了好些年并发编程设计,觉得虽然主流语言如Java、C/C++都不同程度地通过线程的方式对多核提供了支持,但是要真正理解并发,使用自如,绝非易事。对软件开发人员才是瓶颈的软件工业来说,改革底层的编程模型,以简便地支持多核程序设计,是未来的方向。

为并发而生的 Erlang 已经存活了很多年,只是一直不温不火,阳春白雪。而Java平台上的Scala,在这两年迅速进入软件工业界的视野。尽管其Actor模型借鉴自Eralng,运行在JVM之上,不像Erlang那样纯粹、简单, 但得力于Java平台庞大的现有代码、庞大的程序员阵营和各企业组织的大规模现有投资,很多人都认为Scala会更加成功。即使热爱 Erlang 的 Yariv Sadan — 他在 Facebook 用 Erlang 开发 Facebook Chat ,也在其争论 Erlang 与 Scala 的博文 《Erlang vs. Scala》 的结尾写道:“ Regardless of these differences, though, I think that Scala has a good chance of being the more popular language of the two.” 这篇文章很精彩,后面的评论也很精彩,值得一读。 有趣的是,作者说有些人认为 Erlang来自外国 (作者应该是美国人,而Erlang来自瑞典) 是其未能成为主流语言的一个原因。(A few times, I’ve seen the following take on the relative mertis of Scala and Erlang: Erlang is great for concurrent programming and it has a great track record in its niche, but it’s unlikely to become mainstream because it’s foreign and it doesn’t have as many libraries as Java.)

去年跟风了解了 Golang,写过几个小程序。Golang的方式略有有些易于C/Java,但总的来说,还是非常像的,上手不难。尤其是喜欢C的人,要选择一门多核时代的语言去学习,Golang 是最合适的了。不过,我还没有弄透Golang对并发的支持为什么优于传统语言,是的,它的并发基于消息传递的,但到底是怎么实现的?又为何如此实现呢?

Erlang 给我的感受则完全不同,变量竟然是immutable的,= 竟然不是赋值符号,字符串是整形值,一个类型也没有 :) , 刘姥姥进了大观园,完全是一个惊奇的世界。不过除了惊讶,我便很快喜欢它immutable的变量,也对久仰大名的 List Comprehension 感到异常惊喜。List Comprehension 跟 Map/Reduce的区别还是很明显的。 Erlang 真的是太惊艳了,学习它是必须的。毋庸置疑,它肯定会促使我改变思考事情的方式。

对于Scala,扫描了下它的文档。不过我更关心的是 ActorCSP 。Scala 与 Erlang 的并发都源自 Actor模型,有Erlang就可以了解了。对于一个常跟Java打交道的程序员,Scala需要用了将来再现学就行。

我想要理解的是Erlang/Go与Java/C不同的并发模型,它们的内存模型有哪些异同。也许你会说减少了上下文切换,减少了CPU Cache丢失,减少系统内存开销,减少抢断带来的损失等等,但我想探寻的是它们具体是怎么做的呢? 多核的变革已经来临,或许改进应该从计算机体系的顺序模型开始,硬件结构与软件模型一起改进(看看 Comuunications Of ACM《Memory Models: A Case for Rethinking Parallel Languages and Hardware》)。 何况除了并发,Erlang 还有更多值得发掘的东西。

下面这是相关的几篇值得一读的好文:

———————————–
参考:

Categories: $Programming Tags: , , ,

Memory leak caused by Proxool and the fix

April 5th, 2011 西坪 No comments

Update: Be cautions if you don’t apply the patch to work with Oracle JDBC. The finalize() method in the WrappedConnection instances is always invoked when they are being finalized; then the finalize() in the physical connection class would be invoked. If the JDBC driver places some code there, it could destroy the pooled connections. (Apr 10th, 2011)
============================

Someone has reported a possible memory leak related with Proxool connection pool. And we addressed the bug recently in our applications, which are Java backend applications that work with Oracle 11g through Oracle JDBC5 on IBM JDK 5 platform.

The finalize() method of the proxied OracleConnection instances are the root cause. OracleConnection classes have no explict finalize() method, but each proxied object has one generated by cglib. When such a proxied connection instance is being finalized, the stack should be as the figure shows. You can see Connection#isClosed() method is invoked, which is SYNCHRONIZED (the monitor is the connection object itself). Then the finalizer thread and the worker thread using the physical connection could block each other. Finally, the proxied connection objects in finalizing queue and all referenced objects couldn’t be garbage-collected. Sometimes Connections aren’t so busy – you are lucky – the heap could suspend or even decrease; but OOM is waiting for you at the end usually.

thread_blocked_2

I gave a fix that avoid invoking Connection#isClosed() while finalizing. Actually, I changed the line 114 of WrappedConnection from

if (proxyConnection != null && proxyConnection.isReallyClosed()) {

to

if (proxyConnection != null && !concreteMethod.getName().equals(FINALIZE_METHOD) && proxyConnection.isReallyClosed()) {

The fix is proved to be effective in our serveral systems in production .

You can download the patch in the bug page in proxool bugtrack site.

Or you can download a compiled version of proxool-0.9.1.jar(WITHOUT WARRANT). (It’s comipled from proxool-0.9.1-source.zip)

Any questions please let me know by giving a comment.

Categories: $Performance Tags: , , ,

Proxool导致的内存泄漏

April 1st, 2011 西坪 No comments

Proxool是一个很优秀的开源连接池。我曾经比较过dbcp, c3p0proxool 这三个连接池,阅读和分析它们的代码。相比之下,proxool 用了cglib,其源码显得相当简洁优雅。 但是很不幸,在这一次,内存泄漏的帐要算在它头上。 我们使用的是proxool 的0.9.1的版本,环境是Oracle JDBC5 + IBM JDK5。

0.9.1版本是proxool 的最新版本。我们有一个应用据称运行以来,就一直处于内存溢出的阴霾之中。追赶数据时的某些巨忙的系统可能会一天内死好些次,甚至达十几次。不得不采用一些监控脚本定时重启。去年12月我接手以来,也被这个问题困扰,最糟糕的时候甚至某些硬件设施很好的部署实例的多达50+GB的堆也能在一天之内吃光。

出错的原因很简单,proxool使用了cglib,它用WrappedConnection代理实际的Conneciton。在运行WrappedConnection的方法时,包括其finalize方法,都会调用Conneciton.isClosed()方法去判断是否真的需要执行某些操作。不幸的是Oracle JDBC中的这个方法是同步的,锁是连接对象本身。于是, Finalizer线程回收刚执行过的WrappedConnection对象时就总会与还在使用Connection的各个工作线程争用锁。

贴一张简单的图,你就懂了:
thread_blocked_2

解决方案是在WrappedConnection.java的第114行,做一点小小的改动,在调用的方法为finalize方法时,不要去掉isClosed()方法。事实上,WrappedConnection#finalize()方法的调用与Connection根本没啥关系。Oracle JDBC本身没有实现finalize()方法,是cglib代理的对象自己生成了一个finalize()方法而已。
修改前的114行:

if (proxyConnection != null && proxyConnection.isReallyClosed()) {

修改后的114行:

if (proxyConnection != null && !concreteMethod.getName().equals(FINALIZE_METHOD) && proxyConnection.isReallyClosed()) {

点此查看sourceforge上的完整的WrappedConnection.java

效果是很明显的。一方面,内存消耗明显降低。之前,所有的部署实例都会隔段时间就当掉,某些部署实例的52G的堆都会溢出!现在,系统再忙其堆大小也基本上在原来那个设得老大的Xms参数之下。 第二方面,吞吐量忽然提高了许多,没有烦人的回收线程(其优先级要高点)的干扰,当然应该要跑得更快些。

感兴趣的朋友可以下载我这里提供的jar包(-target 1.5),(源码是直接从官网下的proxool-0.9.1-source.zip)。这个jar包也解决了ProxoolDataSource在Spring中作为bean配置时有三个属性(maximumConnectionLifetime,houseKeepingSleepTime,overloadWithoutRefusalLifetime)不能注入的问题。详细请看这篇博文

下载 proxool-0.9.1

也可以应用patch自己编译一个。patch我上传到Bug页面了,请去那下载。

拍拍我这几年买的书

March 31st, 2011 西坪 No comments

虽然很多都没有好好地读,但是这几年确实攒了不少书。书都搁不下了,房子太小,两个书架都满了,好好归置了一下,在电视柜边再放了两摞。

房东的四层书架

IMG_2180

自己又买了一个

IMG_2181

放不下了,在电视柜旁堆了两摞

用《Communications of ACM》挡住了一些杂七杂八的东西。

IMG_2182

Categories: @生活 Tags:

解决Java序列化的版本冲突

March 19th, 2011 西坪 No comments

序列化机制有很多种,Java原生的序列化机制是最方便、简单、直接的序列化方法之一。尽管这种机制有很多缺点,但还是有不少项目使用。我们有一个项目,将部分对象序列化后存储在Memcached中,后来程序出了升级版本。不幸的是,序列化对象对应的类有了一些变化,新版本的程序不能反序列化之前的数据。在协助升级Memcached中的数据的过程中才发现这个问题,这时便需提前将Memcached中的数据针对新版本程序做转换。 Memcached的导出机制我已经在前面的博文中作过介绍。

什么时候Java序列化会发生冲突

Java的序列化,实际上序列化的是对象的内容,也即只包括域(含父类的域)。因此类中方法的变化不会对序列化造成影响,只有类的体系和属性会对序列化造成影响。主要的导致版本冲突的变化包括:

  • 删除域
  • 改变类继承体系,讲一个实例域修改为静态或将非瞬时(transient)域修改为瞬时域
  • 修改域类型
  • 修writeObject/readObject导致不兼容的修改
  • 以及其他更多….

详细请参见官方文档

我们的程序修改了一个属性的类型,导致新版本程序不能反序列化之前的数据。

使用Reflection解决版本冲突

最早我尝试使用动态类加载和反射(Reflection),将旧版本的classes加入到类路径中,使用旧版本的classes将数据反序列化,再通过Reflection将值设置到新版本class的实例上,再将新版本的实例反序列化回Memcached中。伪代码如下:

//Add old classes to classpath and 
import ***.stat.***.service.impl.AAAContext;
 //return: new Instance of AAAContext;
 //param: serialized data of old instance AAAContext
 private byte[] getNewInstance(byte[] serializedData) throws Exception {
 
   //get the old instance by deserializing data 
   AAAContext oldObje = deserialize(serializedData);
 
   //load new classes from external jar files
   URLClassLoader clazzLoader ;
   Class aaaContextClazz;
   URL jarfile = new URL("jar", "","file:E:\\workshop\\new-lib.jar!/");
   URL jarfile1 = new URL("jar", "","file:E:\\workshop\\dependencies.jar!/");
   clazzLoader = new URLClassLoader(new URL[]{ jarfile, jarfile1}, null);
   aaaContextClazz = clazzLoader.loadClass("***.stat.***.service.impl.AAAContext");
   Object oldins = aaaContextClazz.newInstance();
 
   //get setters on the new clazz, MUST find every setter method by hand coding
   Method m[] = aaaContextClazz.getDeclaredMethods();
 
   m[0].invoke(ins, oldObject.getProp1());
   m[1].invoke(ins, oldObject.getProp2());
   .... ....
 
   //return the serialized data of new instance
   return serializeObj(ins);
}

其中比较特殊的是 “clazzLoader = new URLClassLoader(new URL[]{ jarfile, jarfile1}, null);” 这一行代码,因为Java类加载体系的继承方式,必须指定new URLClassLoader()的第二个参数为null,去掉parent加载器。在不指定该参数时,会使用当前类加载器作为parent加载器,于是则会先从当前的类加载器体系中去寻找,只能得到类路径(classpath)下的同名类。

这种方法因为要序列化的字段太多,实现起来不易,且需要老版本的各依赖包,工程人员实施起来麻烦,故转而采取了下面的方法。

使用内置的序列化措施解决冲突

花了点时间研究Java的序列化机制后,发现有更好的方法支持序列化数据的版本变化。其中最简单的就是添加readObject/writeObject方法,自定义实现序列化,完成新旧版本间的转换。主要有下面三步要做。

第一步:自定义要更新的字段

Java序列化机制容许我们指定哪些字段要序列化(官方文档)。将类型变化了的属性的序列化类型指定为旧版本中的类型(Long.class,新类型为String.class),以兼容老版本的序列化信息。定义如下:

  //定义的序列化版本ID必须与新/旧的class一致
  private static final long serialVersionUID = 3495267477129405823L;
  private static final
  ObjectStreamField[] serialPersistentFields = {
	new ObjectStreamField("id", Long.class),
	new ObjectStreamField("aaa_BeginTime", Date.class),
	new ObjectStreamField("flag", Integer.class),
              //略去几十个
 
              //将类型变化了的属性的序列化类型为旧版本中的类型
              new ObjectStreamField("sess_parameter", Long.class)
 };
第二步:定义readObject方法

定义readObject方法,读取老版本的数据,并做新旧类型转换:

private void readObject(ObjectInputStream ois)
    throws ClassNotFoundException, IOException {
  // Read version one types
  ObjectInputStream.GetField fields =  ois.readFields();
 
  this.id = (Long)fields.get("id", null);
  this.aaa_BeginTime = (Date)fields.get("aaa_BeginTime", null);
  this.flag = (Integer)fields.get("flag", null);
  //略去几十个
 
  //按旧版本读取,在转化为新版本的类型
  Long old_param = (Long)fields.get("sess_parameter", null);
 
  this.sess_parameter = String.valueOf(old_param);
}
第三步,定义writeObject方法

定义writeObject方法,以新版本class的序列化格式得到新的序列化数据:

private void writeObject(ObjectOutputStream oos)
    throws IOException {
   PutField fields = oos.putFields();
  fields.put("id", this.id);
	fields.put("aaa_BeginTime", this.aaa_BeginTime);
	fields.put("RoamFlag", this.RoamFlag);
 
  //按新版本方式写入
  fields.put("sess_parameter", this.sess_parameter);
  oos.writeFields();
}

以这种方式,只需要新增一个类就完成了转化工作。 这个类不是新版本程序中的类,实际上只是用于中间转化操作的临时代码,它以旧版本class的序列化方式读取数据,再按新版本class的序列化方式输出数据。

教训

使用默认的Java序列化机制,会使得具体的类成为数据的schema,持久化的数据受限于类的实现细节。这带来了多方面的约束。所以,如果你用Java的原生序列化,起码应该制定一个合适的Schema,并清楚类的实现细节变化可能导致数据需要特别的迁移处理。我建议选择一种别的与类的实现细节无关的序列化方案。

参考资料

  1. Java Serialization Specification (1.5)
  2. Type Changes Affecting Serialization
  3. Java序列化算法透析: 国人写的一篇Java序列化算法的分析,解析很透彻
  4. The Java serialization algorithm revealed : 一篇英文的有关序列化机制的分析文章,与前一篇参考很接近。
  5. Implementing Serializable : 简明扼要的概述了序列化机制
  6. Advanced Serialization
  7. Discover the secrets of the Java Serialization API
  8. Dynamic Class Loading and Reloading in Java

在Android上走出天朝局域网

March 8th, 2011 西坪 3 comments

天朝的防火长城越来越牛逼了,我常用的一些网站如 Gmail, Goolgle Search,Google Docs 都很难访问。买了Android手机以后,一直在考虑如何访问这些网站。经过一番苦战,终于搞定,把相关经验写下来分享。

文章比较长,比较有价值的内容包括:

  • 第二部分介绍Opera mini,除了能实现访问抽疯的国外网站外,还能提高访问速度。
  • 第三部分主要介绍SSH Tunnel,能解决DNS污染的问题,并可实现浏览器以及其他非浏览器应用的翻墙访问。

我的手机是Huawei C8500, firmware:2.1-update1, kernel: 2.6.29-perf。

第一阶段:不给力的http_proxy和VPN

最早,我尝试用Android自身的HTTP代理方式。网上有不少帖子读在讨论给Android的一个系统设置表增加http_proxy记录来实现http代理,主要方式包括通过手动使用sqlite3修改data/data/com.google.android.providers.settings/databases/settings.db表,增加http_proxy代理记录(详细参考此文:Tips: Howto Connect Android Emulator behind proxy),如下所示:

./adb shell sqlite3 /DATA/DATA/com.google.android.providers.settings/DATABASES/settings.db
 “\”INSERT INTO system VALUES(99,’http_proxy’,[host_or_IP]:[port]);\”"

但经过验证,至少在我的机器上是不行的。

然后又照着这篇文章([Android] How to set proxy for android browser),写了一个插件直接设置浏览器代理。这个方法试了试,偶尔能行,偶尔不能行。

后来再尝试VPN,试验了几个VPN供应商提供的试用账户,都没有成功。

第二阶段:翻墙麻利、访问提速的Opera mini

Opera mini 是老资历的翻墙工具了,但自从前段时间出了Opera中国版的事情以后,官方版本不支持翻墙了,必须自己破解,并搭建一个IP位于国外的中转服务器。中转服务器其实很简单,只是接收请求,然后用CURL去Opera官方服务器去获取实际内容,再将内容发回。虽然弄一个使用自己的中转服务器的破解版Opera mini比较麻烦,但是搭建起来以后觉得速度相当快。尤其是访问国外几个大站如Google, facebook 和 twitter的移动版,那是相当流畅,远比 Andriod 自带的浏览器流畅。从服务器端Squid代理的日志来看,使用Opera mini访问同一个网页,貌似最终只有一个HTTP请求,图片和JS,CSS等都不用再单独请求了。从日志看,请求数明显减少,网页体积也缩小很多,流量也省不少。

详细参考:

搭建php版的中转服务器十分简单,就一个PHP文件,内容如下(文件内容参考自opera mini 翻墙大法提供的文件):

if ($_SERVER['REQUEST_METHOD'] == 'GET') {
       //......
} else {
        $curlInterface = curl_init();
        $headers[] = 'Connection: Keep-Alive';
        $headers[] = 'content-type: application/xml';
        $headers[] = 'User-Agent: Java0';
        curl_setopt_array($curlInterface, array(
                CURLOPT_URL => 'http://server4.operamini.com',
                CURLOPT_HTTPHEADER => $headers,
                CURLOPT_POST => 1,
                CURLOPT_POSTFIELDS => @file_get_contents('php://input'))
    );
        $result = curl_exec($curlInterface);
        curl_close($curlInterface);
        header('Content-Type: application/octet-stream');
        header('Cache-Control: priavte, no-cache');
        echo $result;
}

顺带提一句,在你翻墙以后(前提是使用第三部分介绍的方式或者其他方式实现443/80端口翻墙以后),你可以使用最新的Opera Mobile 10翻墙。Opera Mobile 10支持手动设置代理,可直接使用国外IP的代理服务器。 Opera Mobile 10 有Android的原生版本,并且不经过Opera的中间服务器代理,你不需要担心安全问题。不过相比较经过中间服务器的Opera mini版本而言,速度慢了不少,且体积庞大,安装完成后有二十几兆大。

有关Opera Mobile 10的代理设置参考这里

第三阶段:使用SSH Tunnel为所有80/443端口的访问翻墙

但还有一个问题,就是GMS里的一部分应用被墙了,如Gmail,这必须使用能全局代理的代理软件才行。在桌面系统上,有很多工具实现全局代理。其中让系统以socks5代理协议通过SSH隧道就是一种常用方式。SSH隧道在本地服务器与SSH服务器之间建立起一个加密信道,本地系统数据经过这条隧道传递给目标服务器。 这种代理方式所有的内容都经过了加密,并会对流量进行适度压缩。其需要的资源也比较少,只要拥有一个ssh账户就可以实现。这篇文章 (A short guide to SSH port forwarding) 对通过SSH做端口转发做了详细介绍。

不过在Android系统上,因为系统本身不支持代理设置,尤其像Huawei C8500这样的手机,在Wifi/3G网络的设置中都不能设置代理,必须要有一种方式将本地数据包转发到SSH隧道的本地端口。因为Android是基于Linux的,通常我们可以用iptables来实现数据包的转发。有关iptables的内容,可以参阅一下这篇中文指南,了解一些基本知识。

除此以外,使用端口转发的方式,还需要在服务器端建立一个http代理服务器监听SSH隧道的另一端,接收请求并将请求转给目标服务器,并将处理结果再通过隧道传回给手机。常用的http代理服务器有 squid, nigix 等。

一开始我找到的是TransparentProxy, 但这个软件需要手机除了需要支持 iptables外, 内核还需支持 iptables 的 REDIRECT 表。我的手机不支持 redirect,只好作罢。

然后试了试 SSHTunnel,结果没报告错误,但就是访问不了。 因为这个项目是开源的,翻开代码一看,跟TransparentProxy一样,需要 iptables 的 redirect 支持。

尝试了用 connectBot 做动态代理,然后再写了个软件将所有端口的内容转发到这个connectBot。转发通过 iptables 的 DNAT 来实现。不过这样操作起来还是比较麻烦,要开启这两个软件,而且 connectbot 耗电太厉害了,开了一段时间以后,手机发热。

最后,签出 SSHTunnel 的代码,修改成在内核不支持 redirect 的手机上,自动使用 DNAT 实现转发。问题终于搞定。

完成后,向该项目提交了这个patch,该项目已经接受此patch,现在SSHTunnel支持更多的与华为C8500的固件类似的手机了。

SSH Tunnel 项目主页: 详细使用介绍请去该项目的帮助页面

J2EE/Oracle性能调优实录

February 18th, 2011 西坪 No comments

这两天,有一个新接手的项目性能表现较差,不能满足要求。这个项目在这之前已经写完很久了,但是一些关键部署点一直没有跟上进度。此系统是用Java编写的多线程程序的,主要是从Oracle中读取数据,然后根据业务进行统计运行,在将计算结果放回Oracle中。

这个系统的性能的直观指标,就是每天能处理多少数据量。这个指标很不理想,速度很慢,拖后与现场数据生成的速度。调优的标准,就是每日处理量。

第一次优化: 提高JDBC的fetchSize

首先,分析系统各个环节的时间消耗,弄清瓶颈在哪里。

在进行了一些综合分析后,得出主要时间耗费在数据库的查询上。而从v$session_wait中查询,发现有很多 SQL*Net message from client 事件的等待,时间很长,有数百秒之多。 显然是客户端慢了。但是因为应用端是有连接池的,是不是空闲连接导致的呢? 有一些怀疑。

接下来,在数据库上用 sqlplus 执行了相同的SQL,测量其时间其实是很快的,远远小于应用消耗在数据获取的时间。联系其上面提到的等待事件,事情变逐渐明朗: 网络传输慢了。

于是检查了代码,发现大批量的数据查询竟然没有提升fetchSize属性。在调整该属性为500以后,很快收到了正面反馈。处理速度提升了将近一倍。

回去在检查Oracle中的等待事件,发现已经降到200名以外了(是与其他应用共用数据库的)。

有关 sqlnet message from client 事件的分析,可以参考 Asktom 上的文章 AskTom上的另一篇文章,则描述了类似于连接池等待,或者是在sqlplus中打开会话不操作也会导致该事件。所以在见到这个事件时还是需要仔细甄别一下的。

结论: 适当调整 FetchSize是很有必要的。Oracle JDBC默认的fetchSize为10。FetchSize的提升,一方面是减少了客户端与服务器之间的来回交互次数;另一方面,也会有效减少数据库服务器逻辑读的数量(记得是《Troubleshooting Oracle Performance》中有讲到)。

修改完成后,除了观测到 sqlnet message from client 事件消失外,发现 sys cpu 也有小幅增加。

第二次优化:减小锁定(lock)范围

但是,接下来的观测,证明这个速度还是不合意,达不到要求。于是继续分析。

仍然是分析其主要的时间消耗在什么地方。这次发现时间主要是消耗在程序对数据的处理过程中。数据的处理大部分都是在Java进程中的运算,处理过程中还可能调用到Memcached。是不是网络速度比较慢呢?尝试测试了一下网速,千兆网络的吞吐率在应用并发时还能挤出三分之一左右用来做测试文件的传输,用ping发现其latency也不算高。

在此前的观测中,发现应用服务器上的 user cpu 比较好,在30%~70%之间,主要是被JVM进程消耗了,系统负载在5.0左右。觉得有些奇怪的,因为一般这样的应用瓶颈都出在IO上。于是做了一次代码复查,很快发现有一个在方法签名上的synchronize 关键字。这个类是一个服务bean, 在spring中管理的。这时问题变得清晰起来,所有的处理任务线程都要在这里排队。这样正好符合了此前观测到的cpu现象。

改掉这个关键字,缩小锁定范围,速度又上升了将近一倍。user cpu很快降下来,系统负载也下来了,稍稍大于1。

此时,速度虽能勉强应付,但还是不够快。此时更多的时间消耗在数据的写回上面。处理速度提升了,写回阶段的压力便也迅速提升,每单位数据的写回时间有20%左右的上浮。于是,需要第三次优化,在数据写回方面提升速度。

第三次优化

(Update Apr 6th):
没心思再仔细写过程了,简略如下:
1) Oracle是多个系统共享的,数据写入成为瓶颈。在内存问题解决后,在本地内存中缓存结果,最后再集中提交,减少更新次数。数度大增2.5倍左右。
2) 将从数据库中的查询和在内存中的处理划分为两个线程并发处理,速度提高50-70%。
3) 然后,Memcached集群成为瓶颈,内网延迟成为问题。但此时速度已经足够快了,因此暂时搁置下一步优化。如果要继续提速,则可能在本地建立一个Memcached实例,或者在程序中使用一个足够大的LRU缓存以减少与Memcached交互的次数。

至此,数据后台处理速度完全达到并超过预期。

Categories: $Performance Tags: , , , , ,

[译]Chubby — 用于松耦合分布式系统的锁服务

December 31st, 2010 西坪 No comments

本文翻译自Google的[The Chubby lock service for loosely-coupled distributed systems]。翻译此文旨在传播更多信息。翻译水平有限,时间少且文章太长了,质量一般。完整版本请阅读原文。如果转载请完整转载,并包含本申明。

=========================================================================

用于松耦合分布式系统的锁服务Chubby

摘要

我们描述了我们在Chubby锁服务方面的经历。Chubby的目标是为松散耦合的分布式系统,提供粗粒度的锁定、可靠的存储(尽管容量不大)。Chubby提供了一个很像带有意向锁的分布式文件系统的接口(interface),不过它的设计重点是可用性和可靠性,而不是高性能。许多Chubby服务实例已经使用了超过一年,其中的几个实例每个都同时处理着数万台客户端。本文描述了最初的设计和预期使用,将之与实际的使用相比较,并解释了设计是怎样修改以适应这些差别的。

1. 简介

本文介绍了一种锁服务: Chubby。它被计划用在由适度规模的大量小型计算机通过高速网络互联构成的的松散耦合的分布式系统中。例如,一个Chubby实例(也叫Chubby单元(Cell))可能服务于通过1Gbit/s网络互联的一万台4核计算机。大部分Chubby单元限于一个数据中心或者机房,不过我们运行着至少一个各个副本(replica)之间相隔数千公里的Chubby单元。

锁服务的目的是允许它的客户应用们(clients)同步它们的活动,并对它们所处环境的基本信息取得一致。主要的目标包括在适度大规模的客户应用群时的可靠性、可用性,以及易于理解的语义;吞吐量和存储容量被认为是次要的。Chubby的客户端接口很像这样一个简单的文件系统:能执行整文件的读和写,另外还有意向锁和和多种诸如文件改动等事件的通知。

我们预期Chubby帮助开发者处理他们的系统中的粗粒度同步,特别是处理从一组各方面相当的服务器中选举领导者。例如Google文件系统(Google File System)[7]使用一个Chubby锁来选择GFS Master 服务器,Bigtable[3]以数种方式使用Chbbuy:选择领导者;使得Master可以发现它控制的服务器;使得客户应用(client)可以找到Master。此外,GFS和Bigtable都用Chubby作为众所周知的、可访问的存储小部分元数据(meta-data)的位置;实际上它们用Chubby作为它们的分布式数据结构的根。一些服务使用锁在多个服务器间(在粗粒度级别上)拆分工作。

在Chubby发布之前,Google的大部分分布式系统使用必需的、未提前规划的(ad hoc)方法做主从选举(primary election)(在工作可能被重复但无害时),或者需要人工干预(在正确性至关重要时)。对于前一种情况,Chubby可以节省一些计算能力。对于后一种情况,它使得系统在失败时不再需要人工干预,显著改进了可用性。

熟悉分布式计算的读者会意识到在多个相同体(peer)间primay选举是分布式协同(distributed consensus)问题的一个特例,同时意识到我们需要一种异步(asynchronous)通信的解决方案。异步(asynchronous)这个术语描述了绝大多数真实网络(real networks)如以太网或因特网的行为:它们容许数据包丢失、延时和重排序。(专家们一般应该了解(真实网络的)协议集建立在对环境做了很强假设的模型之上。) 异步一致性由Paxos协议[12, 13]解决了。同样的协议被Oki和Liskov(见于他们有关Viewstamped replication的论文[19, $4])使用,其他人使用了等价的协议[14, $6]。实际上,迄今为止我们遇到的所有可用的异步协同协议的核心都有Paxos。Paxos不需要计时假设来维持安全性,但必须引入时钟来确保活跃度(liveness)。这克服了Fisher等人的不可能性结果(impossibility result of Fisher et al.)[5, $1]

构建Chubby是一种满足上文提到的各种需求的工程上的工作,不是学术研究。我们声明没有提出新的算法和技术。本文的目的在于描述我们做了什么以及为什么这么做,而不是提出这些。在接下来的章节中,我们描述Chubby的设计和实现,以及在实际过程中它是怎么改变的。我们描述了预料之外的Chubby的使用方式,以及被证明是错误的特性。我们忽略了在其他文献中已经包括的细节,例如一致性协议或者RPC系统。
Read more…