你還不明白如何解決分布式Session?看這篇就夠了!
Hollis的新書(shū)限時(shí)折扣中,一本深入講解Java基礎(chǔ)的干貨筆記!
平常做的項(xiàng)目都是在一臺(tái)應(yīng)用系統(tǒng),并且所有的操作都在一臺(tái)Tomcat服務(wù)器上,并不會(huì)引發(fā)Session共享的問(wèn)題,所以并不會(huì)對(duì)我們的系統(tǒng)產(chǎn)生影響,但是當(dāng)我們部署多個(gè)微服務(wù)的時(shí)候,再搭配N(xiāo)ginx進(jìn)行負(fù)載均衡時(shí),如果不處理分布式Session問(wèn)題,我們?cè)谙到y(tǒng)中訪問(wèn)不同功能時(shí)就會(huì)頻繁出現(xiàn)用戶(hù)登錄的操作。
圖解分析原因:

前提:用戶(hù)登錄功能和圖中的商品訂單模塊、秒殺搶購(gòu)模塊屬于單獨(dú)的微服務(wù)模塊
用戶(hù)登錄成功后想要訪問(wèn)圖中其他兩個(gè)模塊的功能時(shí),由于Nginx使用默認(rèn)負(fù)載均衡策略(輪詢(xún)),這時(shí)請(qǐng)求會(huì)按照時(shí)間順序逐一分發(fā)到后端應(yīng)用上,也就是說(shuō)用戶(hù)在Tomcat1上登錄成功之后,用戶(hù)的信息放在Tomcat1的Session里,過(guò)了一會(huì),用戶(hù)想要進(jìn)行秒殺活動(dòng)的功能操作,請(qǐng)求又被Nginx分發(fā)到了Tomcat2,而這時(shí)的Tomcat2上的Session里還沒(méi)有用戶(hù)信息,于是就是出現(xiàn)讓用戶(hù)重新登錄的情況,在微服務(wù)分布式項(xiàng)目中,不同的功能模塊必然會(huì)被分列成各自的微服務(wù),假設(shè)訪問(wèn)一個(gè)功能都需要重新登錄一次,用戶(hù)的體驗(yàn)必然會(huì)大幅度下降!
那如何來(lái)解決分布式Session問(wèn)題呢?
一、解決方案列舉
1. Session復(fù)制
優(yōu)點(diǎn):
無(wú)需修改代碼,只需要修改Tomcat配置
缺點(diǎn):
Session同步傳輸占用內(nèi)網(wǎng)寬帶 多臺(tái)Tomcat同步性能指數(shù)級(jí)下降 Session占用內(nèi)存,無(wú)法有效水平擴(kuò)展
2. 前端存儲(chǔ)
優(yōu)點(diǎn):
不占用服務(wù)器內(nèi)存
缺點(diǎn):
存在安全風(fēng)險(xiǎn) 數(shù)據(jù)大小受cookie限制 占用外網(wǎng)寬帶
3. Session粘滯
優(yōu)點(diǎn):
無(wú)需修改代碼 服務(wù)端可以水平擴(kuò)展
缺點(diǎn):
增加新機(jī)器,會(huì)重新Hash,導(dǎo)致重新登錄 應(yīng)用重新啟動(dòng)后,需要重新登錄
4. 后端集中存儲(chǔ)
優(yōu)點(diǎn):
安全 容易水平擴(kuò)展
優(yōu)點(diǎn):
增加復(fù)雜度 需要修改代碼
二、Java代碼實(shí)現(xiàn)解決分布式Session
1. SpringSession - Redis解決分布式Session
添加依賴(lài)
<!--Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--commons-pools2 對(duì)象池依賴(lài)-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--spring session 依賴(lài),公眾號(hào):Java精選-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
添加Redis配置
## Redis配置
spring:
redis:
# 服務(wù)器地址
host: localhost
# 端口
port: 6379
# 數(shù)據(jù)庫(kù)
database: 0
# 超時(shí)時(shí)間
connect-timeout: 10000ms
lettuce:
pool:
# 最大連接數(shù)
max-active: 8
# 最大連接阻塞等待時(shí)間 默認(rèn) -1
max-wait: 10000ms
# 最大空閑時(shí)間 默認(rèn)8
max-idle: 200
# 最小空閑連接 默認(rèn)8
min-idle: 5
業(yè)務(wù)邏輯實(shí)現(xiàn)
/**
* 登錄功能
* @param loginVo
* @return
*/
@Override
public RespBean doLogin(LoginVo loginVo, HttpServletRequest request, HttpServletResponse response) {
String username = loginVo.getUserName();
String password = loginVo.getPassword();
User user = userMapper.selectByUserName(username);
if (user == null){
throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
}
//判斷密碼是否正確
if (!MDUtils.formPassToDBPass(password,user.getSalt()).equals(user.getPassword())){
throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
}
//使用UUID生成字符串代替Cookie
String ticket = UUIDUtil.uuid();
request.getSession().setAttribute(ticket,user);
CookieUtil.setCookie(request,response,"userTicket",ticket);
return RespBean.success();
}
視圖控制層
/**
* 跳轉(zhuǎn)商品列表
* @param session
* @param model
* @return
*/
@RequestMapping("/toList")
public String toList(HttpSession session, Model model,@CookieValue("userTicket")String ticket){
if (StringUtils.isEmpty(ticket)){
return "login";
}
User user = (User) session.getAttribute(ticket);
if (user == null){
return "login";
}
model.addAttribute("user",user);
return "goodsList";
}
登錄測(cè)試
打開(kāi)Redis管理軟件發(fā)現(xiàn)Session信息已經(jīng)添加到Redis中了

