Redis @type的一个坑

redis中@type导致取数据解析报错

java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to
新建一个对象存入redis中,对象中会出现一个字段@type

LoginUser user = new LoginUser ()
......
redisTemplate.opsForValue().set(key, user)

存入redis中数据如下

127.0.0.1:6379> get login_tokens:5be4de32-6eb5-44a5-b212-56d93e3fc067
"{\"@type\":\"com.common.core.domain.model.LoginUser\",\"deptId\":103L,\"expireTime\":1710463649132,\"token\":\"xxxx\",\"user\":{\"admin\":true,\"createBy\":\"admin\",\"dept\":{\"deptId\":103L,\"deptName\":\"xxx",\"orderNum\":1,\"params\":{\"@type\":\"java.util.HashMap\"},\"parentId\":101L,\"status\":\"0\"},\"deptId\":103L,\"loginDate\":\"2024-03-14 14:35:56\",\"loginIp\":\"127.0.0.1\",\"nickName\":\"xxx\",\"params\":{\"@type\":\"java.util.HashMap\"},\"phonenumber\":\"15888888888\",\"sex\":\"1\",\"status\":\"0\",\"userId\":1L,\"userName\":\"admin\"},\"userId\":1L,\"username\":\"admin\"}"
127.0.0.1:6379>

取数据时,redisTemplate.opsForValue().get(key);
如果LoginUser对象的包与存入时的包路径不一致,会报错java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to

redis缓存序列化导致存储数据没有@type

在使用redis注解将数据缓存的时候发现存储进去的数据是这样的,没有@type

127.0.0.1:6379> get login_tokens:5be4de32-6eb5-44a5-b212-56d93e3fc067
"{\"deptId\":103L,\"expireTime\":1710463649132,\"token\":\"xxxx\",\"user\":{\"admin\":true,\"createBy\":\"admin\",\"dept\":{\"deptId\":103L,\"deptName\":\"xxx",\"orderNum\":1,\"parentId\":101L,\"status\":\"0\"},\"deptId\":103L,\"loginDate\":\"2024-03-14 14:35:56\",\"loginIp\":\"127.0.0.1\",\"nickName\":\"xxx\",\"phonenumber\":\"15888888888\",\"sex\":\"1\",\"status\":\"0\",\"userId\":1L,\"userName\":\"admin\"},\"userId\":1L,\"username\":\"admin\"}"
127.0.0.1:6379>

之前通过set方法放进去的数据是这样的

127.0.0.1:6379> get login_tokens:5be4de32-6eb5-44a5-b212-56d93e3fc067
"{\"@type\":\"com.common.core.domain.model.LoginUser\",\"deptId\":103L,\"expireTime\":1710463649132,\"token\":\"xxxx\",\"user\":{\"admin\":true,\"createBy\":\"admin\",\"dept\":{\"deptId\":103L,\"deptName\":\"xxx",\"orderNum\":1,\"params\":{\"@type\":\"java.util.HashMap\"},\"parentId\":101L,\"status\":\"0\"},\"deptId\":103L,\"loginDate\":\"2024-03-14 14:35:56\",\"loginIp\":\"127.0.0.1\",\"nickName\":\"xxx\",\"params\":{\"@type\":\"java.util.HashMap\"},\"phonenumber\":\"15888888888\",\"sex\":\"1\",\"status\":\"0\",\"userId\":1L,\"userName\":\"admin\"},\"userId\":1L,\"username\":\"admin\"}"
127.0.0.1:6379>

原因:
是因为set方法的序列化方法和注解的序列化方法不同
在这里插入图片描述
解决办法:
将序列化方法更换成set方法所使用的序列化方法

下面是序列化方法

public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
    @SuppressWarnings("unused")
    private ObjectMapper objectMapper = new ObjectMapper();

    public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    private Class<T> clazz;

    static {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    }

    public FastJson2JsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);
        return JSON.parseObject(str, clazz);
    }

    public void setObjectMapper(ObjectMapper objectMapper) {
        Assert.notNull(objectMapper, "'objectMapper' must not be null");
        this.objectMapper = objectMapper;
    }

    protected JavaType getJavaType(Class<?> clazz) {
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}

