<dependencies> <!-- ... --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> </dependencies>
小編說:
本文使用Spring Session實現了Spring Boot水平擴展,每一個Spring Boot應用與其餘水平擴展的Spring Boot同樣,都能處理用戶請求。
若是宕機,Nginx會將請求反向代理到其餘運行的Spring Boot應用上,若是系統須要增長吞吐量,只須要再啓動更多的Spring Boot應用便可。
本文選自《Spring Boot 2精髓:從構建小系統到架構分佈式大系統》一書。html
Spring Boot應用一般會部署在多個Web服務器上同時提供服務,這樣作有不少好處:前端
單個應用宕機不會中止服務,升級應用能夠逐個升級而沒必要中止服務。html5
提升了應用總體的吞吐量。java
咱們稱這種部署方式爲水平擴展,前端經過Nginx提供反向代理,會話管理能夠經過Spring Session,使用Redis來存放Session。部署Spring Boot應用到任意一臺Web服務器上,從而提升了系統可靠性和可伸縮性。nginx
當系統想提高處理能力的時候,一般用兩種選擇,一種是重置擴展架構,即提高現有系統硬件的處理能力,好比提升CPU頻率、使用更好的存儲器。另一種選擇是水平擴展架構,即部署系統到更多的服務器上同時提供服務。這兩種方式各有利弊,如今一般都優先採用水平擴展架構,這是由於:git
缺點:架構中的硬件提高能力有限,並且硬件能力提高每每須要更多的花銷;github
優勢:應用系統不須要作任何改變。web
優勢:成本便宜;正則表達式
缺點:更多的應用致使管理更加複雜。對於Spring Boot 應用,會話管理是一個難點。
Spring Boot 應用水平擴展有兩個問題須要解決,一個是將用戶的請求派發到水平部署的任意一臺Spring Boot應用,一般用一個反向代理服務器來實現,本文將使用Nginx做爲反向代理服務器。redis
反向代理(Reverse Proxy)方式是指接收internet上的鏈接請求,而後將請求轉發給內部網絡上的服務器,並將從服務器上獲得的結果返回給internet上請求鏈接的客戶端,此時代理服務器對外就表現爲一個反向代理服務器。
正向代理服務器:局域網內經過一個正向代理服務器訪問外網。
另一個須要解決的問題是會話管理, 單個Spring Boot應用的會話由Tomcat來管理,會話信息與Tomcat存放在一塊兒。若是部署多個Spring Boot應用,對於同一個用戶請求,即便請求經過Nginx派發到不一樣的Web服務器上,也能共享會話信息。有兩種方式能夠實現。
複製會話:Web服務器一般都支持Session複製,一臺應用的會話信息改變將馬上覆制到其餘集羣的Web服務器上。
集中式會話:全部Web服務器都共享一個會話,會話信息一般存放在一臺服務器上,本文使用Redis服務器來存放會話。
複製會話的缺點是每次會話改變須要複製到多臺Web服務器上,效率較低。所以Spring Boot應用採用第二種方式(集中式會話方式),結構以下圖所示。
上圖是一個大型分佈式系統架構,包含了三個獨立的子系統。業務子系統一和業務子系統二分別部署在一臺Tomcat服務器上,業務子系統三部署在兩臺Tomcat服務器上,採用水平擴展。
架構採用Nginx做爲反向代理,其後的各個子系統都採用Spring Session,將會話存放在Redis中,所以,這些子系統雖然是分開部署的,支持水平擴展,但能整合成一個大的系統。Nginx提供統一的入口,對於用戶訪問,將按照某種策略,好比根據訪問路徑派發到後面對應的Spring Boot應用中,Spring Boot調用Spring Session取得會話信息,Spring Session並無從本地存取會話,會話信息存放在Redis服務器上。
Nginx是一款輕量級的Web 服務器/反向代理服務器及電子郵件(IMAP/POP3)、TCP/UDP代理服務器,並在一個BSD-like協議下發行。由俄羅斯的程序設計師Igor Sysoev開發,供俄國大型的入口網站及搜索引擎Rambler使用。其特色是佔有內存少,併發能力強,事實上Nginx的併發能力確實在同類型的網頁服務器中表現較好,國內使用Nginx的網站有百度、新浪、網易、騰訊等。
打開Nginx網站(http://nginx.org/ ),進入下載頁面,根據本身的操做系統選擇下載,以Windows系統爲例,下載nginx/Windows-1.11.10版本,直接解壓,而後運行Nginx便可。
若是是Mac,能夠運行:
>brew install nginx
Nginx默認會安裝在/usr/local/Cellar/nginx/目錄下,配置文件在/usr/local/etc/nginx/nginx.conf目錄下,日誌文件在 /usr/local/var/log/nginx/目錄下。
如下是Nginx的經常使用命令:
nginx,啓動Nginx,默認監聽80端口。
nginx -s stop,快速中止服務器。
nginx -s quit,中止服務器,但要等到請求處理完畢後關閉。
nginx -s reload,從新加載配置文件。
Nginx啓動後,能夠訪問http://127.0.0.1:80 ,會看到Nginx的歡迎頁面,以下圖所示。
若是80端口訪問不了,則多是由於你下載的版本的緣由,Nginx的HTTP端口配置成其餘端口,編輯conf/nginx.conf,找到:
server { listen 80; }
修改listen參數到80端口便可。
Nginx的log目錄下提供了三個文件:
access.log,記錄了用戶的請求信息和響應。
error.log,記錄了Nginx運行的錯誤日誌。
nginx.pid,包含了Nginx的進程號。
Nginx的配置文件conf/nginx.conf下包含多個指令塊,咱們主要關注http塊和location塊。
http塊:能夠嵌套多個Server,配置代理、緩存、日誌定義等絕大多數功能和第三方模塊,如mime-type定義、日誌自定義、是否使用sendfile傳輸文件、鏈接超時時間、單鏈接請求數等。
location塊:配置請求的路由,以及各類頁面的處理狀況。
因爲本文主要是講水平擴展Spring Boot應用,所以,咱們須要在http塊中增長upstream指令,內容以下:
http { upstream backend { server 127.0.0.1:9000; server 127.0.0.1:9001 } }
backend也能夠爲任意名字,咱們在下面的配置將要引用到:
location / { proxy_pass http://backend; }
location後能夠是一個正則表達式,咱們這裏用「/」表示全部客戶端請求都會傳給http:// backend,也就是咱們配置的backend指令的地址列表。所以,整個http塊相似下面的樣子:
http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; upstream backend { server 127.0.0.1:9000; server 127.0.0.1:9001; } server { listen 80; server_name localhost; location / { proxy_pass http://backend; } } }
咱們在後面將建立一個Spring Boot應用,並分別以9000和9001兩個端口啓動,而後在Spring Session的基礎上一步步來完成Spring Boot應用的水平擴展。
注意:Nginx反向代理默認狀況下會輪詢後臺應用,還有一種配置是設置ip_hash,這樣,固定客戶端老是反向代理到後臺的某一個服務器。這種設置方式就不須要使用Spring Session來管理會話,使用Tomcat的會話管理便可。但弊端是若是服務器宕機或者由於維護重啓,則會話丟失。ip_hash設置以下:
upstream backend { ip_hash; server 127.0.0.1:9000; server 127.0.0.1:9001 }
在默認狀況下,Spring Boot使用Tomcat服務器的Session實現,咱們編寫一個例子用於測試:
@Controller public class SpringSessionCrontroller { Log log = LogFactory.getLog(SpringSessionCrontroller.class); @RequestMapping("/putsession.html") public @ResponseBody String putSession(HttpServletRequest request){ HttpSession session = request.getSession(); log.info(session.getClass()); log.info(session.getId()); String name = "xiandafu"; session.setAttribute("user", name); return "hey,"+name; } }
若是訪問服務/putsession.html,控制檯輸出爲:
SpringSessionCrontroller : class org.apache.catalina.session.StandardSessionFacade SpringSessionCrontroller : F567C587EA25CBD5B9A75C62AB51904D
能夠看到,Session管理是經過Tomcat提供的org.apache.catalina.session.StandardSessionFacade實現的。
在配置文件application.properties中添加以下內容:
spring.session.store-type=Redis|JDBC|Hazelcast|none
Spring Boot配置很容易切換到不一樣的Session管理方式,總共有如下幾種:
Redis,Session數據存放Redis中。
JDBC,會話數據存放在數據庫中,默認狀況下SPRINGSESSION表存放Session基本信息,如sessionId、建立時間、最後一次訪問時間等,SPRING_SESSION ATTRIBUTES存放了session數據,ATTRIBUTE_NAME列保存了Session的Key,ATTRIBUTE_BYTES列以字節形式保存了Session的Value,Spring Session會自動建立這兩張表。
Hazelcast,Session數據存放到Hazelcast。
None,禁用Spring Session功能。
經過配置屬性spring.session.store-type來指定Session的存儲方式,如:
spring.session.store-type=Redis
修改成配置和增長Spring Session依賴後,若是訪問服務/putsession.html,控制檯輸出爲:
SpringSessionCrontroller : class org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper$HttpSessionWrapper SpringSessionCrontroller : d4315e92-48e1-4a77-9819-f15df9361e68
能夠看到,Session已經替換爲HttpSessionWrapper實現,這個類負責Spring Boot 的Session存儲類型的具體實現。
本將用Redis來保存Session,你須要安裝Redis,如未安裝,請參考《Spring Boot 2精髓:從構建小系統到架構分佈式大系統》中Redis一章,Spring Boot的配置以下:
spring.session.store-type=Redis spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.password=Redis!123
還須要引入對Redis的依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
再次訪問/putsession.html後,咱們經過Redis客戶端工具訪問Redis,好比使用redis-cli,輸入以下命令:
keys spring:session:*
查詢全部「spring:session:sessions」開頭的keys,輸出以下:
3) "spring:session:sessions:expires:863c7e73-8249-4780-a08e-0ff2bdddda86" ... 7) "spring:session:sessions:863c7e73-8249-4780-a08e-0ff2bdddda86"
會話信息存放在「spring:sesssion:sessions:」開頭的Key中,863c7e73-8249-4780-a08e-0ff2bdddda86表明一個會話id,「spring:session:sessions」是一個Hash數據結構,能夠用Redis HASH相關的命令來查看這個用戶會話的數據,使用hgetall查看會話全部的信息:
>hgetall "spring:session:sessions:863c7e73-8249-4780-a08e-0ff2bdddda86" 1) "sessionAttr:user" 2) "maxInactiveInterval" .......
使用如下命令來查看該Session的user信息:
>HMGET "spring:session:sessions:863c7e73-8249-4780-a08e-0ff2bdddda86" sessionAttr:user
sessionAttr:user是Spring Session存入Redis的Key值,sessionAttr:是其前綴,user是咱們在Spring Boot中設置會話的Key。其餘Spring Boot默認建立的Key還有:
creationTime,建立時間。
maxInactiveInterval,指定過時時間(秒)。
lastAccessedTime,上次訪問時間。
sessionAttr,以「sessionAttr:」爲前綴的會話信息,好比sessionAttr: user。
所以,Spring Session使用Redis保存的會話將採用以下的Redis操做,相似以下:
>HMSET spring:session:sessions:863c7e73-8249-4780-a08e-0ff2bdddda86 creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:attrName someAttrValue sessionAttr:attrName2 someAttrValue2
注意:Spring Session的Redis實現並非每次經過Session類獲取會話信息或者保存的時候都會調用Redis操做,它會先嚐試從內部的HashMap讀取值,若是沒有,才調用Redis的HMGET操做。一樣,當保存會話的時候,也沒有當即調用Redis操做,而是先保存到HashMap中,等待服務請求結束後再將變化的值使用HMSET更新。若是你想在保存會話操做後當即更新到Redis中,須要配置成IMMEDIATE模式,修改配置屬性:
spring.session.redis.flushMode=IMMEDIATE
咱們注意到,還有另一個Redis Key是「spring:session:sessions:863c7e73-8249-4780- a08e-0ff2bdddda86」,
這是由於Redis會話過時並無直接使用在spring:session:sessions key變量上,而是專門用在spring:session:sessions:expires:key上,當此Key過時後,會自動清除對應的會話信息。使用ttl查看會話過時時間:
>ttl spring:session:sessions:expires:863c7e73-8249-4780-a08e-0ff2bdddda86 (integer) 1469
默認是1800秒,即30分鐘,如今只剩下1469秒。
在前文中,咱們已經配置了:
upstream backend { server 127.0.0.1:9000; server 127.0.0.1:9001 }
假設在本機上部署了兩個Spring Boot應用,使用端口分別是9000和9001。進入工程目錄,運行mvn package,咱們看到ch15.springsession\target\目錄下生成了ch17.springsession-0.0.1- SNAPSHOT.jar。而後進入命令行,進入target目錄,啓動這個Spring Boot應用:
java -jar target/ch15.springsession-0.0.1-SNAPSHOT.jar --server.port=9000
打開另一個命令窗口,進入工程目錄,運行:
java -jar target/ch15.springsession-0.0.1-SNAPSHOT.jar --server.port=9001
這時候,咱們就有兩臺Spring Boot應用。接下來,咱們訪問如下地址,並刷新屢次:
http://127.0.0.1/putsession.html
這時候就看到兩個Spring Boot應用均有日誌輸出,好比9000端口的應用控制檯輸出以下:
class org.springframework.session.web.http.SessionRepositoryFilter.... 863c7e73-8249-4780-a08e-0ff2bdddda86
9001端口的Spring Boot應用也有相似輸出:
class org.springframework.session.web.http.SessionRepositoryFilter.... 863c7e73-8249-4780-a08e-0ff2bdddda86
咱們看到,兩個Spring Boot應用都具備相同的sessionId,若是停掉任意一臺應用,系統還有另一臺服務器提供服務,會話信息保存在Redis中。
http://www.broadview.com.cn/article/41342
This guide describes how to use Spring Session to transparently leverage Redis to back a web application’s HttpSession
when using Spring Boot.
The completed guide can be found in the boot sample application. |
Before you use Spring Session, you must ensure to update your dependencies. We assume you are working with a working Spring Boot web application. If you are using Maven, ensure to add the following dependencies:
<dependencies> <!-- ... --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> </dependencies>
Spring Boot provides dependency management for Spring Session modules, so there’s no need to explicitly declare dependency version.
After adding the required dependencies, we can create our Spring Boot configuration. Thanks to first-class auto configuration support, setting up Spring Session backed by Redis is as simple as adding a single configuration property to your application.properties
:
spring.session.store-type=redis # Session store type.
Under the hood, Spring Boot will apply configuration that is equivalent to manually adding @EnableRedisHttpSession
annotation. This creates a Spring Bean with the name of springSessionRepositoryFilter
that implements Filter. The filter is what is in charge of replacing the HttpSession
implementation to be backed by Spring Session.
Further customization is possible using application.properties
:
server.servlet.session.timeout= # Session timeout. If a duration suffix is not specified, seconds will be used. spring.session.redis.flush-mode=on-save # Sessions flush mode. spring.session.redis.namespace=spring:session # Namespace for keys used to store sessions.
For more information, refer to Spring Session portion of the Spring Boot documentation.
Spring Boot automatically creates a RedisConnectionFactory
that connects Spring Session to a Redis Server on localhost on port 6379 (default port). In a production environment you need to ensure to update your configuration to point to your Redis server. For example, you can include the following in your application.properties
spring.redis.host=localhost # Redis server host. spring.redis.password= # Login password of the redis server. spring.redis.port=6379 # Redis server port.
For more information, refer to Connecting to Redis portion of the Spring Boot documentation.
Our Spring Boot Configuration created a Spring Bean named springSessionRepositoryFilter
that implements Filter
. The springSessionRepositoryFilter
bean is responsible for replacing the HttpSession
with a custom implementation that is backed by Spring Session.
In order for our Filter
to do its magic, Spring needs to load our Config
class. Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our springSessionRepositoryFilter
for every request. Fortunately, Spring Boot takes care of both of these steps for us.
The Boot Sample Application demonstrates how to use Spring Session to transparently leverage Redis to back a web application’s HttpSession
when using Spring Boot.
You can run the sample by obtaining the source code and invoking the following command:
For the sample to work, you must install Redis 2.8+ on localhost and run it with the default port (6379). Alternatively, you can update the |
$ ./gradlew :spring-session-sample-boot-redis:bootRun
You should now be able to access the application at http://localhost:8080/
Try using the application. Enter the following to log in:
Username user
Password password
Now click the Login button. You should now see a message indicating your are logged in with the user entered previously. The user’s information is stored in Redis rather than Tomcat’s HttpSession
implementation.
Instead of using Tomcat’s HttpSession
, we are actually persisting the values in Redis. Spring Session replaces the HttpSession
with an implementation that is backed by Redis. When Spring Security’s SecurityContextPersistenceFilter
saves the SecurityContext
to the HttpSession
it is then persisted into Redis.
When a new HttpSession
is created, Spring Session creates a cookie named SESSION in your browser that contains the id of your session. Go ahead and view the cookies (click for help with Chrome or Firefox).
If you like, you can easily remove the session using redis-cli. For example, on a Linux based system you can type:
$ redis-cli keys '*' | xargs redis-cli del
The Redis documentation has instructions for installing redis-cli. |
Alternatively, you can also delete the explicit key. Enter the following into your terminal ensuring to replace 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
with the value of your SESSION cookie:
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.
https://docs.spring.io/spring-session/docs/2.0.8.RELEASE/reference/html5/guides/boot-redis.html#boot-sample
Spring Session provides an API and implementations for managing a user’s session information.
Spring Session provides an API and implementations for managing a user’s session information, while also making it trivial to support clustered sessions without being tied to an application container specific solution. It also provides transparent integration with:
HttpSession - allows replacing the HttpSession
in an application container (i.e. Tomcat) neutral way, with support for providing session IDs in headers to work with RESTful APIs.
WebSocket - provides the ability to keep the HttpSession
alive when receiving WebSocket messages
WebSession - allows replacing the Spring WebFlux’s WebSession
in an application container neutral way.
https://docs.spring.io/spring-session/docs/2.0.8.RELEASE/reference/html5/#httpsession-redis-jc