从Redis反序列化UserDetails对象异常后发现FastJson序列化的一些问题

2023-11-26 20:01

本文主要是介绍从Redis反序列化UserDetails对象异常后发现FastJson序列化的一些问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        最近在使用SpringSecurity+JWT实现认证授权的时候,出现Redis在反序列化userDetails的异常。通过实践发现,使用不同的序列化方法和不同的fastJson版本,异常信息各不相同。所以特地记录了下来。

一、项目代码

        先来看看我项目中redis相关配置信息。

1.自定义的redis序列化器

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.util.Assert;
import java.nio.charset.Charset;/*** Redis使用FastJson序列化** @author mosul*/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T>
{public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");private Class<T> clazz;static{ParserConfig.getGlobalInstance().setAutoTypeSupport(true);}public FastJsonRedisSerializer(Class<T> clazz){super();this.clazz = clazz;}@Overridepublic byte[] serialize(T t) throws SerializationException{if (t == null){return new byte[0];}return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);}@Overridepublic 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);}protected JavaType getJavaType(Class<?> clazz){return TypeFactory.defaultInstance().constructType(clazz);}
}

2.redis配置类

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
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.StringRedisSerializer;@Configuration
public class RedisConfig {/*** 指定特定的连接工厂* @return*//*@Beanpublic RedisConnectionFactory redisConnectionFactory() {return new LettuceConnectionFactory();}*/@Bean@SuppressWarnings(value = { "unchecked", "rawtypes" })public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory){RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);// 使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(serializer);// Hash的key也采用StringRedisSerializer的序列化方式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(serializer);template.afterPropertiesSet();return template;}
}

3.redis工具类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;import java.util.*;
import java.util.concurrent.TimeUnit;/*** Redis帮助类** @author mosul*/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisHelper
{@Autowiredpublic RedisTemplate redisTemplate;/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值*/public <T> void setCacheObject(final String key, final T value){redisTemplate.opsForValue().set(key, value);}/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值* @param timeout 时间* @param timeUnit 时间颗粒度*/public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit){redisTemplate.opsForValue().set(key, value, timeout, timeUnit);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout){return expire(key, timeout, TimeUnit.SECONDS);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @param unit 时间单位* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout, final TimeUnit unit){return redisTemplate.expire(key, timeout, unit);}/*** 获得缓存的基本对象。** @param key 缓存键值* @return 缓存键值对应的数据*/public <T> T getCacheObject(final String key){ValueOperations<String, T> operation = redisTemplate.opsForValue();return operation.get(key);}/*** 删除单个对象** @param key*/public boolean deleteObject(final String key){return redisTemplate.delete(key);}/*** 删除集合对象** @param collection 多个对象* @return*/public long deleteObject(final Collection collection){return redisTemplate.delete(collection);}/*** 缓存List数据** @param key 缓存的键值* @param dataList 待缓存的List数据* @return 缓存的对象*/public <T> long setCacheList(final String key, final List<T> dataList){Long count = redisTemplate.opsForList().rightPushAll(key, dataList);return count == null ? 0 : count;}/*** 获得缓存的list对象** @param key 缓存的键值* @return 缓存键值对应的数据*/public <T> List<T> getCacheList(final String key){return redisTemplate.opsForList().range(key, 0, -1);}/*** 缓存Set** @param key 缓存键值* @param dataSet 缓存的数据* @return 缓存数据的对象*/public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet){BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);Iterator<T> it = dataSet.iterator();while (it.hasNext()){setOperation.add(it.next());}return setOperation;}/*** 获得缓存的set** @param key* @return*/public <T> Set<T> getCacheSet(final String key){return redisTemplate.opsForSet().members(key);}/*** 缓存Map** @param key* @param dataMap*/public <T> void setCacheMap(final String key, final Map<String, T> dataMap){if (dataMap != null) {redisTemplate.opsForHash().putAll(key, dataMap);}}/*** 获得缓存的Map** @param key* @return*/public <T> Map<String, T> getCacheMap(final String key){return redisTemplate.opsForHash().entries(key);}/*** 往Hash中存入数据** @param key Redis键* @param hKey Hash键* @param value 值*/public <T> void setCacheMapValue(final String key, final String hKey, final T value){redisTemplate.opsForHash().put(key, hKey, value);}/*** 获取Hash中的数据** @param key Redis键* @param hKey Hash键* @return Hash中的对象*/public <T> T getCacheMapValue(final String key, final String hKey){HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();return opsForHash.get(key, hKey);}/*** 删除Hash中的数据** @param key* @param hkey*/public void delCacheMapValue(final String key, final String hkey){HashOperations hashOperations = redisTemplate.opsForHash();hashOperations.delete(key, hkey);}/*** 获取多个Hash中的数据** @param key Redis键* @param hKeys Hash键集合* @return Hash对象集合*/public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys){return redisTemplate.opsForHash().multiGet(key, hKeys);}/*** 获得缓存的基本对象列表** @param pattern 字符串前缀* @return 对象列表*/public Collection<String> keys(final String pattern){return redisTemplate.keys(pattern);}
}

4.自己系统中的UserDetails

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {private static final long serialVersionUID = 1L;// 系统用户private SysUser user;// 用户权限列表private List<SysPermission> permissionList;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return permissionList.stream().filter(permission -> permission.getPermission() != null).map(permission -> new SimpleGrantedAuthority(permission.getPermission())).collect(Collectors.toList());}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

5.登录设置


@Overridepublic String login(SysUser sysUser) {String token = null;//密码需要客户端加密后传递try {UserDetails userDetails = sysUserService.loadUserByUsername(sysUser.getUsername());if(!passwordEncoder.matches(sysUser.getPassword(),userDetails.getPassword())){throw new BadCredentialsException("密码不正确");}UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authentication);token = jwtTokenUtil.generateToken(userDetails);String key = "login:" + sysUser.getUsername();//设置redisredisHelper.setCacheObject(key,userDetails);//insertLoginLog(username);} catch (AuthenticationException e) {LOGGER.warn("登录异常:{}", e.getMessage());}return token;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUser sysUser = sysUserMapper.selectOne(new QueryWrapper<SysUser>().eq("username", username));List<SysPermission> permissionsByUser = sysUserRoleMapper.findPermissionsByUser(sysUser.getUserId());sysUser.setPassword(new BCryptPasswordEncoder().encode(sysUser.getPassword()));// 将系统的用户信息和权限信息封装成UserDetailsUserDetails userDetail = new LoginUser(sysUser, permissionsByUser);return userDetail;}

6.JWT校验

 @Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String authHeader = request.getHeader(this.tokenHeader);if (authHeader != null && authHeader.startsWith(this.tokenHead)) {String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer "String username = jwtTokenUtil.getUserNameFromToken(authToken.trim());LOGGER.info("checking username:{}", username);if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {//从redis中获取userDetailsString redisKey = "login:" + username;UserDetails userDetails = redisHelper.getCacheObject(redisKey);if(Objects.isNull(userDetails)){throw new RuntimeException("用户未登录");}if (jwtTokenUtil.validateToken(authToken, userDetails)) {//存入SecurityContextHolder//TODO 获取权限信息封装到Authentication中UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));LOGGER.info("authenticated user:{}", username);SecurityContextHolder.getContext().setAuthentication(authentication);}}}//放行filterChain.doFilter(request, response);}

7.fastjson版本

        <!--fastjson依赖--><!--第一个版本--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.47</version></dependency><!--第二个版本--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.22</version></dependency>

        上面的代码中,先根据用户名获取用户对应的用户信息和权限信息,然后构建SpringSecurity的UserDetails对象,用户登录的时候将这个UserDetails对象放入redis中,后续校验请求携带的token与redis中的信息是否一致。

二、异常信息

1.版本一报错信息

        需要说明的是,在redis系列化时,是正常的,对应的值也成功设置近缓存了,但是在JWT校验阶段,执行UserDetails userDetails = redisHelper.getCacheObject(redisKey);时出现异常,反序列化失败。

        针对这个问题,首先上面的代码逻辑是没有问题的,但是与fastjson反序列化不兼容导致的问题。

        根据异常信息提示,设置属性authorities错误,猜想下是因为LoginUser中没有authorities属性,但也说不过去,同样没有属性username和password怎么不会报错?

        带着这个疑问,我们先给将LoginUser代码改为下面这种形式。

@Data
public class LoginUser implements UserDetails {private static final long serialVersionUID = 1L;private SysUser user;private List<SysPermission> permissionList;private List<GrantedAuthority> authorities;public LoginUser() {}public LoginUser(SysUser user, List<SysPermission> permissionList) {this(user,permissionList,null);}/*** 针对fastJson中redis反序列化报错的改进* org.springframework.data.redis.serializer.SerializationException:* Could not deserialize: set authorities error; nested exception is com.alibaba.fastjson.JSONException: set authorities error** @param user* @param permissionList* @param authorities*/public LoginUser(SysUser user, List<SysPermission> permissionList, List<GrantedAuthority> authorities) {//返回当前用户的权限List<GrantedAuthority> authoritieList = permissionList.stream().filter(permission -> permission.getPermission() != null).map(permission -> new SimpleGrantedAuthority(permission.getPermission())).collect(Collectors.toList());this.user = user;this.permissionList = permissionList;this.authorities = authoritieList;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorities;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

        发现这里改完之后,是可以正常运行的。 而且发现,当我们给getAuthorities()赋简单的值的时候,不会出现这个问题。

@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<GrantedAuthority> authoritieList = new ArrayList<>();authoritieList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));return authoritieList;}

        而且我们在构造函数中提前将权限列表加载出来,赋值给一个属性,属性不一定非得名为authorities,如属性名为authoritiesList。然后返回这个属性也不会报错。

@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authoritieList;}

        通过上面两个测试可以猜测下,如果构造函数中提前将比较复杂的实现暴露了,反系列化也不会报错。可能出现异常的原因fastjson是对不确定结果无法反系列化,如果是简单的结果或在构造器中就已经知道了确定结果,就不会出现反序列化的异常。 

2.版本二报错信息

         在使用fastJson 2.x版本的时候,同时需要对redis配置类做如下修改。

@Bean@SuppressWarnings(value = { "unchecked", "rawtypes" })public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory){RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);/*java.lang.ClassCastException:* com.alibaba.fastjson.JSONObject cannot be cast to org.springframework.security.core.userdetails.UserDetails* */String[] acceptNames = {"org.springframework.security.core.authority.SimpleGrantedAuthority"};GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer(acceptNames);// 使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(serializer);// Hash的key也采用StringRedisSerializer的序列化方式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(serializer);template.afterPropertiesSet();return template;}

        可能有些朋友的序列化器配置如下:

