<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    莊周夢蝶

    生活、程序、未來
       :: 首頁 ::  ::  :: 聚合  :: 管理

     

        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 閱讀(3942) | 評論 (0)編輯 收藏


        轉載請注明出處 http://www.tkk7.com/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 閱讀(9281) | 評論 (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.tkk7.com/killme2008/archive/2011/06/30/353441.html
      
      

    posted @ 2011-06-30 16:01 dennis 閱讀(9014) | 評論 (5)編輯 收藏


    去年做的分享,一直上傳slideshare失敗,今天又試了下,成功了。這個主題主要介紹Java NIO編程的技巧和陷阱,解讀了一些NIO框架的源碼,以及編寫高性能NIO網絡框架所需要注意的技巧和缺陷。關注這方面的朋友可以看一下。去年寫了篇blog提供了pdf版本的下載,看這里

    posted @ 2011-06-30 11:07 dennis 閱讀(5929) | 評論 (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 閱讀(4226) | 評論 (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 閱讀(11639) | 評論 (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 閱讀(3549) | 評論 (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 閱讀(3667) | 評論 (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 閱讀(3526) | 評論 (7)編輯 收藏


          一個公司大了,總有部分人要去做一些通用的東西給大家用,我這里說的基礎產品就是這類通用性質的東西,不一定高科技,但是一定很多人依賴你的東西來完成各種各樣的功能。做這樣的東西,有些體會可以說下。

        首先,能集中存儲的,就不要分布存儲,數據集中存儲有單點的危險,但是比之分布式存儲帶來的復雜度不可同日而語。況且集中式的存儲也可以利用各種機制做備份,所謂單點風險遠沒有想象中那么大。

       其次,能利用開源框架的,就不要重復造輪子。程序員都喜歡造輪子,但是造輪子的周期長,并且不一定造的更好。在強調開發效率的互聯網時代,如果能直接利用現有框架組裝出你想要的東西,迅速占領市場,比你造的高性能、高可用、高科技的輪子更實用。這個跟做新產品開發有點類似,迅速組裝,高效開發,然后再想辦法改進。

       第三,要文本,不要二進制。協議要文本化,配置要文本化。不要擔心性能,在可見的時間里,你基本不會因為文本化的問題遇到性能瓶頸。

       第四,要透明,不要黑盒。基礎產品尤其需要對用戶透明,你的用戶不是小白用戶,他們也是程序員,而程序員天生對黑盒性質的東西充滿厭惡,他們總想知道你的東西背后在做什么,這對于查找問題分析問題也很重要。怎么做到透明呢?設計,統計,監控,日志等等。

       第五,要擁抱標準,不要另搞一套。已經有了久經考驗的HTTP協議,你就不要再搞個STTP,有了AMQP協議,你就不要再搞個BMQP。被廣泛認可的標準是一些業界的頂尖專家制定出來的,他們早就將你沒有考慮到的問題都考慮進去了。你自己搞的那一套,隨著時間推移你會發現跟業界標準越來越像,因為面對的問題是一樣的。使用標準的額外好處是,你有一大堆可用的代碼或者類庫可以直接使用,特別是在面對跨語言的時候。

        第六,能Share nothing,就不要搞狀態復制。無狀態的東西是最可愛的,天然的無副作用。水平擴展不要太容易。

        第七,要將你的系統做的越不“重要”越好,如果太多的產品依賴你的系統,那么當你的系統故障的時候,整個應用就完蛋了。我們不要擔這個責任,我們要將系統做的越來越“不重要”,別人萬一沒了你也能重啟,也能一定時間內支撐正常的工作。

        第八,要專注眼前,適當關注未來。有遠見是好事,但是太多遠見就容易好高騖遠。為很小可能性設計的東西,沒有機會經歷實際檢驗,當故障真的發生的時候,你也不可能完全信賴它。更好的辦法是將系統設計得可介入,可在緊急情況下人工去介入處理,可介入是不夠的,還要容易介入。

        第九,不要對用戶有假設,假設你的用戶都是smart programmer,假設你的用戶不需要位運算,假設你的用戶要同步不要異步。除非你對這個領域非常熟悉并實際使用過類似的東西,否則還是不要假設。

        第十,咳咳,似乎沒有第十了,一大早憋了這么篇無頭無腦的Blog,大伙將就看看。





    posted @ 2011-05-22 10:30 dennis 閱讀(6270) | 評論 (6)編輯 收藏

    僅列出標題
    共56頁: First 上一頁 2 3 4 5 6 7 8 9 10 下一頁 Last 
    主站蜘蛛池模板: 久久国产亚洲精品无码| 亚洲精品美女网站| 亚洲人成网站18禁止| 一区二区三区免费视频播放器| 99re视频精品全部免费| 国产一区二区三区在线免费观看| 亚洲国产精品无码久久久秋霞2| 亚洲第一成人在线| 中国一级特黄的片子免费| 特级做A爰片毛片免费69| 亚洲午夜久久久久久噜噜噜| 日韩亚洲产在线观看| 中文字幕久精品免费视频| 日韩a级毛片免费观看| 亚洲欧洲在线观看| 日本激情猛烈在线看免费观看| 狼群影院在线观看免费观看直播| 亚洲精品人成无码中文毛片 | 三年片免费高清版| 成人特黄a级毛片免费视频| 亚洲区小说区激情区图片区| 亚洲精品伦理熟女国产一区二区| 久久青草91免费观看| 亚洲AV中文无码乱人伦| 在线aⅴ亚洲中文字幕| 一级毛片不卡片免费观看| 亚洲国产成人久久综合野外| 激情五月亚洲色图| 一区二区三区四区免费视频| 亚洲国产一成久久精品国产成人综合 | 亚洲日本国产精华液| 男女一进一出抽搐免费视频| 国产麻豆剧传媒精品国产免费 | 亚洲专区先锋影音| 中国毛片免费观看| 亚洲v国产v天堂a无码久久| 亚洲粉嫩美白在线| 黄色网址免费大全| 亚洲第一精品福利| a毛片在线免费观看| 亚洲免费在线观看|