庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理

 

    aviator是一个轻量级的、高性能的Java表达式求值器,主要应用在如工作流引擎节点条件判断、MQ中的消息过滤以及某些特定的业务场景。

    自从上次发布1.0后,还发过1.01版本,不过都没怎么宣传。这次发布一个2.0的里程碑版本,主要改进如下:


1、完整支持位运算符,与java完全一致。位预算符对实现bit set之类的需求还是非常必须的。

2、性能优化,平均性能提升100%,函数调用性能提升200%,最新的与groovy和JEXL的性能测试看这里

http://code.google.com/p/aviator/wiki/Performance

3、添加了新函数,包括long、double、str用于类型转换,添加了string.indexOf函数。

4、完善了用户手册,更新性能测试。

 

下载地址:  http://code.google.com/p/aviator/downloads/list

项目主页:  http://code.google.com/p/aviator/

用户指南:  http://code.google.com/p/aviator/w/list

性能报告:  http://code.google.com/p/aviator/wiki/Performance

源码:          https://github.com/killme2008/aviator

 

Maven引用(感谢许老大的帮助):

    <dependency>
            
<groupId>com.googlecode.aviator</groupId>
            
<artifactId>aviator</artifactId>
                        
<version>2.0</version>
    
</dependency>

     这个项目目前用在我们的MQ产品中做消息过滤,也有几个公司外的用户告诉我他们也在用,不过估计不会很多。有这种需求的场景还是比较少的。这个项目实际上是为我们的MQ定制的,我主要想做到这么几点:

(1)控制用户能够使用的函数,不允许调用任何不受控制的函数。

(2)轻量级,不需要嵌入groovy这么大的脚本引擎,我们只需要一个剪裁过的表达式语法即可。

(3)高性能,最终的性能在某些场景比groovy略差,但是已经非常接近。

(4)易于扩展,可以容易地添加函数扩展功能。语法相对固定。

(5)函数的调用避免使用反射。因此没使用dot运算符的函数调用方式,而是更类似c语言和lua语言的函数调用风格。函数是一等公民,seq库的风格很符合我的喜好。

  seq这概念来自clojure,我将实现了java.util.Collection接口的类和数组都称为seq集合,可以统一使用seq库操作。例如假设我有个list:

        Map<String, Object> env = new HashMap<String, Object>();
        ArrayList
<Integer> list = new ArrayList<Integer>();
        list.add(
3);
        list.add(
100);
        list.add(
-100);
        env.put(
"list", list);

   可以做这么几个事情,度量大小:
count(list)
   判断元素是否存在:
include(list,3)
   过滤元素,返回大于0的元素组成的seq:
filter(list,seq.gt(0))
   对集合里的元素求和,应用reduce:
reduce(list,+,0)
   遍历集合元素并打印:
map(list,println)
   最后,你还可以排序:
sort(list)

    这些函数类似FP里的高阶函数,使用起来还是非常爽的。

    对函数调用的优化,其实只干了一个事情,原来函数调用我是将所有参数收集到一个list里面,然后再转成数组元素交给AviatorFunction调用。这里创建了两个临时对象:list和数组。这其实是没有必要的,我只要在AviatorFunction里定义一系列重载方法,如:
   public AviatorObject call(Map<String, Object> env);


    
public AviatorObject call(Map<String, Object> env, AviatorObject arg1);


    
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2);


    
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2, AviatorObject arg3);

    

   就不需要收集参数,而是直接invokeinterface调用AviatorFunction相应的重载方法即可。我看到在JRuby和Clojure里的方法调用都这样干的。过去的思路走岔了。最终也不需要区分内部的method和外部的function,统一为一个对象即可,进一步减少了对象创建的开销。

posted @ 2011-07-13 22:34 dennis 阅读(3932) | 评论 (0)编辑 收藏


    转载请注明出处 http://www.blogjava.net/killme2008/archive/2011/07/10/354062.html

    上周在线上系统发现了两个bug,值得记录下查找的过程和原因。以后如果还有查找bug比较有价值的经历,我也会继续分享。
    第一个bug的起始,是在线上日志发现一个频繁打印的异常——java.lang.ArrayIndexOutOfBoundsException。但是却没有堆栈,只有一行一行的ArrayIndexOutOfBoundsException。没有堆栈,不知道异常是从什么地方抛出来的,也就不能找到问题的根源,更谈不上解决。题外,工程师在用log4j记录错误异常的时候,我看到很多人这样用(假设e是异常对象):
