<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
我們在使用Jedis的時候,經常會出現型別轉換異常,有如下情況:
Jedis是執行緒不安全的,如果存在多執行緒使用同一個Jedis,就會出現型別轉換異常網上也流傳著很多錯誤的解釋,下面我們以一個案例來複現下這個問題,這個很好理解。
即使在單執行緒的情況下,也是會出現型別轉換異常的,下面就針對此做一個案例分析
案例是從這裡來的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
案例執行過程描述:
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。
網上很多人說造成上述場景的型別轉換異常是因為:
出現SocketTimeoutException異常後,RedisOutputStream的buf中殘留上次命令,沒做清理處理,導致再執行其他命令時連同之前的命令一起傳送過去了。
經過檢視RedisOutputStream的原始碼,buf中確實不會去主動清除原有資料,而是每次都是直接覆蓋,有count指標來標記,但是這也不會造成上述所說的影響,RedisOutputStream是OK的。
首先我們要明白什麼是SocketTimeoutException異常: 上述Jedis的Socket在傳送完成資料後,就會去執行讀取資料,即讀取Socket的InputStream中的資料,並且又一定的阻塞時間,如果redis伺服器遲遲不返回資料,一旦超過SO_TIMEOUT(即Socket的讀取超時時間),使用者端就會丟擲一個SocketTimeoutException異常。
造成這種異常的原因有很多:
上述原因都會造成使用者端讀取超時。一旦超時,我們的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(); } }
可以想到2方面的問題:
即要求使用者自覺的close,不自覺後果自負
如果是我們在開發框架給被人使用,那就要儘量避免這種API的設計,把close自動隱藏在框架內部,避免了使用人員的誤使用,同時減少了程式碼的複雜度,即使是上述最終的寫法也是很醜陋的,要完成一個set功能,要關注太多地方了,這部分完全可以框架底層包裝起來,只給使用者一個set方法即可。
這種不匹配的問題在同步和非同步的時候分別怎麼處理?
在設計的時候,必須傳送一次請求就要讀取一次響應,通過這種方式來匹配。然而在某些情況下,讀取響應有一定的超時時間,一旦超時,就丟擲SocketTimeoutException異常,從而結束本次讀取,而響應可能後來又到達了,這種情況就會造成不匹配的現象。要避免這種情況,就必須要廢棄掉這個Socket了,所以如果使用者端設計成同步通訊的時候,一旦遇到這種異常,則就需要廢棄了,重新建立連線了。
在設計的時候一般會為每個請求分配一個請求id,伺服器端在處理請求後,會把這個請求id返回給使用者端,使用者端根據返回的請求id來匹配是那一次的請求對應的響應,就不會出現上述那種匹配錯亂的問題。非同步通訊在讀取資料的時候也通常是有資料可讀才會去執行讀操作,可以減少同步通訊中因網路擁堵或其他原因造成的SocketTimeoutException問題。非同步通訊好處的代價就是比同步通訊複雜。
所以如果我們在設計的時候,就需要去考慮這樣的問題,避免造出一個大坑來。
以上就是redis分散式Jedis型別轉換的異常深入研究的詳細內容,更多關於redis分散式Jedis型別轉換異常的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45