大白话讲解调用Redis的increment失败原因及推荐使用详解

大家在项目中基本都会接触到redis,在spring-data-redis-2.*.*.release.jar中提供了两个helper class,可以让我们更方便的操作redis中存储的数据。这两个helper class分别是redistemplate和stringredistemplate,其中stringredistemplate是redistemplate在存储string类型的时候的一个扩展子类。所以大家在使用redis的时候:

1、如果操作的是string类型,优先考虑用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就可以自动的帮我们实现序列化和反向序列,其中二者的主要区别就是序列化机制,

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失败原因内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!

(0)
上一篇 2022年3月21日
下一篇 2022年3月21日

相关推荐