2010年7月26日星期一

Nginx的Module中使用X-Accel-Redirect的问题

再前面的一篇blog中,提到了在做一个持久配置中心。当时为了提升效率,在同事用tomcat实现的同时,我在尝试直接用nginx来完成。想通过完成一个nginx的模块,进行逻辑处理,然后把需要的数据返回回去。
    在tomcat中,是重定向请求到一个具体的文件。而我在nginx中,也是这么干的。
    在nginx的module中,我想通过 X-Accel-Redirect 这么一个header来完成,但是死活都不行,而给出的例子,都是在nginx后端所连接的系统中设置这个header,就可以起作用。比如后面挂一个tomcat,或者搞一个fastcgi去连php,都行的。按照我的理解,我觉得在nginx的模块中,直接设置这个header也应该工作的,不过事实告诉我,确实不行,这个得去问问公司这方面的专家,看看为啥是不能工作的。找个解释。

      晚上邮件咨询了下同事,同事告诉我:“这个feature是给后端服务器用的(在Nginx里面,后端服务器称为upstream,比如FastCGI、Proxy、memcached等)。在Nginx模块里是不能用这个的,可以使用internal redirect机制,就是调用ngx_http_internal_redirect”。我这下知道怎么回事儿了,不过暂时这个模块的尝试hold了,今后如果继续,就能够用这个方式了。

框架对性能的影响

  首先声明,这里提到框架对性能的影响,只是针对非常特定的场景。
    这篇博客还是源于之前的一个项目--持久配置中心。当时在压测的时候,我对并发去读配置的性能不是很满意。 
    看了代码,发现在Get的时候,还是处理了一些逻辑,之前一直以为是一个synchronized引起的,这个同步块中,是对一个计数的变量做操作,这个计数是用于判断,是否告诉当前请求的客户端,换一个服务器(为了降低本机压力)。把这个地方改为用Atomic的操作后,发现性能没有太多的提升,索性注释掉了所有的逻辑,直接返回数据。发现,好像还是不够快。我们是采用Spring MVC来完成的服务,后来直接用写了一个Servlet来提供服务,就快多了,开始用SpringMVC的响应时间平均在20ms左右,换成了Servlet后,就变为5-6ms了。
    这里不是说框架不好,其实框架对开发是带来了很多的帮助,加快了速度,降低了开发成本,这里只是想提醒大家一下,对于特别关键性能相关的地方,还是要考虑下框架是否会可能带来性能的影响。对于特别关注性能的地方,是可以用看起来不那么优雅,设置可能有些ugly的方法来实现。

一个内部简单系统-持久配置中心的经验

公司内部有一个配置中心,采用推送的方式来推送配置到客户端,对于Runtime的数据和持久的数据,都采用同样的方式处理。整个系统是一个集群。持久的数据也都是存在每个节点上,节点之间的数据会复制。
    我总觉得,持久的数据的特性和非持久(Runtime)的会有所不同,一般Runtime的数据的特点是需要非常快的更新到客户端,让客户端感知到数据的变化。而持久的数据,需要的是客户端启动时的可靠获取,以及配置数据持久保存的安全性等。
    后来主导,起了一个新的产品,专门用于管理持久配置数据的,提供了Get的高可用性,采用数据库并且主备的方式,保证数据本身的安全。
    产品出来后,发现了几个问题,
        第一个,内部有了两个配置中心,一个是管理持久数据的,一个是Runtime数据的,但是,对于内部的开发人员来说,复杂了
第二个,原来都是使用一个客户端,现在迁移的时候,有一定的迁移成本,而不是完全透明的
第三个,对于运维的同事和线下的配置管理员来说,多了一套系统
第四个,其实之前没有仔细分析清楚,就是对于持久的配置数据,一般是不会改动的,但是真的改动的时候,也是需要能够立即通知到客户端的。而这个地方,确实新的持久配置中心没有解决好的。
    在开始这个产品之前,觉得是尽量把持久的配置中心做到很简单,做到可以应对意外情况(比如意外的时候,可以读取本地配置;配置是文本的,可以方便的人工介入),可以,还是有不少的地方没有想清楚。这个真是教训啊。
    对于一个看似简单的产品,
         1 还是需要想清楚真实的使用场景
 2 考虑对现有的影响,包括但不限于使用这个产品的人;运维人员;配管人员;测试人员等等。
 3 类似概念的产品,最好只有一个。对于Runtime的配置,持久的配置,其实都是配置,虽然特性上可能会有很大的差别,但是对于使用者,还是隐藏一些使用上的细节比较好。

2010年5月4日星期二