2. Redis解決分布式Session
導(dǎo)入依賴(lài)
<!--Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--commons-pools2 對(duì)象池依賴(lài)-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
Redis配置參考 【SpringSession - Redis解決分布式Session】
業(yè)務(wù)邏輯層
@Override
public RespBean doLogin(LoginVo loginVo, HttpServletRequest request, HttpServletResponse response) {
String username = loginVo.getUserName();
String password = loginVo.getPassword();
User user = userMapper.selectByUserName(username);
if (user == null){
throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
}
//判斷密碼是否正確
if (!MDUtils.formPassToDBPass(password,user.getSalt()).equals(user.getPassword())){
throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
}
//成功Cookie
String ticket = UUIDUtil.uuid();
//將用戶(hù)信息存入到redis中
redisTemplate.opsForValue().set("userTicket",ticket);
redisTemplate.opsForValue().set("user:"+ticket,user);
//request.getSession().setAttribute(ticket,user);
CookieUtil.setCookie(request,response,"userTicket",ticket);
return RespBean.success();
}
/**
* 根據(jù)cookie獲取cookie
* @param ticket
* @return
*/
@Override
public User getUserByByCookie(String ticket,HttpServletRequest request,HttpServletResponse response) {
if (StringUtils.isEmpty(ticket)){
return null;
}
User user = (User) redisTemplate.opsForValue().get("user:" + ticket);
if (user == null){
CookieUtil.setCookie(request,response,"userTicket",ticket);
}
return user;
}
視圖控制層
/**
* 跳轉(zhuǎn)商品列表
* @param session
* @param model
* @return
*/
@RequestMapping("/toList")
public String toList(HttpSession session, Model model,HttpServletRequest request,HttpServletResponse response){
String ticket = (String) redisTemplate.opsForValue().get("userTicket");
if (StringUtils.isEmpty(ticket)){
return "login";
}
//User user = (User) session.getAttribute(ticket);
User user = userService.getUserByByCookie(ticket, request, response);
if (user == null){
return "login";
}
model.addAttribute("user",user);
return "goodsList";
}
測(cè)試成功

查看Redis管理工具

作者:派 大 星.
https://blog.csdn.net/Gaowumao/article/details/124309548
完
我的新書(shū)《深入理解Java核心技術(shù)》已經(jīng)上市了,上市后一直蟬聯(lián)京東暢銷(xiāo)榜中,目前正在6折優(yōu)惠中,想要入手的朋友千萬(wàn)不要錯(cuò)過(guò)哦~長(zhǎng)按二維碼即可購(gòu)買(mǎi)~
長(zhǎng)按掃碼享受6折優(yōu)惠
往期推薦

一次簡(jiǎn)單的 JVM 調(diào)優(yōu),拿去寫(xiě)到簡(jiǎn)歷里

高并發(fā)下如何防重?

入職就想run的公司特征
有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號(hào)
好文章,我在看??

