如未做特殊說明,本文均為原創,轉載請注明出處
前言
上篇文章有介紹到,在分布式架構中,會出現很多分布式問題,本文將要概述的就是分布式Session
一致性的問題。
? Session一致性:服務器集群Session
共享問題
那么首先剖析下session
到底是什么鬼。。。
什么是Session
? session 是一種服務端的會話機制。(被稱為域對象)作為范圍是一次會話的范圍。
? 服務器為每個用戶創建一個會話,存儲用戶的相關信息,以便多次請求能夠定位到同一個上下文。這樣,當用戶在應用程序的 Web 頁之間跳轉時,存儲在 Session 對象中的變量將不會丟失,而是在整個用戶會話中一直存在下去。當用戶請求來自應用程序的 Web 頁時,如果該用戶還沒有會話,則 Web 服務器將自動創建一個 Session 對象。當會話過期或被放棄后,服務器將終止該會話。
? Web開發中,web-server可以自動為同一個瀏覽器的訪問用戶自動創建session,提供數據存儲功能。最常見的,會把用戶的登錄信息、用戶信息存儲在session中,以保持登錄狀態。
那么Session為什么會不一致呢?
? 在基于請求與響應的HTTP
通訊中,當第一次請求來時,服務器端會接受到客戶端請求,會創建一個session
,使用響應頭返回sessionid
給客戶端。瀏覽器獲取到sessionid
后會保存到本地cookie
中。
? 當第二次請求來時,客戶端會讀取本地的sessionid
,存放在請求頭中,服務端在請求頭中獲取對象的sessionid
在本地session
內存中查詢。
// 默認創建一個session,默認值為true,如果沒有找到對象的session對象,就會創建該對象,并且將生成的sessionid 存入到響應頭中。
HttpSession session = request.getSession();
@Override
public HttpSession getSession() {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
return getSession(true);
}
// 默認情況下就是true,如果session不存在,則創建一個存入到本地,
// 假設修改為false會是什么樣子的呢,就會關閉session功能。
但是session
屬于會話機制,當當先會話結束時,session
就會被銷毀,并且web
程序會為每一次不同的會話創建不同的session
,所以在分布式場景下,即使是調用同一個方法執行同樣的代碼,但是他們的服務器不同,自然web
程序不同,整個上下文對象也不同,理所當然session
也是不同的。
分布式Session的誕生
? 單服務器web
應用中,session
信息只需存在該服務器中,這是我們前幾年最常接觸的方式,但是近幾年隨著分布式系統的流行,單系統已經不能滿足日益增長的百萬級用戶的需求,集群方式部署服務器已在很多公司運用起來,當高并發量的請求到達服務端的時候通過負載均衡的方式分發到集群中的某個服務器,這樣就有可能導致同一個用戶的多次請求被分發到集群的不同服務器上,就會出現取不到session數據的情況,于是session的共享就成了一個問題。
如上圖,假設用戶包含登錄信息的session都記錄在第一臺web-server
上,反向代理如果將請求路由到另一臺web-server
上,可能就找不到相關信息,而導致用戶需要重新登錄。
Session一致性解決方案
1.session復制(同步)Tomcat自帶該功能
思路:多個web-server
之間相互同步session
,這樣每個web-server
之間都包含全部的session
優點:web-server
支持的功能,應用程序不需要修改代碼
不足:
-
session
的同步需要數據傳輸,占內網帶寬,有時延 - 所有
web-server
都包含所有session
數據,數據量受內存限制,無法水平擴展 - 有更多
web-server
時要歇菜
2.客戶端存儲法
思路:服務端存儲所有用戶的session
,內存占用較大,可以將session存儲到瀏覽器cookie中,每個端只要存儲一個用戶的數據了
優點:服務端不需要存儲
缺點:
- 每次
http
請求都攜帶session
,占外網帶寬 - 數據存儲在端上,并在網絡傳輸,存在泄漏、篡改、竊取等安全隱患
-
session
存儲的數據大小受cookie
限制
這種方式,雖然不是很常用,但也可行。
3.反向代理hash一致性
思路:web-server
為了保證高可用,有多臺冗余,反向代理層能不能做一些事情,讓同一個用戶的請求保證落在一臺web-server
上呢?
使用Nginx
的負載均衡算法其中的hash_ip
算法將ip
固定到某一臺服務器上,這樣就不會出現session
共享問題,因為同一個ip
訪問下,永遠是同一個服務器。
缺點:失去了Nginx
負載均衡的初心。
優點:
- 只需要改
nginx
配置,不需要修改應用代碼 - 負載均衡,只要
hash
屬性是均勻的,多臺web-server
的負載是均衡的 - 可以支持
web-server
水平擴展(session
同步法是不行的,受內存限制)
不足:
- 如果
web-server
重啟,一部分session
會丟失,產生業務影響,例如部分用戶重新登錄 - 如果
web-server
水平擴展,rehash
后session
重新分布,也會有一部分用戶路由不到正確的session
。
4.后端統一集中存儲
思路:將session存儲在web-server后端的存儲層,數據庫或者緩存
優點:
- 沒有安全隱患
- 可以水平擴展,數據庫/緩存水平切分即可
- web-server重啟或者擴容都不會有session丟失
不足:增加了一次網絡調用,并且需要修改應用代碼
對于db存儲還是cache,個人推薦后者:session讀取的頻率會很高,數據庫壓力會比較大。如果有session高可用需求,cache可以做高可用,但大部分情況下session可以丟失,一般也不需要考慮高可用。
方案:使用Spring Session
框架,相當于將Session
之緩存到Redis
中。
問:在項目發布的時候,Session
如何控制不會失效的?
答:使用緩存框架,緩存Session
的值(這里可以使用Redis
加上EhCache
實現一級和危機緩存)
5.使用Token的方式代替Session功能
? 在移動端,是沒有Session
這個概念的,都是使用Token
的方式來實現的。
token
最終會存放到Redis
中,redis-cluster
分片集群中是默認支持分布式共享的。完美的解決的共享問題。
推薦使用 4、5方式。
使用Spring Session實現Session一致性
? Spring Session 可以零侵入的解決Session
一致性的問題。
Spring-Session實現Session共享實現原理以及源碼解析
實現原理這里簡單說明描述:
就是當Web服務器接收到http請求后,當請求進入對應的Filter進行過濾,將原本需要由web服務器創建會話的過程轉交給Spring-Session進行創建,本來創建的會話保存在Web服務器內存中,通過Spring-Session創建的會話信息可以保存第三方的服務中,如:redis,mysql等。Web服務器之間通過連接第三方服務來共享數據,實現Session共享!
/**
* 配置redis服務器連接
*
* @author by Assume
* @date 2019/3/30 20:19
*/
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)//單位秒
public class SessionConfig {
@Value("${redis.hostname}")
private String hostName;
@Value("${redis.port}")
private int port;
@Value("${redis.password}")
private String password;
@Bean
public JedisConnectionFactory connectionFactory() {
JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
connectionFactory.setPort(port);
connectionFactory.setHostName(hostName);
connectionFactory.setPassword(password);
return connectionFactory;
}
}
/**
* 初始化Session配置
*
* @author by Assume
* @date 2019/3/30 20:30
*/
public class SessionInitializer extends AbstractHttpSessionApplicationInitializer {
public SessionInitializer() {
super(SessionConfig.class);
}
}
最靠譜的分布式Session解決方案
基于令牌(Token)方式實現Session解決方案,因為Session本身就是分布式共享連接。
將生成的Token 存入到Redis中。