大白話講解調用Redis的increment失敗原因及推薦使用詳解
大家在項目中基本都會接觸到redis,在spring-data-redis-2.*.*.RELEASE.jar中提供了兩個Helper class,可以讓我們更方便的操作redis中存儲的數據。這兩個Helper class分別是RedisTemplate和StringRedisTemplate,其中StringRedisTemplate是RedisTemplate在存儲String類型的時候的一個擴展子類。所以大家在使用redis的時候:
1、如果操作的是String類型,優(yōu)先考慮用StringRedisTemplate;
2、如果是復雜對象類型,則有限考慮RedisTemplate。
如果大家在使用redis來進行計數場景,比如記錄調用次數、記錄接口的調用閾值等,用RedisTemplate出現了以下錯誤ERR value is not an integer or out of range,那么先說下解決方案,如下:
//類中直接注入StringRedisTemplate
@Autowired
private StringRedisTemplate stringRedisTemplate;
//方法體中用以下方式進行計數
stringRedisTemplate.opsForValue().increment("check:incr:str");
即用StringRedisTemplate代替RedisTemplate。
出現上面的原因,就是因為序列化導致的。我們知道數據是以二進制形式存儲在redis的,那么就必然涉及到序列化和反向序列化,上面提到的這兩個Helper class就可以自動的幫我們實現序列化和反向序列,其中二者的主要區(qū)別就是序列化機制,
1、StringRedisTemplate的序列化機制是通過StringRedisSerializer來實現的;
2、RedisTemplate的序列化機制是通過JdkSerializationRedisSerializer來實現的。
increment操作底層就是讀取數據,然后+1,然后set,只不過這三個步驟被redis加了原子操作保證,所以我們從StringRedisTemplate和RedisTemplate的set方法來分析。先看StringRedisTemplate的源碼如下:
1、stringRedisTemplate.opsForValue().set("check:incr:str", "1");
2、查看第一步的set方法,進入DefaultValueOperations類的set方法
@Override
public void set(K key, V value) {
byte[] rawValue = rawValue(value);
execute(new ValueDeserializingRedisCallback(key) {
@Override
protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
connection.set(rawKey, rawValue);
return null;
}
}, true);
}
3、查看第二步的處理value的rawValue方法,進入AbstractOperations.rawValue方法
@SuppressWarnings("unchecked")
byte[] rawValue(Object value) {
if (valueSerializer() == null && value instanceof byte[]) {
return (byte[]) value;
}
return valueSerializer().serialize(value);
}
4、看到第三步最終調用了序列化類對value做了序列化處理,我們知道StringRedisTemplate的序列化類StringRedisSerializer,
可知第三步的最后一行serialize最后調用了如下方法
@Override
public byte[] serialize(@Nullable String string) {
return (string == null ? null : string.getBytes(charset));
}
5、到此我們知道了,value最后存在redis的最底層原理就是第四步的return返回的byte[]。那么就可以這樣認為,如果調用了
stringRedisTemplate.opsForValue().set("check:incr:str", "1");就好比是在redis存儲了"1".getBytes("UTF-8")
public static void main(String[] args) throws UnsupportedEncodingException {
byte[] str = "1".getBytes("UTF-8");
for(int i = 0 ; i< str.length; i++) {
System.out.println(str[i]);
}
}
結論:
上面main方法打印出來了49,了解了set方法后,可以猜測調用increment時相當于對49直接加1,變成50,get數據時再反向序列化可知對應是數字2
(注意標綠的是我的猜測,可以比較容易理解為什么可以直接increment,事實是否這樣需要看redis源碼,若有同學確認了,可以回復下我),
所以可以理解為什么StringRedisTemplate支持increment。
(注意這里并不是說RedisTemplate不支持,而是說RedisTemplate的默認配置的序列化實現機制,會導致RedisTemplate不支持increment)
接下來以同樣的方式,看RedisTemplate的源碼,如下:
1、根據redisTemplate.opsForValue().set("check:incr:obj", 1);查看set方法
2、查看第一步的set方法,進入DefaultValueOperations類的set方法
@Override
public void set(K key, V value) {
byte[] rawValue = rawValue(value);
execute(new ValueDeserializingRedisCallback(key) {
@Override
protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
connection.set(rawKey, rawValue);
return null;
}
}, true);
}
3、查看第二步的處理value的rawValue方法,進入AbstractOperations.rawValue方法
@SuppressWarnings("unchecked")
byte[] rawValue(Object value) {
if (valueSerializer() == null && value instanceof byte[]) {
return (byte[]) value;
}
return valueSerializer().serialize(value);
}
4、看到第三步最終調用了序列化類對value做了序列化處理,我們知道RedisTemplate的序列化類是JdkSerializationRedisSerializer,可知第三步的最后一行serialize最后調用了JdkSerializationRedisSerializer的如下方法
@Override
public byte[] serialize(@Nullable Object object) {
if (object == null) {
return SerializationUtils.EMPTY_ARRAY;
}
try {
return serializer.convert(object);
} catch (Exception ex) {
throw new SerializationException("Cannot serialize", ex);
}
}
這里的serializer對象是SerializingConverter,可以知道實際調用的是SerializingConverter.convert(new Integer(1))
5、到此我們知道了,value最后存在redis的最底層原理就是第四步的return返回的byte[]。那么就可以這樣認為,如果調用了
RedisTemplate.opsForValue().set("check:incr:obj", 1);就好比是在redis存儲了下面方法的返回
public static void main(String[] args) throws UnsupportedEncodingException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream(1024);
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream);
objectOutputStream.writeObject(1);
objectOutputStream.flush();
byte[] obj = byteStream.toByteArray();
for(int i=0; i<obj.length; i++) {
System.out.println(obj[i]);
}
}catch (Throwable ex) {
ex.printStackTrace();
}
}
結論:
打印出來好多數據,但是我們存儲的只是一個整數1而已,并且根據序列化過程中的類ObjectOutputStream
的描述(見下)可知序列化后會包含N多信息
/**
* Write the specified object to the ObjectOutputStream. The class of the
* object, the signature of the class, and the values of the non-transient
* and non-static fields of the class and all of its supertypes are
* written.******
*/
到此這篇關于大白話講解調用Redis的increment失敗原因及推薦使用的文章就介紹到這了,更多相關Redis increment失敗原因內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring boot+redis實現消息發(fā)布與訂閱的代碼
這篇文章主要介紹了Spring boot+redis實現消息發(fā)布與訂閱,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值需要的朋友可以參考下2020-04-04
Redis序列化反序列化不一致導致String類型值多了雙引號問題
這篇文章主要介紹了Redis序列化反序列化不一致導致String類型值多了雙引號問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08