@Configuration
public class RedisConfiguration {@Beanpublic RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){RedisTemplate<Object,Object> template = new RedisTemplate();template.setConnectionFactory(redisConnectionFactory);/** 序列化后会产生java类型说明,如果不需要用“Jackson2JsonRedisSerializer”* 和“ObjectMapper ”配合效果更好*/Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();template.setKeySerializer(stringRedisSerializer);template.setValueSerializer(jackson2JsonRedisSerializer);template.setHashKeySerializer(stringRedisSerializer);template.setHashValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();return template;}}

        这种配置同样也会出现异常,异常信息如下:org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Cannot construct instance of `org.springframework.security.core.authority.SimpleGrantedAuthority` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator) 

        同样的,我们可以将fastjson改为如下版本:

<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.22</version></dependency

        同时使用其自带的序列化器GenericFastJsonRedisSerializer, 进行如下配置:

/*解决如下两个异常需要添加的配置:* 1.java.lang.ClassCastException:* com.alibaba.fastjson.JSONObject cannot be cast to org.springframework.security.core.userdetails.UserDetails** 2.org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Cannot construct instance of `org.springframework.security.core.authority.SimpleGrantedAuthority`*  (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)* */String[] acceptNames = {"org.springframework.security.core.authority.SimpleGrantedAuthority"};GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer(acceptNames);

        序列化配置修改完成之后,还是会出现设置属性authorities错误,同样需要对LoginUser做上述修改。 

3.发现FastJson反系列的一般问题

        正如上面所说的,同样没有属性username和password怎么不会报错?于是做了一系列测试。发现了在低版本的fastJson中,对应集合类型接口方法中包含较复杂的实现(不是直接显示赋值),反序化可能要求必须有对应的属性。

        定义了一个有不同返回值类型的几种方法来测试。

public interface CrazyDetails {List<String> getApps();User getUser();String getName();String[] getNodes();Collection<String> getTests();List<User> getUsers();
}

        定义一个实现类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MosulApp implements CrazyDetails{@Overridepublic User getUser() {return new User(this.appInfo.name);}private AppInfo appInfo;@Overridepublic List<User> getUsers() {List<User> userList = new ArrayList<>();for(int i = 0; i < this.appInfo.name.length(); i ++) {User user1 = new User("" + i);userList.add(user1);}return userList;}@Overridepublic String[] getNodes() {String[] strings = new String[2];strings = new String[]{this.appInfo.name,this.appInfo.details};return strings;}/*private List<String> apps;*///报错,添加需要private List<String> tests@Overridepublic Collection<String> getTests() {List<String> list = Arrays.asList(appInfo.name);return list;}@Overridepublic List<String> getApps() {List<String> objects = new ArrayList<>();for(int i = 0; i < this.appInfo.name.length(); i ++) {objects.add("tt" + i);}return objects;}@Overridepublic String getName() {return this.appInfo.name;}}

       

        在测试发现fastJson 1.x版本对于Arrays.asList(appInfo.name);反序列化失败,fastJson 2.x版本则可以反序列化成功,但对于UserDetails中Collection<? extends GrantedAuthority> getAuthorities()中如果有比较复杂的实现,fastJson 2.x版本反序列化还是会失败。所以为了保险起见,最后在自定义的UserDetails中添加authorities属性,除了这种方法能外,应该也跟自定义的序列化器相关设置有关,需要进行探索。

三、Redis和SpringSecutiry相关配置

        基于上述测试,最终fastJson选用2.0.22版本,最后将redis配置类和SpringSecutiry中UserDetails实现类修改为如下所示。

1.redis配置类

@Configuration
public class RedisConfig {/*** 指定特定的连接工厂* @return*//*@Beanpublic RedisConnectionFactory redisConnectionFactory() {return new LettuceConnectionFactory();}*/@Bean@SuppressWarnings(value = { "unchecked", "rawtypes" })public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory){RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);/* FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);*//*解决java.lang.ClassCastException:* com.alibaba.fastjson.JSONObject cannot be cast to org.springframework.security.core.userdetails.UserDetails* */String[] acceptNames = {"org.springframework.security.core.authority.SimpleGrantedAuthority"};GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer(acceptNames);// 使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(serializer);// Hash的key也采用StringRedisSerializer的序列化方式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(serializer);template.afterPropertiesSet();return template;}
}

2.LoginUser类

@Data
public class LoginUser implements UserDetails {private static final long serialVersionUID = 1L;// 用户信息private SysUser user;// 用户权限列表private List<SysPermission> permissionList;// SpringSecurity对应的权限信息private List<GrantedAuthority> authorities;public LoginUser() {}public LoginUser(SysUser user, List<SysPermission> permissionList) {this(user,permissionList,null);}/*** 针对fastJson中redis反序列化报错的改进* org.springframework.data.redis.serializer.SerializationException:* Could not deserialize: set authorities error; nested exception is com.alibaba.fastjson.JSONException: set authorities error** @param user* @param permissionList* @param authorities*/public LoginUser(SysUser user, List<SysPermission> permissionList, List<GrantedAuthority> authorities) {//返回当前用户的权限List<GrantedAuthority> authoritieList = permissionList.stream().filter(permission -> permission.getPermission() != null).map(permission -> new SimpleGrantedAuthority(permission.getPermission())).collect(Collectors.toList());this.user = user;this.permissionList = permissionList;this.authorities = authoritieList;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorities;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

这篇关于从Redis反序列化UserDetails对象异常后发现FastJson序列化的一些问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/426243

相关文章

详谈redis跟数据库的数据同步问题

《详谈redis跟数据库的数据同步问题》文章讨论了在Redis和数据库数据一致性问题上的解决方案,主要比较了先更新Redis缓存再更新数据库和先更新数据库再更新Redis缓存两种方案,文章指出,删除R... 目录一、Redis 数据库数据一致性的解决方案1.1、更新Redis缓存、删除Redis缓存的区别二

oracle数据库索引失效的问题及解决

《oracle数据库索引失效的问题及解决》本文总结了在Oracle数据库中索引失效的一些常见场景,包括使用isnull、isnotnull、!=、、、函数处理、like前置%查询以及范围索引和等值索引... 目录oracle数据库索引失效问题场景环境索引失效情况及验证结论一结论二结论三结论四结论五总结ora

Redis与缓存解读

《Redis与缓存解读》文章介绍了Redis作为缓存层的优势和缺点,并分析了六种缓存更新策略,包括超时剔除、先删缓存再更新数据库、旁路缓存、先更新数据库再删缓存、先更新数据库再更新缓存、读写穿透和异步... 目录缓存缓存优缺点缓存更新策略超时剔除先删缓存再更新数据库旁路缓存(先更新数据库,再删缓存)先更新数

Redis事务与数据持久化方式

《Redis事务与数据持久化方式》该文档主要介绍了Redis事务和持久化机制,事务通过将多个命令打包执行,而持久化则通过快照(RDB)和追加式文件(AOF)两种方式将内存数据保存到磁盘,以防止数据丢失... 目录一、Redis 事务1.1 事务本质1.2 数据库事务与redis事务1.2.1 数据库事务1.

element-ui下拉输入框+resetFields无法回显的问题解决

《element-ui下拉输入框+resetFields无法回显的问题解决》本文主要介绍了在使用ElementUI的下拉输入框时,点击重置按钮后输入框无法回显数据的问题,具有一定的参考价值,感兴趣的... 目录描述原因问题重现解决方案方法一方法二总结描述第一次进入页面,不做任何操作,点击重置按钮,再进行下

mac安装redis全过程

《mac安装redis全过程》文章内容主要介绍了如何从官网下载指定版本的Redis,以及如何在自定义目录下安装和启动Redis,还提到了如何修改Redis的密码和配置文件,以及使用RedisInsig... 目录MAC安装Redis安装启动redis 配置redis 常用命令总结mac安装redis官网下

解决mybatis-plus-boot-starter与mybatis-spring-boot-starter的错误问题

《解决mybatis-plus-boot-starter与mybatis-spring-boot-starter的错误问题》本文主要讲述了在使用MyBatis和MyBatis-Plus时遇到的绑定异常... 目录myBATis-plus-boot-starpythonter与mybatis-spring-b

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

SpringBoot使用注解集成Redis缓存的示例代码

《SpringBoot使用注解集成Redis缓存的示例代码》:本文主要介绍在SpringBoot中使用注解集成Redis缓存的步骤,包括添加依赖、创建相关配置类、需要缓存数据的类(Tes... 目录一、创建 Caching 配置类二、创建需要缓存数据的类三、测试方法Spring Boot 熟悉后,集成一个外

Redis分布式锁使用及说明

《Redis分布式锁使用及说明》本文总结了Redis和Zookeeper在高可用性和高一致性场景下的应用,并详细介绍了Redis的分布式锁实现方式,包括使用Lua脚本和续期机制,最后,提到了RedLo... 目录Redis分布式锁加锁方式怎么会解错锁?举个小案例吧解锁方式续期总结Redis分布式锁如果追求