Zookeeper客戶端錯誤:Packet len* is out of range!

主頁:www.howardliu.cn
博文:Zookeeper客戶端錯誤:Packet len8854970 is out of range!

這是一個生產環境使用zookeeper異常的情況,錯誤是java.io.IOException: Packet len8854970 is out of range!。然後就換了一個namespace,就沒有在出錯,以爲是偶然發生,所以沒有重視。但是年後居然又出現問題,才意識到嚴重性。分析之後發現,每隔一段時間,某一個znode節點下超過客戶端所設置的大小,客戶端連接會失敗,zkCli.sh操作該節點也會失敗。如果對於簡單依賴zookeeper的系統,這種錯誤可以容忍(但是必須解決);如果是強依賴zookeeper的系統,這種錯誤可以說是災難。

1 發現問題

問題的發現比較曲折,首先是發現服務器磁盤寫滿了(吐槽下運維居然沒有對磁盤添加監控),致使項目中的報警功能失效。然後就把無用日誌刪除,習(tou)慣(lan)的把異常的應用重啓了下。幸好有個比較好的習慣就是,項目啓動成功後,都會打看日誌跟蹤下,確定無誤纔會關掉終端。結果就被錯誤日誌刷屏了:

ERROR 2017-02-20 10:45:44,729 [Curator-Framework-0] o.a.c.f.i.CuratorFrameworkImpl.logError() (CuratorFrameworkImpl.java:557) - Background retry gave up
org.apache.curator.CuratorConnectionLossException: KeeperErrorCode = ConnectionLoss
        at org.apache.curator.framework.imps.CuratorFrameworkImpl.performBackgroundOperation(CuratorFrameworkImpl.java:838) [curator-framework-2.10.0.jar:na]
        at org.apache.curator.framework.imps.CuratorFrameworkImpl.backgroundOperationsLoop(CuratorFrameworkImpl.java:809) [curator-framework-2.10.0.jar:na]
        at org.apache.curator.framework.imps.CuratorFrameworkImpl.access$300(CuratorFrameworkImpl.java:64) [curator-framework-2.10.0.jar:na]
        at org.apache.curator.framework.imps.CuratorFrameworkImpl$4.call(CuratorFrameworkImpl.java:267) [curator-framework-2.10.0.jar:na]
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_111]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_111]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.8.0_111]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_111]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_111]
        at java.lang.Thread.run(Thread.java:745) [na:1.8.0_111]

因爲zookeeper的客戶端使用的是Apache Curator,與zookeeper的連接斷開會重新創建連接,所以會出現大量的連接失敗異常。

2 分析問題

在這個項目中,對於zookeepr的使用有下面幾種場景:
1. 服務狀態數據:將服務狀態寫入znode節點
2. 服務註冊:將服務地址寫入znode節點
3. 服務發現:獲取znode節點中的可用服務地址
4. 監聽節點:監聽某個znode節點或該節點的字節點狀態

這幾種情況都需要與zookeeper的znode節點創建連接,然後執行操作。根據錯誤提示,java.io.IOException: Packet len8854970 is out of range!,out of range就是超過了某個限制,只能查看代碼了。

protected final ByteBuffer lenBuffer = ByteBuffer.allocateDirect(4);
protected ByteBuffer incomingBuffer = lenBuffer;

protected void readLength() throws IOException {
    int len = incomingBuffer.getInt();
    if (len < 0 || len >= ClientCnxn.packetLen) {
        throw new IOException("Packet len" + len + " is out of range!");
    }
    incomingBuffer = ByteBuffer.allocate(len);
}

public static final int packetLen = Integer.getInteger("jute.maxbuffer", 4096 * 1024);

從代碼就能夠很容易的看出,這個錯誤是因爲len小於0或大於packetLen,根據代碼邏輯,len不小於0,那就是大於packetLen。而packetLen的值是jute.maxbuffer系統變量定義或默認的4096 * 1024(4M)。

繼續深扒代碼,因爲代碼比較長,這裏就不寫了。大體邏輯就是,創建與zookeeper連接之後,要對某個節點進行讀寫操作,爲了提高吞吐量,先判斷下該節點數據量大小是否超過設置的jute.maxbuffer,如果是,就拋出異常。在zookeeper客戶端中,這一部分異常的處理比較粗糙,因爲註釋上也寫着“this is ugly, you have a better way speak up”。

3 解決問題

根據上面的糾錯,答案就很明顯了。只有兩種方案:

  • 把待操作節點的大小減下來,小於默認的4M
  • 把默認的jute.maxbuffer大小提高

對於第一種方式,需要根據自身具體情況具體操作。這裏沒有什麼有效建議。

對於第二種方式,就比較簡單了。只要在創建Zookeeper對象之前,設置System.setProperty("jute.maxbuffer", 4096 * 1024 * 10 + "");,這裏的大小根據自己的系統設置,我這裏只是一個測試值(如果設置太大,這個節點真的比較大的話,會影響吞吐)。

因爲我這裏使用的是Apache Curator,不需要自己創建Zookeeper對象,所以需要在創建CuratorFramework對象之前添加這個變量。

解決問題要徹底,不能留下禍患。(此處應該伴隨陰笑和冷風。。。)

java客戶端的問題解決了,但是通過zkCli.sh連接時,還是會出現這個問題。報錯如下:

2017-02-20 12:08:03,999 [myid:] - WARN  [main-SendThread(localhost:2181):ClientCnxn$SendThread@1102] - Session 0x1591b713cefd2b3 for server localhost/127.0.0.1:2181, unexpected error, closing socket connection and attempting reconnect
java.io.IOException: Packet len8854970 is out of range!
    at org.apache.zookeeper.ClientCnxnSocket.readLength(ClientCnxnSocket.java:112)
    at org.apache.zookeeper.ClientCnxnSocketNIO.doIO(ClientCnxnSocketNIO.java:79)
    at org.apache.zookeeper.ClientCnxnSocketNIO.doTransport(ClientCnxnSocketNIO.java:366)
    at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1081)

WATCHER::

WatchedEvent state:Disconnected type:None path:null
Exception in thread "main" org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss for /it-monitor/com/wfj/monitor/sales-overview-info
    at org.apache.zookeeper.KeeperException.create(KeeperException.java:99)
    at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
    at org.apache.zookeeper.ZooKeeper.getChildren(ZooKeeper.java:1472)
    at org.apache.zookeeper.ZooKeeper.getChildren(ZooKeeper.java:1500)
    at org.apache.zookeeper.ZKUtil.listSubTreeBFS(ZKUtil.java:114)
    at org.apache.zookeeper.ZKUtil.deleteRecursive(ZKUtil.java:49)
    at org.apache.zookeeper.ZooKeeperMain.processZKCmd(ZooKeeperMain.java:703)
    at org.apache.zookeeper.ZooKeeperMain.processCmd(ZooKeeperMain.java:588)
    at org.apache.zookeeper.ZooKeeperMain.executeLine(ZooKeeperMain.java:360)
    at org.apache.zookeeper.ZooKeeperMain.run(ZooKeeperMain.java:323)
    at org.apache.zookeeper.ZooKeeperMain.main(ZooKeeperMain.java:282)

找到zkCli.sh,最下面的java命令中添加對jute.maxbuffer的定義(使用D參數):

"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \
     "-Djute.maxbuffer=41943040" \
     -cp "$CLASSPATH" $CLIENT_JVMFLAGS $JVMFLAGS \
     org.apache.zookeeper.ZooKeeperMain "$@"

當然,爲了運維方便,可以把jute.maxbuffer的值設置成變量,通過修改配置來設置值。避免因爲修改sh腳本出現其他問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章