利用spring-data-redis實(shí)現(xiàn)incr自增的操作
應(yīng)該有不少人在使用spring-data-redis時(shí)遇到各種各樣的問(wèn)題。反正我是遇到了。
由于是隔了一段時(shí)間才寫(xiě)的本篇博客,也懶得去重現(xiàn)哪些錯(cuò)誤場(chǎng)景了,下面憑著記憶寫(xiě)了幾個(gè)我遇到的問(wèn)題:
redis.clients.jedis.exceptions.JedisDataException: ERR value is not an integer or out of range
使用的RedisTemplate,做讀寫(xiě)操作時(shí)候,都是要經(jīng)過(guò)序列化和反序列化。
這時(shí)你使用redisTemplate.opsForValue().increment()就可能報(bào)錯(cuò)redis.clients.jedis.exceptions.JedisDataException: ERR value is not an integer or out of range了。
valueOper.get(key) 獲取不到自增的值。
于是我去看了一下redis的官方文檔,找到一個(gè)解決方法
使用spring-data-redis實(shí)現(xiàn)incr自增
/**
*
* @param key
* @param liveTime
* @return
*/
public Long incr(String key, long liveTime) {
RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
Long increment = entityIdCounter.getAndIncrement();
if ((null == increment || increment.longValue() == 0) && liveTime > 0) {//初始設(shè)置過(guò)期時(shí)間
entityIdCounter.expire(liveTime, TimeUnit.SECONDS);
}
return increment;
}
這樣,上面的increment就是自增后的新知值,然后中間通過(guò)entityIdCounter.expire(liveTime, TimeUnit.SECONDS);設(shè)置過(guò)期時(shí)間。
當(dāng)然這里比較討厭,spring沒(méi)有在創(chuàng)建RedisAtomicLong對(duì)象的時(shí)候一起設(shè)置過(guò)期時(shí)間。
可以看看其源碼,new RedisAtomicLong最終調(diào)用的是這個(gè)方法:
private RedisAtomicLong(String redisCounter, RedisConnectionFactory factory, Long initialValue) {
Assert.hasText(redisCounter, "a valid counter name is required");
Assert.notNull(factory, "a valid factory is required");
RedisTemplate<String, Long> redisTemplate = new RedisTemplate<String, Long>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericToStringSerializer<Long>(Long.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);
}
}
可以看到,初始值是0。
然后根進(jìn)set方法
public void set(long newValue) {
operations.set(key, newValue);
}
可以看到,他是采用的operations.set(key, newValue);但是明明還有一個(gè)重載的方法void set(K key, V value, long timeout, TimeUnit unit);可以設(shè)置過(guò)期時(shí)間,為啥spring不提供呢。
為了解決這個(gè)問(wèn)題,我們可以自己模擬RedisAtomicLong方法,去實(shí)現(xiàn)一個(gè)帶有過(guò)期時(shí)間的自增方法。比較簡(jiǎn)單,讀者自行擼代碼吧,這里就不寫(xiě)出了。
補(bǔ)充知識(shí):關(guān)于spring boot使用redis的increment()方法自增問(wèn)題
需求是限制IP頻繁訪(fǎng)問(wèn)某接口,用的方案是使用redis記錄訪(fǎng)問(wèn)IP的值,先設(shè)定好初始值,每次訪(fǎng)問(wèn)自增,達(dá)到某限定值后,進(jìn)行阻止。
用的是自定義工具類(lèi),使用spring封裝的spring-data-redis進(jìn)行操作,在對(duì)某key進(jìn)行increment()方法時(shí),報(bào)錯(cuò):
redis ERR value is not an integer or out of range
代碼邏輯如下:
Integer count = (Integer) redisUtil.get(ipAddress);//取得key的value
if (count == null){
redisUtil.set(ipAddress,1,10);
return false;
}else if(count == 3){
return false;
}else {
redisUtil.incr(ipAddress,1);
return false;
}
第一次進(jìn)來(lái),如果沒(méi)有redis中沒(méi)有數(shù)據(jù),則設(shè)置key,value和time,key是ip, value初始值為1,有效時(shí)長(zhǎng)為10秒。
如果沒(méi)達(dá)到限制次數(shù),則對(duì)key自增1。
redisUtil.incr()方法實(shí)現(xiàn)如下:
@Resource
private RedisTemplate<String, Object> redisTemplate; //這里使用的是redisTemplate
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 遞增
* @param key 鍵
// * @param by 要增加幾(大于0)
* @return
*/
public long incr(String key, long delta){
if(delta<0){
throw new RuntimeException("遞增因子必須大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
開(kāi)始以為是incr方法接受的參數(shù)是long型,但我傳入的是INTEGER類(lèi)型,但轉(zhuǎn)換后還是沒(méi)有解決問(wèn)題,問(wèn)題不是出在這,后來(lái)通過(guò)查找資料發(fā)現(xiàn),Spring對(duì)Redis序列化的策略有兩種,分別是StringRedisTemplate和RedisTemplate,其中StringRedisTemplate用于操作字符串,RedisTemplate使用的是JDK默認(rèn)的二進(jìn)制序列化。
大家都知道redis序列化是將key,value值先轉(zhuǎn)換為流的形式,再存儲(chǔ)到redis中。
RedisTemplate是使用的JdkSerializationRedisSerializer序列化,序列化后的值包含了對(duì)象信息,版本號(hào),類(lèi)信息等,是一串字符串,所以無(wú)法進(jìn)行數(shù)值自增操作。
而StringRedisTemplate序列化策略是字符串的值直接轉(zhuǎn)為字節(jié)數(shù)組,所以存儲(chǔ)到redis中是數(shù)值,所以可以進(jìn)行自增操作。
StringRedisSerializer源碼:
public class StringRedisSerializer implements RedisSerializer<String> {
private final Charset charset;
public StringRedisSerializer() {
this(StandardCharsets.UTF_8);
}
public StringRedisSerializer(Charset charset) {
Assert.notNull(charset, "Charset must not be null!");
this.charset = charset;
}
public String deserialize(@Nullable byte[] bytes) {
return bytes == null ? null : new String(bytes, this.charset);
}
public byte[] serialize(@Nullable String string) {
return string == null ? null : string.getBytes(this.charset); //注意這里是字節(jié)數(shù)組
}
}
所以問(wèn)題出在這里,我們需要自定義序列化策略,在application啟動(dòng)類(lèi)中添加如下:
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
//定義key序列化方式
//RedisSerializer<String> redisSerializer = new StringRedisSerializer();//Long類(lèi)型會(huì)出現(xiàn)異常信息;需要我們上面的自定義key生成策略,一般沒(méi)必要
//定義value的序列化方式
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// template.setKeySerializer(redisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
費(fèi)了2多小時(shí)才成功解決問(wèn)題,RedisUtil.incr()能夠成功對(duì)key進(jìn)行自增了,如有錯(cuò)誤之處請(qǐng)歡迎指出。
以上這篇利用spring-data-redis實(shí)現(xiàn)incr自增的操作就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot中dubbo+zookeeper實(shí)現(xiàn)分布式開(kāi)發(fā)的應(yīng)用詳解
這篇文章主要介紹了SpringBoot中dubbo+zookeeper實(shí)現(xiàn)分布式開(kāi)發(fā)的應(yīng)用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
IDEA-SpringBoot項(xiàng)目Debug啟動(dòng)不了(卡住不動(dòng))的原因分析
這篇文章主要介紹了IDEA-SpringBoot項(xiàng)目Debug啟動(dòng)不了(卡住不動(dòng))的原因分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
Java線(xiàn)程通信之wait-notify通信方式詳解
這篇文章主要為大家詳細(xì)介紹了Java線(xiàn)程通信之wait-notify通信方式,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03
Spring Cloud OAuth2中/oauth/token的返回內(nèi)容格式
Spring Cloud OAuth2 生成access token的請(qǐng)求/oauth/token的返回內(nèi)容就需要自定義,本文就詳細(xì)介紹一下,感興趣的可以了解一下2021-07-07
深入了解SpringBoot中@InitBinder注解的使用
這篇文章主要介紹了深入了解SpringBoot中@InitBinder注解的使用,@InitBinder注解可以作用在被@Controller注解的類(lèi)的方法上,表示為當(dāng)前控制器注冊(cè)一個(gè)屬性編輯器,用于對(duì)WebDataBinder進(jìn)行初始化,且只對(duì)當(dāng)前的Controller有效,需要的朋友可以參考下2023-10-10
SpringMVC中Json數(shù)據(jù)格式轉(zhuǎn)換
本文主要介紹了SpringMVC中Json數(shù)據(jù)格式轉(zhuǎn)換的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-03-03
Spring?Boot?利用?XML?方式整合?MyBatis
這篇文章主要介紹了Spring?Boot?利用?XML?方式整合?MyBatis,文章圍繞主題的相關(guān)資料展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,組要的小伙伴可以參考一下2022-05-05
實(shí)例詳解Java中ThreadLocal內(nèi)存泄露
這一篇文章我們來(lái)分析一個(gè)Java中ThreadLocal內(nèi)存泄露的案例。分析問(wèn)題的過(guò)程比結(jié)果更重要,理論結(jié)合實(shí)際才能徹底分析出內(nèi)存泄漏的原因。2016-08-08
MyBatis?核心組件Configuration實(shí)例詳解
Configuration用于描述 MyBatis 的主配置信息,其他組件需要獲取配置信息時(shí),直接通過(guò) Configuration 對(duì)象獲取,這篇文章主要介紹了MyBatis核心組件Configuration,需要的朋友可以參考下2023-08-08

