SpringBoot session 共享(copy自微信公眾號的文章)
在傳統的單服務架構中,一般來說,只有一個伺服器,那麼不存在 Session 共享問題,但是在分散式/叢集專案中,Session 共享則是一個必須面對的問題,先看一個簡單的架構圖:
在這樣的架構中,會出現一些單服務中不存在的問題,例如客戶端發起一個請求,這個請求到達 Nginx 上之後,被 Nginx 轉發到 Tomcat A 上,然後在 Tomcat A 上往 session 中儲存了一份資料,下次又來一個請求,這個請求被轉發到 Tomcat B 上,此時再去 Session 中獲取資料,發現沒有之前的資料。對於這一類問題的解決,思路很簡單,就是將各個服務之間需要共享的資料,儲存到一個公共的地方(主流方案就是 Redis)
當所有 Tomcat 需要往 Session 中寫資料時,都往 Redis 中寫,當所有 Tomcat 需要讀資料時,都從 Redis 中讀。這樣,不同的服務就可以使用相同的 Session 資料了。
這樣的方案,可以由開發者手動實現,即手動往 Redis 中儲存資料,手動從 Redis 中讀取資料,相當於使用一些 Redis 客戶端工具來實現這樣的功能,毫無疑問,手動實現工作量還是蠻大的。
一個簡化的方案就是使用 Spring Session 來實現這一功能,Spring Session 就是使用 Spring 中的代理過濾器,將所有的 Session 操作攔截下來,自動的將資料 同步到 Redis 中,或者自動的從 Redis 中讀取資料。
對於開發者來說,所有關於 Session 同步的操作都是透明的,開發者使用 Spring Session,一旦配置完成後,具體的用法就像使用一個普通的 Session 一樣。
#實戰
1 建立工程
首先 建立一個 Spring Boot 工程,引入 Web、Spring Session 以及 Redis:
建立成功之後,pom.xml 檔案如下:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <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> </dependencies>
注意:
這裡我使用的 Spring Boot 版本是 2.1.4 ,如果使用當前最新版 Spring Boot2.1.5 的話,除了上面這些依賴之外,需要額外新增 Spring Security 依賴(其他操作不受影響,僅僅只是多了一個依賴,當然也多了 Spring Security 的一些預設認證流程)。
2 配置 Redis
spring.redis.host=192.168.66.128
spring.redis.port=6379
spring.redis.password=123
spring.redis.database=0
3 使用
配置完成後 ,就可以使用 Spring Session 了,其實就是使用普通的 HttpSession ,其他的 Session 同步到 Redis 等操作,框架已經自動幫你完成了:
@RestController public class HelloController { @Value("${server.port}") Integer port; @GetMapping("/set") public String set(HttpSession session) { session.setAttribute("user", "javaboy"); return String.valueOf(port); } @GetMapping("/get") public String get(HttpSession session) { return session.getAttribute("user") + ":" + port; } }
考慮到一會 Spring Boot 將以叢集的方式啟動 ,為了獲取每一個請求到底是哪一個 Spring Boot 提供的服務,需要在每次請求時返回當前服務的埠號,因此這裡我注入了 server.port 。
接下來 ,專案打包:
打包之後,啟動專案的兩個例項:
java -jar sessionshare-0.0.1-SNAPSHOT.jar --server.port=8080
java -jar sessionshare-0.0.1-SNAPSHOT.jar --server.port=8081
然後先訪問 localhost:8080/set 向 8080 這個服務的 Session 中儲存一個變數,訪問完成後,資料就已經自動同步到 Redis 中 了 :
然後,再呼叫 localhost:8081/get 介面,就可以獲取到 8080 服務的 session 中的資料:
此時關於 session 共享的配置就已經全部完成了,session 共享的效果我們已經看到了,但是每次訪問都是我自己手動切換服務例項,因此,接下來我們來引入 Nginx ,實現服務例項自動切換。
4 引入 Nginx
簡單,進入 Nginx 的安裝目錄的 conf 目錄下(預設是在 /usr/local/nginx/conf),編輯 nginx.conf 檔案:
在這段配置中:
- upstream 表示配置上游伺服器
- javaboy.org 表示伺服器叢集的名字,這個可以隨意取名字
- upstream 裡邊配置的是一個個的單獨服務
- weight 表示服務的權重,意味者將有多少比例的請求從 Nginx 上轉發到該服務上
- location 中的 proxy_pass 表示請求轉發的地址, / 表示攔截到所有的請求,轉發轉發到剛剛配置好的服務叢集中
- proxy_redirect 表示設定當發生重定向請求時,nginx 自動修正響應頭資料(預設是 Tomcat 返回重定向,此時重定向的地址是 Tomcat 的地址,我們需要將之修改使之成為 Nginx 的地址)。
配置完成後,將本地的 Spring Boot 打包好的 jar 上傳到 Linux ,然後在 Linux 上分別啟動兩個 Spring Boot 例項:
nohup java -jar sessionshare-0.0.1-SNAPSHOT.jar --server.port=8080 &
nohup java -jar sessionshare-0.0.1-SNAPSHOT.jar --server.port=8081 &
其中
nohup 表示當終端關閉時,Spring Boot 不要停止執行
& 表示讓 Spring Boot 在後臺啟動
配置完成後,重啟 Nginx:
/usr/local/nginx/sbin/nginx -s reload
Nginx 啟動成功後,我們首先手動清除 Redis 上的資料,然後訪問 192.168.66.128/set 表示向 session中儲存資料,這個請求首先會到達 Nginx 上,再由 Nginx 轉發給某一個 SpringBoot 例項:
如上,表示埠為 8081 的 SpringBoot 處理了這個 /set 請求,再訪問 /get 請求:
可以看到, /get 請求是被埠為 8080 的服務所處理的。