分佈式Session的幾種解決方案
1、Session複製
Tomcat服務器互相同步session。
優點:
web-server (Tomcat) 原生支持
,只需要修改配置文件
缺點:
1、session同步需要數據傳輸,佔用大量網絡帶寬,降低了服務器羣的業務處理能力
2、任意一臺web-server保存的數據都是所有web-server的sesdion總和,受到內存限制無法水平擴展更多的web-server
3、大型分佈式集羣情況下,由於所有web-server都全量保存數據,所以此方案不可取。
2、客戶端存儲
將session存儲在cookie中。
優點:
服務器不需存儲session,用戶保存自己的session信息到cookie中,節省服務端資源。
缺點:
這只是一種思路,不會使用該方式,具體原因如下:
1、每次http請求,攜帶用戶在cookie中的完整信息,浪費網絡帶寬。
2、session數據放在cookie中,cookie有長度限制4K,不能保存大量信息。
3、session數據放在cookie中,存在泄漏、算改、竊取等安全隱患。
3、Hash一致性
利用ip_hash
的一致性,瀏覽器不變,IP不變,讓每一次訪問都落在同一個服務器上。同時也可以配合id值,比如圖中的sid=456的請求每次落在第一個服務器,sid=123的請求每次落在第二個服務器。
優點:
1、只需要改nginx配置,不需要修改應用代碼。
2、負載均衡,只要hash屬性的值分佈是均勻的,多臺web-server的負載是均衡的。
3、可以支持web-server水平擴展(session同步法是不行的,受內存限制)。
缺點:
1、session還是存在web-server中的,所以web-server重啓可能導致部分session丟失,影響業務,如部分用戶需要重新登錄。
2、如果web-server水平擴展,rehash後session重新分佈,也會有一部分用戶路由不到正確的session。
但是以上缺點問題也不是很大,因爲session本來都是有有效期的。所以這兩種反向代理的方式可以使用。
4、統一存儲
將session
存儲到數據庫(Redis)
中,每次都從數據庫中獲取。
優點:
1、沒有安全隱患
2、可以水平擴展,數據庫/緩存水平切分即可
3、web-server重啓或者擴容都不會有session丟失
缺點:
增加了一次網絡調用,並且需要修改應用代碼;如將所有的getSession方法替換爲從Redis查數據的方式,redis獲取數據比內存慢很多
上面缺點可以用SpringSession
完美解決
5、不同服務,子域Session共享
jsessionid
這個cookie默認是當前系統域名的。當我們分拆服務,不同域名部署的時候,我們可以使用如下解決方案:
第一次使用Session的時候,命令瀏覽器Cookie保存卡號(JESSIONID),以後瀏覽器訪問哪個網站都會帶上這個網站的cookie。
髮卡的時候指定域名爲父域名,即使是子域系統發的卡,也能讓父域直接使用。
給父域發jesessionid
卡,子域共享。比如給liuchengyin.com髮卡,auth.liuchengyin.com、item.liuchengyin.com共享。
SpringSession的使用
SpringBoot整合SpringSession。官方文檔:spring-session-data-redis文檔
1、引入spring-session-data-redis依賴
<!-- 整合SpringSession完成Session共享問題 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
2、配置文件 - properties或yml
所有使用分佈式Session的微服務都要配置,當然肯定還需要配置Redis相關的內容。比如在A服務中存儲,在B、C服務中獲取,A、B、C服務都需要配置。
# Session保存類型 - Redis
spring.session.store-type=redis
# Session過期時間 - 默認30分鐘
server.servlet.session.timeout=30m
# 同時,也需要配置Redis連接信息
spring.redis.host=192.168.200.134
spring.redis.port=6379
3、配置Redis - 如果已經配置過則無需再配置
@EnableRedisHttpSession // 使用RedisHttpSession,
public class Config {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
}
4、配置作用域和Session序列化
@Configuration
public class LiuchengyinSessionConfig {
/**
* 設置作用域
*/
@Bean
public CookieSerializer cookieSerializer(){
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
/*
設置父域 - liuchengyin.com
則xxx.liuchengyin.com都會共享
*/
cookieSerializer.setDomainName("liuchengyin.com");
cookieSerializer.setCookieName("LCYSESSION"); // 自定義SessionId
return cookieSerializer;
}
/**
* JSON序列化 - 使用FastJson,需要引入Fastjson的依賴
*/
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(){
return new GenericFastJsonRedisSerializer();
}
}
5、正常調用session存儲數據即可
public String saveData(HttpSession session, Long id){
// 從數據庫中查詢數據
User user = userDao.selectUserById(id);
// 保存數據
session.setAttribute("loginUser",user);
return user;
}
6、查看Redis中保存的數據
7、查看瀏覽器Cookie
此時就可以去.liuchengyin.com
的子域也可以獲取到session
。
SpringSession原理淺析
原理其實就是運用了裝飾者模式。且Redis中的Session也有過期時間,和原生Session一致,會自動續期。
@EnableRedisHttpSession註解
導入了RedisHttpSessionConfiguration
配置
①RedisHttpSessionConfiguration
添加了RedisOperationsSessionRepository
組件,即使用Redis操作session,session的增刪改查封裝類。即SessionRepository ====>>> 【RedisOperationsSessionRepository】
②RedisHttpSessionConfiguration
繼承自SpringHttpSessionConfiguration
,可以在SpringHttpSessionConfiguration
中看到一個過濾器SessionRepositoryFilter<? extends Session>
,即Session存儲過濾器,每個請求過來都必須經過Filter
。
1、創建Session的時候就自動從容器中獲取了SessionRepository
。
2、原始的request和response都被包裝成SessionRepositoryRequestWrapper和SessionRepositoryResponseWrapper
3、以後獲取session
,即通過reuqest.getSession()。就相當於調用wrappedRequest.getSession()。如果wrappedRequest.getSession()有自定義的實現,就相當於Session的獲取方法改變了。
4、wrappedRequest.getSession()就是從SessionRepository獲取到的。