log.error("发生错误:"+e);
或者:
log.error("发生错误:"+e.getMessage());
    这样的写法是不对,只记录了异常的信息,而没有将堆栈输出到日志,正确的写法是利用error的重载方法:
log.error("xxx发生错误",e);

    这样才能在日志中完整地输出异常堆栈来。如何写好日志是另一个话题,这里不展开。继续我们的找bug经历。刚才提到,我们线上日志一直出现一行错误信息ArrayIndexOutOfBoundsException却没有堆栈,是我们没有正确地写日志吗?检查代码不是的,这个问题其实是跟JDK5引入的一个新特性有关,对于一些频繁抛出的异常,JDK为了性能会做一个优化,在JIT重新编译后会抛出没有堆栈的异常。在使用server模式的时候,这个优化是开启的,我们的服务器跑在server模式下并且jdk版本是6,因此在频繁抛出ArrayIndexOutOfBoundsException异常一段时间后优化开始起作用,只抛出没有堆栈的异常信息了。

    那么怎么解决这个问题呢?因为这个优化是在JIT重新编译后才起作用,因此一开始抛出异常的时候还是有堆栈的,所以可以查看较旧的日志,寻找有完整堆栈的异常信息。但是由于我们的日志太大,会定期删除,我们的服务器也启动了很长时间,因此查找日志不是很靠谱的方法。
    另一个解决办法是暂时禁用这个优化,强制要求每次都要抛出有堆栈的异常。幸好JDK提供了选项来关闭这个优化,配置JVM参数
-XX:-OmitStackTraceInFastThrow
    就可以禁止这个优化(注意选项中的减号,加号是启用)。

    我们找了台机器,配置了这个参数并重启一下。过了一会就找到问题所在,堆栈类似这样
Caused by: java.lang.ArrayIndexOutOfBoundsException: -1831238
    at sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:
436)
    at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:
2081)
    at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:
1996)
    at java.util.Calendar.setTimeInMillis(Calendar.java:
1109)
    at java.util.Calendar.setTime(Calendar.java:
1075)
    at java.text.SimpleDateFormat.format(SimpleDateFormat.java:
876)
    at java.text.SimpleDateFormat.format(SimpleDateFormat.java:
869)
    at java.text.DateFormat.format(DateFormat.java:
316)

    读者肯定猜到了,这个问题是由于SimpleDateFormat的误用引起的。SimpleDateFormatjavadoc中有这么句话:
Date formats are not synchronized. It is recommended to create separate format instances for each thread.
If multiple threads access a format concurrently, it must be 
synchronized externally.
    但是很悲剧的是这句话是放在整个doc的最后面,在我看来,这句话应该放在最前面才对。简单来说就是SimpleDateFormat不是线程安全的,你要么每次都new一个来用,要么做加锁来同步使用。

    出问题的代码就是由于工程师认为SimpleDateFormat的创建代价很高,然后搞了个map做缓存,所有线程共用这个instance做format,同时没有做同步。悲剧就诞生了。
    这里就引出我想提到的第二点问题,在使用一个类或者方法的时候,最好能详细地看下该类的javadoc,JDK的javadoc是做的非常好的,javadoc除了做说明之外,通常还会给示例,并且会点出一些关键问题,如线程安全性和平台移植性。

    最后,我将做个测试,到底在使用SimpleDateFormat怎么做才是最好的方式?假设我们要实现一个formatDate方法将日期格式化成"yyyy-MM-dd"的格式。
    第一个方法是每次使用都创建一个instance,并调用format方法:
   public static String formatDate1(Date date) {
        SimpleDateFormat format 
= new SimpleDateFormat("yyyy-MM-dd");
        
return format.format(date);
    }

    第二个方法是只创建一个instance,但是在调用方法的时候做同步:
   private static final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");

    
public static synchronized String formatDate2(Date date) {
        
return formatter.format(date);
    }

    第三个方法比较特殊,我们为每个线程都缓存一个instance,存放在ThreadLocal里,使用的时候从ThreadLocal里取就可以了:
   private static ThreadLocal<SimpleDateFormat> formatCache = new ThreadLocal<SimpleDateFormat>() {

        @Override
        
protected SimpleDateFormat initialValue() {
            
return new SimpleDateFormat("yyyy-MM-dd");
        }

    };

   
