大家在项目中基本都会接触到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失败原因及推荐使用的更多相关文章

  1. 玩转Windows服务系列——无COM接口Windows服务启动失败原因及解决方案

    将VS创建的Windows服务项目编译生成的程序,通过命令行 “服务.exe -Service”注册为Windows服务后,就可以通过服务管理器进行管理了. 问题 通过服务管理器进行启动的时候,发现服 ...

  2. php调用Redis队列使用例子

    1.Controller顶部引入use think\Queue; 2.Controller调用Redis任务 // 1.当前任务将由哪个类来负责处理. $job = 'app\api\job\Resu ...

  3. 7.Redis主线程阻塞原因

    7.Redis主线程阻塞原因7.1 发现阻塞7.2 内在原因7.2.1 API或数据结构使用不合理7.2.2 CPU饱和7.2.3 持久化阻塞7.3 外在原因7.3.1 CPU竞争7.3.2 内存交换 ...

  4. soapui调用redis,获取短信验证码

    1.首先,调用redis需要引入redis的jar包,放入到soapui指定目录中,例如我的目录D:\Program Files\SmartBear\SoapUI-Pro-5.1.2\bin\ext ...

  5. pip install 提示代理连接失败原因及解决办法

    # pip install 提示代理连接失败原因及解决办法 1. 错误提示 在公司电脑上安装Python的虚拟环境时输入命令: pip install virtualenv 系统提示以下异常信息: R ...

  6. [88221008]调用新下单接口失败,result:162020004,resInfo

    [88221008]调用新下单接口失败,result:162020004,resInfo

  7. 38 一次 redis 连接泄露的原因 以及 ShardedJedisPool

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u011039332/article/details/85381051前言 这个是接着 上次的 这篇文 ...

  8. windows下使用c++调用redis

    不废话,unix下c++调用 redis可以看这个: http://blog.csdn.net/youngqj/article/details/8266177 ==================== ...

  9. [HAL]5.中断里调用HAL_Delay()进入死循环的原因

    中断里调用HAL_Delay()进入死循环的原因  摘自:http://blog.csdn.net/alwxkxk/article/details/47204677 CUBE生成的程序中, SysTi ...

随机推荐

  1. 接口管理工具swagger

    swagger,一款致力于解决接口规范化.标准化.文档化的开源库,一款真正的开发神器. swagger三大部分 Editor https://swagger.io/tools/swagger-edit ...

  2. whistle安装

    可参考官方帮助文档:https://wproxy.org/whistle/install.html 系统:windows10   jdk:1.8.0_171    node:10.16.0    np ...

  3. 华为云计算IE面试笔记-其它知识点

    * 虚拟化与云计算的区别?云计算和虚拟化关系?华为云计算怎么做的? 虚拟化是一种具体的技术,实现的是对硬件资源的虚拟化,提升资源利用率,降低能耗,云计算是一种概念各种技术组件的集合,针对的是对各种资源 ...

  4. 如何借助 JuiceFS 为 AI 模型训练提速 7 倍

    背景 海量且优质的数据集是一个好的 AI 模型的基石之一,如何存储.管理这些数据集,以及在模型训练时提升 I/O 效率一直都是 AI 平台工程师和算法科学家特别关注的事情.不论是单机训练还是分布式训练 ...

  5. VirtualBox设置双网卡实现主宿互访及虚拟机访问互联网总结

    1,配置网络 注:VirtualBox要在全局工具-主机网络管理器里新建一个虚拟网卡. 然后虚拟机的网卡1设置为host-only,界面名称为新建的虚拟网卡(我这里为了不跟主机ip冲突,设置成了不同网 ...

  6. Rafy 框架 - 实体支持只更新部分变更的字段

    Rafy 快一两年没有大的更新了.并不是这个框架没人维护了.相反,主要是因为自己的项目.以及公司在使用的项目,都已经比较稳定了,也没有新的功能添加.但是最近因为外面使用了 Rafy 的几个公司,找到我 ...

  7. Windows 10下CUDA及cuDNN的安装 —— Pytorch

    Windows 10下CUDA及cuDNN的安装 CUDA简介与下载地址 CUDA(ComputeUnified Device Architecture),是显卡厂商NVIDIA推出的运算平台. CU ...

  8. 一文了解MySQL性能测试及调优中的死锁处理方法,你还看不明白?

    一文了解MySQL性能测试及调优中的死锁处理方法,你还看不明白? 以下从死锁检测.死锁避免.死锁解决3个方面来探讨如何对MySQL死锁问题进行性能调优. 死锁检测 通过SQL语句查询锁表相关信息: ( ...

  9. 【数据结构 C++】排序——冒泡、插入、选择、希尔、归并、快排、堆排序

    LeetCode 912. 排序数组 给你一个整数数组 nums,请你将该数组升序排列. 示例 1: 输入:nums = [5,2,3,1] 输出:[1,2,3,5] 示例 2: 输入:nums = ...

  10. 联想SR658安装显卡驱动【NVIDIA Tesla V100】

    1. 安装基础依赖环境 yum -y install gcc kernel-devel kernel-headers 2.查看内核和源码版本是否一致 查看内核版本: ls /boot | grep v ...