分布式解決方案-分布式session一致性問題


如未做特殊說明,本文均為原創(chuàng),轉載請注明出處

前言
上篇文章有介紹到,在分布式架構中,會出現(xiàn)很多分布式問題,本文將要概述的就是分布式Session一致性的問題。

? Session一致性:服務器集群Session共享問題

那么首先剖析下session到底是什么鬼。。。

什么是Session

? session 是一種服務端的會話機制。(被稱為域對象)作為范圍是一次會話的范圍。

? 服務器為每個用戶創(chuàng)建一個會話,存儲用戶的相關信息,以便多次請求能夠定位到同一個上下文。這樣,當用戶在應用程序的 Web 頁之間跳轉時,存儲在 Session 對象中的變量將不會丟失,而是在整個用戶會話中一直存在下去。當用戶請求來自應用程序的 Web 頁時,如果該用戶還沒有會話,則 Web 服務器將自動創(chuàng)建一個 Session 對象。當會話過期或被放棄后,服務器將終止該會話。

? Web開發(fā)中,web-server可以自動為同一個瀏覽器的訪問用戶自動創(chuàng)建session,提供數(shù)據(jù)存儲功能。最常見的,會把用戶的登錄信息、用戶信息存儲在session中,以保持登錄狀態(tài)。

那么Session為什么會不一致呢?

? 在基于請求與響應的HTTP通訊中,當?shù)谝淮握埱髞頃r,服務器端會接受到客戶端請求,會創(chuàng)建一個session,使用響應頭返回sessionid給客戶端。瀏覽器獲取到sessionid后會保存到本地cookie中。

第一次請求

? 當?shù)诙握埱髞頃r,客戶端會讀取本地的sessionid,存放在請求頭中,服務端在請求頭中獲取對象的sessionid在本地session內存中查詢。

第二次請求
// 默認創(chuàng)建一個session,默認值為true,如果沒有找到對象的session對象,就會創(chuàng)建該對象,并且將生成的sessionid 存入到響應頭中。
HttpSession session = request.getSession();

    @Override
    public HttpSession getSession() {

        if (request == null) {
            throw new IllegalStateException(
                            sm.getString("requestFacade.nullRequest"));
        }

        return getSession(true);
    }
// 默認情況下就是true,如果session不存在,則創(chuàng)建一個存入到本地,
// 假設修改為false會是什么樣子的呢,就會關閉session功能。

但是session屬于會話機制,當當先會話結束時,session就會被銷毀,并且web程序會為每一次不同的會話創(chuàng)建不同的session,所以在分布式場景下,即使是調用同一個方法執(zhí)行同樣的代碼,但是他們的服務器不同,自然web程序不同,整個上下文對象也不同,理所當然session也是不同的。

分布式Session的誕生

? 單服務器web應用中,session信息只需存在該服務器中,這是我們前幾年最常接觸的方式,但是近幾年隨著分布式系統(tǒng)的流行,單系統(tǒng)已經(jīng)不能滿足日益增長的百萬級用戶的需求,集群方式部署服務器已在很多公司運用起來,當高并發(fā)量的請求到達服務端的時候通過負載均衡的方式分發(fā)到集群中的某個服務器,這樣就有可能導致同一個用戶的多次請求被分發(fā)到集群的不同服務器上,就會出現(xiàn)取不到session數(shù)據(jù)的情況,于是session的共享就成了一個問題。

session一致性問題的產(chǎn)生

如上圖,假設用戶包含登錄信息的session都記錄在第一臺web-server上,反向代理如果將請求路由到另一臺web-server上,可能就找不到相關信息,而導致用戶需要重新登錄。

Session一致性解決方案

1.session復制(同步)Tomcat自帶該功能

session復制

思路:多個web-server 之間相互同步session,這樣每個web-server之間都包含全部的session

優(yōu)點web-server 支持的功能,應用程序不需要修改代碼

