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

本文使用Spring Session实现了Spring Boot水平扩展,每个Spring Boot应用与其他水平扩展的Spring Boot一样,都能处理用户请求。如果宕机,Nginx会将请求反向代理到其他运行的Spring Boot应用上,如果系统需要增加吞吐量,只需要再启动更多的Spring Boot应用即可。

  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,会话数据存放在数据库中,默认情况下SPRING_SESSION表存放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:”开头的keys,输出如下:

3) "spring:session:sessions:expires:863c7e73-8249-4780-a08e-0ff2bdddda86"
...
7) "spring:session:sessions:863c7e73-8249-4780-a08e-0ff2bdddda86"

  会话信息存放在“spring:session: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:expires:863c7e73-8249-4780- a08e-0ff2bdddda86”,这是因为Redis会话过期并没有直接使用在session:sessions:key变量上,而是专门用在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.springsessiontarget目录下生成了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中。

  以上内容节选自《Spring Boot2精髓:从构建小系统到架构分布式大系统》,点此链接可在博文视点官网查看此书。
                  
  想及时获得更多精彩文章,可在微信中搜索“博文视点”或者扫描下方二维码并关注。
                    

时间: 2024-10-29 07:49:54

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

Spring Session 2.0.0.M1 发布,分布式解决方案

我很高兴地宣布Spring Session 2.0.0.M2 发布了.此版本的重点主要是确保与Spring Framework 5.0.0.RC2和Spring Data Kay-M4的兼容性.我们期望Spring Session 2.0.0.M3将拥有一个新的Java 8友好的API并支持Spring WebFlux. 完整内容请查看发行公告. 下载地址: Source code (zip) Source code (tar.gz) 版本更新内容请关注发布主页. 文章转载自 开源中国社区[ht

Spring.net(一)----Spring.NET框架简介及模块说明

Spring Framework天生丽质,因为其先进的架构和对一系列前沿开发思想的集成,使它从一产生就深得开发者欢迎,它将设计模式运用到了炉火纯青的地方,它的幽雅和魅力征服了许多JAVA开发者,使这些开发者成为了它的信徒,得程序员心者得天下,Spring引领了J2EE开发的潮流,同时Spring和各种框架的强强联手也为它的信徒的尽早飞翔插上了翅膀,比如老大哥SSH(配置烦琐的struts和Hibernate.Spring的集成).后起之秀EJSA(身材轻便使用灵活及零配置的EASYJWEB+JP

spring data jpa + spring mvc的事务控制问题

问题描述 spring data jpa + spring mvc的事务控制问题 问题大概是这样的,如下@Transactionalpublic void save(A a){ a = aRepository.save(a); B b = new B();b.setAId(a.getId());bRepository.save(b); }这样是获取不到a对象的id的,因为方法没有执行完,这个事务没有提交,数据没有更新到数据库,请问如果我想在save之后通过返回的对象就能拿到主键需要怎么做,事务是

【Spring开发】—— Spring Core

前言 最近由于一些工作的需要,还有自己知识的匮乏再次翻开spring.正好整理了一下相关的知识,弥补了之前对spring的一些错误认知.这一次学习,更加深入的理解了Ioc和AOP的思想,并对其架构模块有了更深一步的理解. 刚开始翻看spring技术内幕,虽然有了一点看源码的经验,但是直接看如此深的源码,还是很头疼.spring由于业务的扩展,以及用户群的增加,对于某些模块的类封装的很深!因此追溯源码是个很头疼的问题,而直接看这本书,也是压力山大. 于是回去复习一下spring的基本知识,先学会走

公有云环境下应用程序的自动化部署与水平扩展问题

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://dgd2010.blog.51cto.com/1539422/1690176 先介绍了一下公有云计算环境下的一些特点,再根据这些特点探讨一下作为云计算用户而言,如何对应用程序做好自动化部署和水平扩展(弹性计算)的问题.阅读本文需要有一定的云计算知识.开发运维知识. 公有云环境的优势及其特点 公有云为企业用户或个人用户(以下统称为用户)可提供三种服务,基础服务.应用服务和运维服务.

spring数据源-关于spring AbstractRoutingDataSource和切面执行顺序问题

问题描述 关于spring AbstractRoutingDataSource和切面执行顺序问题 我想spring AbstractRoutingDataSource和spring aop配合使用,达到动态切换数据源的功能.现在有一个问题是,如果我把aop before设置到service层上,发现一个请求过来会先进入spring mvc的controller,然后再进入AbstractRoutingDataSource,最后才进去aop before中,这样就导致不能在service层动态切换

《架构真经:互联网技术架构的设计》水平扩展

本节书摘来自华章出版社<架构真经:互联网技术架构的设计>一书中的第1章,第3节,作者 小象学院 杨 磊,更多章节内容可以访问"华章计算机"公众号查看. 水平扩展 在实践中,我们经常告诉客户,"向上扩展注定会失败".这是因为在超高速增长的环境里,公司计划以水平方式扩展(又称之为向外扩展)至关重要.大多数情况下,这是通过对跨越多个系统工作负荷的拆分或者复制完成的.数据拆分的实施,类似我们在第2章中描述的众多方法中的一种,当超高速增长的公司无法扩展时,他们唯一

框架-Spring mvc和spring的区别

问题描述 Spring mvc和spring的区别 在书里看到的例子,业务层和持久层用的spring框架,而表现层由Spring mvc 实现 spring mvc和spring的区别在哪里?应用的地方不同吗? 这样的话只有表现层用了MVC的设计模式?其它层可以用spring mvc框架吗? spring可以整合其他的框架,意思说三层可以由不同的框架实现? 刚学spring,问题比较多,先谢了 解决方案 三层架构和MVC是有明显区别的,MVC应该是展现模式(三个加起来以后才是三层架构中的UI层)

《解读NoSQL》——2.6 通过数据库分片获得水平扩展能力

2.6 通过数据库分片获得水平扩展能力 随着一个组织存储的数据量增加,可能在某个时候,业务运行所需的数据量超过了当前环境所能运行的最大值,这时候,一些将数据分成合理的数据块的机制是必要的.组织和系统可以将数据库自动分片(将一个数据库划分为一些块,这些块称作数据库分片,它们遍布在一些分布式服务器上)作为持续存储数据并且最小化宕机时间的手段.在稍早的系统上手动配置数据库并将数据从旧系统复制到新系统时,这个操作可能会耗费系统数小时,然而NoSQL系统会自动进行这个操作.数据库的成长性和自动分区数据的容