集群中Session共享解決方案分析


一.為什么要Session共享

  Session存儲在服務器的內存中,比如Java中,Session存放在JVM的中,Session也可以持久化到file,MySQL,redis等,SessionID存放在Cookie中。

  比如一個系統登錄后,假如用戶的請求通過Nginx被轉發到tomcat1上,這時一些當前用戶的信息放入session中,比如登錄信息讓用戶一直處於登錄狀態。那么Nginx負載均衡后,可能用戶刷新頁面后重新跳轉到了tomcat2,而tomcat2上沒有Session,系統就會要求用戶再次去登錄,這樣明顯會給用戶帶來不好的體驗。

二.session共享解決方案(Java)

  1.tomcat可以直接配置

<!-- 第1步:修改server.xml,在Host節點下添加如下Cluster節點 -->
<!-- 用於Session復制 -->
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="8">
    <Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true" />
    <Channel className="org.apache.catalina.tribes.group.GroupChannel">
        <Membership className="org.apache.catalina.tribes.membership.McastService" address="228.0.0.4" 
                    port="45564" frequency="500" dropTime="3000" />
        <!-- 這里如果啟動出現異常,則可以嘗試把address中的"auto"改為"localhost" -->
        <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" address="auto" port="4000" 
                  autoBind="100" selectorTimeout="5000" maxThreads="6" />
        <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
            <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender" />
        </Sender>
        <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector" />
        <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor" />
    </Channel>
    <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter="" />
    <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve" />
    <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer" tempDir="/tmp/war-temp/" 
              deployDir="/tmp/war-deploy/" watchDir="/tmp/war-listen/" watchEnabled="false" />
    <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener" />
</Cluster>

 

<!-- 第2步:在web.xml中添加如下節點 -->
<!-- 用於Session復制 -->
<distributable/>

  2.使用token重寫Session

    這里token是JSON Web Token,一般用它來替換掉Session實現數據共享(共享token?)

  3.使用Cookie的方式

    這個方式原理是將系統用戶的Session信息加密、序列化后以Cookie的方式, 統一 種植在根域名下(如:.host.com),利用瀏覽器訪問該根域名下的所有二級域名站點時,會傳遞與之域名對應的所有Cookie內容的特性,從而實現 用戶的Cookie化Session 在多服務間的共享訪問。

    這個方案的優點無需額外的服務器資源;缺點是由於受http協議頭信心長度的限制,僅能夠存儲小部分的用戶信息,同時Cookie化的 Session內容需要進行安全加解密(如:采用DES、RSA等進行明文加解密;再由MD5、SHA-1等算法進行防偽認證),另外它也會占用一定的帶寬資源,因為瀏覽器會在請求當前域名下任何資源時將本地Cookie附加在http頭中傳遞到服務器,最重要的是存在安全隱患。

  4.使用Nginx中的ip綁定方式

    這個只需要在Nginx中簡單配置一句 ip_hash; 就可以了,但是該方式的缺點也很明顯。具體可以參考的的上一篇博客 https://www.cnblogs.com/ywb-articles/p/10686673.html

  5.基於數據庫的Session共享

    以為MySQL為例,每次將session數據存到數據庫中。這個方案還是比較可行的,不少開發者使用了這種方式。但它的缺點在於Session的並發讀寫能力取決於MySQL數據庫的性能,對數據庫的壓力大,同時需要自己實現Session淘汰邏輯,以便定時從數據表中更新、刪除 Session記錄,當並發過高時容易出現表鎖,雖然可以選擇行級鎖的表引擎,但很多時候這個方案不是最優方案。

  6.使用redis的方式(Spring Session + redis實現,也可以是其他NoSQL)(推薦)

     該方式配置簡單,數據安全且穩定,效率高,被普遍使用。下面是簡單的案例

      1.創建Spring Boot項目,導入下列相關的依賴,還有web模塊的依賴

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>4.3.1</version>
        </dependency>

        <!-- springboot - Redis -->
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
        <!--spring session 與redis應用基本環境配置,需要開啟redis后才可以使用,不然啟動Spring boot會報錯 -->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

      2.在主配置類中開啟RedisHttpSession

@EnableRedisHttpSession

      3.編寫控制器

@Controller
public class MyController {
@RequestMapping("/set")
public String setSession (HttpServletRequest request){
String name = (String)request.getSession().getAttribute("name");
if(name !=null){//如果session存在了,直接跳轉到成功頁面,否則添加session並跳轉到首頁
return "success";
}
request.getSession().setAttribute("name","zhangsan");
return "index";
}

@RequestMapping("/get")
public String getSession (HttpServletRequest request){
String name = (String)request.getSession().getAttribute("name");
System.out.println(name);
if(name != null){
return "success";
}else{
return "error";
}
}
}

    4.在application.properties中配置

server.port=8080
#遠程連接redis的配置 spring.redis.host=192.168.2.130 spring.redis.port=6379 spring.redis.database=0 spring.redis.password=123456 spring.redis.timeout=5000 spring.redis.pool.max-idle=8 spring.redis.pool.min-idle=0 spring.redis.pool.max-active=8 spring.redis.max-wait=-1

    5.在template中寫幾個頁面方便測試,這幾個頁面中每個頁面放一句話就可以了,如下圖

      

    6.運行Spring Boot項目,再修改配置文件中server.port=8081,運行Spring Boot項目(同時用兩個不同端口的tomcat運行項目)

    7.測試:

      首先查看session是否存在,如下圖所示,發現session都是null,跳轉到錯誤頁面

        

         

      在8080中第一次set,發現session為null后跳轉到index頁面,如下圖

        

      刷新后,因為session存在,所以跳轉到了成功頁面

        

      再在8081中get,發現也跳轉到了成功頁面,說明8081的tomcat獲取到了session

        

      最后,我們可以在redis中查看,發現多了一個session 數據包,如下圖

        

      說明Spring Session + redis成功解決集群中session共享的問題

       注意:在redis中刪除這個數據包,發現8080和8081都get不到session了,說明session沒有存放在JVM中了,而是轉存放在redis中了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM