美文网首页technology-integration全面解析程序员
technology-integration(八)---使用Re

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

作者: 海苔胖胖 | 来源:发表于2018-08-28 13:24 被阅读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全面解析专题

    相关文章

      网友评论

        本文标题:technology-integration(八)---使用Re

        本文链接:https://www.haomeiwen.com/subject/vncfwftx.html