一、什麼是 session ? 每一個用戶 只擁有一個 session 。
(1)Session 的 作用 就是 讓我們的 服務器記住 是哪個用戶,也就是瀏覽器訪問過我。當我發起一個 會話,我們的服務器端 就會 爲我們產生 session,每一個session對應一個sessionID,然後將這個sessionID存放在cookie中然後將cookie存放在我們的瀏覽器中,然後我們在不關閉瀏覽器的 條件下,每次發送請求的時候,會默認的無條件的 將我們的 對應這個服務器的 cookie 重新發送給我們的 服務器,這樣我們的 服務器就重新難道了 存放在cookie中的sessionID,從而就找到了對應的session,這樣就實現了服務器記住我們用戶。 我們 在 很多 情況 都會 使用過 cookie 來跟蹤 我們的 session,用cookie來存放 sessionID實現跟蹤。
每一個用戶(瀏覽器) 都對應一個 唯一的獨有的 sessionID,通過這個sessionID可以找到唯一對應的 session,我們瀏覽器的cookie中保存着 第一次 會話 由 服務器傳遞過來的sessionID。
【URL重寫技術 解決 我們的session追蹤問題】:
如果 我們的 瀏覽器 禁止 了 向 cookie 中存放數據,那麼我們的 服務器 就沒有辦法 向我們的 cookie中存放 sessionID,那樣我們怎樣實現 對 session的 跟蹤 ?
解答: 這裏我們使用到了一個 【URL重寫】技術 。
這個技術 非常好理解: 就是在我們每次發送請求的時候,我們的瀏覽器會將從服務器哪裏接收到的 sessionID 當做一個 請求參數 掛在請求的 最後,如:
Http://localhost:8080/myweb/index.jsp?sessionId=xxxxx 。
這個參數 在我們的 服務器端 會 被接收,我們服務器拿到這個參數之後,用來查找判斷這個sessionId對應哪個session。
【session 的管理】
(1)session 通常是 交給我們的 tomcat容器管理的。因爲我們的tomcat是用來處理動態資源的,所以我們得tomcat中的一個容器專門用來存放我們的 session 。
(2)但是我們的 實際開發中我們不單單隻涉及一個 tomcat服務器了,我們是多個tomcat服務器集羣的,而且 我們用戶和tomcat之間還存在一個nginx,這個nginx是用來做 負載均衡的。這時候就出現了一個問題:
我們的 nginx負載均衡如果不做任何的配置,nginx接收到的請求分發到的tomcat可能是不相同的,因爲nginx是根據tomcat的壓力和一定的規則來進行分發請求的,所以我們用戶 每次發出的請求 處理請求的 tomcat不一定是相同的。
但是我們的 每一個tomcat中都部署 相同的 web項目,所以我們的 用戶 在每次 訪問我們的頁面的時候 雖然是不同的 tomcat進行處理,但是顯示的網站是相同 的。
因爲每次 處理請求的tomcat 是不相同的,就會造成一個問題:session的丟失和不能共享的問題:
因爲每次由nginx負載均衡的原因分到的tomcat不相同,所以每次都找不到上一次請求會話sessionId對應的session,所以導致這次的tomcat認爲這個請求是新的請求,也是新的一次會話,所以產生新的 session,然後返回新的sessionId,然後代替掉瀏覽器中的cookie中的sessionId,這樣就 每次 用戶登錄之後 發出一個請求然後刷新一下頁面就退出了,就變成了登錄又退出登錄退出這樣的情況。 這就是tomcat集羣部署之後存在的問題。
****我們怎樣解決tomcat集羣部署和nginx負載均衡產生的 session不共享丟失的問題呢?
【第一種方案】:ip_hash策略:這個策略是 我們nginx負載均衡時在nginx.Conf配置文件下配置的 請求分配策略。
Ip_hash的原理: 一個ip地址綁定一個tomcat服務器。其中涉及一個計算公式:
Hash函數( ip地址 ) % 2 == 取餘,餘數對應tomcat的編號。
所以只要ip地址不變 ,對應的 tomcat編號就不變。
但是 這種 解決 session丟失問題的 方法不好:因爲 這樣我們的ip地址就不能變了,如果我們今天從山東用的山東移動,明天去了河北,用的河北移動,這樣session就沒了,意思是ip地址不能改變的,如果你ip變了,就有可能調到了另一個服務器中去了,左腳在北京 右腳進了河北,ip變化了,然後跳轉tomcat,然後用戶就丟失了session,就退出登錄了。
【第二種方案】:Spring Session 框架。
這種技術 可以使 session 同步 ,可以對session進行管理,解決集羣部署之後 產生的session丟失不同步的 問題。
*****它是怎麼實現的?
我們的spring session 以中立的身份,取代和代替 我們 tomcat中的 httpsession,然後將session中的信息數據 存放在 redis 中。
知識點:爲什麼存放在redis中呢?
我們的redis是將數據存放在內存中的,而且我們可以給數據設置存放的最大生命時間,達到最大生命時間之後就會被自動刪除,這一點和我們的session的最大空閒時間是相同相似的。而且我們從內存中讀取數據是很快的。
作用步驟:
(1)用自身的 Spring-Session 以中立的方式 替換我們的 http-Session。
(2)然後 將 session中的數據信息 存放進 redis 中 。
(3)只需要配置 不需要 寫任何的代碼,就可以實現springsesson功能。
開發步驟:使用springsession開發步驟。
1.添加springsession的 maven 依賴。
1)
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
這個依賴 是對我們的 spring-data-redis依賴的封裝。這是依賴是我們springsession所依賴的jar包 。
2)
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.8.RELEASE</version>
</dependency>
這個依賴 是我們 在 spring框架中 【操作redis】時 所需要的依賴。
3)
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
我們的【spring-data-redis】這個依賴 在操作 redis的時候,底層所依賴的jar包是jedis包,實際上 我們【spring-data-redis】包底層是通過【jedis】包來操作我們的redis的。
4)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
我們的spring-session是在web項目中使用,所以涉及到了web項目和spring,所以需要使用【spring-web】這個依賴。
5)我們在開發web項目時,所需要的一些依賴,可加可不加,加了有提示不報錯。
<!-- servlet依賴的jar包start -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<!-- servlet依賴的jar包start -->
<!-- jsp依賴jar包start -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
<!-- jsp依賴jar包end -->
<!--jstl標籤依賴的jar包start -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--jstl標籤依賴的jar包end -->
分別是【servlet依賴】,【jsp依賴】 和 【jstl依賴】。
2.我們在web.xml文件文件中配置springSessionRespositoryFilter過濾器。
SpringSessionRespositoryFilter:springSession倉庫過濾器。
我認爲這個過濾器的作用是:將 我們 所有的 httpSession替換成 springsession,然後將我們session中的信息數據存放在redis中。
applicationContext-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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- spring註解、bean的處理器 -->
<!--這個標籤 是用來 激活註解 的-->
<context:annotation-config/>
<!-- Spring session 的配置類 -->
<!--RedisHttpSessionConfiguration這個類專門是用來處理 redis 和 httpsession之間的關係的類-->
<!--
我們的RedisHttpSessionConfiguration 這個類中 有很多很多的註解,但是 RedisHttpSessionConfiguration裏面的這些方法上類上的註解並沒有被激活。
所以我們需要 <context:annotation-config/> 這個標籤激活一下 我們 我們的RedisHttpSessionConfiguration類中的註解。
如果我們不進行 註解激活,那麼我們的RedisHttpSessionConfiguration是不能使用的,會報錯。
-->
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
<!--配置 redis的連接時 所需要的 信息 的-->
<!--
spring 封裝了 RedisTemplate 對象來進行對redis的各種操作,它支持所有的 redis 原生的 api。
-->
<!--
讀取redis.properties屬性配置文件
在我們的 applictionContext-session.xml中引入redis的配置文件,然後就可以使用EL表達式進行賦值了。
-->
<context:property-placeholder location="classpath:redis.properties"/>
<!--下面的配置是爲了告訴我們的springsession怎麼連接redis的,只需要告訴我們的springsession怎麼連接redis,我們都不需要創建RedisTemplate 對象 -->
<!-- 配置jedis連接工廠,用於連接redis -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.hostName}"/>
<property name="port" value="${redis.port}"/>
<property name="password" value="${redis.password}"/>
<property name="usePool" value="${redis.usePool}"/>
<property name="timeout" value="${redis.timeout}"/>
</bean>
</beans>
redis.properties :
redis.hostName=192.168.xxx.xxx
redis.port=6379
redis.password=xxxx
redis.usePool=true
redis.timeout=15000
Web.xml :
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<!--SpringSessionRespositoryFilter 這是一個spring-session框架的全局過濾器-->
<!--org.springframework.web.filter.DelegatingFilterProxy就是我們的SpringSessionRespositoryFilter的jar包-->
<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>
</filter-mapping>
<!--
(1)因爲涉及到了spring,所以我們需要spring配置文件,所以需要使用 contextLoader監聽器,來監視我們context創建的時刻。
(2)在context創建的時刻,就將我們的spring配置文件 讀取和加載進我們的內存,然後spring容器中就創建好我們需要的實體對象了。
(3)使用contextLoaderListener 就是爲了加載我們的 spring配置文件 。
-->
<!-- needed for ContextLoaderListener -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- Bootstraps the root web application context before servlet initialization -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
我們的session 通過 Spring-session這個框架存放到了我們的redis中。我們session默認最大空閒時間爲30minute。
這個類因爲有這個註解,所以它有一個定時任務的功能。每個段時間就檢查一下我們的session最後一次訪問時間,看看我們在最後一次訪問之後,有沒有過30分鐘啊,如果30分鐘都沒有訪問過:
就利用 圈1 和 圈3 這兩個 key配合起來 對我們 圈1,2,3 這三個key的刪除。
我們真正的session存放在我們的 圈2裏面,我們圈1 和圈3 是配合起來在session失效的時候刪除這三個key的。
Redis中 存放的資源類型:
- 動態資源 或 常量:因爲我們的 Redis管理數據是以鍵值對的方式進行管理的。 但是存放的 動態資源和常量我們一般都設置最大生命時間,所以說 在redis中存放的數據 都是不持久的動態資源,雖然可以長久的存放,但是我們人爲的設置了最大生命時間了。
- 利用spring-session框架 存放我們的 session對象,並且存放的session對象最大空閒會話時間爲30分鐘。
********爲什麼 我們的 spring-session 框架 可以 讓我們的 session對象共享?
由上面 關係圖 我們可以看出 :
(1) redis是在【所有】的tomcat服務器的後面。
(2) redis中 存放了所有的 session對象 。
(3)所有的 tomcat 想獲取 session 都需要去 redis中獲取。
(4)tomcat 從 redis 中 獲取到 session 的過程 不需要我們參與,全權由我們的Spring-session操作完成,spring-session幫我們將session攔截到然後存放進我們的 redis中,然後我們想要獲取的時候,spring-session通過瀏覽器傳遞過來的cookie中的sessionId來自動從redis中幫我們查找對應sessionId的session。
總結:
之所以我們的session共享了,是因爲 我們將所有的session存放進了一個公共的空間了,所有的tomcat都可以隨便的自動的從這個公共空間獲取session了。 原來沒有配置springsession的時候 我們 的session是存放在 tomcat中的容器裏,這個容器是封閉的,是與其他tomcat沒有任何交涉的,所以session不共享。
*********我們判斷一個用戶是否登陸的依據是什麼?
我們的用戶登錄之後 我們的tomcat 就會創建一個session對象,這個session對象是和我們的用戶一一對應的,session裏面存放着這個用戶的所有信息。當我們的用戶瀏覽器發送請求後,請求攜帶者cookie中的sessionId到達我們的服務器,然後我們的tomcat服務器從redis中獲取 sessionId對應的session,如果找到了 說明 這個用戶是登陸狀態就顯示登陸,如果沒有找到對應的session對象,說明該用戶沒有登錄,就顯示未登錄狀態。
Path是cookie的路徑不相同,產生兩個session:
P2p項目
Show項目
因爲name相同,如果path也相同,那麼他們就是同一個session了,鍵值對嘛,鍵不能重複。
讓不同的項目 共享 session 的 方法 :
指定cookie的存儲方式:
利用下面這個類來進行序列化,修改cookie的存儲格式:
這個類如何序列化,修改cookie存儲格式的:
上面這個類實現了這個接口。
配置完成之後:
P2p項目:
Show項目:
這兩個項目都是有同一個瀏覽器用戶發出的請求,所以他們的session應該保持一致,如果不一致,就用這種方法讓session在兩個項目或多個項目中保持共享。
域名不相同,項目不相同,怎麼實現session共享:
讓兩個項目的session保存在同一個路徑下:
這兩個項目的根域名相同,所以都存放在根域名下,路徑爲:根域名/
分別制定:domain 和 path這兩個值。
Web.com是 一級域名。
項目名 爲 二級域名。
包結構:
--Web
--Com
--p2p
--show
上面這個地址 是 存放cookie的地址。
Cookie中是用鍵值對來保存 sessionId的。
Name就是key:session 就代表sessionId
Value 裏面就存放着sessionId的值。
Domian + path 就是存放cookie的位置。
強行將 cookie放在abc.com這個域名下,這樣違反了cookie安全策略,在寫到瀏覽器的時候 會 被禁止,無法寫到瀏覽器上。Cookie只能保存在當前域名下: cookie的安全策略不允許寫到別的域名下。
這種情況怎麼實現 一個網站登錄其他信任網站也登陸呢?
單點故障:如果只有一個節點一個服務器,如果這個節點服務器發生故障了,整個項目網站服務就不能用了。所以至少佈置兩臺服務器節點,但是兩臺就會出現session不共享的問題,所以我們需要 使用springsession框架實現我們的session共享。
知識點補充:
Init是初始化方法。以前出現的init方法都是初始化方法。
判斷 日誌級別是否爲 debug級別,如果是debug級別,就打印日誌。
Init初始化方法中的核心代碼:
代理過濾器中重寫這個方法:
GetFilterName()方法底層代碼:
意思是:從配置文件中獲取到<filter-name>之間的值。
獲取的是標籤對之間的值:
獲取 相同名字,相同類型過濾器所涉及的方法 :
我們 spring容器中獲取到了過濾器的實體bean對象:
<bean id=”springSessionRepositoryFilter” class=”SessionRepositoryFilter的全限定類名”/>
我們調用的是springSessionRepositoryFilter對象中的 doFilter()方法,實質上其實是調用的SessionRepositoryFilter類中的doFilter()方法。
而我們的SessionRepositoryFilter類 中的doFilter()是從我們的OncePerRequestFilter抽象類中繼承過來之後重寫的,OncePerRequestFilter抽象類中的doFilter()是從我們的Filter接口中得到的。
調用 dofilter()方法的過程:
這個Dofilter方法中的核心代碼爲:
這個方法在我們的 中被重寫:代碼內容如下:
是這個doFilter()方法的核心代碼,因爲這段代碼和管理session,存儲session有關:
這個內部類 繼承了 父類中的 getSession方法,並且重寫了這個方法。
重寫的是【父類】這裏面的這些方法:
重寫的getSesion方法中的代碼內容:核心代碼紅線(創建一個session對象)
創建session對象的方法的代碼內容如下:
創建session對象的方法中的核心代碼:
我們將session存放進redis中。
添加屬性,如下圖:向Map集合中添加屬性就相當於下圖。redis中的效果圖:
保存session的方法:
上圖doFilter()方法中有下圖這一段代碼,專門是用來存儲session的:
CommitSession方法代碼內容如下:
核心代碼中save方法內容如下:
Save核心代碼 中 saveDelta()方法內容如下:
這個putAll()方法屬於我們的spring-data-redis依賴中的方法,這裏已經到了 spring-data-redis中了:
我們spring-data-redis依賴底層向redis存放數據又要依賴 jedis的jar包:
Jedis幫我們保存 session,jedis涉及的方法如下:
上面的源碼就是這個圖的步驟和知識點。
方法就等於複寫了方法。
知識點補充:我們不從配置文件中聲明bean對象,我們用java代碼來實現創建bean對象。
上面 兩種方式 都是相當於在 spring容器中 配置和聲明瞭一個 bean對象。
實際開發中,命名 redis中key的規範: