利用spring-data-redis实现incr自增的操作
应该有不少人在使用spring-data-redis时遇到各种各样的问题。反正我是遇到了。
由于是隔了一段时间才写的本篇博客,也懒得去重现哪些错误场景了,下面凭着记忆写了几个我遇到的问题:
redis.clients.jedis.exceptions.JedisDataException:ERRvalueisnotanintegeroroutofrange
使用的RedisTemplate,做读写操作时候,都是要经过序列化和反序列化。
这时你使用redisTemplate.opsForValue().increment()就可能报错redis.clients.jedis.exceptions.JedisDataException:ERRvalueisnotanintegeroroutofrange了。
valueOper.get(key)获取不到自增的值。
于是我去看了一下redis的官方文档,找到一个解决方法
使用spring-data-redis实现incr自增 /** * *@paramkey *@paramliveTime *@return */ publicLongincr(Stringkey,longliveTime){ RedisAtomicLongentityIdCounter=newRedisAtomicLong(key,redisTemplate.getConnectionFactory()); Longincrement=entityIdCounter.getAndIncrement(); if((null==increment||increment.longValue()==0)&&liveTime>0){//初始设置过期时间 entityIdCounter.expire(liveTime,TimeUnit.SECONDS); } returnincrement; }
这样,上面的increment就是自增后的新知值,然后中间通过entityIdCounter.expire(liveTime,TimeUnit.SECONDS);设置过期时间。
当然这里比较讨厌,spring没有在创建RedisAtomicLong对象的时候一起设置过期时间。
可以看看其源码,newRedisAtomicLong最终调用的是这个方法:
privateRedisAtomicLong(StringredisCounter,RedisConnectionFactoryfactory,LonginitialValue){ Assert.hasText(redisCounter,"avalidcounternameisrequired"); Assert.notNull(factory,"avalidfactoryisrequired"); RedisTemplateredisTemplate=newRedisTemplate (); redisTemplate.setKeySerializer(newStringRedisSerializer()); redisTemplate.setValueSerializer(newGenericToStringSerializer (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。
然后根进set方法
publicvoidset(longnewValue){ operations.set(key,newValue); }
可以看到,他是采用的operations.set(key,newValue);但是明明还有一个重载的方法voidset(Kkey,Vvalue,longtimeout,TimeUnitunit);可以设置过期时间,为啥spring不提供呢。
为了解决这个问题,我们可以自己模拟RedisAtomicLong方法,去实现一个带有过期时间的自增方法。比较简单,读者自行撸代码吧,这里就不写出了。
补充知识:关于springboot使用redis的increment()方法自增问题
需求是限制IP频繁访问某接口,用的方案是使用redis记录访问IP的值,先设定好初始值,每次访问自增,达到某限定值后,进行阻止。
用的是自定义工具类,使用spring封装的spring-data-redis进行操作,在对某key进行increment()方法时,报错:
redisERRvalueisnotanintegeroroutofrange
代码逻辑如下:
Integercount=(Integer)redisUtil.get(ipAddress);//取得key的value if(count==null){ redisUtil.set(ipAddress,1,10); returnfalse; }elseif(count==3){ returnfalse; }else{ redisUtil.incr(ipAddress,1); returnfalse; }
第一次进来,如果没有redis中没有数据,则设置key,value和time,key是ip,value初始值为1,有效时长为10秒。
如果没达到限制次数,则对key自增1。
redisUtil.incr()方法实现如下:
@Resource privateRedisTemplateredisTemplate;//这里使用的是redisTemplate publicvoidsetRedisTemplate(RedisTemplate redisTemplate){ this.redisTemplate=redisTemplate; } /** *递增 *@paramkey键 //*@paramby要增加几(大于0) *@return */ publiclongincr(Stringkey,longdelta){ if(delta<0){ thrownewRuntimeException("递增因子必须大于0"); } returnredisTemplate.opsForValue().increment(key,delta); }
开始以为是incr方法接受的参数是long型,但我传入的是INTEGER类型,但转换后还是没有解决问题,问题不是出在这,后来通过查找资料发现,Spring对Redis序列化的策略有两种,分别是StringRedisTemplate和RedisTemplate,其中StringRedisTemplate用于操作字符串,RedisTemplate使用的是JDK默认的二进制序列化。
大家都知道redis序列化是将key,value值先转换为流的形式,再存储到redis中。
RedisTemplate是使用的JdkSerializationRedisSerializer序列化,序列化后的值包含了对象信息,版本号,类信息等,是一串字符串,所以无法进行数值自增操作。
而StringRedisTemplate序列化策略是字符串的值直接转为字节数组,所以存储到redis中是数值,所以可以进行自增操作。
StringRedisSerializer源码:
publicclassStringRedisSerializerimplementsRedisSerializer{ privatefinalCharsetcharset; publicStringRedisSerializer(){ this(StandardCharsets.UTF_8); } publicStringRedisSerializer(Charsetcharset){ Assert.notNull(charset,"Charsetmustnotbenull!"); this.charset=charset; } publicStringdeserialize(@Nullablebyte[]bytes){ returnbytes==null?null:newString(bytes,this.charset); } publicbyte[]serialize(@NullableStringstring){ returnstring==null?null:string.getBytes(this.charset);//注意这里是字节数组 } }
所以问题出在这里,我们需要自定义序列化策略,在application启动类中添加如下:
@Bean publicRedisTemplateredisTemplate(RedisConnectionFactoryfactory){ StringRedisTemplatetemplate=newStringRedisTemplate(factory); //定义key序列化方式 //RedisSerializer redisSerializer=newStringRedisSerializer();//Long类型会出现异常信息;需要我们上面的自定义key生成策略,一般没必要 //定义value的序列化方式 Jackson2JsonRedisSerializerjackson2JsonRedisSerializer=newJackson2JsonRedisSerializer(Object.class); ObjectMapperom=newObjectMapper(); 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(); returntemplate;
费了2多小时才成功解决问题,RedisUtil.incr()能够成功对key进行自增了,如有错误之处请欢迎指出。
以上这篇利用spring-data-redis实现incr自增的操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。