網站首頁 編程語言 正文
RedisAtomicInteger計數出現少計
最近工作中遇到了這樣一個場景
同一個外部單號生成了多張出庫單,等待所有相關的出庫單都出庫成功后回復成功消息外部系統調用方。因為是分布式布系統,我使用了RedisAtomicInteger計數器來判斷出庫單是否全部完成,數量達成時回復成功消息給外部系統調用方。
在本地測試和測試環境測試時都沒有發現問題,到了生產環境后,發現偶爾出現所有出庫單都已經出庫,但沒有回復消息給調用方,如:出庫單15張,但計數器只有14。
分析
開始以為是有單據漏計算了,通過日志分析,發現所有的出庫單都統計進去了。
然后通過增加打開調試日志,發現最開始的2張出庫單統計后的值都為1,少了1個。
原因
redis的increment是原子性,但new RedisAtomicInteger時會調用set方法來設置初始值,set方法是可以被后面的方法覆蓋的。
edisAtomicInteger redisAtomicInt = new RedisAtomicInteger(countKey, redisTemplate.getConnectionFactory());
??
// spring-data-redis-1.8.13原碼
public RedisAtomicInteger(String redisCounter, RedisConnectionFactory factory) {
?? ??? ?this(redisCounter, factory, null);
?? ?}
??
private RedisAtomicInteger(String redisCounter, RedisConnectionFactory factory, Integer initialValue) {
?? ??? ?RedisTemplate<String, Integer> redisTemplate = new RedisTemplate<String, Integer>();
?? ??? ?redisTemplate.setKeySerializer(new StringRedisSerializer());
?? ??? ?redisTemplate.setValueSerializer(new GenericToStringSerializer<Integer>(Integer.class));
?? ??? ?redisTemplate.setExposeConnection(true);
?? ??? ?redisTemplate.setConnectionFactory(factory);
?? ??? ?redisTemplate.afterPropertiesSet();
?
?? ??? ?this.key = redisCounter;
?? ??? ?this.generalOps = redisTemplate;
?? ??? ?this.operations = generalOps.opsForValue();
?
?? ??? ?if (initialValue == null) {
?? ??? ??? ?if (this.operations.get(redisCounter) == null) {
?? ??? ??? ??? ?set(0);
?? ??? ??? ?}
?? ??? ?} else {
?? ??? ??? ?set(initialValue);
?? ??? ?}
?? ?}
解決方法
網上看到的都是加業務鎖或升級spring-data-redis版本。
但老項目升級spring-data-redis版本可能會引起兼容性問題,加業務鎖又增加了代碼復雜度。
那有沒有更簡單方法呢,有。竟然是set方法導致的值覆蓋,那就不走set方法就可以了。
增加下面一行代碼解決問題
// Fixed bug 前幾個數累計重復問題
redisTemplate.opsForValue().setIfAbsent(countKey, 0);
使用RedisAtomicInteger中間遇到的問題
RedisAtomicInteger是springdata中在redis的基礎上實現的原子計數器,在以下maven依賴包中:
<groupId>org.springframework.data</groupId> ? ?? <artifactId>spring-data-redis</artifactId>?
當使用RedisAtomicInteger(String redisCounter, RedisOperations<String, Integer> template,...)函數構建實例的情況下,在使用INCR或者DECR時,會遇到ERR value is not an integer or out of range錯誤,顯示操作的數據不是一個整數或者超出范圍。
參考redis命令說明我們知道incr對操作值的要求
這是一個針對字符串的操作,因為 Redis 沒有專用的整數類型,所以 key 內儲存的字符串被解釋為十進制 64 位有符號整數來執行 INCR 操作。如果值包含錯誤的類型,或字符串類型的值不能表示為數字,那么返回一個錯誤。
從redis實例中查看該key的value,會發現結果類似這樣:
"\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x01"
原因在于value使用的序列化方式是JdkSerializationRedisSerializer,這和INCR命令對結果的要求是違背的。
該使用哪種序列化方式把value放進去呢?按照INCR命令對結果的要求,最容易想到StringRedisSerializer,但經過嘗試這也行不通
會報java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String。
如果看過RedisAtomicInteger的源碼,在private RedisAtomicInteger(String redisCounter, RedisConnectionFactory factory, Integer initialValue)中會發現方法內部創建了RedisTemplate實例,對value設置的序列化方式是GenericToStringSerializer。
該序列化內部使用spring core包下的
org.springframework.core.convert.support.DefaultConversionService作為默認的對象和字符串的轉換方式,主要為了滿足大多數環境的要求。
至此,我們終于知道了錯誤的根本原因,構造RedisAtomicInteger時傳入的redisTemplate是有問題的,value的默認序列化方式不滿足RedisAtomicInteger的需要。那么問題也迎刃而解,將GenericToStringSerializer作為redisTemplate的value序列化方式。
這樣雖然解決了問題,但很麻煩,很可能為了RedisAtomicInteger的要求需要再創建一個redisTemplate,簡直不能忍受。再看RedisAtomicInteger的源碼,發現構造函數除了可以用redisTemplate,還可以用RedisConnectionFactory,嘗試之后,完美解決。
原文鏈接:https://blog.csdn.net/ckg529456/article/details/125161150
相關推薦
- 2022-09-13 Linux中一對多配置日志服務器的詳細步驟_Linux
- 2021-12-09 Ubuntu環境安裝Anaconda3完整步驟_Linux
- 2024-04-08 Stack.peek()與Stack.pop() 以及 poll與pop
- 2022-04-21 flutter實現一個列表下拉抽屜的示例代碼_Android
- 2022-04-17 小程序中元素滾動到元素的底部, 一直保持在底部
- 2022-03-27 關于Android?Device?Monitor?無法打開問題_Android
- 2023-07-24 navigator里包含tabBar內容時失效
- 2022-07-26 FastDFS-支持雙IP、IPV6
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支