QCon北京归来小结

   参加QCon北京站回来已经有两个礼拜,一直想写一点自己的感想。今天终于是可以抽点时间来记录一下了。

    这里就谈谈自己的几点感受。具体TOPIC的内容,我想很多朋友也有些分享,包括也有PPT可以去参考。

  1.  英语的听说能力真的很重要。国内的教育(起码我读书的年代)培养出来的学生,英语的读写能力还行,但是听和说就会比较薄弱。自己在QCon中,直接听英文的,能听懂部分,有些是小部分,有些是大部分,总之是有些障碍的,听同声翻译,有些时候可能还不如直接听英语。这里就首先想说下英语的重要。
  2. 在解决问题的前提下,简单就好。不是越复杂越炫就好,而是用最简单的方式、设计、架构来解决所面临的问题,才是最好的。
  3. 分区(Partition)  分区的概念提了很久,但是关键还是如何分、分了以后是否有数据复制,如果有数据复制如何来解决。这些是需要根据自身的业务、目标来决定如何做。比较常见的,根据时间来分、根据用户来分、根据存储对象的owner来分(比如交易,owner可以认为有两个,卖家、买家,这个时候就会有双份,有数据复制的问题产生)。另外,一个基本的原则是分区之间尽量不要有强依赖,这样的分区是比较能够部署在物理上的多个地方。
  4. 制定特定问题域中的解决方案,在工作中,我们不要去追求一些貌似能够通用的方案,而是能够分析清楚问题域,并且针对这个问题域,制定高效、稳定的方案,来解决问题。

2009年6月19日星期五

Java中Tree的序列化

工作中,遇到了在Java里面序列化一颗树、然后反序列化的时候出现Stack Overflow异常的情况。整棵树的层次和在处理每层的当前对象的消耗(在栈上的消耗)累计起来造成了Stack Overflow。
晚上在家里的时候,写了个小程序,实现了Java中对一个Tree的序列化。
这个程序主要想避免层次太深的问题,所以出发点很简单,就是要改变序列化的时候的对象关系,我们要把一个层次的关系变成平的。
这个程序完成了三个版本的序列化方式。先看第一个
第一个版本在写的时候没怎么想:大概的思路是这样的,就是我们对Tree上的每个节点独立序列化,然后我们保存节点间的父子关系,序列化后的结构数据结构是这样的:
NodeCount:4bytes
//每个节点的数据
NodeId:4bytes
Node序列化后的长度:4bytes
Node序列化后的数据:1byte*序列化后的长度
Node的Child个数:4bytes
Child的NodeId:4bytes*Child个数
......表示多个节点数据
其中,NodeId是从0开始自增 

完成这个版本后,大概测试了一下,序列化后的长度比Java直接序列化多了14%左右,不爽。

思考了一下,其实NodeId是不需要的,因此,很快的改出了第二个版本,序列化后格式是这样的:
NodeCount:4bytes
//每个节点的数据
Node序列化后的长度:4bytes
Node序列化后的数据:1byte*序列化后的长度
Child的NodeId:4bytes*Child个数
......表示多个节点数据
测试了一下,长度有所减少,但是非常非常不明显,基本上是比Java序列化多了13%,还是不爽。

最后,想了一下,还是把Tree上的所有节点放到一个Array中,都交给Java序列化吧。这个时候,序列化后的格式就变为了
NodeCount:4bytes
Array序列化后的长度:4bytes
Array序列化后的内容
按照宽度优先的方式的每个TreeNode的ChildCount

这次之后,序列化后的数据和直接Java比,相差无几了,比Java直接序列化差不多少1%不到一些。

2008年5月1日星期四

InnoDB 和 MyISAM中的Blob

   最近在用的一直是Mysql的Innodb,昨天测试了一下包含Blob字段的数据的插入,真的是很惨不忍睹。在表中记录数小于2k的时候,速度还行,超过2k速度就下降,从470/s持续的下降,在表中有1w条记录的时候速度就只有240/s了。到5w的时候,就只有178/s了。到20w的时候,则是130/s。后来换成了Myisam,基本上在200w记录的时候还能够有1000/s的速度。Myisam不支持事务,速度快,这些都是很早就知道的。只是不知道会有这么大的差距。也不清楚是不是innodb的什么设置我没有设置正确。现在从性能上考虑,要采用MyISAM了。而不能保证事务的这个现实,需要在APP上进行处理了。这个差距真的是太惊人了。
     不过如果没有Blob等字段,只有简单类型的话,InnoDB还好了。性能还说的过去。并且关键的是不会因为表中的有一些数据而导致性能急剧下降。

DBCP

DBCP是apache下面的一个开源的数据库连接池,谈谈几个经验
1 Connection出问题后的释放
       Connection建立成功后,如果在某次操作的时候,连接本身出现异常,可能需要废弃掉这个连接,创建新连接。当然对于mysql,支持autoReconnect的则不存在问题(Connection本身不需要废弃),但是如果不支持autoReconnect的driver,就存在这个问题。DBCP中使用的是PoolableConnection,关闭的时候,判断PoolableConnection是否close,因为这个PoolableConnection上还有代理,所以不会重复关闭,那么就判断底层的Connection的isClosed是否为true,如果是true,那么就会丢弃这个连接。JDK中Connection接口的isClosed只在Connection.close被调用后为true,在Driver的实现上,产生和数据库连接的异常后,Driver必须要自己调用close方法,才能保证这个地方让DBCP去丢弃连接。另外就是在close的时候,还调用了ConnectionFactory的passiveObject方法,这个地方出异常也会导致连接被丢弃掉。
2 关于Idle
      默认DBCP的minIdle和maxIdle都是-1,设置以后的话,对于maxIdle,如果maxIdle小于maxActive,那么在调用returnObject的时候,如果当前的idle已经等于maxIdle了,会释放掉这个连接。
3 evict
     如果设置了evict的time,那么会启动一个evictor的线程,这个是对idle的object进行检查的。基本上的逻辑是验证idle的object,删除idle超时的object,然后要保证idle的数量到达minIdle的值。