網(wǎng)站首頁 編程語言 正文
1 類型轉(zhuǎn)換異常場景
我們在使用Jedis的時候,經(jīng)常會出現(xiàn)類型轉(zhuǎn)換異常,有如下情況:
多線程環(huán)境
Jedis是線程不安全的,如果存在多線程使用同一個Jedis,就會出現(xiàn)類型轉(zhuǎn)換異常網(wǎng)上也流傳著很多錯誤的解釋,下面我們以一個案例來復現(xiàn)下這個問題,這個很好理解。
單線程環(huán)境
即使在單線程的情況下,也是會出現(xiàn)類型轉(zhuǎn)換異常的,下面就針對此做一個案例分析
2 Jedis類型轉(zhuǎn)換異常案例
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); // 繼續(xù)休眠一段時間 等待網(wǎng)絡(luò)完全恢復 boolean isMember = jedis.sismember("urls", "baidu"); System.out.println("isMember " + isMember); jedis.close(); }
以及包含2個阻斷和解除網(wǎng)絡(luò)通信的命令
阻斷網(wǎng)絡(luò)通信
sudo iptables -A INPUT -p tcp --dport 6379 -j DROP
解除網(wǎng)絡(luò)阻塞
sudo iptables -F
案例運行過程描述:
- 1 創(chuàng)建Jedis,發(fā)送get命令,啟動與redis的連接,連接成功后獲取到響應數(shù)據(jù)
- 2 程序阻塞在System.in.read(),等待輸入,此時我們需要將網(wǎng)絡(luò)連接阻塞,執(zhí)行上述阻斷網(wǎng)絡(luò)命令
- 3 輸入任意數(shù)據(jù),讓程序不再阻塞,繼續(xù)走下去,執(zhí)行g(shù)et命令,此時由于網(wǎng)絡(luò)不通,導致出現(xiàn)SocketTimeoutException異常
- 4 打印出異常,繼續(xù)往下走,sleep 50s,此時我們需要解除網(wǎng)絡(luò)阻塞,執(zhí)行上述對應命令
- 5 50s過完,就會執(zhí)行jedis的sismember方法,此時就會出現(xiàn)類型轉(zhuǎn)換異常
2.2 Jedis原理介紹
Jedis內(nèi)部有一個Socket與redis服務(wù)器建立連接。在創(chuàng)建Jedis對象的時候,并沒有去建立連接,而是在執(zhí)行命令的時候才會先檢查是否已連接,未連接的話,才建立連接。
Socket一旦連接建立,就會獲取到Socket的OutputStream,并用RedisOutputStream進行包裝,獲取到Socket的InputStream,并用RedisInputStream進行包裝。RedisOutputStream內(nèi)部含有一個byte buf[]數(shù)組。
也就是說在jedis在向OutputStream寫入命令的時候,會先寫入到上述buf數(shù)組中,然后在讀取的時候,才會flush上述數(shù)據(jù),將數(shù)據(jù)寫入到Socket的OutputStream中,并調(diào)用flush,以Jedis的get方法為例
public String get(final String key) { checkIsInMulti(); client.sendCommand(Protocol.Command.GET, key); return client.getBulkReply(); }
client.sendCommand方法會將數(shù)據(jù)寫入到RedisOutputStream內(nèi)部的buf中 client.getBulkReply方法會首先執(zhí)行一次flush,即將buf中數(shù)據(jù)寫入到Socket的OutputStream中,并調(diào)用Socket的OutputStream的flush。
2.3 類型轉(zhuǎn)換異常的原因
網(wǎng)上很多人說造成上述場景的類型轉(zhuǎn)換異常是因為:
出現(xiàn)SocketTimeoutException異常后,RedisOutputStream的buf中殘留上次命令,沒做清理處理,導致再執(zhí)行其他命令時連同之前的命令一起發(fā)送過去了。
經(jīng)過查看RedisOutputStream的源碼,buf中確實不會去主動清除原有數(shù)據(jù),而是每次都是直接覆蓋,有count指針來標記,但是這也不會造成上述所說的影響,RedisOutputStream是OK的。
首先我們要明白什么是SocketTimeoutException異常: 上述Jedis的Socket在發(fā)送完成數(shù)據(jù)后,就會去執(zhí)行讀取數(shù)據(jù),即讀取Socket的InputStream中的數(shù)據(jù),并且又一定的阻塞時間,如果redis服務(wù)器遲遲不返回數(shù)據(jù),一旦超過SO_TIMEOUT(即Socket的讀取超時時間),客戶端就會拋出一個SocketTimeoutException異常。
造成這種異常的原因有很多:
- 網(wǎng)絡(luò)閃斷(會TCP重傳):上述案例情景就是網(wǎng)絡(luò)斷開,數(shù)據(jù)包發(fā)送失敗,會TCP重傳
- 網(wǎng)絡(luò)沒有斷,但是傳輸比較慢,或者redis服務(wù)器處理很慢
上述原因都會造成客戶端讀取超時。一旦超時,我們的Jedis程序拋出異常,繼續(xù)往下走,如果此時再次執(zhí)行其他命令的話,仍然會讀取服務(wù)器端響應,此時讀到的響應就是上次請求的響應了,所以會導致類型轉(zhuǎn)換異常。如果與上次請求的類型一致,那就更可怕了,錯誤就會被深深的掩蓋過去了。
3 Jedis類型轉(zhuǎn)換異常的解決辦法
上述問題就是:我們沒有正確對待這個SocketTimeoutException異常,即一旦出現(xiàn)SocketTimeoutException異常,我們是必須要廢棄掉這個Jedis的。所以對于單線程環(huán)境下的Jedis來說,一旦出現(xiàn)這種異常,我們需要重新new一個新的Jedis來使用。
Jedis在內(nèi)部執(zhí)行出現(xiàn)異常,如SocketTimeoutException異常的時候,會標記一個boolean broken=true,即意味著該連接已經(jīng)廢棄了。
重要的大坑在這里,我們通常使用JedisPool來應對多線程環(huán)境下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異常,調(diào)用pool的returnBrokenResource方法來釋放Jedis(該方法會將Jedis實例標記為下線,無法被他人獲取到了),但是不推薦這種,還要考慮其他異常等等
2 另一個就是直接調(diào)用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,則調(diào)用pool的returnBrokenResource方法,否則調(diào)用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,不自覺后果自負
如果是我們在開發(fā)框架給被人使用,那就要盡量避免這種API的設(shè)計,把close自動隱藏在框架內(nèi)部,避免了使用人員的誤使用,同時減少了代碼的復雜度,即使是上述最終的寫法也是很丑陋的,要完成一個set功能,要關(guān)注太多地方了,這部分完全可以框架底層包裝起來,只給用戶一個set方法即可。
問題2:請求和響應的不匹配問題
這種不匹配的問題在同步和異步的時候分別怎么處理?
同步通信:
在設(shè)計的時候,必須發(fā)送一次請求就要讀取一次響應,通過這種方式來匹配。然而在某些情況下,讀取響應有一定的超時時間,一旦超時,就拋出SocketTimeoutException異常,從而結(jié)束本次讀取,而響應可能后來又到達了,這種情況就會造成不匹配的現(xiàn)象。要避免這種情況,就必須要廢棄掉這個Socket了,所以如果客戶端設(shè)計成同步通信的時候,一旦遇到這種異常,則就需要廢棄了,重新建立連接了。
異步通信:
在設(shè)計的時候一般會為每個請求分配一個請求id,服務(wù)器端在處理請求后,會把這個請求id返回給客戶端,客戶端根據(jù)返回的請求id來匹配是那一次的請求對應的響應,就不會出現(xiàn)上述那種匹配錯亂的問題。異步通信在讀取數(shù)據(jù)的時候也通常是有數(shù)據(jù)可讀才會去執(zhí)行讀操作,可以減少同步通信中因網(wǎng)絡(luò)擁堵或其他原因造成的SocketTimeoutException問題。異步通信好處的代價就是比同步通信復雜。
所以如果我們在設(shè)計的時候,就需要去考慮這樣的問題,避免造出一個大坑來。
原文鏈接:https://my.oschina.net/pingpangkuangmo/blog/737122
相關(guān)推薦
- 2023-04-24 Python?語法錯誤:"SyntaxError:?invalid?character?in?ide
- 2022-09-17 Python高效處理大文件的方法詳解_python
- 2022-09-03 .NET使用System.Timers.Timer類實現(xiàn)程序定時執(zhí)行_實用技巧
- 2023-01-21 Python參數(shù)解析器configparser簡介_python
- 2022-04-03 Flutter折疊控件使用方法詳解_Android
- 2024-03-24 required a single bean, but 2 were found
- 2021-12-15 C語言數(shù)據(jù)結(jié)構(gòu)與算法之圖的遍歷(二)_C 語言
- 2022-07-18 IDEA如何快速切換項目(快捷鍵切換)
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支