Redis实战之session共享

当线上集群时候,会出现session共享问题。

虽然Tomcat提供了session copy的功能,但是缺点比较明显:

1:当Tomcat多的时候,session需要大量同步到多台集群上,占用内网宽带

2:同一个用户session,需要在多个Tomcat中都存在,浪费内存空间

如果要替换掉Tomcat的session共享,替代方案应该满足:

1:数据共享

2:内存存储

3:key\value结构

32513e73b243ec122ea183b9683cc5de.png

基于Redis实现共享session登录

本文由凯哥Java(gz#h:kaigejava),个人blog:www#kaigejava#.com。发布于简书

再来回顾下将验证码保存在session中业务流程

0f7ad2613d0c2945a3521a444ac48373.png

我们在session中存放 因为session的特点,每次访问都是一个新的sessionId.我们可以直接使用code作为key.思考:那么如果换成了Redis,还能使用code作为可以吗?

将用户信息存放在session中流程:

949a02a0dc7a161f878989367a1dc0a0.png

用户信息在session中存放:session.setAttribute("user", user); 同样思考:那么如果换成了Redis,还能使用user作为可以吗?

将code和user信息存放在Redis中,流程如下:

image.png

验证码数据结构是:string类型的

用户对象数据类型是:hash类型的

根据上面分析,我们修改原来代码:

需要考虑的是:Redis的key规则、过期时间

1:发送验证码的时候,将验证码存放到Redis中时候,需要考虑过期时间。其核心代码如下:

6a6187b3f0126a1ec1705e9ace4ca0a6.png

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

2:用户登录的时候,将校验验证码以及用户信息存放到Redis中后,返回token

需要考虑的:

1:token不能重复

2:用户过期时间

3:登录成功后,要将token返回给前端

4:用户只要访问,Redis中的过期时间就要延长-在拦截器中处理的

用户登录核心代码修改:

e44e9661d6e2f3d8f501dba50a448535.png

<pre class="brush:as3;toolbar:false" style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(248, 248, 248);">//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);

</pre>

需要注意:

在使用stringRedisTemplate存放hash对象的时候,对象中所有的key只能是string类型,如果存在非string类型会报错的。所以这里使用了hootool的BeanUtil工具类:

<pre class="brush:as3;toolbar:false" style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(248, 248, 248);">Map<String,Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>()
, CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
</pre>

拦截器修改代码:

因为拦截器是我们自定义的,所以不能被spring容器管理的,RedisTemplate就不能自动注入了。我们就使用有参构造器,传递:

243cb12436424394a79705bf719ff850.png

<pre class="brush:as3;toolbar:false" style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(248, 248, 248);">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();
}

}
</pre>

总结:

在使用Redis替换session的时候,需要考虑的问题:

1:选择合适的数据结构

2:选择合适的key

3:选择合适的存储粒度

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容