中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

redis分布式Jedis類型轉換的異常分析

發布時間:2022-03-25 09:14:36 來源:億速云 閱讀:126 作者:iii 欄目:開發技術

這篇文章主要講解了“redis分布式Jedis類型轉換的異常分析”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“redis分布式Jedis類型轉換的異常分析”吧!

    1 類型轉換異常場景

    我們在使用Jedis的時候,經常會出現類型轉換異常,有如下情況:

    多線程環境

    Jedis是線程不安全的,如果存在多線程使用同一個Jedis,就會出現類型轉換異常網上也流傳著很多錯誤的解釋,下面我們以一個案例來復現下這個問題,這個很好理解。

    單線程環境

    即使在單線程的情況下,也是會出現類型轉換異常的,下面就針對此做一個案例分析

    2 Jedis類型轉換異常案例

    2.1 案例介紹

    案例是從這里來的Jedis returnResource使用注意事項

    代碼如下:

    public static void main(String[] args) throws Exception{
    	Jedis jedis = new Jedis("192.168.126.131", 6379);
    	System.out.println("get name=" + jedis.get("name"));
    	System.out.println("Make SocketTimeoutException");
    	System.in.read(); //等待制造SocketTimeoutException
    	try {
    	    System.out.println(jedis.get("name"));
    	} catch (Exception e) {
    	    e.printStackTrace();
    	}
    	System.out.println("Recover from SocketTimeoutException");
    	Thread.sleep(50000); // 繼續休眠一段時間 等待網絡完全恢復
    	boolean isMember = jedis.sismember("urls", "baidu");
    	System.out.println("isMember " + isMember);
    	jedis.close();
    }

    以及包含2個阻斷和解除網絡通信的命令

    阻斷網絡通信

    sudo iptables -A INPUT -p tcp --dport 6379 -j DROP

    解除網絡阻塞

    sudo iptables -F

    案例運行過程描述:

    1 創建Jedis,發送get命令,啟動與redis的連接,連接成功后獲取到響應數據

    2 程序阻塞在System.in.read(),等待輸入,此時我們需要將網絡連接阻塞,執行上述阻斷網絡命令

    3 輸入任意數據,讓程序不再阻塞,繼續走下去,執行get命令,此時由于網絡不通,導致出現SocketTimeoutException異常

    4 打印出異常,繼續往下走,sleep 50s,此時我們需要解除網絡阻塞,執行上述對應命令

    5 50s過完,就會執行jedis的sismember方法,此時就會出現類型轉換異常

    2.2 Jedis原理介紹

    Jedis內部有一個Socket與redis服務器建立連接。在創建Jedis對象的時候,并沒有去建立連接,而是在執行命令的時候才會先檢查是否已連接,未連接的話,才建立連接。

    Socket一旦連接建立,就會獲取到Socket的OutputStream,并用RedisOutputStream進行包裝,獲取到Socket的InputStream,并用RedisInputStream進行包裝。RedisOutputStream內部含有一個byte buf[]數組。

    也就是說在jedis在向OutputStream寫入命令的時候,會先寫入到上述buf數組中,然后在讀取的時候,才會flush上述數據,將數據寫入到Socket的OutputStream中,并調用flush,以Jedis的get方法為例

    public String get(final String key) {
    	checkIsInMulti();
    	client.sendCommand(Protocol.Command.GET, key);
    	return client.getBulkReply();
    }

    client.sendCommand方法會將數據寫入到RedisOutputStream內部的buf中 client.getBulkReply方法會首先執行一次flush,即將buf中數據寫入到Socket的OutputStream中,并調用Socket的OutputStream的flush。

    2.3 類型轉換異常的原因

    網上很多人說造成上述場景的類型轉換異常是因為:

    出現SocketTimeoutException異常后,RedisOutputStream的buf中殘留上次命令,沒做清理處理,導致再執行其他命令時連同之前的命令一起發送過去了。

    經過查看RedisOutputStream的源碼,buf中確實不會去主動清除原有數據,而是每次都是直接覆蓋,有count指針來標記,但是這也不會造成上述所說的影響,RedisOutputStream是OK的。

    首先我們要明白什么是SocketTimeoutException異常: 上述Jedis的Socket在發送完成數據后,就會去執行讀取數據,即讀取Socket的InputStream中的數據,并且又一定的阻塞時間,如果redis服務器遲遲不返回數據,一旦超過SO_TIMEOUT(即Socket的讀取超時時間),客戶端就會拋出一個SocketTimeoutException異常。

    造成這種異常的原因有很多:

    • 網絡閃斷(會TCP重傳):上述案例情景就是網絡斷開,數據包發送失敗,會TCP重傳

    • 網絡沒有斷,但是傳輸比較慢,或者redis服務器處理很慢

    上述原因都會造成客戶端讀取超時。一旦超時,我們的Jedis程序拋出異常,繼續往下走,如果此時再次執行其他命令的話,仍然會讀取服務器端響應,此時讀到的響應就是上次請求的響應了,所以會導致類型轉換異常。如果與上次請求的類型一致,那就更可怕了,錯誤就會被深深的掩蓋過去了。

    3 Jedis類型轉換異常的解決辦法

    上述問題就是:我們沒有正確對待這個SocketTimeoutException異常,即一旦出現SocketTimeoutException異常,我們是必須要廢棄掉這個Jedis的。所以對于單線程環境下的Jedis來說,一旦出現這種異常,我們需要重新new一個新的Jedis來使用。

    Jedis在內部執行出現異常,如SocketTimeoutException異常的時候,會標記一個boolean broken=true,即意味著該連接已經廢棄了。

    重要的大坑在這里,我們通常使用JedisPool來應對多線程環境下Jedis的使用,一般使用方式如下:

    Jedis jedis = null;//從pool中獲取資源  
    try{
    	jedis = pool.getResource();
        jedis.set("k1", "v1");  
    }catch(Exception e){  
        e.printStackTrace();
    }finally{
    	if(jedis != null){
    		pool.returnResource(jedis);//向連接池“歸還”資源,千萬不要忘記。
    	}
    }

    而對于JedisPool,我們會使用returnResource方法來向pool中釋放回Jedis,而這個returnResource卻忽視了上述boolean broken屬性,直接將一個標記廢棄的連接放回到了pool中,下次別人取的時候,必然出問題。

    所以針對JedisPool這種情況,解決辦法如下:

    1 在上述catch中捕獲SocketTimeoutException異常,調用pool的returnBrokenResource方法來釋放Jedis(該方法會將Jedis實例標記為下線,無法被他人獲取到了),但是不推薦這種,還要考慮其他異常等等

    2 另一個就是直接調用Jedis的close方法,最新版2.9.0(其他版本沒驗證)中close方法對上述boolean broken標記進行了處理,并且將returnResource標記成廢棄了,處理如下

    public void close() {
    	if (dataSource != null) {
    	  if (client.isBroken()) {
    	    this.dataSource.returnBrokenResource(this);
    	  } else {
    	    this.dataSource.returnResource(this);
    	  }
    	} else {
    	  client.close();
    	}
    }

    上述this.dataSource可以理解為JedisPool。 即一旦是broken,則調用pool的returnBrokenResource方法,否則調用pool的returnResource方法。

    所以最終寫法應該如下:

    Jedis jedis = null;//從pool中獲取資源  
    try{
    	jedis = pool.getResource();
        jedis.set("k1", "v1");  
    }finally{
    	if(jedis != null){
    		jedis.close();
    	}
    }

    4 問題深思

    可以想到2方面的問題:

    問題1:jedis為什么要暴漏這么個危險的API給用戶使用(即要求用戶自覺的close,不自覺后果自負)

    如果是我們在開發框架給被人使用,那就要盡量避免這種API的設計,把close自動隱藏在框架內部,避免了使用人員的誤使用,同時減少了代碼的復雜度,即使是上述最終的寫法也是很丑陋的,要完成一個set功能,要關注太多地方了,這部分完全可以框架底層包裝起來,只給用戶一個set方法即可。

    問題2:請求和響應的不匹配問題

    這種不匹配的問題在同步和異步的時候分別怎么處理?

    同步通信:在設計的時候,必須發送一次請求就要讀取一次響應,通過這種方式來匹配。然而在某些情況下,讀取響應有一定的超時時間,一旦超時,就拋出SocketTimeoutException異常,從而結束本次讀取,而響應可能后來又到達了,這種情況就會造成不匹配的現象。要避免這種情況,就必須要廢棄掉這個Socket了,所以如果客戶端設計成同步通信的時候,一旦遇到這種異常,則就需要廢棄了,重新建立連接了。

    異步通信:在設計的時候一般會為每個請求分配一個請求id,服務器端在處理請求后,會把這個請求id返回給客戶端,客戶端根據返回的請求id來匹配是那一次的請求對應的響應,就不會出現上述那種匹配錯亂的問題。異步通信在讀取數據的時候也通常是有數據可讀才會去執行讀操作,可以減少同步通信中因網絡擁堵或其他原因造成的SocketTimeoutException問題。異步通信好處的代價就是比同步通信復雜。

    所以如果我們在設計的時候,就需要去考慮這樣的問題,避免造出一個大坑來。

    感謝各位的閱讀,以上就是“redis分布式Jedis類型轉換的異常分析”的內容了,經過本文的學習后,相信大家對redis分布式Jedis類型轉換的異常分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    台山市| 镇坪县| 土默特右旗| 宁波市| 云安县| 敖汉旗| 连云港市| 法库县| 常州市| 田林县| 太原市| 阿鲁科尔沁旗| 京山县| 嫩江县| 沿河| 巴东县| 聂荣县| 穆棱市| 遂平县| 盐山县| 团风县| 丹寨县| 台南市| 承德市| 平顺县| 吉林省| 昆山市| 镇宁| 安康市| 凭祥市| 澄江县| 惠来县| 夏津县| 明光市| 满洲里市| 广宗县| 陕西省| 忻城县| 吉林省| 蒙自县| 康平县|