踩坑二:Redis + Zuul 实现 Session 共享
踩坑二:Redis + Zuul 实现 Session 共享
1、分布式Session的常用策略
在之前的单体应用中,我们经常使用 Session 来保存用户会话信息,这是非常 OK 的;但是 Session 是保存在服务器内存中的,所以在分布式多机(多台服务器,使用负载均衡)情况下,用户的请求每次可能会访问到不同的服务器,原生 Session 方案将不再可行;对于分布式应用,我们处理 Session 有以下几种常用策略,当然了各有利弊:
- 会话保持(案例:
Nginx、Haproxy) - 会话复制(案例:
Tomcat) - 会话共享(案例:Memcached、
Redis) - 会话持久化
1.1、会话保持
也叫粘性 Session,通过负载均衡配置(如 Nginx 的 ip_hash 和 url_hash),在请求分发时将同一个用户的所有请求都固定到同一个服务器上,从而达到会话保持的目的;
- 优点:简单,不需要对 session 做任何处理。
- 缺点:缺乏容错性,如果当前访问的服务器发生故障,用户被转移到第二个服务器上时,他的 session 信息都将失效。
- 适用场景:发生故障对客户产生的影响较小;服务器发生故障是低概率事件。
- 实现方式(
Nginx):
upstream mycluster{
#这里添加的是上面启动好的两台Tomcat服务器
ip_hash;#粘性Session
server 192.168.22.229:8080 weight=1;
server 192.168.22.230:8080 weight=1;
}1.2、会话复制
任何一个服务器上的 Session 发生改变(增删改),该节点会把这个 Session 的所有内容序列化,然后广播给所有其它节点,不管其他服务器需不需要 Session,以此来保证 Session 同步。
- 优点:可容错,各个服务器间 Session 能够实时响应。
- 缺点:会对网络负荷造成一定压力,如果 Session 量大的话可能会造成网络堵塞,拖慢服务器性能。
1.3、会话共享
会话共享就是把 Session 放到一个统一的地方,这样集群中的所有节点都在一个地方进行存取 Session 就可以了,放到哪里呢?放到数据库即第四点会话持久化;Session 肯定是频繁使用的,所以还是建议存放在性能更好的分布式 KV 数据库中,如 Memcached 和 Redis。
这种方式效率高,成本低,扩展性好,是最常用的一种方式。
- 优点:可容错,Session 实时响应
1.4、会话持久化
指的是将 Session 存到数据库中
- 优点:服务器出现问题,Session 不会丢失
- 缺点:如果网站的访问量很大,把 Session 存储到数据库中,会对数据库造成很大压力,还需要增加额外的开销维护数据库。
2、Spring Cloud + Redis 实现 Session 共享
以下针对 Web 项目,即 Eureka Client 项目
2.1、加入 pom 依赖
<!-- Spring使用Redis管理Session -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>2020 年 3 月 17 日 21:11:28 更新,以上引用方式已过期,请使用下面的方式!
<!-- 引入redis支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<!-- 引入redis session 会话支持 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<!-- 只引用以上两个可能会报 -->
<!-- ClassNotFoundException: org.apache.commons.pool2.impl.GenericObjectPoolConf -->
<!-- 异常,所以需要引入jedis客户端 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>2.2、配置 application.yml
2.2.1、Redis 信息
server:
port: 8080
spring:
application:
name: test-session
redis:
database: 0
host: 127.0.0.1
port: 6379
password:
timeout: 0
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 02.2.2、Eureka 注册信息
# 注册中心配置
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
client:
serviceUrl:
defaultZone: http://127.0.0.1:8761/eureka/2.3、Java 配置类
创建 RedisSessionConfig 配置类如下:
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@Configuration
//maxInactiveIntervalInSeconds 默认是1800秒过期,这里测试修改为60秒
@EnableRedisHttpSession(maxInactiveIntervalInSeconds=60)
public class RedisSessionConfig{
}笔者猜测在 application.yml 中配置“spring.session.store-type=redis”应该可以不用写这个
Java类
2.4、Controller 使用
在 Controller 方法中就正常使用 HttpSession 对象即可,此时的 HttpSession 已经交由 Spring 来管理了,并使用 Redis 的方式存储。
3、重点问题
3.1、通过 Zuul 访问服务每次会新建 Session
使用 Zuul 作为网关时,每次请求通过 Zuul 去后台寻找服务,都会新建一个 Session;这是由于 Zuul 默认会丢弃原来的 Session 并生成新的 Session,可以在Zuul 项目的 application.yml 中配置“sensitiveHeaders”:
zuul:
routes:
api-a:
sensitiveHeaders: "*"
path: /api-a/**
serviceId: test-session
