轉載請注明原創出處,謝謝!
GreenMountains
http://www.lxweimin.com/u/2a14d4dd5ba4
情景復現
當jedis的連接池不夠,或者網絡抖動請求redis超時,出現JedisConnectionException,會導致NullPointerException、ClassCastException等一些靈異異常
示例代碼:
ShardedJedisPool jedisPool = JedisUtils.getJedisPool();
ShardedJedis shardedJedis = jedisPool.getResource();
shardedJedis.setex("key",60,"value");
System.out.println(shardedJedis.get("key"));
System.out.println("Make SocketTimeoutException. cmd: sudo iptables -A INPUT -p tcp --dport 6379 -j DROP ");
System.in.read();
try{
System.out.println(shardedJedis.get("hi"));
}catch(JedisConnectionException e){
e.printStackTrace();
}
System.out.println("Recover from SocketTimeoutException. cmd: sudo iptables -F ");
System.in.read();
System.out.println(shardedJedis.get("key"));
System.out.println(shardedJedis.get("key"));
System.out.println(shardedJedis.get("hi"));
最后的返回值分別為:null,value,value
創建一個Socket套接字實例,操作系統就會為其分配緩沖區以存放接收和要發送的數據。JAVA可以設置讀寫緩沖區的大小,Socket類setReceiveBufferSize(int size)、setSendBufferSize(int size)
向輸出流寫數據并不意味著數據實際上已經被發送,它們只是被復制到發送緩沖區隊列SendQ,就是在Socket的OutputStream上調用flush()方法,也不能保證數據能夠立即發送到網絡。真正的數據發送是由操作系統的TCP協議棧模塊從緩沖區中取數據發送到網絡來完成的
當有數據從網絡來到時,TCP協議棧模塊接收數據并放入接收緩沖區隊列RecvQ,輸入流InputStream通過read方法從RecvQ中取出數據
jedis與redis-server的通信主要是通過對RedisInputStream和RedisOutputStream的讀寫操作來完成
jedis調用Protocol類的sendCommand方法,發送命令字節流到RedisOutputStream。獲取數據時,調用Connection類的getBinaryBulkReply方法,先進行flush,將RedisOutputStream里的命令復制到環形緩沖區SendQ等待發送,之后RedisInputStream復制環形緩沖區RecvQ數據,解析字節流獲取redis數據
當jedis連接超時,flush方法會繼續write命令到緩沖區,直到SendQ隊列填滿。SendQ保留了斷線超時時間段的所有命令。當連接恢復后,SendQ發送命令請求數據,RedisInputStream獲取到之前所有超時的命令數據,并將超時的錯誤數據返回給當前jedis調用
比如共發送6條命令,前1、2條命令超時,當第3條命令時恢復連接,則3獲取到1的數據,4獲取到2的數據,5獲取到3的數據,6獲取到4的數據。超時導致數據竄位,獲取到臟數據
當出現JedisConnectionException,為了避免RedisInputStream緩沖區的臟數據,不應該使用broken的連接,而是需要return回連接池,然后remove掉broken連接
try{
System.out.println(shardedJedis.get("hi"));
}catch(JedisConnectionException e){
e.printStackTrace();
}finally{
shardedJedis.close();
}
只要最后finally里close即可,官方支持的,媽媽再也不用擔心我的學習。close方法有個broken的標志位,會循環去回收異常的connection。
總結:異常時returnBrokenResource,正常時returnResource即可。兩者只能執行一個!
為什么returnBrokenResource就能解決上面的問題呢?
原因:1.returnBrokenResource把jedispoll里面是當前異常連接remove掉了
2.returnBrokenResource 把等待隊列的異常連接remove掉了。
3.returnBrokenResource ?會把之前的socket連接關閉。即客戶端發起關閉FIN請求。開始執行socket斷開四次握手。(為了關閉客戶端socket,和服務器端socket端斷開,減少服務端資源開銷。)
4.如果網絡是持續斷開,那么這個FIN到不了服務端,則服務端的socket將繼續打開。(TCP連接稱為半打開)。
若連接池配置了testOnBorrow=true,每次取jedis時,都會測試jedis.isConnected和ping一下服務端,但這樣會造成redis的壓力,testOnBorrow和testOnReturn在生產環境一般是不開啟的,主要是性能考慮。失效連接主要通過testWhileIdle保證,如果獲取到了不可用的數據庫連接,一般由應用處理異常
參考配置:
jedis對connection的test源碼:
jedis.isConnected() && jedis.ping().equals("PONG")