聊聊几种常见的分布式Session解决方案


highlight: xcode

theme: vuepress

问题引入:什么是分布式Session?

分布式 Session 是指在多台服务器之间共享和管理用户的会话数据,使得用户的会话状态能够在不同的服务器上保持一致。这样,无论用户的请求被路由到哪台服务器,都能够访问到相同的会话信息,从而保证用户体验的一致性。

回顾一下单机服务的 HttpSession 的存储:

在传统的 JavaWeb 的 Tomcat + Servlet 的项目中,HttpSession 通常存储在 JVM 内存中。浏览器第一次访问服务之后,会得到一个名为 JSESSIONID 的 Cookie。在后续的请求中,浏览器都会携带此 Cookie。用一个图来简单说明一下:

在 HttpSession 中存储用户数据最原始的做法是怎么做的呢?初学者一般都是这么写(因为教科书上也是这么写的🐶):

java public void doGet(HttpServletRequest req, HttpServletResponse resp) { HttpSession session = req.getSession(); // 获取用户名 String username = req.getParameter("username"); session.setAttribute("username", username); // 后续操作... }

这种写法只适合单机部署的场景,在分布式场景下是不可行的,因为请求会到不同的机器上,每台机器上的数据都不一样。而且服务端对同一个客户端的请求不能共享 Session。

常见解决方案

对于分布式 Session,通常有以下几种解决方案:

  1. Session 粘滞:通过负载均衡器(如 Nginx、F5 等)将同一用户的请求始终路由到同一台服务器上。
  2. 数据库共享 Session:将 Session 存储在数据库中(如 MySQL、PostgreSQL 等),各个服务器访问同一个数据库。
  3. Token 方式:将 Session 数据编码为 Token(如 JWT),并由客户端保存(通常在 Cookie 或 HTTP 头中传输)。
  4. 分布式文件系统:将 Session 存储在分布式文件系统(如 NFS、GlusterFS 等)中,各个服务器访问共享的文件系统。
  5. 缓存共享 Session:使用分布式缓存系统(如 Redis、Memcached 等)存储 Session 数据,各个服务器共享同一个缓存。

Session 粘滞

以 nginx 的 IP Hash 策略为例,通过 IP Hash 策略可以将客户端的 IP 地址哈希到特定的后端服务器上,从而确保同一个客户端的请求总是被路由到同一台服务器上。

示例配置:

```conf http { upstream backend { ip_hash; server backend1.example.com; server backend2.example.com; server backend3.example.com; }

server {
    location / {
        proxy_pass http://backend;
    }
}

} ```

寥寥几行,通过简单的配置即可实现。同一客户端的请求总是路由到同一台服务器,因此可以保证会话的一致性,避免 Session 丢失。

但是缺点也很明显,由于 IP 地址的分布不均匀,可能导致某些服务器负载过重,而其他服务器负载较轻。如果某台服务器宕机,与其绑定的所有客户端会话信息都会丢失,无法自动迁移到其他服务器。添加或移除服务器会改变 IP 到服务器的映射,导致会话丢失。对于跨地域的用户,IP Hash 可能导致某些地域的用户集中到特定的服务器,进一步加剧负载不均衡。

明显缺点大于优点,所以基本不推荐。

数据库共享Session

使用数据库存储 Session,虽然实现起来很简单,但是会导致每次请求都要查询数据库让数据库的负载变大。 在一些性能敏感的系统中,性能瓶颈明显,扩展性很差。所以也不推荐。

Token方式

将 Session 数据编码为 Token,并由客户端保存可以让服务端无状态化,扩展性好,不依赖集中存储。但是 Token 很可能被截取和伪造,安全性低,需要加密和签名。也不是很推荐。

分布式文件系统

使用分布式文件系统才存储 Session,实现起来相对简单,可以利用现有的文件系统。但是性能比较差,文件系统的同步和一致性问题需要考虑,所以也不是很推荐。

缓存共享Session

上面说了几种方案都不推荐,这个总得推荐了吧?哈哈哈,那肯定啊,不然这篇文章都没有写的必要了。

使用缓存系统存储 Session,读写速度快,而且支持高并发。需要注意的是缓存失效策略。以 Redis 为例存储分布式 Session。

创建一个 Spring Boot 工程,导入 Spring Session 的依赖:

```xml org.springframework.boot spring-boot-starter-web

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

```

配置一下 application.yml 文件:

```yaml server: port: 8081

spring: redis: port: 6379 host: localhost ```

写一个简单的 Controller:

```java @RestController public class TestController {

@GetMapping("/")
public String hello(HttpServletRequest req) {
    HttpSession session = req.getSession(true);
    System.out.println(session);
    Cookie[] cookies = req.getCookies();
    if (cookies == null) {
        return "Hello World";
    }
    for (Cookie cookie : cookies) {
        System.out.println(cookie.getName());
    }
    return "Hello World";
}

} ```

访问一下浏览器:

可以看到名称为 JSESSIONID 的 Cookie。

再看一下 Redis 中的数据:

可以看到 Session 数据已经自动存到 Redis 中了。你可能疑问为什么会自动存进去了,我并没有干什么操作啊?

因为 spring-session-data-redis 这个包在 Spring 接收到了请求的时候自动帮我们做了这个操作。有兴趣的话可以分析一下源码。

我们可以再开启一个实例观察一下 Cookie 是否发生了变化:

访问一下 http://localhost:8082 然后打开 F12 查看 Cookie:

可以看到 8081 和 8082 的 JSESSIONID 的 Cookie 是一样的,最终实现了 Session 的共享。

怎么样,你学会了吗?

相关推荐
月夜星辉雪3 分钟前
【RabbitMQ 项目】服务端:数据管理模块之消息管理
分布式·rabbitmq
guitarCC3 小时前
spark Rdd的创建方式
大数据·分布式·spark
Yz98764 小时前
Hadoop里面MapReduce的序列化与Java序列化比较
java·大数据·jvm·hadoop·分布式·mapreduce·big data
不能再留遗憾了5 小时前
RabbitMQ 高级特性——发送方确认
分布式·rabbitmq·ruby
益达_z5 小时前
中间件知识点-消息中间件(Rabbitmq)一
分布式·中间件·rabbitmq
.生产的驴8 小时前
SpringBoot 消息队列RabbitMQ在代码中声明 交换机 与 队列使用注解创建
java·spring boot·分布式·servlet·kafka·rabbitmq·java-rabbitmq
lipviolet9 小时前
Redis系列---Redission分布式锁
数据库·redis·分布式
spiker_17 小时前
RabbitMQ 常见使用模式详解
分布式·rabbitmq
不能再留遗憾了17 小时前
RabbitMQ 高级特性——持久化
分布式·rabbitmq·ruby
成为大佬先秃头17 小时前
解决RabbitMQ设置TTL过期后不进入死信队列
分布式·中间件·rabbitmq·java-rabbitmq