key设计 短信验证存redis_technology-integration(八)---使用Redis缓存提升token验证速度...

为什么使用Redis加速

上一章里,我们对token的每次验证都是需要查询数据库的,这就很容易导致数据库压力上升,前后端分离的情况下,接口调用的次数会比未分离状态下会更多,另外就是数据库的访问速度也是相对较慢的,使得接口调用速度下降,影响用户体验。

原结构

改造后

key键生成策略

Redis对于数据结构的选择还是很重要的,选择一个合适的数据结构能大大提高Redis的并发量,从而提升系统的性能。

假设我们现在系统有100万的总用户量,如果我们直接采用String类型进行存储,这就意味着每条用户信息都会生成一个key,那样我们的Redis中就保存有100万个key,这个数量还是比较庞大的,容易出现慢查询的情况。我们出于优化Redis查询可以使用Hash类型进行存储,Redis中Hash的数据结构是这样的---(key,field,value)。比较重要的是key的生成策略,key生成采用Token生成时间进行分组,比如某个用户的Token是2018-05-20 11:22:56这个时间段生成的,那这个用户的信息将会归于user:info:2011这个键中。也就是---- user:info:+日期天数+小时。而已field字段则是存储用户的Id,value则存储的是用户的详细信息。 用user:info:+日期天数+小时做为key还有一个原因是因为缓存过期问题,我们不可能一直把无用的缓存留到Redis中,但Hash数据结构并不能让某个key里面的field过期,所以综合下采取这种方式

开始改造

配置Redis

添加保存Redis key的标识符的线程变量(ThreadLocal)

修改过滤器

修改UserService方法

导入jar

pom.xml

org.springframework.boot

spring-boot-starter-data-redis

配置Redis

以下两个类均位于com.viu.technology.config.redis包下,自行创建.

由于使用的是FastJson序列化方法,所以我们需要创建一个FastJsonRedisSerializer类,用于Redis序列化存储

public class FastJsonRedisSerializer implements RedisSerializer {

public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

private Class clazz;

public FastJsonRedisSerializer(Class 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);

//不加配置无法自动转换为对应类型

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

return JSON.parseObject(str, clazz);

}

}

RedisConfiguration.java

该类配置了Redis连接工厂,以及配置FastJson序列化方式,使用@EnableCaching注解开启Redis缓存

@Configuration

@EnableCaching

@EnableTransactionManagement

public class RedisConfiguration extends CachingConfigurerSupport {

///使用fastjson序列化

@Bean

public RedisSerializer fastJson2JsonRedisSerializer() {

return new FastJsonRedisSerializer(Object.class);

}

@Bean

public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory, RedisSerializer fastJson2JsonRedisSerializer) {

StringRedisTemplate template = new StringRedisTemplate(factory);

template.setValueSerializer(fastJson2JsonRedisSerializer);

///开启事务支持

template.setEnableTransactionSupport(true);

template.afterPropertiesSet();

return template;

}

///替换Redis Cache方案使用JDK序列化方式为FastJson序列化

@Bean

@Primary

public RedisCacheConfiguration redisCacheConfiguration(){

FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);

RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();

configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer));

configuration.entryTtl(Duration.ofDays(30));

return configuration;

}

///配置Redis连接工厂

@Bean

public LettuceConnectionFactory masterSource() {

return new LettuceConnectionFactory(new RedisStandaloneConfiguration("127.0.0.1", 6379));

}

}

创建TokenContextHolderToken线程信息保存类

TokenContextHolderToken.java

位于com.viu.technology.context包下,自行创建

public class TokenContextHolder {

private static final ThreadLocal cachePrefix = new ThreadLocal<>();

public static String getCachePrefix() {

return cachePrefix.get();

}

public static void setCachePrefix(String prefix) {

cachePrefix.set(prefix);

}

}

创建RedisKeyEnum枚举类

RedisKeyEnum.java

稍微规范化一下,创建了一个枚举类用来获取指定Redis缓存的key前缀

public enum RedisKeyEnum {

REDIS_USER_KEY("user:info:");

private String key;

RedisKeyEnum(String key) {

this.key = key;

}

public String getKey() {

return key;

}

}

修改Token验证过滤器

JwtAuthenticationTokenFilter.java

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

String authHeader = request.getHeader("Authorization");

String tokenHead = "tech-";

if (authHeader != null && authHeader.startsWith(tokenHead)) {

String authToken = authHeader.substring(tokenHead.length());

String userId = JwtTokenUtil.getUsernameFromToken(authToken);

if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) {

///使用JwtTokenUtil工具类获取Claims 对象,该对象保存着Token里的信息

Claims claims = JwtTokenUtil.getClaimsFromToken(authToken);

//获取Token的创建时间

Date createDate = claims.getExpiration();

//通过日期天数和小时数拼接字符串

String pre = "" +createDate.getDate() +createDate.getHours();

//放到TokenContextHolder线程变量中,交由UserService方法取出使用

TokenContextHolder.setCachePrefix(pre);

UserDetails userDetails = this.userDetailsService.loadUserByUsername(userId);

if (JwtTokenUtil.validateToken(authToken, userDetails)) {

UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

SecurityContextHolder.getContext().setAuthentication(authentication);

}

}

} else {

log.info("没有获取到token");

}

chain.doFilter(request, response);

}

修改UserService

UserServiceImpl.java

User getUserAndRoleById(String id);

User insertUserInfoToCache(String userId, User user);

UserServiceImpl.java

insertUserInfoToCache()方法用于将用户信息缓存至Redis中

@Override

public User insertUserInfoToCache(String suffix,User user) {

JSONObject userJson = JsonUtil.objectToJsonObject(user);

String redisKey = RedisKeyEnum.REDIS_USER_KEY.getKey()+ suffix;

//redisTemplate.opsForHash().put(redisKey, user.getId(), JsonUtil.objectToString(user));

if (redisTemplate.hasKey(redisKey)) {

redisTemplate.opsForHash().put(redisKey, user.getId(), JsonUtil.objectToString(user));

} else {

redisTemplate.opsForHash().put(redisKey, user.getId(), JsonUtil.objectToString(user));

//设置Key的过期时间为7天

redisTemplate.expire(redisKey, 7, TimeUnit.DAYS);

}

return user;

}

@Override

public User getUserAndRoleById(String id) {

String suf = TokenContextHolder.getCachePrefix();

String redisKey = RedisKeyEnum.REDIS_USER_KEY.getKey() + suf;

User user = null;

//从Redis中获取对应信息,如果获取失败则查询数据库并将结果缓存到Redis中

String userJson = (String) redisTemplate.opsForHash().get(redisKey, id);

if (null!=userJson) {

user = JSONObject.parseObject(userJson, User.class);

} else {

user = userDao.selUserAndRoleById(id);

if (null != user) {

userService.insertUserInfoToCache(suf, user);

}

}

return user;

}

测试

同样调用我们上章写的接口,获取用户自身的详细信息/user/self/info

温馨提醒:

application.yml配置文件中加入logging.level.com.viu.technology.mapper: debug

这句代码意思是com.viu.technology.mapper包下输出debug级别的日志,数据库SQL查询则属于debug级别

在第一次调用的时候我们可以看到控制台输出了SQL查询语句,证明数据是从MySQL中读取的;接下来我们调用第二次,发现控制台并没有输出SQL语句,证明这次的数据是从Redis中获取的

你可能感兴趣的:(key设计,短信验证存redis)