SpringSession+Redis实现集群会话共享

0) 前言

WEB应用开发完成后部署到Tomcat或其余容器中供用户访问. 小型应用在一台服务器上安装Tomcat并部署WEB应用. 随着访问量增大, Tomcat的压力会愈来愈大, 直至崩溃. 为了保证WEB应用的承载能力, 须要对WEB应用进行集群处理.html

技术发展到今天, 集群/负载均衡已经变的相对简单了. 下面用通俗的语言给刚入门的同窗介绍下这两个概念:nginx

某KFC开业时只有一个点餐窗口(一台Tocmat服务器, 能够节约成本)对外提供点餐服务. 应对平常点餐没有问题, 当饭口或者周末时一个窗口就会排起长队(高并发). 不只顾客有怨言(请求响应时间长, 用户体验差), 服务员也会很累, 终于有一天他累倒了(Tomcat挂掉了).git

这时在侧面增长了一个窗口(增长一台Tomcat服务器)提供点餐服务, 可是不少顾客不知道新窗口, 依旧在原有窗口排起了长队(用户依旧访问原来的Tomcat), 这时须要有一我的专门站在门口根据每一个窗口的排队状况指引顾客去哪一个窗口点餐(负载均衡器). 这我的做用是为了让各个窗口的点餐人数大体相等, 避免有的窗口很忙, 有的很闲. 随着顾客增长, 点餐窗口也会相应增长(Tomcat愈来愈多).github

  • 集群: 一群服务器集合在一块儿提供服务, 上例中多个点餐窗口(多台Tomcat)共同提供点餐服务就是集群.
  • 负载均衡: 让集群中每一个点餐窗口(每一个Tomcat)的负载状况保持均衡, 不要出现某一个或几个太空闲.

两个概念是同时出现的, 没有集群的服务(单一Tomcat)也不存在负载均衡之说, 集群的服务没有负载均衡会浪费资源.web

WEB负载均衡方案不少, Nginx+Tomcat是经常使用的方案之一. Nginx做为负载均衡器根据每一个Tomcat的负载状况进行分流.正则表达式

  • 每一个Tomcat都至关于点餐窗口, 均可以提供点餐服务
  • 每次要点餐都得先通过Nginx
  • Nginx会根据每一个窗口的空闲状况进行分配用户去哪一个窗口点餐
  • 第一次在1号窗口点餐, 点完后立刻再次点餐, 有可能被分配到2号窗口

下面咱们搭建负载均衡的WEB应用redis

1) 搭建WEB应用

准备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 : 返回5677
  • http:// localhost:5688/port/get : 返回5688

2) Nginx配置负载均衡

Window下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的负载均衡.

  • 配置1)中定义的两个tomcat, 在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; 
}
复制代码
  • 配置当访问Nginx的全部请求转发至两个服务器处理
location / {
    # root D:/;
    # 转发至名称为springsession的upstream处理
    proxy_pass http://springsession; 
}
复制代码

3) 测试负载均衡

访问http://localhost:88/port/get, Nginx将请求转发至两台tomcat中的一个进行处理. 能够发现请求返回的结果是不同的

  • 根据配置的权重, 每4次访问有3次由5688上, 1次由5677处理.
  • 权重配置只是最终平均值为3/4和1/4, 不必定是前三次访问都会由5688处理.
  • 不配置weight时, 一次请求两个tomcat被分配到的几率各占50%

负载均衡配置好了, 有这样一个问题:

你在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

  • 两次访问都在同一Tomcat下的SESSOIN ID是不变的
  • 两次访问在不一样的Tomcat下的SESSION ID是变化的
  • 访问不一样的Tomcat后, 再次访问同一Tomcat时SESSION ID也是变化的

出现上述状况的缘由以下:

  1. 访问5677, 因为没有SESSION, Tomcat5677生成SESSION, ID为1, 并将1返回客户端
  2. 访问5677, 浏览器携带SESSION_ID=1, Tomcat5677找到对应的SESSION. 所以SESSION_ID为1
  3. 访问5688, 浏览器携带SESSION_ID=1, Tomcat5688找不到对应的SESSION, 从新生成SESSION, ID为2, 并将2返回客户端
  4. 访问5677, 浏览器携带SESSION_ID=2, Tomcat5677找不到对应的SESSION, 从新生成SESSION, ID为3, 并将3返回客户端
  5. 访问5688, 浏览器携带SESSION_ID=3, Tomcat5688找不到对应的SESSION, 从新生成SESSION, ID为4, 并将4返回客户端

4) 统一SESSION管理

下面咱们来用Spring Session来管理WEB应用的SESSION

1) 安装Redis并开启

参见文章http://www.cnblogs.com/jaign/articles/7920588.html

2) 添加Spring Session依赖

// Spring Session依赖
"org.springframework.session:spring-session-data-redis:2.0.5.RELEASE",
// Redis依赖
"io.lettuce:lettuce-core:5.0.4.RELEASE"
复制代码

3) 配置Spring Session过滤器

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>
复制代码

4) SpringSession/Redis配置

在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)
 
}
复制代码

5) 测试

启动项目, 屡次访问http://localhost:88/sessionid/get, 不管如何访问SESSION ID都是同样的.

同时Redis中也出现了当前SESSION的记录.

使用Spring Session后访问集群下的WEB应用时SESSION处理过程:

  1. 访问5677, 因为Redis中没有SESSION, 所以会生成一个SESSION并存入Redis, ID为1, 并将1返回客户端
  2. 访问5677, 浏览器携带SESSION_ID=1, Tomcat5677Redis中找到了SESSION. 所以SESSION_ID1
  3. 访问5688, 浏览器携带SESSION_ID=1, Tomcat5688Redis中找到了SESSION. 所以SESSION_ID1
  4. 清除Redis, 再次访问5677, 因为Redis中没有ID为1SESSION, 所以会从新生成, ID也相应变化了

5) 示例代码

此时咱们已经实现了统一管理SESSION, 不管访问任一TOMCAT均可以找到相同的SESSION.

当咱们的应用进行集群后, 统一管理SESSION势在必行, 实现统一管理SESSION的方式不少, 本文只是其中一种方式. 重在让同窗们理解统一管理SESSION的重要性和他的基本原理.

  • 示例代码地址: https://github.com/atd681/alldemo
  • 示例项目名称: atd681-springsession
相关文章
相关标签/搜索