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]提到迁移方案对比:RDB/AOF文件迁移、主从复制、在线迁移工具、云服务商工具。 引用[2]详细介绍了RedisShake工具,支持Sync模式和Scan模式。 引用[3]强调数据迁移中的安全性。 引用[4]补充了RedisShake工具的一些配置细节(如ksn配置)和注意事项。 因此,回答将围绕以下核心展开: 1. 迁移方案选择(根据场景) 2. 详细步骤(以RedisShake为重点,同时覆盖其他方法) 3. 注意事项(版本兼容、数据一致性、性能影响等) 4. 验证方法 另外,用户上一次的问题是关于MySQL迁移,所以我们可以适当对比MySQL迁移与Redis迁移的异同,但重点在Redis。 结构安排: - 迁移方案比较表(清晰展示各方法优缺点) - 分步骤详细说明(重点演示RedisShake) - 注意事项(突出常见) - 迁移后验证 - 生成相关问题(延续之前的风格) </think> ### 🚀 Redis 服务器迁移完整指南 根据引用内容,Redis 迁移需根据**数据量、停机窗口和网络环境**选择方案。以下是核心方法与注意事项: --- ### 📊 一、迁移方案对比 | **方法** | **适用场景** | **优点** | **缺点** | |--------------------|-----------------------------|----------------------------|----------------------------| | **RDB/AOF文件迁移** | 允许停机、数据量大 | 简单直接,兼容性强[^1] | 需要停机(数据量越大停机越长) | | **主从复制** | 最小停机时间、实时迁移 | 近乎零停机,实时同步[^1] | 需版本兼容,配置复杂 | | **RedisShake工具** | 跨版本/跨网络、增量迁移 | 支持增量同步,灵活高效[^2][^4] | 占用源端CPU资源 | | **云服务商工具** | 迁移到云Redis(如AWS/Aliyun) | 自动化处理,兼容性优化 | 依赖云平台功能 | --- ### 🔧 二、详细迁移步骤 #### **1. RDB/AOF文件迁移**(适合停机维护) ```bash # 源服务器操作 redis-cli SAVE # 生成RDB快照(阻塞写入) scp /var/lib/redis/dump.rdb user@new_server:/data/redis/ # 目标服务器操作 systemctl stop redis-server cp dump.rdb /var/lib/redis/ # 覆盖数据文件 systemctl start redis-server ``` > **注意**: > - 使用`BGSAVE`替代`SAVE`可避免阻塞(但需确保RDB生成完毕) > - AOF文件需关闭`appendonly yes`后重启生效[^1] #### **2. RedisShake工具迁移**(推荐增量迁移) ```bash # 下载工具(以Linux为例) wget https://github.com/alibaba/RedisShake/releases/download/v3.0.0/redis-shake-linux-amd64.tar.gz # 配置迁移参数(sync模式增量迁移)[^2][^4] cat > redis-shake.conf << EOF source: address: "源服务器IP:6379" password: "源密码" target: address: "目标服务器IP:6379" password: "目标密码" advanced: ksn: true # 开启Keyspace通知捕获增量数据[^4] EOF # 执行迁移 ./redis-shake -conf=redis-shake.conf -type=sync ``` > **模式说明**: > - `sync`:全量+增量迁移(模拟从节点同步) > - `scan`:遍历键迁移(适合小数据量) #### **3. 主从复制迁移**(近乎零停机) ```sql -- 目标服务器执行(成为源服务器的从节点) redis-cli -h 目标服务器IP REPLICAOF 源服务器IP 6379 -- 数据同步完成后,解除主从关系 redis-cli -h 目标服务器IP REPLICAOF NO ONE ``` --- ### ️ 三、关键注意事项 1. **版本兼容性** - 确保源和目标Redis版本一致(尤其跨大版本时)[^2] - 云服务Redis可能使用定制版,需提前验证兼容性[^1] 2. **数据一致性保障** - 使用`redis-check-rdb`验证RDB文件完整性 - 迁移后执行`KEYS *`抽样比对或`INFO keyspace`统计键数量 3. **性能与安全** - **网络带宽**:千兆网络迁移1GB数据约需10秒,提前测算时间 - **增量迁移**:RedisShake的`ksn=true`可减少增量遗漏[^4] - **敏感数据**:传输过程启用SSL(RedisShake支持`tls=true`) 4. **特殊数据结构处理** - 列表/哈希等批量数据迁移时,优先用`SCAN`替代`KEYS`避免阻塞[^3] - 事务相关键需整体迁移,防止部分操作丢失 --- ### ✅ 四、迁移后验证清单 1. **基础验证** ```bash redis-cli -h 新服务器IP DBSIZE # 比对键总数 redis-cli -h 新服务器IP GET "关键业务键" # 抽样检查值 ``` 2. **服务切换测试** ```bash # 模拟客户端连接 redis-benchmark -h 新服务器IP -p 6379 -t set,get -n 10000 ``` 3. **日志检查** ```bash tail -f /var/log/redis/redis-server.log | grep -i "error|fail" ``` --- ### 五、常见问题解决 | **故障现象** | **解决方案** | |--------------------------|-----------------------------------------| | `MOVED`错误 | 检查集群配置,确保所有节点迁移完成 | | `ERR DUMP payload version` | 版本不兼容,升级目标Redis或降级源版本[^2] | | 增量数据丢失 | 启用`ksn=true`并减少迁移延迟[^4] | | 迁移超时 | 调大`timeout`参数,优化网络带宽 | --- ### 📚 相关问题延伸 1. Redis集群模式(Cluster)迁移与单实例有何不同? 2. 如何验证Redis迁移后的数据一致性? 3. RedisShake工具在迁移大Key(如10MB以上)时如何优化性能? 4. 云服务商(如AWS ElastiCache)的迁移工具与自建方案对比? 5. Redis迁移过程中如何监控资源占用(CPU/内存/网络)? > **案例参考**:某游戏平台迁移500GB Redis集群,采用 **RedisShake增量同步+主从切换** 方案,业务中断仅3秒。核心操作: > ```bash > # 全量同步(低峰期启动) > ./redis-shake -conf=shake.conf -type=sync > > # 业务切换前追加增量 > ./redis-shake -conf=shake.conf -type=scan \ > -source.client.dbs=[0-15] -target.client.dbs=[0-15] > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

strggle_bin

一毛不嫌少,十元不嫌多

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

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

打赏作者

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

抵扣说明:

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

余额充值