不足

  • session 的同步需要數(shù)據(jù)傳輸,占內網(wǎng)帶寬,有時延
  • 所有web-server 都包含所有session數(shù)據(jù),數(shù)據(jù)量受內存限制,無法水平擴展
  • 有更多web-server 時要歇菜

2.客戶端存儲法

思路:服務端存儲所有用戶的session,內存占用較大,可以將session存儲到瀏覽器cookie中,每個端只要存儲一個用戶的數(shù)據(jù)了

優(yōu)點:服務端不需要存儲

缺點

  • 每次http請求都攜帶session,占外網(wǎng)帶寬
  • 數(shù)據(jù)存儲在端上,并在網(wǎng)絡傳輸,存在泄漏、篡改、竊取等安全隱患
  • session存儲的數(shù)據(jù)大小受cookie限制

這種方式,雖然不是很常用,但也可行。

3.反向代理hash一致性

思路web-server為了保證高可用,有多臺冗余,反向代理層能不能做一些事情,讓同一個用戶的請求保證落在一臺web-server 上呢?

使用Nginx的負載均衡算法其中的hash_ip算法將ip固定到某一臺服務器上,這樣就不會出現(xiàn)session共享問題,因為同一個ip訪問下,永遠是同一個服務器。

缺點:失去了Nginx負載均衡的初心。

優(yōu)點

  • 只需要改nginx配置,不需要修改應用代碼
  • 負載均衡,只要hash 屬性是均勻的,多臺web-server的負載是均衡的
  • 可以支持web-server水平擴展(session 同步法是不行的,受內存限制)

不足

  • 如果web-server重啟,一部分session會丟失,產(chǎn)生業(yè)務影響,例如部分用戶重新登錄
  • 如果web-server水平擴展,rehashsession重新分布,也會有一部分用戶路由不到正確的session。

4.后端統(tǒng)一集中存儲

將Session存儲到數(shù)據(jù)庫或者Redis中

思路:將session存儲在web-server后端的存儲層,數(shù)據(jù)庫或者緩存

優(yōu)點

  • 沒有安全隱患
  • 可以水平擴展,數(shù)據(jù)庫/緩存水平切分即可
  • web-server重啟或者擴容都不會有session丟失

不足:增加了一次網(wǎng)絡調用,并且需要修改應用代碼

對于db存儲還是cache,個人推薦后者:session讀取的頻率會很高,數(shù)據(jù)庫壓力會比較大。如果有session高可用需求,cache可以做高可用,但大部分情況下session可以丟失,一般也不需要考慮高可用。

方案:使用Spring Session框架,相當于將Session之緩存到Redis中。

問:在項目發(fā)布的時候,Session如何控制不會失效的?

答:使用緩存框架,緩存Session的值(這里可以使用Redis加上EhCache實現(xiàn)一級和危機緩存)

5.使用Token的方式代替Session功能

? 在移動端,是沒有Session這個概念的,都是使用Token的方式來實現(xiàn)的。

token最終會存放到Redis中,redis-cluster分片集群中是默認支持分布式共享的。完美的解決的共享問題。

推薦使用 4、5方式。

使用Spring Session實現(xiàn)Session一致性

? Spring Session 可以零侵入的解決Session一致性的問題。

Spring-Session實現(xiàn)Session共享實現(xiàn)原理以及源碼解析

實現(xiàn)原理這里簡單說明描述:

就是當Web服務器接收到http請求后,當請求進入對應的Filter進行過濾,將原本需要由web服務器創(chuàng)建會話的過程轉交給Spring-Session進行創(chuàng)建,本來創(chuàng)建的會話保存在Web服務器內存中,通過Spring-Session創(chuàng)建的會話信息可以保存第三方的服務中,如:redis,mysql等。Web服務器之間通過連接第三方服務來共享數(shù)據(jù),實現(xiàn)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)方式實現(xiàn)Session解決方案,因為Session本身就是分布式共享連接。

將生成的Token 存入到Redis中。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容