public static String formatDate3(Date date) {
        SimpleDateFormat format 
= formatCache.get();
        
return format.format(date);
    }

    然后我们测试下三个方法并发调用下的性能并做一个比较,并发100个线程循环调用1000万次,记录耗时。我们设置了JVM参数:
-Xmx512m -XX:CompileThreshold=10000
    设置堆最大为512M,设置当一个方法被调用1万次的时候就被JIT编译。测试的结果如下:
 
第1次测试
第2次测试
第3次测试
formatDate1
50545
49365
53532
formatDate2
10895
10761
10673
formatDate3 10386 9919 9527
(单位:毫秒)
   
    从结果来看,方法1最慢,方法3最快,但是就算是最慢的方法1也可以达到每秒钟200 20万次的调用量,很少有系统能达到这个量级。这个点很难成为你系统的瓶颈所在。从我的角度出发,我会建议你用方法1或者方法2,如果你追求那么一点性能提升的话,可以考虑用方法3,也就是用ThreadLocal做缓存。

    总结下本文找bug经历想表达的几点想法:
(1)正确地打印错误日志
(2)在server模式下,最好都设置-XX:-OmitStackTraceInFastThrow
(3)使用类或者方法的时候,最好能详细阅读下javadoc,很多问题都能找到答案
(4)使用SimpleDateFormat的时候要注意线程安全性,要么每次new,要么做同步,两者的性能有差距,但是这个差距很难成为你的性能瓶颈。

    下篇文章我再分享另一个bug的查找经历,也是比较有趣,可以看到一些工具的使用。

posted @ 2011-07-10 23:29 dennis 阅读(9251) | 评论 (16)编辑 收藏


    Nagle算法的立意是良好的,避免网络中充塞小封包,提高网络的利用率。但是当Nagle算法遇到delayed ACK悲剧就发生了。Delayed ACK的本意也是为了提高TCP性能,跟应答数据捎带上ACK,同时避免糊涂窗口综合症,也可以一个ack确认多个段来节省开销。
    悲剧发生在这种情况,假设一端发送数据并等待另一端应答,协议上分为头部和数据,发送的时候不幸地选择了write-write,然后再read,也就是先发送头部,再发送数据,最后等待应答。发送端的伪代码是这样
write(head);
write(body);
read(response);

接收端的处理代码类似这样:
read(request);
process(request);
write(response);

   这里假设head和body都比较小,当默认启用nagle算法,并且是第一次发送的时候,根据nagle算法,第一个段head可以立即发送,因为没有等待确认的段;接收端收到head,但是包不完整,继续等待body达到并延迟ACK;发送端继续写入body,这时候nagle算法起作用了,因为head还没有被ACK,所以body要延迟发送。这就造成了发送端和接收端都在等待对方发送数据的现象,发送端等待接收端ACK head以便继续发送body,而接收端在等待发送方发送body并延迟ACK,悲剧的无以言语。这种时候只有等待一端超时并发送数据才能继续往下走。

   正因为nagle算法和delayed ack的影响,再加上这种write-write-read的编程方式造成了很多网贴在讨论为什么自己写的网络程序性能那么差。然后很多人会在帖子里建议禁用Nagle算法吧,设置TCP_NODELAY为true即可禁用nagle算法。但是这真的是解决问题的唯一办法和最好办法吗?

   其实问题不是出在nagle算法身上的,问题是出在write-write-read这种应用编程上。禁用nagle算法可以暂时解决问题,但是禁用nagle算法也带来很大坏处,网络中充塞着小封包,网络的利用率上不去,在极端情况下,大量小封包导致网络拥塞甚至崩溃。因此,能不禁止还是不禁止的好,后面我们会说下什么情况下才需要禁用nagle算法。对大多数应用来说,一般都是连续的请求——应答模型,有请求同时有应答,那么请求包的ACK其实可以延迟到跟响应一起发送,在这种情况下,其实你只要避免write-write-read形式的调用就可以避免延迟现象,利用writev做聚集写或者将head和body一起写,然后再read,变成write-read-write-read的形式来调用,就无需禁用nagle算法也可以做到不延迟。

   writev是系统调用,在Java里是用到GatheringByteChannel.write(ByteBuffer[] srcs, int offset, int length)方法来做聚集写。这里可能还有一点值的提下,很多同学看java nio框架几乎都不用这个writev调用,这是有原因的。主要是因为Java的write本身对ByteBuffer有做临时缓存,而writev没有做缓存,导致测试来看write反而比writev更高效,因此通常会更推荐用户将head和body放到同一个Buffer里来避免调用writev。

   下面我们将做个实际的代码测试来结束讨论。这个例子很简单,客户端发送一行数据到服务器,服务器简单地将这行数据返回。客户端发送的时候可以选择分两次发,还是一次发送。分两次发就是write-write-read,一次发就是write-read-write-read,可以看看两种形式下延迟的差异。注意,在windows上测试下面的代码,客户端和服务器必须分在两台机器上,似乎winsock对loopback连接的处理不一样。

    服务器源码:
package net.fnil.nagle;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;


public class Server {
    
public static void main(String[] args) throws Exception {
        ServerSocket serverSocket 
= new ServerSocket();
        serverSocket.bind(
new InetSocketAddress(8000));
        System.out.println(
"Server startup at 8000");
        
for (;;) {
            Socket socket 
= serverSocket.accept();
            InputStream in 
= socket.getInputStream();
            OutputStream out 
= socket.getOutputStream();

            
while (true) {
                
try {
                    BufferedReader reader 
= new BufferedReader(new InputStreamReader(in));
                    String line 
= reader.readLine();
                    out.write((line 
+ "\r\n").getBytes());
                }
                
catch (Exception e) {
                    
break;
                }
            }
        }
    }
}

服务端绑定到本地8000端口,并监听连接,连上来的时候就阻塞读取一行数据,并将数据返回给客户端。

客户端代码:
package net.fnil.nagle;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;


public class Client {

    
public static void main(String[] args) throws Exception {
        
// 是否分开写head和body
        boolean writeSplit = false;
        String host 
= "localhost";
        
if (args.length >= 1) {
            host 
= args[0];
        }
        
if (args.length >= 2) {
            writeSplit 
= Boolean.valueOf(args[1]);
        }

        System.out.println(
"WriteSplit:" + writeSplit);

        Socket socket 
= new Socket();

        socket.connect(
new InetSocketAddress(host, 8000));
        InputStream in 
= socket.getInputStream();
        OutputStream out 
= socket.getOutputStream();

        BufferedReader reader 
= new BufferedReader(new InputStreamReader(in));

        String head 
= "hello ";
        String body 
= "world\r\n";
        
for (int i = 0; i < 10; i++) {
            
long label = System.currentTimeMillis();
            
if (writeSplit) {
                out.write(head.getBytes());
                out.write(body.getBytes());
            }
            
else {
                out.write((head 
+ body).getBytes());
            }
            String line 
= reader.readLine();
            System.out.println(
"RTT:" + (System.currentTimeMillis() - label) + " ,receive:" + line);
        }
        in.close();
        out.close();
        socket.close();
    }

}


   客户端通过一个writeSplit变量来控制是否分开写head和body,如果为true,则先写head再写body,否则将head加上body一次写入。客户端的逻辑也很简单,连上服务器,发送一行,等待应答并打印RTT,循环10次最后关闭连接。

   首先,我们将writeSplit设置为true,也就是分两次写入一行,在我本机测试的结果,我的机器是ubuntu 11.10:
WriteSplit:true
RTT:
8 ,receive:hello world
RTT:
40 ,receive:hello world
RTT:
40 ,receive:hello world
RTT:
40 ,receive:hello world
RTT:
39 ,receive:hello world
RTT:
40 ,receive:hello world
RTT:
40 ,receive:hello world
RTT:
40 ,receive:hello world
RTT:
40 ,receive:hello world
RTT:
40 ,receive:hello world

    可以看到,每次请求到应答的时间间隔都在40ms,除了第一次。linux的delayed ack是40ms,而不是原来以为的200ms。第一次立即ACK,似乎跟linux的quickack mode有关,这里我不是特别清楚,有比较清楚的同学请指教。

     接下来,我们还是将writeSplit设置为true,但是客户端禁用nagle算法,也就是客户端代码在connect之前加上一行:
        Socket socket = new Socket();
        socket.setTcpNoDelay(
true);
        socket.connect(
new InetSocketAddress(host, 8000));

    再跑下测试:
