WEB应用开发完成后部署到Tomcat或其余容器中供用户访问. 小型应用在一台服务器上安装Tomcat并部署WEB应用. 随着访问量增大, Tomcat的压力会愈来愈大, 直至崩溃. 为了保证WEB应用的承载能力, 须要对WEB应用进行集群处理.html
技术发展到今天, 集群/负载均衡已经变的相对简单了. 下面用通俗的语言给刚入门的同窗介绍下这两个概念:nginx
某KFC开业时只有一个点餐窗口(一台Tocmat服务器, 能够节约成本)对外提供点餐服务. 应对平常点餐没有问题, 当饭口或者周末时一个窗口就会排起长队(高并发). 不只顾客有怨言(请求响应时间长, 用户体验差), 服务员也会很累, 终于有一天他累倒了(Tomcat挂掉了).git
这时在侧面增长了一个窗口(增长一台Tomcat服务器)提供点餐服务, 可是不少顾客不知道新窗口, 依旧在原有窗口排起了长队(用户依旧访问原来的Tomcat), 这时须要有一我的专门站在门口根据每一个窗口的排队状况指引顾客去哪一个窗口点餐(负载均衡器). 这我的做用是为了让各个窗口的点餐人数大体相等, 避免有的窗口很忙, 有的很闲. 随着顾客增长, 点餐窗口也会相应增长(Tomcat愈来愈多).github
两个概念是同时出现的, 没有集群的服务(单一Tomcat)也不存在负载均衡之说, 集群的服务没有负载均衡会浪费资源.web
WEB负载均衡方案不少, Nginx
+Tomcat
是经常使用的方案之一. Nginx做为负载均衡器根据每一个Tomcat的负载状况进行分流.正则表达式
下面咱们搭建负载均衡的WEB应用redis
准备WEB应用, 用两个Tomcat部署, 测试时为了可以区分请求是由哪一个Tomcat进行处理, 将Tomcat端口号做为结果返回.spring
/**
* 获取部署项目的Tomcat端口号
*/
@RequestMapping("/port/get")
@ResponseBody
public String getPort(HttpServletRequest request) {
return String.valueOf(request.getLocalPort());
}
复制代码
本例中分别使用5677
, 5688
两个端口部署该项目. 访问/port/get
请求返回结果为Tomcat的端口号数据库
http:// localhost:5677/port/get
: 返回5677http:// localhost:5688/port/get
: 返回5688Window下Nginx安装比较简单, 不会安装的同窗自行百度, 简单介绍下Nginx配置文件: nginx.conf
浏览器
# Nginx进程数
worker_processes 1;
events {
# 最大并发连接数
worker_connections 1024;
}
# Nginx处理HTTP请求相关的配置
# http不能重复, 全局惟一
http {
# 虚拟主机, 可配置多个虚拟主机
# Nginx监听88,89,90三个端口, 可配置三个server
server {
# 端口号, 访问88端口会都按照该server下的配置进行处理
listen 88;
# 主机名称
server_name localhost;
# 根据正则表达式匹配URL, 匹配到的URL按照该location下的配置进行处理
# /表明访问88端口的全部请求
location / {
# 静态资源所在根目录, 会从该目录下查找静态资源
# 例: 访问/a.html, 找到D:/a.html并返回
root D:/;
}
}
}
复制代码
上述配置文件最基础的Nginx配置, 当咱们访问http://localhost:88
时会由Nginx处理, 下面咱们配置Nginx的负载均衡.
http
节点下添加以下代码:# 定义须要进行负载均衡的服务器信息
# upstream为关键字, springsession为自定义的名称
# server为关键字, 表明一个服务或服务(一个tomcat)
# server的内容为服务器的信息, 形式为ip:端口
# weight定义了服务器负载的权重, 每4次请求有3次转发到5688, 1次到5677
upstream springsession {
server localhost:5677 weight=1;
server localhost:5688 weight=3;
}
复制代码
location / {
# root D:/;
# 转发至名称为springsession的upstream处理
proxy_pass http://springsession;
}
复制代码
访问http://localhost:88/port/get
, Nginx将请求转发至两台tomcat中的一个进行处理. 能够发现请求返回的结果是不同的
5688
上, 1次由5677
处理.5688
处理.负载均衡配置好了, 有这样一个问题:
你在1号窗口点餐时把钥匙暂存到该窗口, 下次在点餐可能被分配到2号窗口或其余窗口(也有可能分配到1号窗口), 那么在其余窗口取钥匙显然是行不通的. 由于其余窗口没有你的钥匙. 这时你只能祈祷能快速把你分配到1号窗口.
若是保存钥匙的操做变为在SESSION中保存信息, 那么当你的请求被
Tomcat1
处理时,Tomcat1
会为你生成一个SESSION, 你在SESSION里面设置了信息, 下次你的请求被分配到Tomcat2
处理,Tomcat2
又会为你生成一个SESSION. 这是两个独立的而且不共享的SESSION. 所以你是不可能的在Tomcat2
中获取你在Tomcat1
中保存的信息.
登陆的原理其实就是在SESSION中保存登陆状态, 按照上面的分析, 登陆在集群部署的服务中就失效了. 在Tomcat1中登陆, 下次访问Tomcat2, 此时经过SESSION判断登陆状态获得的必定是未登陆, 还须要再次登陆. 用户疯, 你疯, 老板也会疯...
若是有一个公用位置用来存放东西, 全部的点餐窗口都在公用位置存取顾客物品, 上面的问题就迎刃而解了.
这就是本文重点: 统一管理集群下各WEB应用的SESSION.
容器的选择: 咱们须要一个可以统一存放SESSION的容器. 从如下3点分析, Redis
无疑是最合适的. SESSION是常常被读取的, 所以数据库, 文件系统均不适合, 最好是从内存操做. SESSION是有ID的, 一个ID对应一个SESSION, 最好是一个K/V的容器 SESSION是有时效性的(时间长不用, 须要删除). 最好可以设置过时时间
SESSION存取机制: 因为SESSION是Tomcat生成的, 所以首先想到的是修改Tomcat的SESSION机制, 从Redis
中存取SESSION, 这样会带来一个问题, 假设Tocmat升级了, 咱们还须要从新对Tomcat进行修改. 所以这个方案可行性较差. 咱们能够这样考虑, 即便Tomcat生成了SESSION, 咱们也是在WEB应用中使用, 为何不在WEB应用中从新生成一个SESSION呢, 编写这样一个过滤器, 在进入WEB应用以前, 抛弃Tomcat的SESSION, 从Redis
中获取SESSION.
恰巧有这样一个框架帮助咱们完成上面的想法, 只须要配置一下便可实现统一管理SESSION. 他就是Spring Session.
为了对Spring Session
的功能印象深入, 咱们先来测试一下没有Spring Session时咱们的集群应用是怎样处理SESSION的
把咱们负载均衡的WEB应用中增长一个控制器方法, 把每次的SESSION ID
输出一下.
/**
* 获取部署项目的SESSION ID
*/
@RequestMapping("/sessionid/get")
@ResponseBody
public String getPort(HttpServletRequest request, HttpSession session) {
int port = request.getLocalPort(); // 端口
String sessionId = request.getSession().getId(); // SESSION ID
return "port: " + port + ", session id: " + sessionId;
}
复制代码
启动项目, 屡次访问http://localhost:88/sessionid/get
SESSOIN ID
是不变的SESSION ID
是变化的SESSION ID
也是变化的出现上述状况的缘由以下:
5677
, 因为没有SESSION, Tomcat5677
生成SESSION, ID为1
, 并将1
返回客户端5677
, 浏览器携带SESSION_ID=1
, Tomcat5677
找到对应的SESSION. 所以SESSION_ID为1
5688
, 浏览器携带SESSION_ID=1
, Tomcat5688
找不到对应的SESSION, 从新生成SESSION, ID为2
, 并将2
返回客户端5677
, 浏览器携带SESSION_ID=2
, Tomcat5677
找不到对应的SESSION, 从新生成SESSION, ID为3
, 并将3
返回客户端5688
, 浏览器携带SESSION_ID=3
, Tomcat5688
找不到对应的SESSION, 从新生成SESSION, ID为4
, 并将4
返回客户端下面咱们来用Spring Session
来管理WEB应用的SESSION
参见文章http://www.cnblogs.com/jaign/articles/7920588.html
// Spring Session依赖
"org.springframework.session:spring-session-data-redis:2.0.5.RELEASE",
// Redis依赖
"io.lettuce:lettuce-core:5.0.4.RELEASE"
复制代码
在Web.xml
中配置Spring Session
提供的过滤器, 该过滤器主要负责将Tomcat生成的SESSION替换成Redis中保存的SESSION.
<!-- Spring Session过滤器 -->
<!-- 负责在进入WEB应用以前将Tomcat生成的SESSION替换为Redis中的SESSION -->
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
复制代码
在Spring配置文件中增长Spring Session
配置和Redis
配置
beans {
xmlns context: "http://www.springframework.org/schema/context"
// 启动注解方式
context.'annotation-config'()
// 配置Spring Session
// 其实是配置Web.xml中使用的Spring Session过滤器
// 将Tomcat的Session替换为Redis中管理的Session
sessionConfig(RedisHttpSessionConfiguration)
// 配置Redis客户端链接
// 默认链接本地6379端口
lettuce(LettuceConnectionFactory)
}
复制代码
启动项目, 屡次访问http://localhost:88/sessionid/get
, 不管如何访问SESSION ID
都是同样的.
同时Redis
中也出现了当前SESSION的记录.
使用Spring Session
后访问集群下的WEB应用时SESSION处理过程:
5677
, 因为Redis
中没有SESSION
, 所以会生成一个SESSION
并存入Redis
, ID为1
, 并将1
返回客户端5677
, 浏览器携带SESSION_ID=1
, Tomcat5677
在Redis
中找到了SESSION
. 所以SESSION_ID
为1
5688
, 浏览器携带SESSION_ID=1
, Tomcat5688
在Redis
中找到了SESSION
. 所以SESSION_ID
为1
Redis
, 再次访问5677
, 因为Redis
中没有ID为1
的SESSION
, 所以会从新生成, ID也相应变化了此时咱们已经实现了统一管理SESSION, 不管访问任一TOMCAT均可以找到相同的SESSION.
当咱们的应用进行集群后, 统一管理SESSION势在必行, 实现统一管理SESSION的方式不少, 本文只是其中一种方式. 重在让同窗们理解统一管理SESSION的重要性和他的基本原理.
https://github.com/atd681/alldemo
atd681-springsession