technology-integration全面解析程序员

technology-integration(八)---使用Re

2018-08-28  本文已影响912人  海苔胖胖

为什么使用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过期,所以综合下采取这种方式


开始改造

  1. 配置Redis
  2. 添加保存Redis key的标识符的线程变量(ThreadLocal)
  3. 修改过滤器
  4. 修改UserService方法

导入jar

pom.xml
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

配置Redis

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

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

public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {

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

    private Class<T> clazz;

    public FastJsonRedisSerializer(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);
        //不加配置无法自动转换为对应类型
        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>(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<Object> 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<String> 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中获取的


更多文章请关注该 technology-integration全面解析专题

上一篇下一篇

猜你喜欢

热点阅读