WriteSplit:true
RTT:
0 ,receive:hello world
RTT:
0 ,receive:hello world
RTT:
0 ,receive:hello world
RTT:
0 ,receive:hello world
RTT:
1 ,receive:hello world
RTT:
0 ,receive:hello world
RTT:
0 ,receive:hello world
RTT:
0 ,receive:hello world
RTT:
0 ,receive:hello world
RTT:
0 ,receive:hello world

   这时候就正常多了,大部分RTT时间都在1毫秒以下。果然禁用Nagle算法可以解决延迟问题。
   如果我们不禁用nagle算法,而将writeSplit设置为false,也就是将head和body一次写入,再次运行测试(记的将setTcpNoDelay这行删除):
WriteSplit:false
RTT:
7 ,receive:hello world
RTT:
1 ,receive:hello world
RTT:
0 ,receive:hello world
RTT:
0 ,receive:hello world
RTT:
0 ,receive:hello world
RTT:
0 ,receive:hello world
RTT:
0 ,receive:hello world
RTT:
0 ,receive:hello world
RTT:
0 ,receive:hello world
RTT:
0 ,receive:hello world

   结果跟禁用nagle算法的效果类似。既然这样,我们还有什么理由一定要禁用nagle算法呢?通过我在xmemcached的压测中的测试,启用nagle算法在小数据的存取上甚至有一定的效率优势,memcached协议本身就是个连续的请求应答的模型。上面的测试如果在windows上跑,会发现RTT最大会在200ms以上,可见winsock的delayed ack超时是200ms。

   最后一个问题,什么情况下才应该禁用nagle算法?当你的应用不是这种连续的请求——应答模型,而是需要实时地单向发送很多小数据的时候或者请求是有间隔的,则应该禁用nagle算法来提高响应性。一个最明显是例子是telnet应用,你总是希望敲入一行数据后能立即发送给服务器,然后马上看到应答,而不是说我要连续敲入很多命令或者等待200ms才能看到应答。

   上面是我对nagle算法和delayed ack的理解和测试,有错误的地方请不吝赐教。

   转载请注明出处:http://www.blogjava.net/killme2008/archive/2011/06/30/353441.html
  
  

posted @ 2011-06-30 16:01 dennis 阅读(8845) | 评论 (5)编辑 收藏


去年做的分享,一直上传slideshare失败,今天又试了下,成功了。这个主题主要介绍Java NIO编程的技巧和陷阱,解读了一些NIO框架的源码,以及编写高性能NIO网络框架所需要注意的技巧和缺陷。关注这方面的朋友可以看一下。去年写了篇blog提供了pdf版本的下载,看这里

posted @ 2011-06-30 11:07 dennis 阅读(5919) | 评论 (4)编辑 收藏

    开源memcached的java客户端xmemcached发布1.3.3,主要改进如下:

1、memcached 1.6添加了不少新特性,具体可以参考《what's new in memcached》(1) (2)这两个帖子。xmemcached将及时跟进这些新特性。1.3.3这个版本实现了二进制协议中新的两个命令touch和GAT(get and touch)。这两个功能可以说是千呼万唤始出来,终于可以不用get-set来重新设置数据的超时时间,利用touch或者GAT可以简单地更新数据的超时时间。1.3.3新增加四个方法:
    public boolean touch(final String key, int exp, long opTimeout)
            
throws TimeoutException, InterruptedException, MemcachedException;
    
public boolean touch(final String key, int exp) throws TimeoutException,
            InterruptedException, MemcachedException;
        
public <T> T getAndTouch(final String key, int newExp, long opTimeout)
            
throws TimeoutException, InterruptedException, MemcachedException;
    
public <T> T getAndTouch(final String key, int newExp)
            
throws TimeoutException, InterruptedException, MemcachedException;

其中touch用于设置数据新的超时时间,getAndTouch则是在获取数据的同时更新超时时间。例如用memcached存储session,可以在每次get的时候更新下数据的超时时间来保活。请注意,这四个方法仅在使用memcached 1.6并且使用二进制协议的时候有效

2、setLoggingLevelVerbosity方法可以作用于二进制协议。


3、重构错误处理模块,使得异常信息更友好。


4、将KeyIterator和getKeyIterator声明为deprecated,因为memached 1.6将移除stats cachedump协议,并且stats cachedump返回数据有大小限制,遍历功能不具实用性。

5、修复Bug,包括issue 126 ,issue 127,issue 128,issue 129

