小编说:
本文使用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

使用Spring Session实现Spring Boot水平扩展的更多相关文章

  1. spring session 和 spring security整合

    背景: 我要做的系统前面放置zuul. 使用自己公司提供的单点登录服务.后面的业务应用也是spring boot支撑的rest服务. 目标: 使用spring security管理权限包括权限.用户请 ...

  2. 实战开发,使用 Spring Session 与 Spring security 完成网站登录改造!!

    上次小黑在文章中介绍了四种分布式一致性 Session 的实现方式,在这四种中最常用的就是后端集中存储方案,这样即使 web 应用重启或者扩容,Session 都没有丢失的风险. 今天我们就使用这种方 ...

  3. Spring Boot与Spring Session集成

    1. 参考资料 https://docs.spring.io/spring-session/docs/current/reference/html5/guides/boot-redis.html ht ...

  4. 通过Spring Session实现新一代的Session管理

    长期以来,session管理就是企业级Java中的一部分,以致于我们潜意识就认为它是已经解决的问题,在最近的记忆中,我们没有看到这个领域有很大的革新. 但是,现代的趋势是微服务以及可水平扩展的原生云应 ...

  5. Spring Session加Redis

    session是一个非常常见的概念.session的作用是为了辅助http协议,因为http是本身是一个无状态协议.为了记录用户的状态,session机制就应运而生了.同时session也是一个非常老 ...

  6. Spring Session加Redis(山东数漫江湖)

    session是一个非常常见的概念.session的作用是为了辅助http协议,因为http是本身是一个无状态协议.为了记录用户的状态,session机制就应运而生了.同时session也是一个非常老 ...

  7. 转:通过Spring Session实现新一代的Session管理

    长期以来,session管理就是企业级Java中的一部分,以致于我们潜意识就认为它是已经解决的问题,在最近的记忆中,我们没有看到这个领域有很大的革新. 但是,现代的趋势是微服务以及可水平扩展的原生云应 ...

  8. 通过 Spring Session 实现新一代的 Session 管理

    长期以来,session 管理就是企业级 Java 中的一部分,以致于我们潜意识就认为它是已经解决的问题,在最近的记忆中,我们没有看到这个领域有很大的革新. 但是,现代的趋势是微服务以及可水平扩展的原 ...

  9. Re:从零开始的Spring Session(二)

    上一篇文章介绍了一些Session和Cookie的基础知识,这篇文章开始正式介绍Spring Session是如何对传统的Session进行改造的.官网这么介绍Spring Session: Spri ...

随机推荐

  1. 基于ARM-contexA9-Linux驱动开发:如何获取板子上独有的ID号

    每个CPU,都有它固定的ID号,ID号就是这个CPU唯一的标识,它可能隐含着CPU的生产日期,版本号,型号等等,那么,在我们的这款友善之臂Tiny4412的板子上,我的这个CPU的ID又是多少呢?从我 ...

  2. 【Qt编程】基于Qt的词典开发系列<十二>调用讲述人

    我们知道,win7系统自带有讲述人,即可以机器读出当前内容,具体可以将电脑锁定,然后点击左下角的按钮即可.之前在用Matlab写扫雷游戏的时候,也曾经调用过讲述人来进行游戏的语音提示.具体的Matla ...

  3. 【Android 应用开发】BluetoothServerSocket详解

    一. BluetoorhServerSocket简介 1. 继承关系 public final class BluetoothServerSocket extends Object implement ...

  4. Android实训案例(一)——计算器的运算逻辑

    Android实训案例(一)--计算器的运算逻辑 应一个朋友的邀请,叫我写一个计算器,开始觉得,就一个计算器嘛,很简单的,但是写着写着发现自己写出来的逻辑真不严谨,于是搜索了一下,看到mk(没有打广告 ...

  5. ZeroC Ice Ice Registry实现负载均衡

    Registry介绍         对于多个IceBox集群该怎么负载均衡?以服务注册表Registry为依托的Service Locator组件,以及依赖其而诞生的强大的分分布式框架-IceGri ...

  6. git使用中checkout生成临时br的问题(吓出一身冷汗啊)

    git中几天前漫不经心的使用了git checkout ver_hash的命令,结果push到远程库都提示everything is up-to-date,实际神马都没提交上去啊!但看本地log中的确 ...

  7. 恶补web之八:jQuery(1)

    jquery是一个js库,极大的简化了js编程.jquery是一个写的更少,但做的更多的轻量级js库. jquery位于一个js文件中,其中包含了所有jquery函数,可以用如下标记把jquery添加 ...

  8. solr研磨之性能调优

    作者:战斗民族就是干  转载请注明地址:http://www.cnblogs.com/prayers/p/8982141.html 本篇文章我们来了解一下solr的性能方面的调优,分为Schema优化 ...

  9. log4j2.xml全配置文件

    可以参考如下配置 <?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF &g ...

  10. python中的类

    以下内容是python tutorial的读书笔记: 一.命名空间的分层 二.local赋值语句,nonlocal和global的区别 local赋值语句,它是无法实现对于最里层的作用域的重新绑定的 ...