爲了同窗們看起來一目了,特按以下思路進行講解。java
1.出現的場景緩存
2.分析及解決的過程安全
3.總結session
最近公司要使用zookeeper作配置管理(後面簡稱ZK),而後本身就提早用虛擬機進行了ZK三臺集羣的搭建。以後開始選擇使用zookeeper的java client工具,google了半天,發現了一個很名強大的Apache的Curator工具,不少底層的東西都已經給你封裝好了,因此用起來很方便,由於我使用的場景是作配置管理,因此使用Curator的Framework就夠了。Curator相對於zookeeper,就至關於Guava之於Java.ide
由於天天的訪問量上億級的,因此考慮的因素仍是不少,所以從網上找了一些demo,而後本身就開始寫一些測試的類,下邊的這個方法是用於獲取客戶端,而且加入了一些監聽和輸出:工具
private static CuratorFramework getClient(String namespace) throws Exception{ ACLProvider aclProvider = new ACLProvider() { private List<ACL> acl ; @Override public List<ACL> getDefaultAcl() { if(acl ==null){ ArrayList<ACL> acl = ZooDefs.Ids.CREATOR_ALL_ACL; acl.clear(); acl.add(new ACL(Perms.ALL, new Id("auth", "admin:admin") )); this.acl = acl; } return acl; } @Override public List<ACL> getAclForPath(String path) { return acl; } }; String scheme = "digest"; byte[] auth = "admin:admin".getBytes(); int connectionTimeoutMs = 1000; String connectString = "127.0.0.1:2181"; CuratorFramework client = CuratorFrameworkFactory.builder(). aclProvider(aclProvider). authorization(scheme, auth). connectionTimeoutMs(1). connectString(connectString).sessionTimeoutMs(50). namespace(namespace). retryPolicy(new RetryOneTime(1)).build(); client.getConnectionStateListenable().addListener(new ConnectionStateListener() { @Override public void stateChanged(CuratorFramework client, ConnectionState newState) { System.out.println("** STATE CHANGED TO : " + newState); } }); client.start(); client.getZookeeperClient().internalBlockUntilConnectedOrTimedOut(); // client.getZookeeperClient().blockUntilConnectedOrTimedOut(); System.out.println(client.getZookeeperClient().isConnected()); System.out.println(client.getState()); return client; }
獲取客戶端以後就能夠啓動(client.start())客戶端而且建立至關的Node以及它的payload. 感受寫的已經能夠了,並且通過簡單的測試,以爲能夠了,而後就上到測試環境上了,測試環境的訪問量並非很大,因此也沒有什麼特別異常,以後就放到線上了。性能
當把程序放到線上去以後,系統的JVM監控系統就開始報警,線程數由幾百迅速增長到了三、4千個,直接超過了咱們設置的報警閾值,因此感受使用jstack命令 jstack -l pid > threadDump,找一個 stack analyzer online的一個網站 fastthread.io, upload作好的threadDump文件,上邊有不少彙總,而後基本上一目瞭然:學習
1700多個TIMED_WATING,還有1700多個TIMED_WATING,這裏邊確定有問題,而後繼續往下拉,會按線程分組進行展現:測試
會發現有大概有77%的線程和Curator有關係,這個應該就是它的問題了,那麼點開裏邊的內容,就能看到線程的明細了,繼續:優化
裏邊有Curator Framework的代碼了,找到至關的行907,發現只要Client一啓動的話就會使得BlockingQueue會有一個take()的動做,這個take的含義是將head取到,若是沒有的話就等待,這就是線程WAITING的狀態,而後繼續看是在什麼地方調用的它。
找到了,原來是客戶端啓動(client.start())的時候進行的調用,由於我在網上看到不少地方說build模式拿到的Client是線程安全的,因此我就每次拿一次client,而後調用其start()。這樣每一個不一樣的線程就會都等待在那個位置上。我沒有在Finally調用 CloseableUtils.closeQuietly(client); 由於請求量太大,若是頻繁的調用關閉客戶端會形成性能降低,必須保持一個長鏈接。
打開Curator的官網上,裏邊也進行了說明,建立採用build的方式是線程安全的,可是要保持單例。
這樣問題找到了,下邊開始想着若是修復和優化,首先讓它實現單例,同時還不能用完以後就直接關閉。同時要保持長鏈接,在特定狀況下進行鏈接關閉,那就若是出現異常爲
KeeperException.ConnectionLossException時須要捕獲而且進行計數和關閉。同時也爲了效率考慮,再獲取Node的payload時將payload進行緩存,這樣再次減小了對zk的大量訪問。同時能夠根據本身的實際狀況去考慮緩存的時間。
if(client == null || client.getState().equals(CuratorFrameworkState.STOPPED) || !client.getNamespace().equals(namespace)) { synchronized (ZookeeperUtil.class) { if(client == null || client.getState().equals(CuratorFrameworkState.STOPPED) || !client.getNamespace().equals(namespace)) {
CloseableUtils.closeQuietly(client); client = getClient(namespace); } } }
同時在網上找到zookeeper集羣上從3.4開始,從客戶端鏈接數maxClientCnxns(配置在zoo.cfg)默認鏈接數爲60,改成0時不限制。
總結:
1. 當遇到線程數增長或CPU太高時須要使用jstack將JVM的線程數據導出到文件,而後經過在線工具或本身下載的工具進行分析,我仍是比較喜歡這個在線的分析工具,它能分析出總的線程數中按狀態進行分析,還能夠按線程類型進行分組,很強大。
2. 遇到問題要冷靜思考,而後多寫幾個小的demo進行測試。我其實在寫這個問題的過程當中我是寫了測試類進行模擬的,而後經過本機的jvisualvm查看棧的狀況,根我推斷的一致的,因此就會找到解決的方法。
3.有些技術知識仍是從官方網站學習,並且若是看書的話,須要從頭看到尾,這樣的話基本上能瞭解事務的所有內容,不然只看到部份內容。
若是有寫的不對的地方,歡迎同窗們來拍磚~