天天看點

spring-session實作分布式叢集session的共享

  HttpSession是通過Servlet容器建立和管理的,像Tomcat/Jetty都是儲存在記憶體中的。但是我們把應用搭建成分布式的叢集,然後利用LVS或Nginx做負載均衡,那麼來自同一使用者的Http請求将有可能被分發到多個不同的應用中。那問題來了,如何保證不同的應用能夠共享同一份session資料呢?最簡單的想法,就是把session資料儲存到記憶體以外的一個統一的地方,例如Memcached/Redis等資料庫中。那問題又來了,如何替換掉Servlet容器建立和管理的HttpSession的實作呢?

  1、利用Servlet容器提供的插件功能,自定義HttpSession的建立和管理政策,并通過配置的方式替換掉預設的政策。這方面其實早就有開源項目了,例如memcached-session-manager(可以參考負載均衡+session共享(memcached-session-manager實作),以及tomcat-redis-session-manager。不過這種方式有個缺點,就是需要耦合Tomcat/Jetty等Servlet容器的代碼。

  2、設計一個Filter,利用HttpServletRequestWrapper,實作自己的 getSession()方法,接管建立和管理Session資料的工作。spring-session就是通過這樣的思路實作的。

參考 spring-session之一 初探 spring-session

  本部落格不涉及session解釋,關于session大家自行去查資料;關于spring-session的相關概念大家可以去spring官網查閱(http://projects.spring.io/spring-session/)。

  我們先來看下單機應用,應用很簡單,就是在session中設定變量,然後擷取這些設定的變量進行展示 ,具體代碼如下

  pom.xml:

spring-session實作分布式叢集session的共享
spring-session實作分布式叢集session的共享

View Code

  web.xml

spring-session實作分布式叢集session的共享
spring-session實作分布式叢集session的共享

  SessionServlet.java

spring-session實作分布式叢集session的共享
spring-session實作分布式叢集session的共享

  index.jsp

spring-session實作分布式叢集session的共享
spring-session實作分布式叢集session的共享

  整個項目結構非常簡單,如下如

  

spring-session實作分布式叢集session的共享

  本地運作起來,效果如下

spring-session實作分布式叢集session的共享

  火狐浏覽器與360浏覽器代表不同的使用者,各自都能擷取各自session中的設定的全部變量,很正常,沒毛病。

  單機應用中,session肯定沒問題,就存在本地的servlet容器中,那麼在分布式叢集中會像單機一樣正常嗎?我們接着往下看

  搭建高可用的、實作負載均衡的分布式叢集環境可參考nginx實作請求的負載均衡 + keepalived實作nginx的高可用,沒搭建的需要先把分布式環境搭建起來

    應用不變,代碼與單機中的完全一緻,将代碼部署到分布式叢集中去

    全部運作起來,效果如下

spring-session實作分布式叢集session的共享

    結果是:無論給session設定多少個值,session中的值都擷取不到(離我的預期還是有差距,具體什麼差距請看我的問題)

    應用有所變化,代碼與之前有所不同,具體差別如下(SessionServlet與index.jsp不變)

    pom.xml

spring-session實作分布式叢集session的共享
spring-session實作分布式叢集session的共享

    web.xml

spring-session實作分布式叢集session的共享
spring-session實作分布式叢集session的共享

    spring-session.xml

spring-session實作分布式叢集session的共享
spring-session實作分布式叢集session的共享

    session-redis.properties

    整個項目結構如下如

    

spring-session實作分布式叢集session的共享

    将代碼部署到分布式叢集中去,重新運作起來,效果如下

spring-session實作分布式叢集session的共享

    效果與單機應用的效果一樣,這也就說明了session共享實作了,我們來看下redis中是否有session資料,如下圖,redis中是存有session資訊的

spring-session實作分布式叢集session的共享

    前面是用的一台redis伺服器:192.168.0.221做的session伺服器,隻有一台的話一旦出現單點故障,那麼整個session服務就沒了,影響太大。為了避免出現單點故障問題,需要搭建一個session叢集。搭建叢集的時候,登入認證就不要打開了(requirepass注釋不要打開,具體原因後續會有說明)

    redis叢集環境

      192.168.0.221:3個節點(7000,7001,7002)

      192.168.0.223:3個節點(7003,7004,7005)

    redis叢集搭建的過程具體可參考Redis叢集搭建與簡單使用

    redis各個節點搭建成功之後,啟動情況如下

    192.168.0.221

      

spring-session實作分布式叢集session的共享

    192.168.0.223

spring-session實作分布式叢集session的共享

    # ./redis-trib.rb create  --replicas  1  192.168.0.221:7000 192.168.0.221:7001  192.168.0.221:7002 192.168.0.223:7003  192.168.0.223:7004  192.168.0.223:7005

     随便在哪一台(192.168.0.221、192.168.0.223中任意一台)執行如上指令即可,若出現下圖資訊,則表示叢集搭建成功

spring-session實作分布式叢集session的共享

    redis叢集已經搭建好,接下來就是将redis叢集應用到我們的工程中,代碼是在spring-sesson實作session共享的基礎上進行的,有差别的檔案就隻有spring-session.xml和session-redis.properties

spring-session實作分布式叢集session的共享
spring-session實作分布式叢集session的共享
spring-session實作分布式叢集session的共享
spring-session實作分布式叢集session的共享

    據我親測,效果與單節點redis的效果是一樣的,我就不放效果圖了,但是大家最好還是去親測一下。

    工程位址:spring-session

  1、單機應用中,HttpSession是通過Servlet容器建立和管理的,servlet容器一旦停止服務,那麼session也随之消失;但如果session被儲存到redis中,隻要redis服務沒停且session在有效期間内,那麼servlet容器停止服務了,session還是存在的,這有什麼好處了,好處就是servlet容器出現閃停閃修複的情況,使用者就不用重新登入了。

  2、spring中的ContextLoaderListener與DispatcherServlet不知道大家了解不,嚴格的來講這兩者負責加載的bean是有差別的,也最好設定成加載不同的bean,不然可能會發生一些你意想不到的情況。不知道差別的可以去閱讀淺談ContextLoaderListener及其上下文與DispatcherServlet的差別。

  3、測試的時候可以從底往高進行測試,也就是說先測試tomcat,再測試nginx,最後測試VIP。

  4、redis中可以手動删除session,不一定非要等到session過期。

  5、分布式測試的時候,最好在index.jsp加一些标記(例如ip,就寫死成index.jsp所在伺服器的ip),用來區分不同的伺服器,那樣測試起來更加明顯。

  6、spring-session官網提供的例子中,用注解的方式進行配置的,可我壓根就沒看到web.xml中有spring的配置,但實際上spring容器啟動了,并且執行個體化了需要的bean,應用也能跑起來,這讓我很是費解,spring容器是什麼時候初始化的? 這其實是servlet3.0的新特性,servlet3.0開始支援無web.xml的注解配置方式,而AbstractHttpSessionApplicationInitializer(AbstractHttpSessionApplicationInitializer implements WebApplicationInitializer)就是接入點(就如在web.xml中配置spring一樣),更多的詳細資訊需要大家去查閱資料了。

  7、設定redis叢集的時候,若設定了密碼登入(将redis.conf中requirepass打開并設定了自己的密碼),那麼執行# ./redis-trib.rb create  --replicas  1  192.168.0.221:7000 192.168.0.221:7001  192.168.0.221:7002 192.168.0.223:7003  192.168.0.223:7004  192.168.0.223:7005的時候會提示[ERR] Sorry, can't connect to node 192.168.0.221:7000,那麼需要将/usr/lib/ruby/gems/1.8/gems/redis-3.3.0/lib/redis/client.rb中的password改成自己的密碼即可,當然了,redis的所有執行個體的密碼要一緻,或者說全部的redis.conf中密碼設定的值要一樣,修改/usr/lib/ruby/gems/1.8/gems/redis-3.3.0/lib/redis/client.rb如下

    之前說過,利用redis叢集來存儲session的時候,登入認證不要打開,因為jedis好像還不支援redis的叢集密碼設定。

  1、分布式叢集的沒設定session共享的情況中,為什麼設定進去的值一個都擷取不到,按我的了解應該是每次傳回回來的資料應該是某個tomcat上的session中的資料,當設定的值多了後,每次都應該有值傳回,而測試得到的結果卻是無論你設定多少值,沒有任何值傳回回來,這裡沒搞清楚原因。

  2、jedis這麼設定叢集密碼,目前還不知道,知道的請留個言; 或者知道lettuce怎麼設定redis叢集和叢集密碼的也可以留個言;再或者有其他方式的也可以留個言; 在此表示感謝了!

  spring-session之一 初探 spring-session

  利用spring session解決共享Session問題

  【Spring】淺談ContextLoaderListener及其上下文與DispatcherServlet的差別

  Spring Session

  探 Spring 3.1之無web.xml式 基于代碼配置的servlet3.0應用

  Redis cluster tutorial

  Redis Cluster

  Redis叢集搭建與簡單使用

繼續閱讀