Session與Cookie基礎
由於http協議是無狀態的協議,爲了能夠記住請求的狀態,於是引入了Session和Cookie的機制。我們應該有一個很明確的概念,那就是Session是存在於服務器端的,在單體式應用中,他是由tomcat管理的,存在於tomcat的內存中,當我們爲了解決分佈式場景中的session共享問題時,引入了redis,其共享內存,以及支持key自動過期的特性,非常契合session的特性,我們在企業開發中最常用的也就是這種模式。但是隻要你願意,也可以選擇存儲在JDBC,Mongo中,這些,spring都提供了默認的實現,在大多數情況下,我們只需要引入配置即可。而Cookie則是存在於客戶端,更方便理解的說法,可以說存在於瀏覽器。
代碼示例
使用Springboot編寫一個非常簡單的服務端,來加深對其的理解。需求很簡單,當瀏覽器訪問localhost:8000/test/cookie?browser=xxx時,如果沒有獲取到session,則將request中的browser存入session;如果獲取到session,便將session中的browser值輸出。順便將request中的所有cookie打印出來。
創建一個hellospring項目。
@Controller
public class CookieController {
@RequestMapping("/test/cookie")
public String cookie(@RequestParam("browser") String browser, HttpServletRequest request, HttpSession session) {
//取出session中的browser
Object sessionBrowser = session.getAttribute("browser");
if (sessionBrowser == null) {
System.out.println("不存在session,設置browser=" + browser);
session.setAttribute("browser", browser);
} else {
System.out.println("存在session,browser=" + sessionBrowser.toString());
}
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
System.out.println(cookie.getName() + " : " + cookie.getValue());
}
}
return "index";
}
}
使用Redis集成Spring Session
引入依賴,Spring Boot的版本採用1.5.6
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
配置類開啓Redis Http Session
@Configuration
@EnableRedisHttpSession
public class HttpSessionConfig {
}
爲了方便演示多節點之間的session共享,我們生成多個配置文件。
application.properties
spring.redis.host=172.17.0.61
spring.redis.port=7200
spring.redis.database=0
spring.application.name=hellospring
application-default.properties
server.port=8001
application-dev1.properties
server.port=8001
application-dev2.properties
server.port=8002
進入cmd模式,在項目執行mvn package。
分別啓動dev1和dev2。
java -jar hellospring-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev1
java -jar hellospring-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev2
安裝NGINX,將請求分發到兩個不同節點,配置文件如下。
listen 8000;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
proxy_pass http://localhost;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
upstream localhost{
#hash $remote_addr consistent;
server 127.0.0.1:8001 weight=1;
server 127.0.0.1:8002 weight=1;
}
訪問http://localhost:8000/test/cookie?browser=chrome,觀察console輸出內容。
可以看到session當中沒有值,browser被保存到session中,查看redis,發現session數據已經被緩存到redis。
解析一下這個redis store。
1 spring:session是默認的Redis HttpSession前綴(redis中,我們常用’:’作爲分割符)。
2 每一個session都會有三個相關的key,第三個key最爲重要,它是一個HASH數據結構,將內存中的session信息序列化到了redis中。如上文的browser,就被記錄爲sessionAttr:browser=chrome,還有一些meta信息,如創建時間,最後訪問時間等。
3 另外兩個key,expirations:1504446540000和sessions:expires:7079…我發現大多數的文章都沒有對其分析,前者是一個SET類型,後者是一個STRING類型,可能會有讀者發出這樣的疑問,redis自身就有過期時間的設置方式TTL,爲什麼要額外添加兩個key來維持session過期的特性呢?這需要對redis有一定深入的瞭解才能想到這層設計。當然這不是本節的重點,簡單提一下:redis清除過期key的行爲是一個異步行爲且是一個低優先級的行爲,用文檔中的原話來說便是,可能會導致session不被清除。於是引入了專門的expiresKey,來專門負責session的清除,包括我們自己在使用redis時也需要關注這一點。在開發層面,我們僅僅需要關注第三個key就行了。
再次訪問http://localhost:8000/test/cookie?browser=chrome
現在dev1也能正常訪問到session內容。
代碼位置:
https://github.com/39627020/experience/tree/master/java/springboot/hellospring