Redis實(shí)戰(zhàn)之session共享

當(dāng)線上集群時(shí)候镶殷,會(huì)出現(xiàn)session共享問題禾酱。

雖然Tomcat提供了session copy的功能,但是缺點(diǎn)比較明顯:

1:當(dāng)Tomcat多的時(shí)候绘趋,session需要大量同步到多臺(tái)集群上颤陶,占用內(nèi)網(wǎng)寬帶

2:同一個(gè)用戶session,需要在多個(gè)Tomcat中都存在陷遮,浪費(fèi)內(nèi)存空間

如果要替換掉Tomcat的session共享指郁,替代方案應(yīng)該滿足:

1:數(shù)據(jù)共享

2:內(nèi)存存儲(chǔ)

3:key\value結(jié)構(gòu)

32513e73b243ec122ea183b9683cc5de.png

基于Redis實(shí)現(xiàn)共享session登錄

本文由凱哥Java(gz#h:kaigejava),個(gè)人blog:www#kaigejava#.com拷呆。發(fā)布于簡書

再來回顧下將驗(yàn)證碼保存在session中業(yè)務(wù)流程

0f7ad2613d0c2945a3521a444ac48373.png

我們在session中存放 因?yàn)閟ession的特點(diǎn)闲坎,每次訪問都是一個(gè)新的sessionId.我們可以直接使用code作為key.思考:那么如果換成了Redis,還能使用code作為可以嗎茬斧?

將用戶信息存放在session中流程:

949a02a0dc7a161f878989367a1dc0a0.png

用戶信息在session中存放:session.setAttribute("user", user); 同樣思考:那么如果換成了Redis腰懂,還能使用user作為可以嗎?

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

image.png

驗(yàn)證碼數(shù)據(jù)結(jié)構(gòu)是:string類型的

用戶對象數(shù)據(jù)類型是:hash類型的

根據(jù)上面分析项秉,我們修改原來代碼:

需要考慮的是:Redis的key規(guī)則绣溜、過期時(shí)間

1:發(fā)送驗(yàn)證碼的時(shí)候,將驗(yàn)證碼存放到Redis中時(shí)候娄蔼,需要考慮過期時(shí)間怖喻。其核心代碼如下:

6a6187b3f0126a1ec1705e9ace4ca0a6.png

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

2:用戶登錄的時(shí)候,將校驗(yàn)驗(yàn)證碼以及用戶信息存放到Redis中后,返回token

需要考慮的:

1:token不能重復(fù)

2:用戶過期時(shí)間

3:登錄成功后,要將token返回給前端

4:用戶只要訪問岁诉,Redis中的過期時(shí)間就要延長-在攔截器中處理的

用戶登錄核心代碼修改:

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:校驗(yàn)驗(yàn)證碼是否正確
//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("驗(yàn)證碼錯(cuò)誤!");
}
//2.2:根據(jù)手機(jī)號(hào)查詢,如果不存在锚沸,創(chuàng)建新用戶
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:獲取隨機(jī)的token,作為用戶登錄的令牌
    String token = UUID.randomUUID().toString(true);
    //2.3.2:將用戶以hash類型存放到Redis中==》將user對象轉(zhuǎn)換成map
    //user對象里有非string類型的字段,用這個(gè)方法會(huì)報(bào)錯(cuò)的
    // 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對象的時(shí)候,對象中所有的key只能是string類型涕癣,如果存在非string類型會(huì)報(bào)錯(cuò)的哗蜈。所以這里使用了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>

攔截器修改代碼:

因?yàn)閿r截器是我們自定義的,所以不能被spring容器管理的坠韩,RedisTemplate就不能自動(dòng)注入了距潘。我們就使用有參構(gòu)造器,傳遞:

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;

/**
 * 因?yàn)檫@個(gè)類不能被spring管理只搁,所以不能直接注入RedisTemplate對象音比。通過構(gòu)造函數(shù)傳遞
 * @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轉(zhuǎn)對象
    UserDTO user = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
    UserHolder.saveUser(user);
    //刷新token的過期時(shí)間
    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>

總結(jié):

在使用Redis替換session的時(shí)候,需要考慮的問題:

1:選擇合適的數(shù)據(jù)結(jié)構(gòu)

2:選擇合適的key

3:選擇合適的存儲(chǔ)粒度

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末氢惋,一起剝皮案震驚了整個(gè)濱河市洞翩,隨后出現(xiàn)的幾起案子稽犁,更是在濱河造成了極大的恐慌,老刑警劉巖菱农,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異柿估,居然都是意外死亡循未,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門秫舌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來的妖,“玉大人,你說我怎么就攤上這事足陨∩┧冢” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵墨缘,是天一觀的道長星虹。 經(jīng)常有香客問我,道長镊讼,這世上最難降的妖魔是什么宽涌? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮蝶棋,結(jié)果婚禮上卸亮,老公的妹妹穿的比我還像新娘。我一直安慰自己玩裙,他們只是感情好兼贸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吃溅,像睡著了一般溶诞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上决侈,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天很澄,我揣著相機(jī)與錄音,去河邊找鬼颜及。 笑死甩苛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的俏站。 我是一名探鬼主播讯蒲,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肄扎!你這毒婦竟也來了墨林?” 一聲冷哼從身側(cè)響起赁酝,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎旭等,沒想到半個(gè)月后酌呆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡搔耕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年隙袁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弃榨。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡菩收,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鲸睛,到底是詐尸還是另有隱情娜饵,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布官辈,位于F島的核電站箱舞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拳亿。R本人自食惡果不足惜褐缠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望风瘦。 院中可真熱鬧队魏,春花似錦、人聲如沸万搔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瞬雹。三九已至昧谊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間酗捌,已是汗流浹背呢诬。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胖缤,地道東北人尚镰。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像哪廓,于是被迫代替她去往敵國和親狗唉。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容