日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

一次线上Redis类转换异常排查引发的思考

發布時間:2024/4/15 数据库 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一次线上Redis类转换异常排查引发的思考 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

之前同事反饋說線上遇到Redis反序列化異常問題,異常如下:

XxxClass1 cannot be cast to XxxClass2

已知信息如下:

  • 該異常不是必現的,偶爾才會出現;
  • 出現該異常后重啟應用或者過一會就好了;
  • 序列化協議使用了hessian。

因為偶爾出現,首先看了報異常那塊業務邏輯是不是有問題,看了一遍也發現什么問題??戳讼聦罩?#xff0c;發現是在Redis讀超時之后才出現的該異常,因此懷疑redis client操作邏輯那塊導致的(公司架構組對redis做了一層封裝),發現獲取/釋放redis連接如下代碼:

1 try { 2 jedis = jedisPool.getResource(); 3 // jedis業務讀寫操作 4 } catch (Exception e) { 5 // 異常處理 6 } finally { 7 if (jedis != null) { 8 // 歸還給連接池 9 jedisPool.returnResourceObject(jedis); 10 } 11 }

初步認定原因為:發生了讀寫超時的連接,直接歸還給連接池,下次使用該連接時讀取到了上一次Redis返回的數據。因此本地驗證下,示例代碼如下:

1 @Data 2 @NoArgsConstructor 3 @AllArgsConstructor 4 static class Person implements Serializable { 5 private String name; 6 private int age; 7 } 8 @Data 9 @NoArgsConstructor 10 @AllArgsConstructor 11 static class Dog implements Serializable { 12 private String name; 13 } 14 15 public static void main(String[] args) throws Exception { 16 JedisPoolConfig config = new JedisPoolConfig(); 17 config.setMaxTotal(1); 18 JedisPool jedisPool = new JedisPool(config, "192.168.193.133", 6379, 2000, "123456"); 19 20 Jedis jedis = jedisPool.getResource(); 21 jedis.set("key1".getBytes(), serialize(new Person("luoxn28", 26))); 22 jedis.set("key2".getBytes(), serialize(new Dog("tom"))); 23 jedisPool.returnResourceObject(jedis); 24 25 try { 26 jedis = jedisPool.getResource(); 27 Person person = deserialize(jedis.get("key1".getBytes()), Person.class); 28 System.out.println(person); 29 } catch (Exception e) { 30 // 發生了異常之后,未對該連接做任何處理 31 System.out.println(e.getMessage()); 32 } finally { 33 if (jedis != null) { 34 jedisPool.returnResourceObject(jedis); 35 } 36 } 37 38 try { 39 jedis = jedisPool.getResource(); 40 Dog dog = deserialize(jedis.get("key2".getBytes()), Dog.class); 41 System.out.println(dog); 42 } catch (Exception e) { 43 System.out.println(e.getMessage()); 44 } finally { 45 if (jedis != null) { 46 jedisPool.returnResourceObject(jedis); 47 } 48 } 49 }

連接超時時間設置2000ms,為了方便測試,可以在redis服務器上使用gdb命令斷住redis進程(如果redis部署在Linux系統上的話,還可以使用iptable命令在防火墻禁止某個回包),比如在執行?jedis.get("key1".getBytes()?代碼前,對redis進程使用gdb命令斷住,那么就會導致讀取超時,然后就會觸發如下異常:

Person cannot be cast to Dog

既然已經知道了該問題原因并且本地復現了該問題,對應解決方案是,在發生異常時歸還給連接池時關閉該連接即可(jedis.close內部已經做了判斷),代碼如下:

1 try { 2 jedis = jedisPool.getResource(); 3 // jedis業務讀寫操作 4 } catch (Exception e) { 5 // 異常處理 6 } finally { 7 if (jedis != null) { 8 // 歸還給連接池 9 jedis.close(); 10 } 11 }

至此,該問題解決。注意,因為使用了hessian序列化(其包含了類型信息,類似的有Java本身序列化機制),所有會報類轉換異常;如果使用了json序列化(其只包含對象屬性信息),反序列化時不會報異常,只不過因為不同類的屬性不同,會導致反序列化后的對象屬性為空或者屬性值混亂,使用時會導致問題,并且這種問題因為沒有報異常所以更不容易發現。

?

既然說到了Redis的連接,要知道的是,Redis基于RESP(Redis Serialization Protocol)協議來通信,并且通信方式是停等方式,也就說一次通信獨占一個連接直到client讀取到返回結果之后才能釋放該連接讓其他線程使用。小伙伴們可以思考一下,Redis通信能否像dubbo那樣使用單連接+序列號(標識單次通信)通信方式呢?理論上是可以的,不過由于RESP協議中并沒有一個"序列號"的字段,所以直接靠原生的通信方法來實現是不現實的。不過我們可以通過echo命令傳遞并返回"序列號"+正常的讀寫方式來實現,這里要保證二者執行的原子性,可以通過lua腳本或者事務來實現,事務方式如下:

MULTI ECHO "唯一序列號" GET key1 EXEC

然后客戶端收到的結果是一個?[ "唯一序列號", "value1" ]的列表,你可以根據前一項識別出這是你發送的哪個請求。

為什么Redis通信方式并沒有采用類似于dubbo這種通信方式呢,個人認為有以下幾點:

  • 使用停等這種通信方式實現簡單,并且協議字段盡可能緊湊;
  • Redis都是內存操作,處理性能較強,停等協議不會造成客戶端等待時間較長;
  • 目前來看,通信方式這塊不是Redis使用上的性能瓶頸,這一點很重要。

?

推薦閱讀:

  • 別再問我ConcurrentHashMap了
  • 分布式鎖設計與實現

  • ConcurrentHashMap竟然也有死循環問題?

  • 你的ThreadLocal線程安全么

?歡迎小伙伴掃描以下二維碼精彩好文。

?

轉載于:https://www.cnblogs.com/luoxn28/p/11075958.html

總結

以上是生活随笔為你收集整理的一次线上Redis类转换异常排查引发的思考的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。