任何一種技術的出現,都是來解決特定的問題的!
本篇開始學習Spring-Session相關的一些知識學習整理,讓我們開始吧!
Spring-Session介紹
- Spring-Session使用的場景?
HttpSession是通過Servlet容器進行創建和管理的,在單機環境中。通過Http請求創建的Session信息是存儲在Web服務器內存中,如Tomcat/Jetty。
假如當用戶通過瀏覽器訪問應用服務器,session信息中保存了用戶的登錄信息,並且session信息沒有過期失,效那么用戶就一直處於登錄狀態,可以做一些登錄狀態的業務操作!
但是現在很多的服務器都采用分布式集群的方式進行部署,一個Web應用,可能部署在幾台不同的服務器上,通過LVS或者Nginx等進行負載均衡(一般使用Nginx+Tomcat實現負載均衡)。此時來自同一用戶的Http請求將有可能被分發到不同的web站點中去(如:第一次分配到A站點,第二次可能分配到B站點)。那么問題就來了,如何保證不同的web站點能夠共享同一份session數據呢?
假如用戶在發起第一次請求時候訪問了A站點,並在A站點的session中保存了登錄信息,當用戶第二次發起請求,通過負載均衡請求分配到B站點了,那么此時B站點能否獲取用戶保存的登錄的信息呢?答案是不能的,因為上面說明,Session是存儲在對應Web服務器的內存的,不能進行共享,此時Spring-session就出現了,來幫我們解決這個session共享的問題!
- 如何進行Session共享呢?
簡單點說就是請求http請求經過Filter職責鏈,根據配置信息過濾器將創建session的權利由tomcat交給了Spring-session中的SessionRepository,通過Spring-session創建會話,並保存到對應的地方。
實際上實現Session共享的方案很多,其中一種常用的就是使用Tomcat、Jetty等服務器提供的Session共享功能,將Session的內容統一存儲在一個數據庫(如MySQL)或緩存(如Redis,Mongo)中,
而上面說的使用Nginx也可以,使用ip_hash策略。
【Nginx】實現負載均衡的幾種方式
在使用Nginx的ip_hash策略時候,每個請求按訪問ip的hash結果分配,這樣每個訪客固定訪問一個后端服務器,也可以解決session的問題。
- Spring官方介紹
Why Spring Session & HttpSession?
Spring會話提供了與HttpSession的透明集成,允許以應用程序容器(即Tomcat)中性的方式替換HttpSession,但是我們從中得到了什么好處呢?
-
集群會話——Spring會話使支持集群會話變得微不足道,而不需要綁定到應用程序容器的特定解決方案。
-
多個瀏覽器會話——Spring會話支持在單個瀏覽器實例中管理多個用戶會話(也就是多個經過驗證的帳戶,類似於谷歌)。
-
RESTful api——Spring會話允許在header中提供會話id以使用RESTful api。
-
Spring Session & WebSockets的完美集成。
項目搭建
整個項目的整體骨架:
基於XML配置方式的Spring Session
本次只講解xml配置方式,javaConfig配置可以參考官方文檔:Spring Java Configuration
環境說明
本次項目需要用戶Nginx和Redis,如果沒有配置Nginx的請看這里: Windows上Nginx的安裝教程詳解
沒有配置Redis的請看這里:Redis——windows環境安裝Redis和Redis sentinel部署教程
配置好了上面的環境,后下面開始正式的Spring-session搭建過程了!
1.添加項目依賴
首先新建一個Maven的Web項目,新建好之后在pom文件中添加下面的依賴:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.spring</groupId>
<artifactId>learn-spring-session</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>First Learn Spring Session</name>
<properties>
<jdk.version>1.7</jdk.version>
<spring.version>4.3.4.RELEASE</spring.version>
<spring-session.version>1.3.1.RELEASE</spring-session.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>${spring-session.version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>biz.paluch.redis</groupId>
<artifactId>lettuce</artifactId>
<version>3.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</project>
2.web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/*xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--DelegatingFilterProxy將查找一個Bean的名字springSessionRepositoryFilter丟給一個過濾器。為每個請求
調用DelegatingFilterProxy, springSessionRepositoryFilter將被調用-->
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
3.Xml的配置
在resources 下面新建一個xml,名詞為 application-session.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:annotation-config/>
<!--創建一個Spring Bean的名稱springSessionRepositoryFilter實現過濾器。
篩選器負責將HttpSession實現替換為Spring會話支持。在這個實例中,Spring會話得到了Redis的支持。-->
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
<!--創建了一個RedisConnectionFactory,它將Spring會話連接到Redis服務器。我們配置連接到默認端口(6379)上的本地主機!-->
<bean class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>
</beans>
4.測試代碼
新建 LoginServlet.java
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
request.getSession().setAttribute("testKey", "742981086@qq.com");
request.getSession().setMaxInactiveInterval(10*1000);
response.sendRedirect(request.getContextPath() + "/session");
}
}
新建 SessionServlet.java
@WebServlet("/session")
public class SessionServlet extends HttpServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
System.out.println(request.getRemoteAddr());
System.out.print(request.getRemoteHost() + " : " + request.getRemotePort());
String sesssionID = request.getSession().getId();
System.out.println("-----------tomcat2---sesssionID-------" + sesssionID);
String testKey = (String)request.getSession().getAttribute("testKey");
System.out.println("-----------tomcat2-testKey-------" + testKey);
PrintWriter out = null;
try {
out = response.getWriter();
out.append("tomcat2 ---- sesssionID : " + sesssionID);
out.append("{\"name\":\"dufy2\"}" + "\n");
out.append("tomcat2 ----- testKey : " + testKey + "\n");
}catch (Exception e){
e.printStackTrace();
}finally {
if(out != null){
out.close();
}
}
}
}
效果演示
1.啟動Redis,默認端口6379就行!
2.配置Nginx,啟動Nginx
Nginx的配置,輪詢方式:
#user nobody;
worker_processes 1;
events{
worker_connections 1024;
}
http{
upstream myproject {
server 127.0.0.1:8888;
server 127.0.0.1:9999;
}
server {
listen 8080;
server_name localhost;
location / {
proxy_pass http://myproject;
}
}
}
3.啟動Tomcat1和Tomcat2
將上面搭建好的項目放入兩個Tomcat中,分別啟動。使用Nginx負載均衡均驗證Session是否共享成功!
tomcat1 : http://localhost:8888/
tomcat2 : http://localhost:9999/
訪問 http://localhost:8080/ 可以看到,每次刷新頁面,請求都分發到不同的Tomcat里面,如圖
然后使用 http://localhost:8080/login 看到地址欄重定向到/session .發現瀏覽器返回了數據,進入tomcat2中,然后再次刷新請求進入了tomcat1,發現tomcat2中和tomcat1 sessionId一樣,並且在tomcat1中存的testKey,在Tomcat2中也可以獲取,不僅SessionId可以共享,其他一些數據也可以進行共享!
如何在Redis中查看Session數據,可以使用命令,或者在Windows的RedisDesktopManager中查看!
key的簡單介紹說明:
# 存儲 Session 數據,數據類型hash
Key:spring:session:sessions:XXXXXXX
# Redis TTL觸發Session 過期。(Redis 本身功能),數據類型:String
Key:spring:session:sessions:expires:XXXXX
#執行 TTL key ,查看剩余生存時間
#定時Job程序觸發Session 過期。(spring-session 功能),數據類型:Set
Key:spring:session:expirations:XXXXX
驗證成功!
總結
Spring-Session在實際的項目中使用還是比較廣泛的,本次搭建采用最簡單的配置,基本都是采用官方文檔中默認的方式,因為是入門的教程就沒有寫的很復雜,后面慢慢深入!期待后續的教程吧!
參考文章
使用Spring Session和Redis解決分布式Session跨域共享問題57406162
學習Spring-Session+Redis實現session共享
本系列教程
【第一篇】Spring-Session實現Session共享入門教程
【第二篇】Spring-Session實現Session共享Redis集群方式配置教程【更新中...請期待...】
【第三篇】Spring-Session實現Session共享實現原理以及源碼解析【更新中...請期待...】
本系列的源碼下載地址:learn-spring-session-core
備注: 由於本人能力有限,文中若有錯誤之處,歡迎指正。
謝謝你的閱讀,如果您覺得這篇博文對你有幫助,請點贊或者喜歡,讓更多的人看到!祝你每天開心愉快!