废话不多说,直接上代码: 1.在RedisTemplate的配置文件中。设置两个redisTemplate实例,一个是旧的编码方式,另一个是新的编码方式。 通过@Bean(name = “xxx”)来区分是哪种编码方式
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisTemplateConfig { @Bean(name = "defaultRedisTemplate") public RedisTemplate<String,Object> defaultRedisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; } // 设置redisTemplate实例的序列化方式,不然在用命令行查看redis时,会在前缀出现乱码 @Bean(name = "encodedRedisTemplate") public RedisTemplate<String,Object> encodedRedisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(redisConnectionFactory); RedisSerializer stringSerializer = new StringRedisSerializer(); redisTemplate.setDefaultSerializer(stringSerializer); return redisTemplate; } } 用旧编码取出redis库中的所有key,然后用新编码吧数据保存回去。备注:我用的log来打印日志,没用log的可以用system.out.println(xxx)代替 @Autowired @Qualifier("defaultRedisTemplate") RedisTemplate defaultRedisTemplate; @Autowired @Qualifier("encodedRedisTemplate") RedisTemplate encodedRedisTemplate; // 使用SCAN迭代地获取redis中的所有的key public List<String> getRedisKeyList() { List<String> nameList = new ArrayList<>(); RedisConnection redisConnection = null; try { redisConnection = encodedRedisTemplate.getConnectionFactory().getConnection(); // 不要使用keys("*"),因为它会造成死锁,导致线上服务暂时不可用。 // 应该用scan,比如每次遍历50条,这样会对线上服务影响小 ScanOptions options = ScanOptions.scanOptions().match("*").count(50).build(); Cursor<byte[]> c = redisConnection.scan(options); while (c.hasNext()) { // 不断SCAN,直到获取到redis的所有key byte[] bytes = null; String name = ""; try { bytes = c.next(); // 因为scan获得的key包括2种:一种是无乱码的新key,另一种是有乱码的老key,老key要进行反序列化,不然得到的是乱码 name = (String) defaultRedisTemplate.getDefaultSerializer().deserialize(bytes); log.info("得到的老key = {}", name); nameList.add(name); } catch (SerializationException serializationException) { nameList.add(new String(bytes)); log.info("有一个无法反序列化的key = {},暂时跳过", new String(bytes)); } } } finally { redisConnection.close(); //Ensure closing this connection. } // 打印输出所有的key: StringBuilder builder = new StringBuilder(); for(String name : nameList) { // 因为有回车、换行符,不去掉的话,有回车换行符的元素会把之前的元素覆盖掉 builder.append(name.replaceAll("[\\n\\r]", "") + ","); } log.info("redis的所有key:"); log.info(builder.toString()); return nameList; }代码思路:
优先读新:查询redis库时,优先以新编码方式查询,新编码查询不到时,才查旧编码读旧时写新:新编码的key没命中,读旧编码key的时候,会往新编码的key里保存一份:双写:保存数据时,双写到新编码、旧编码的key里,这样能保证旧编码的key里的数据一定是最新的,能防止万一写到新key时出了问题导致数据丢失。做好测试:要针对以上3种情况,设计好测试case,毕竟数据库里的数据还是蛮重要的。等系统稳定运行个一年半年后,就只有新编码key来读、写。旧编码的key就舍弃掉(说明这些key已经一年半年没用到了,仅仅适用于丢失一小部分数据不会有影响的情况)上代码: 1)和上面一样,设置2种编码方式的RedisTemplate实例:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisTemplateConfig { @Bean(name = "defaultRedisTemplate") public RedisTemplate<String,Object> defaultRedisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; } // 设置redisTemplate实例的序列化方式,不然在用命令行查看redis时,会在前缀出现乱码 @Bean(name = "encodedRedisTemplate") public RedisTemplate<String,Object> encodedRedisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(redisConnectionFactory); RedisSerializer stringSerializer = new StringRedisSerializer(); redisTemplate.setDefaultSerializer(stringSerializer); return redisTemplate; } }2)主逻辑(拿我的获取用户个性配置信息的代码为例子):
// 声明2种编码方式的RedisTemplate @Autowired @Qualifier("defaultRedisTemplate") RedisTemplate defaultRedisTemplate; @Autowired @Qualifier("encodedRedisTemplate") RedisTemplate encodedRedisTemplate; // /** * 设置用户配置信息 * 双写到老的和新编码的key中:我存的是map,朋友们根据自己需要存什么都可以 * @param userName:用户名,是我redis库的key,我以服务名+用户名作为redis库的key * @param req 配置信息 */ public void setUserProfile(String userName, SetProfileRequest req) { Map<String, String> map = new HashMap<String, String>(); map.put(req.getProfileName(), req.getProfileValue()); defaultRedisTemplate.opsForHash().putAll(userName, map); encodedRedisTemplate.opsForHash().putAll(getEncodedKey(userName), map); // 新的key log.info("存了一次旧编码key = {}", userName); log.info("存了一次新编码key = {}", getEncodedKey(userName)); log.info("setUserProfile userName = {}, profile = {}", userName, req); } /** * 获取用户的特定配置信息 * PS:读旧编码的key,用新编码的key保存,然后返回新编码key的查询结果 * @param userName * @param profileName 配置信息的key, * @return */ @Override public Map<String, String> getUserProfile(String userName , String profileName) { Map<String,String> res = new HashMap<>(); res.put("userName", userName); res.put("profileName", profileName); /** * 1. 查新编码的key有没有。有则直接返回结果 * 刷库时,对一些乱码的用户名如:123,opsForHash().get()会抛出异常,所以用try-catch包住 * 有则返回结果 * 没有则查旧的key */ try { if(encodedRedisTemplate.opsForHash().get(userName, profileName) != null) { res.put(PROFILE_VALUE, String.valueOf(encodedRedisTemplate.opsForHash().get(userName, profileName))); log.info("新编码的key命中,key= {}", userName); } /** * 2. 查旧的key有没有 * 有则把这个用户的配置信息存一份到新的正确的key里,然后返回查到的结果 * 没有就返回null */ else if(defaultRedisTemplate.opsForHash().get(userName, profileName) != null){ encodedRedisTemplate.opsForHash().putAll(userName, defaultRedisTemplate.opsForHash().entries(userName)); res.put(PROFILE_VALUE, String.valueOf(encodedRedisTemplate.opsForHash().get(userName, profileName))); log.info("新编码的key没命中,旧编码的key命中,旧编码的key= {}。用新编码的key保存了一份,并返回了新编码key的结果", userName); } else { res.put(PROFILE_VALUE, null); } } catch (Exception e) { res.put(PROFILE_VALUE, null); log.error("获取用户 = {} 的特定配置信息 ={} 时出错了:{}", userName, profileName, e.getMessage()); } log.info("getUserProfile userName = {}, profileName = {}, profileValue= {}", userName, profileName, res.get(PROFILE_VALUE)); return res; }