下载地址:http://code.google.com/p/xmemcached/downloads/list
源码:  https://github.com/killme2008/xmemcached
maven引用:
 <dependency>
      
<groupId>com.googlecode.xmemcached</groupId>
      
<artifactId>xmemcached</artifactId>
      
<version>1.3.3</version>
 
</dependency>

posted @ 2011-06-12 13:32 dennis 阅读(4219) | 评论 (3)编辑 收藏

    Update: 如果遇到在search不存在的path报段错误,这是node-zookeeper的一个bug,我暂时修复了下并提交了pull request,你可以暂时用我修改的node-zookeeper  https://github.com/killme2008/node-zookeeper

    我们已经开始在产品使用zookeeper了,那么维护工具也必然需要,所谓兵马未动,粮草先行。请同事帮忙看过几个开源项目后,并没有特别让人满意的。
    我想要的功能比较简单。首先,希望能将zookeeper集群的数据展示为树形结构,跟zookeeper模型保持一致。可以逐步展开每层的节点,每次展开都是延迟加载从zk里取数据,这样不会对zk造成太大压力。其次,除了展示树形结构外,我还希望它能展示每个path的属性和数据,更进一步,如果数据是文本的,我希望它可编辑。当然,因为编辑功能是比较危险的行为,我还希望这个管理工具有个简单的授权验证机制。

    最终,我自己写了这么个东西,取名为node-zk-browser,基于node.js的express.js框架和node-zookeeper客户端实现的。我将它放在了github上

    https://github.com/killme2008/node-zk-browser

    你可以自己搭建这个小app, npm几乎能帮你搞定大部分工作。界面不美观,实用为主,几张运行时截图







posted @ 2011-06-06 01:13 dennis 阅读(11627) | 评论 (3)编辑 收藏


    Node.js Undocumented(1)
    Node.js Undocumented(2)

    写这种系列blog,是为了监督自己,不然我估计我不会有动力写完。这一节,我将介绍下Buffer这个module。js本身对文本友好,但是处理二进制数据就不是特别方便,因此node.js提供了Buffer模块来帮助你处理二进制数据,毕竟node.js的定位在网络服务端,不能只对文本协议友好。

    Buffer模块本身其实没有多少未公开的方法,重要的方法都在文档里提到了,有两个方法稍微值的提下。

    Buffer.get(idx)

    跟buffer[idx]是一样的,返回的是第idx个字节,返回的结果是数字,如果要转成字符,用String.fromCharCode(code)即可。

    Buffer.inspect()
    返回Buffer的字符串表示,每个字节用十六进制表示,当你调用console.dir的时候打印的就是这个方法返回的结果。

    Buffer真正值的一提的是它的内部实现。Buffer在node.js内部的cpp代码对应的是SlowBuffer类(src/node_buffer.cc),但是两者之间并不是一一对应。对于创建小于8K的Buffer,其实是从一个pool里slice出来,只有大于8K的Buffer才是每次都new一个SlowBuffer。查看源码(lib/buffer.js):
Buffer.poolSize = 8 * 1024;
    
if (this.length > Buffer.poolSize) {
      
// Big buffer, just alloc one.
      this.parent = new SlowBuffer(this.length);
      
this.offset = 0;

    } 
else {
      
// Small buffer.
      if (!pool || pool.length - pool.used < this.length) allocPool();
      
this.parent = pool;
      
this.offset = pool.used;
      pool.used 
+= this.length;
    }
 

    因此,我们可以修改Buffer.poolSize这个“静态”变量来改变池的大小

    Buffer.poolSize
    Buffer类创建的池大小,大于此值则每次new一个SlowBuffer,否则从池中slice返回一个Buffer,如果池剩余空间不够,则新创建一个SlowBuffer做为池。下面的例子打印这个值并修改成16K:
console.log(Buffer.poolSize);
Buffer.poolSize
=16*1024;

   SlowBuffer类
   SlowBuffer类我们可以直接使用的,如果你不想使用Buffer类的话,SlowBuffer类有Buffer模块的所有方法实现,例子如下:
