通过spring session实现分布式会话
集群下不会丢失会话 集中管理session

目标

通过spring session实现分布式会话
集群下不会丢失会话 集中管理session

场景

一个Nginx(反向代理,负载均衡) -> 用户服务被部署了3份(tomcat)
默认情况下,使用的session由tomcat的sessionManager提供
在负载均衡下, 用户A的登录会失效 因为其登录的session只存在于其中一个tomcat上
当连接到另一个tomcat时, tomcat发现没找到A的session(sessionId) 会给A新建一个session(同时重写cookie的sessionId)
请求每换了一个tomcat就会发送session新建的操作, 上次的session虽然还在之前的tomcat中, 但凭据(sessionId)已经丢失

解决方案:

  • 方案1: nginx的负载策略 ip-hash 可以保证某用户的请求会转发到相同的tomcat
  • 方案2: tomcat-cluster可以保证节点间的session共享
  • 方案3: 通过第三方session插件替换tomcat自带的sessionManager 如memcached-session-filter,memcached-session-manager
  • 方案4: spring-session将session进行集中管理 直接顶替

构建服务

启动redis

1
2
3
4
5
6
7
8
docker pull redis:4.0.14

mkdir /usr/deepin && cd /usr/deepin 
echo "requirepass pwd123" > redis.conf

docker run -d --name redis -p 11223:6379 \
-v `pwd`/redis.conf:/etc/myredis.conf \
redis:4.0.14 redis-server /etc/myredis.conf --appendonly yes

测试redis

1
2
3
4
5
6
7
8
9
docker exec -it redis

#使用密码登录, 进行存取
root@cfc7fffb424a:/data# redis-cli -a pwd123
Warning: Using a password with '-a' option on the command line interface may not be safe.
127.0.0.1:6379> set name tom
OK
127.0.0.1:6379> get name 
"tom"

spring boot添加spring session

1
2
3
4
5
6
7
#spring boot版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/>
</parent>

spring boot

MyApp.java

1
2
3
4
5
6
7
@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        System.out.println(66);
        SpringApplication.run(MyApp.class, args);
    }
}

spring session

pom.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
  <version>2.1.4.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
  <version>2.1.4.RELEASE</version>
</dependency>

RedisSessionConfig.java

1
2
3
4
@Configuration
@EnableRedisHttpSession
public class RedisSessionConfig {
}

application.properties

1
2
3
4
server.port=8811
spring.redis.host=192.168.88.3
spring.redis.port=11223
spring.redis.password=pwd123

测试

添加测试类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@RestController
public class HelloController {
  @RequestMapping(value = "hello")
  public String add(HttpServletRequest req) throws Exception {
    String k = "count";
    int v = 0;
    HttpSession session = req.getSession();
    Object obj = session.getAttribute(k);
    if(null != obj){
      v = (int) obj;
    }
    v ++;
    session.setAttribute(k, v);

    StringBuilder sb = new StringBuilder("hello-");
    sb.append("\n sid-"+session.getId());
    sb.append("\n num-"+v);

    return sb.toString();
  }

}

程序以8811端口启动 浏览器每次访问 http://127.0.0.1:8822/hello 数字会加1 同时打印了sessionId

修改下application.properties端口为8822 启动第2个程序 同时不关闭上1个程序 访问 http://127.0.0.1:8822/hello 数字是接着上次的数字增加了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#查看redis的数据
root@cfc7fffb424a:/data# redis-cli -a pwd123
Warning: Using a password with '-a' option on the command line interface may not be safe.
127.0.0.1:6379> keys spring:session*
1) "spring:session:expirations:1554631680000"
2) "spring:session:sessions:90546a74-7d5a-49cb-acba-40a79a078eb1"
3) "spring:session:sessions:expires:90546a74-7d5a-49cb-acba-40a79a078eb1"
127.0.0.1:6379> exit

#清空数据
root@cfc7fffb424a:/data# redis-cli -a pwd123 keys "spring:session:*" | xargs redis-cli -a pwd123 del
Warning: Using a password with '-a' option on the command line interface may not be safe.
Warning: Using a password with '-a' option on the command line interface may not be safe.
(integer) 3

此时在浏览器中继续访问
可以发现之前的计数被清空了
另外登录时通常是将UserInfo对象放入session
而session是放在redis中 应注意序列化问题