Session sharing in Redis combat

When clustering online, there will be session sharing problems.

Although Tomcat provides the function of session copy, the disadvantages are obvious:

1: When there are many Tomcats, a large number of sessions need to be synchronized to multiple clusters, occupying intranet bandwidth

2: The same user session needs to exist in multiple Tomcats, wasting memory space

If you want to replace Tomcat's session sharing, the alternative should satisfy:

1: Data sharing

2: memory storage

3: key\value structure

Shared session login based on Redis

This article is written by Kaige Java (gz#h: kaigejava), personal blog: www#kaigejava#.com. Published in CSDN

Let’s review the business process of saving the verification code in the session

 

What we store in the session is: session.setAttribute("code", code); Because of the characteristics of the session, each access is a new sessionId. We can directly use the code as the key. Thinking: So if it is replaced by Redis , can also use code as can?

Process of storing user information in session:

 

User information is stored in the session: session.setAttribute("user", user); Think about the same thing: So if you switch to Redis, can you still use user as the user?

Store code and user information in Redis, the process is as follows:

 

The verification code data structure is: string type

The user object data type is: hash type

According to the above analysis, we modify the original code:

Things to consider: Redis key rules, expiration time

1: When sending the verification code, when storing the verification code in Redis, you need to consider the expiration time. Its core code is as follows:

stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);

2: When the user logs in, store the verification code and user information in Redis, and return the token

Things to consider:

1: token cannot be repeated

2: user expiration time

3: After successful login, the token should be returned to the front end

4: As long as the user visits, the expiration time in Redis will be extended - processed in the interceptor

User login core code modification:

 

//2.1:校验验证码是否正确
        //String code = (String) session.getAttribute("code");
        String code = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        if (StringUtils.isEmpty(code) || !code.equals(loginForm.getCode())) {
            return Result.fail("验证码错误!");
        }
        //2.2:根据手机号查询,如果不存在,创建新用户
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "phone", "nick_name");
        queryWrapper.eq("phone", phone);
        User user = this.getOne(queryWrapper);
        if (Objects.isNull(user)) {
            log.info("新建用户");
            //新建用户
            user = createUserWithPhone(phone);
        }
        //2.3:保存用户到session中
        UserDTO userDTO = new UserDTO();
        userDTO.setId(user.getId());
        userDTO.setIcon(user.getIcon());
        userDTO.setNickName(user.getNickName());
 
        //session.setAttribute("user", userDTO);
        //2.3.1:获取随机的token,作为用户登录的令牌
        String token = UUID.randomUUID().toString(true);
        //2.3.2:将用户以hash类型存放到Redis中==》将user对象转换成map
        //user对象里有非string类型的字段,用这个方法会报错的
        // Map<String,Object> userMap = BeanUtil.beanToMap(userDTO);
        Map<String,Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>()
        , CopyOptions.create()
        .setIgnoreNullValue(true)
        .setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
 
        stringRedisTemplate.opsForHash().putAll(LOGIN_USER_TOKEN_KEY+token,userMap);
        //LOGIN_USER_TOKEN_TTL
        stringRedisTemplate.expire(LOGIN_USER_TOKEN_KEY+token,LOGIN_USER_TOKEN_TTL,TimeUnit.MINUTES);
        //2.3.3: 将token返回
        return Result.ok(token);

requires attention:

When using stringRedisTemplate to store hash objects, all keys in the object can only be of string type, and an error will be reported if there is a non-string type. So the BeanUtil tool class of hootool is used here:

Map<String,Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>()
        , CopyOptions.create()
        .setIgnoreNullValue(true)
        .setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));

Interceptor modification code:

Because the interceptor is customized by us, it cannot be managed by the spring container, and RedisTemplate cannot be automatically injected. We use the parameterized constructor and pass:

public class LoginRedisInterceptor implements HandlerInterceptor {
 
    private StringRedisTemplate stringRedisTemplate;
 
    /**
     * 因为这个类不能被spring管理,所以不能直接注入RedisTemplate对象。通过构造函数传递
     * @param stringRedisTemplate
     */
    public LoginRedisInterceptor(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1:从请求中获取到token
        String token = request.getHeader("authorization");
        if(StringUtils.isEmpty(token)){
            response.setStatus(401);
            return false;
        }
        //2:基于token获取redis中用户对象
        String key = LOGIN_USER_TOKEN_KEY+token;
        Map<Object,Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        //3:判断
        if(userMap.isEmpty()){
            response.setStatus(401);
            return false;
        }
        //将map转对象
        UserDTO user = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        UserHolder.saveUser(user);
        //刷新token的过期时间
        stringRedisTemplate.expire(key,LOGIN_USER_TOKEN_TTL, TimeUnit.MINUTES);
        return true;
    }
 
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

Summarize:

When using Redis to replace session, issues to consider:

1: Choose the right data structure

2: Choose the right key

3: Select the appropriate storage granularity

Guess you like

Origin blog.csdn.net/kaizi_1992/article/details/128900990