MyException - 我的异常网
当前位置:我的异常网» 软件架构设计 » 使用Spring Session实现Spring Boot水准扩展

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

www.MyException.Cn  网友分享于:2013-12-18  浏览:0次
使用Spring Session实现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.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中。

www.meimei333.com

文章评论

不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
旅行,写作,编程
旅行,写作,编程
漫画:程序员的工作
漫画:程序员的工作
中美印日四国程序员比较
中美印日四国程序员比较
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
程序员和编码员之间的区别
程序员和编码员之间的区别
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
为什么程序员都是夜猫子
为什么程序员都是夜猫子
编程语言是女人
编程语言是女人
程序员的鄙视链
程序员的鄙视链
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
每天工作4小时的程序员
每天工作4小时的程序员
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
Java程序员必看电影
Java程序员必看电影
总结2014中国互联网十大段子
总结2014中国互联网十大段子
鲜为人知的编程真相
鲜为人知的编程真相
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
一个程序员的时间管理
一个程序员的时间管理
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
我的丈夫是个程序员
我的丈夫是个程序员
如何成为一名黑客
如何成为一名黑客
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
 程序员的样子
程序员的样子
程序员应该关注的一些事儿
程序员应该关注的一些事儿
老程序员的下场
老程序员的下场
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
代码女神横空出世
代码女神横空出世
程序员都该阅读的书
程序员都该阅读的书
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
那些争议最大的编程观点
那些争议最大的编程观点
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有