使用Spring Session实现Spring Boot水平扩展

小编说:
本文使用Spring Session实现了Spring Boot水平扩展,每个Spring Boot应用与其他水平扩展的Spring Boot一样,都能处理用户请求。
如果宕机,Nginx会将请求反向代理到其他运行的Spring Boot应用上,如果系统需要增加吞吐量,只需要再启动更多的Spring Boot应用即可。
本文选自《Spring Boot 2精髓:从构建小系统到架构分布式大系统》一书。

Spring Boot应用通常会部署在多个Web服务器上同时提供服务,这样做有很多好处:

单个应用宕机不会停止服务,升级应用可以逐个升级而不必停止服务。

提高了应用整体的吞吐量。

我们称这种部署方式为水平扩展,前端通过Nginx提供反向代理,会话管理可以通过Spring Session,使用Redis来存放Session。部署Spring Boot应用到任意一台Web服务器上,从而提高了系统可靠性和可伸缩性。

1 水平扩展实现

当系统想提升处理能力的时候,通常用两种选择,一种是重置扩展架构,即提升现有系统硬件的处理能力,比如提高CPU频率、使用更好的存储器。另外一种选择是水平扩展架构,即部署系统到更多的服务器上同时提供服务。这两种方式各有利弊,现在通常都优先采用水平扩展架构,这是因为:

重置扩展架构

缺点:架构中的硬件提升能力有限,而且硬件能力提升往往需要更多的花销;

优点:应用系统不需要做任何改变。

水平扩展

优点:成本便宜;

缺点:更多的应用导致管理更加复杂。对于Spring Boot 应用,会话管理是一个难点。
Spring Boot 应用水平扩展有两个问题需要解决,一个是将用户的请求派发到水平部署的任意一台Spring Boot应用,通常用一个反向代理服务器来实现,本文将使用Nginx作为反向代理服务器。

反向代理(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服务器上。

2 Nginx的安装和配置

Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)、TCP/UDP代理服务器,并在一个BSD-like协议下发行。由俄罗斯的程序设计师Igor Sysoev开发,供俄国大型的入口网站及搜索引擎Rambler使用。其特点是占有内存少,并发能力强,事实上Nginx的并发能力确实在同类型的网页服务器中表现较好,国内使用Nginx的网站有百度、新浪、网易、腾讯等。

2.1 安装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的进程号。

2.2 配置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
}

3 Spring Session

3.1 Spring Session介绍

在默认情况下,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存储类型的具体实现。

3.2 使用Redis

本将用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秒。

3.3 Nginx+Redis

在前文中,我们已经配置了:

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

 

Spring Session - Spring Boot

Rob Winch, Vedran PavićVersion 2.0.8.RELEASE

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.

1. Updating Dependencies

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:

pom.xml
<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.

2. Spring Boot Configuration

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:

src/main/resources/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:

src/main/resources/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.

3. Configuring the Redis Connection

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

src/main/resources/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.

4. Servlet Container Initialization

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.

5. Boot Sample Application

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.

5.1. Running the Boot Sample Application

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 RedisConnectionFactory to point to a Redis server. Another option is to use Docker to run Redis on localhost. See Docker Redis repository for detailed instructions.

$ ./gradlew :spring-session-sample-boot-redis:bootRun

You should now be able to access the application at http://localhost:8080/

5.2. Exploring the security Sample Application

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.

5.3. How does it work?

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.

1. Introduction

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

 

posted @ 2017-11-28 09:48  沧海一滴  阅读(2736)  评论(0编辑  收藏  举报