SerializerFeature.WriteClassName这个序列化

public static String getString(Object object) {
   return JSON.toJSONString(object, SerializerFeature.WriteClassName);
}

如果加了SerializerFeature.WriteClassName存进redis当中的实体类就会带@type路径地址
“@type”:“com.xxx.xxx.entity.OpenNotice”

问题解决方案:去掉@type 或者 两边@type路径存放路径一致 (包名和实体类修改为一致)

去掉@type使用 JSONObject.toJSONString(obj)来存value实体类

    /**
     * hashMap存值方式
     *
     * @param parentKey
     * @param fieldKey
     * @param obj
     */
    public static void hashSet(String parentKey, String fieldKey, Object obj) {
        try {
            jedis.hset(parentKey, fieldKey, JSONObject.toJSONString(obj));
            //jedis.hset(parentKey, fieldKey, JSONParser.getString(obj)); //"@type":"com.xyz.miniLegion.entity.OpenNotice"
        } finally {
            jedis.close();
        }
    }

获取hashAll数据

   /**
     * 获取hashGetAll
     * @param parentKey
     * @return
     */
    public Map<String, String> hashGetAll(String parentKey) {
        try (Jedis jedis = pools.getResource()) { //这种写法不需要手动close();
            return jedis.hgetAll(parentKey);
        }catch (Exception e) {      
            return null;
        }
    }

测试Redis转实体类`在这里插入代码片

   @Test
    void getSoldierAttribute() {
        Map<String, String> openNoticeMap = redisPoolMgr.hashGetAll(StaticData.OpenNotice);
        //第一种根据key循环
        for (String key : openNoticeMap.keySet()) {
            OpenNotice openNotice = JSONObject.parseObject(openNoticeMap.get(key), OpenNotice.class);
            System.out.println("根据key循环:" +openNotice.getContent());
            System.out.println("根据key循环:" + JSONObject.toJSONString(openNotice));
        }
 
        //第二种根据value循环
        for (String values : openNoticeMap.values()) {
            OpenNotice openNotice = JSONObject.parseObject(values, OpenNotice.class);
            System.out.println("根据value循环:"+openNotice.getContent());
            System.out.println("根据value循环:" + JSONObject.toJSONString(openNotice));
        }
    }

在这里插入图片描述

<think>好的,我现在需要回答用户关于Redis不同版本之间的兼容性问题以及解决方案的提问。首先,我需要仔细理解用户的问题,然后结合提供的引用资料来构建一个清晰的回答。 用户的问题是关于不同Redis版本之间是否存在兼容性问题,导致无法连接,以及相应的解决方案。根据提供的引用,特别是引用[1]和引用[4],我需要整理出可能存在的兼容性问题,以及迁移或连接时的解决方法。 首先,引用[1]提到了阿里云将Redis从2.8迁移到5.0时遇到的和解决方案,使用了redis-port和redis-shake工具。这说明版本迁移可能涉及数据格式或协议的差异,需要特定工具来辅助迁移,以减少兼容性问题。 引用[4]提到可能存在Redis版本或兼容性问题,导致连接异常,尤其是用户可能遇到无法连接的深层次问题。这提示不同版本之间可能存在协议、命令或配置的变化,导致客户端与服务器不兼容。 接下来,我需要考虑常见的Redis版本差异。例如,Redis在3.0之后引入了集群支持,4.0增加了模块化功能,5.0引入了Stream数据类型。这些新特性可能导致旧客户端无法正确处理新数据格式或命令。此外,协议的修改,如返回格式的变化,也可能引发兼容性问题。 然后,解决方案方面,引用[1]的工具可以用于数据迁移,而引用[3]提到分布式锁的实现可能需要特定版本的命令,如Redlock算法,这也可能与版本有关。例如,某些命令在旧版本中不可用,或者语法有变化,导致客户端命令执行失败。 此外,引用[2]提到Redis单线程机制和性能相关的问题,虽然不直接涉及兼容性,但版本升级可能影响性能表现,需要评估是否适合业务场景。 现在,我需要将这些点组织成一个结构化的回答,包括问题分析和解决方案。同时,根据用户要求,生成相关问题。 需要确保回答符合格式要求:行内数学公式用$...$,独立公式用$$...$$,但可能在这个问题中用不到数学公式。另外,引用标注要自然,例如在提到工具时引用[^1],在提到连接问题时引用[^4]。 检查是否有遗漏的信息,比如是否提到协议版本、客户端库的兼容性、配置差异等。比如,某些客户端库可能不支持新版本的Redis命令,需要更新客户端驱动。 总结可能的兼容性问题点: 1. 协议差异:Redis协议在不同版本可能有微小变化,导致客户端无法正确解析响应。 2. 命令变更:新命令的添加或旧命令的废弃,如某些命令可能在更高版本中被移除或修改。 3. 数据格式:如RDB文件格式变化,导致旧版本无法读取新版本的备份文件。 4. 配置参数:不同版本的配置文件选项可能不同,导致启动失败。 5. 客户端库版本:客户端库(如Jedis、Lettuce)需要与Redis服务器版本匹配,否则可能无法支持某些命令。 解决方案包括: - 使用迁移工具如redis-port和redis-shake(引用[1])。 - 升级客户端库版本。 - 逐步升级,验证兼容性。 - 检查配置文件和命令使用情况,替换废弃命令。 最后,生成相关问题,例如不同版本迁移步骤,如何检测兼容性问题,客户端库兼容性处理等。</think>### Redis版本兼容性问题及解决方案 #### 1. **兼容性问题分析** - **协议差异** Redis客户端与服务端通过RESP协议通信,不同版本可能引入协议扩展。例如,Redis 5.0支持Stream类型,若客户端版本过低可能无法解析相关响应。 - **命令变更** 某些命令在新版本中废弃或参数格式变化。例如,`CLUSTER SLOTS`在Redis 5.0后返回值结构改变,旧客户端解析可能出错[^3]。 - **数据持久化格式** RDB文件格式在不同版本间可能不兼容。例如,Redis 4.0的RDB文件若直接加载到Redis 2.8,可能因格式不匹配导致启动失败。 - **客户端库依赖** 如Java的Jedis/Lettuce库需匹配Redis版本。若Redis 6.0启用SSL而客户端库不支持,会引发连接异常[^4]。 #### 2. **常见问题场景** - **跨版本迁移失败** 如从Redis 2.8迁移到5.0时,若直接复制RDB文件,可能因格式不兼容导致数据损坏。 - **命令执行报错** 使用Redis 3.0的客户端调用Redis 6.0的`ACL`命令时,会返回`ERR unknown command`。 - **集群配置差异** Redis 3.0引入集群模式,旧客户端若未适配集群协议,无法正确路由请求[^2]。 #### 3. **解决方案** - **使用迁移工具** 通过`redis-port`或`redis-shake`进行数据迁移,工具会自动处理协议和格式转换。 ```bash # 示例:使用redis-shake同步数据 ./redis-shake -type=sync -source=old_redis:6379 -target=new_redis:6380 ``` - **渐进式升级验证** 1. 在测试环境部署新旧版本,验证命令兼容性。 2. 使用`INFO`命令检查服务端版本,调整客户端配置。 3. 逐步替换生产环境节点,避免全量切换风险。 - **更新客户端库** 例如,Java项目需将Jedis升级至4.0+以支持Redis 6.0的ACL功能: ```xml <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.3.0</version> </dependency> ``` - **配置适配** 修改`redis.conf`以兼容旧客户端,例如关闭新特性: ```conf # 禁用Redis 6.0的ACL功能(部分场景) aclfile "" ``` #### 4. **兼容性测试方法** - **命令覆盖测试** 使用`redis-cli --scan`遍历所有Key,验证读写操作是否正常。 - **性能压测** 通过`redis-benchmark`模拟高并发场景,观察新版本是否引入延迟或错误。 - **日志监控** 检查Redis日志中是否有`WARNING`或`ERROR`级别的协议报错,如: ``` # 日志示例:客户端协议不支持 Warning: Protocol version mismatch (client 2, server 3) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

strggle_bin

一毛不嫌少,十元不嫌多

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值