var SlowBuffer=require('buffer').SlowBuffer
var buf=new SlowBuffer(1024)
buf.write(
"hello",'utf-8');
console.log(buf.toString('utf
-8',0,5));
console.log(buf[
0]);
var sub=buf.slice(1,3);
console.log(sub.length);
   
    请注意,SlowBuffer默认不是Global的,需要require buffer模块。

    使用建议和性能测试

    Buffer的这个实现告诉我们,要使用好Buffer类还是有讲究的,每次创建小于8K的Buffer最好大小刚好能被8k整除,这样能充分利用空间;或者每次创建大于8K的Buffer,并充分重用。我们来看一个性能测试,分别循环1000万次创建16K,4096和4097大小的Buffer,看看耗时多少:
function benchmark(size,repeats){
    
var total=0;
    console.log(
"create %d size buffer for %d times",size,repeats);
    console.time(
"times");
    
for(var i=0;i<repeats;i++){
        total
+=new Buffer(size).length;
    }
    console.timeEnd(
"times");
}
var repeats=10000000;

console.log(
"warm up")
benchmark(
1024,repeats);
console.log(
"start benchmark")
benchmark(
16*1024,repeats);
benchmark(
4096,repeats);
benchmark(
4097,repeats);

    创建1024的Buffer是为了做warm up。在我机器上的输出:
    
start benchmark
create 
16384 size buffer for 10000000 times
times: 81973ms
create 
4096 size buffer for 10000000 times
times: 80452ms
create 
4097 size buffer for 10000000 times
times: 138364ms

  
    创建4096和创建4097大小的Buffer,只差了一个字节,耗时却相差非常大,为什么会这样?读者可以自己根据上面的介绍分析下,有兴趣的可以留言。
    另外,可以看到创建16K和创建4K大小的Buffer,差距非常小,平均每秒钟都能创建10万个以上的Buffer,这个效率已经足以满足绝大多数网络应用的需求。

posted @ 2011-06-04 18:09 dennis 阅读(3541) | 评论 (0)编辑 收藏

    leveldb是google最近开源的一个实现,但是它仅是个lib,还需要包装才能使用。node-leveldb就是一个用node.js包装leveldb的项目,你可以用javascript访问leveldb。node-leveldb仅提供API,不提供网络接口供外部访问。我fork了个分支,搞了个memcached的adapter,将node-leveldb的API暴露为memcached的文本协议,这样一来你可以直接用现有的memcached client甚至直接telnet上去进行测试。感兴趣的朋友可以测试下。adpater就一个文件memcached.js
    
    fork的分支在:
    https://github.com/killme2008/node-leveldb

    编译node-leveldb之后,执行
node memcached-adpater/memcached.js

    即可启动memcached adapter在11211端口,你可以telnet上去测试。目前仅支持get/set/delete/quit协议,不支持flag和exptime。

posted @ 2011-05-31 16:45 dennis 阅读(3661) | 评论 (1)编辑 收藏


    node.js
的API文档做的不是很好,有些部分干脆没文档,最终还是要看代码才能解决。我这里将记录下看源码过程中看到的一些API并补充一些测试例子。在玩node.js的朋友可以瞧瞧。


    process.reallyExit(status)

    用于进程主动退出,status设置退出的状态码。请注意,reallyExit退出的进程不会调用'exit'事件,下面的代码不会有任何输出:
var interval=setInterval(function(){
    process.reallyExit(
1);
},
1000);
process.on('exit',
function(){
    console.log(
"hello");
});

   process._kill(pid,sig)

   用于给指定pid的进程发送指定信号(类似kill命令),这是个“private”方法,你需要慎重使用,下面的代码会杀死自身的进程:
var pid=process.pid
process._kill(pid,
9);

   process.binding(name)

   非常有用的方法,很奇怪API文档里竟然没提到,这个方法用于返回指定名称的内置模块。例如下面的代码将打印node_net模块所有的可以调用的方法或变量(很多是文档没有提到的并且没有exports的,后续我会介绍下):
var binding=process.binding('net');
console.dir(binding);

   process.dlopen(filename,target)

   看源码注释说是用来动态加载node.module的动态链接库的,以后尝试写扩展的时候也许可以尝试一下。

   定时器
   Node.js的定时器模块的实现是有讲究的,对于超时时间after<=0的callback,会在内部new一个Timer并start(本质是使用libev的timer机制);但是对于after>0的callback,却不是这样。因为在实际应用中,大多数定时器事件的超时时间都是一样的,如果每个事件都new一个Timer并start,代价太高。因此node.js采用了一个类似哈希表的方案,将相同after超时时间的定时器事件组织成链表,以after为key,以链表为value整体构成一张表。每个链表只new一个Timer,这个Timer负责整个链表的定时器事件,当某个事件超时调用后,利用ev_timer_again来高效地重新设置超时时间。
   如果你确实希望对于after>0的定时器也每次new一个Timer来处理,那也可以做到,这就要用到前面提到的process.binding方法来获取timer模块,一个例子:
var Timer = process.binding('timer').Timer;

var timer=new Timer();
timer.callback
=function(){
    console.log(
"callback called");
};
timer.start(
1000,0);
  
    timer.callback
    设定timer的回调函数,当超时的时候调用。

    timer.start(after,repeat)
    启动定时器,在after毫秒之后调用超时回调;如果repeat==0,则自动停止定时器;如果repeat>0,则在repeat毫秒之后再次调用callback,以repeat毫秒为间隔不断重复下去。

    ps.这篇blog刚好是我的第499篇blog,不出意外,第500篇还是继续介绍node.js。
   

posted @ 2011-05-31 00:33 dennis 阅读(3519) | 评论 (7)编辑 收藏


      一个公司大了,总有部分人要去做一些通用的东西给大家用,我这里说的基础产品就是这类通用性质的东西,不一定高科技,但是一定很多人依赖你的东西来完成各种各样的功能。做这样的东西,有些体会可以说下。

    首先,能集中存储的,就不要分布存储,数据集中存储有单点的危险,但是比之分布式存储带来的复杂度不可同日而语。况且集中式的存储也可以利用各种机制做备份,所谓单点风险远没有想象中那么大。

   其次,能利用开源框架的,就不要重复造轮子。程序员都喜欢造轮子,但是造轮子的周期长,并且不一定造的更好。在强调开发效率的互联网时代,如果能直接利用现有框架组装出你想要的东西,迅速占领市场,比你造的高性能、高可用、高科技的轮子更实用。这个跟做新产品开发有点类似,迅速组装,高效开发,然后再想办法改进。

   第三,要文本,不要二进制。协议要文本化,配置要文本化。不要担心性能,在可见的时间里,你基本不会因为文本化的问题遇到性能瓶颈。

   第四,要透明,不要黑盒。基础产品尤其需要对用户透明,你的用户不是小白用户,他们也是程序员,而程序员天生对黑盒性质的东西充满厌恶,他们总想知道你的东西背后在做什么,这对于查找问题分析问题也很重要。怎么做到透明呢?设计,统计,监控,日志等等。

   第五,要拥抱标准,不要另搞一套。已经有了久经考验的HTTP协议,你就不要再搞个STTP,有了AMQP协议,你就不要再搞个BMQP。被广泛认可的标准是一些业界的顶尖专家制定出来的,他们早就将你没有考虑到的问题都考虑进去了。你自己搞的那一套,随着时间推移你会发现跟业界标准越来越像,因为面对的问题是一样的。使用标准的额外好处是,你有一大堆可用的代码或者类库可以直接使用,特别是在面对跨语言的时候。

    第六,能Share nothing,就不要搞状态复制。无状态的东西是最可爱的,天然的无副作用。水平扩展不要太容易。

    第七,要将你的系统做的越不“重要”越好,如果太多的产品依赖你的系统,那么当你的系统故障的时候,整个应用就完蛋了。我们不要担这个责任,我们要将系统做的越来越“不重要”,别人万一没了你也能重启,也能一定时间内支撑正常的工作。

    第八,要专注眼前,适当关注未来。有远见是好事,但是太多远见就容易好高骛远。为很小可能性设计的东西,没有机会经历实际检验,当故障真的发生的时候,你也不可能完全信赖它。更好的办法是将系统设计得可介入,可在紧急情况下人工去介入处理,可介入是不够的,还要容易介入。

    第九,不要对用户有假设,假设你的用户都是smart programmer,假设你的用户不需要位运算,假设你的用户要同步不要异步。除非你对这个领域非常熟悉并实际使用过类似的东西,否则还是不要假设。

    第十,咳咳,似乎没有第十了,一大早憋了这么篇无头无脑的Blog,大伙将就看看。





posted @ 2011-05-22 10:30 dennis 阅读(6251) | 评论 (6)编辑 收藏

仅列出标题
共56页: First 上一页 2 3 4 5 6 7 8 9 10 下一页 Last