Redis篇--应用篇1--会话存储(session共享)

devtools/2024/12/29 7:51:48/

1、概述

实现Session共享是构建分布式Web应用时的一个重要需求,尤其是在水平扩展和高可用性要求较高的场景下。

在分布式服务或集群服务中往往会出现这样一个问题:用户登录A服务后可以正常访问A服务中的接口。但是我们知道,分布式服务通常都是有多个微服务一起构建形成的。如果后续请求转发到了B服务,B服务后端没有这个用户的Session信息,就会强制让用户重新登录,导致业务无法顺利完成。因此,就需要将Session进行共享,保证每个系统都能获取用户的Session状态。

Redis可以用来存储Web应用的用户会话信息,支持快速的会话管理和跨服务器的会话共享。

2、常见实现session共享的方式

(1)、客户端存储(使用Cookie)

实现方式

  • 原理:将Session数据直接存储在客户端的Cookie中,每次请求时,浏览器会自动将Cookie发送给服务器。

优点:

  • 无服务器状态:服务器不需要存储Session数据,减少了服务器的内存占用和状态管理开销。
  • 易于扩展:由于服务器无状态,可以轻松地进行水平扩展,无需担心Session同步或共享问题。
  • 简单实现:实现相对简单,适合小型应用或临时解决方案。

缺点:

  • 安全性低:Cookie中的Session数据容易被篡改或窃取,存在安全风险。即使使用加密,也无法完全防止攻击(如XSS攻击)。
  • 可靠性差:如果用户的浏览器禁用了Cookie,或者用户手动清除了Cookie,Session信息将会丢失。
  • 大小限制:Cookie的最大大小为4KB,无法存储较大的Session数据。
  • 跨域问题:Cookie只能在同源域名下有效,跨域时需要额外处理。

适用场景:

  • 轻量级应用:适合对安全性要求不高、Session数据较少且不需要持久化的应用场景。
  • 单页应用(SPA):对于前后端分离的单页应用,可以结合JWT(JSON Web Token)来实现无状态的身份验证。

代码示例:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;@RestController
public class CookieSessionController {private static final String SESSION_COOKIE_NAME = "sessionId";@GetMapping("/set-cookie")public String setCookie(HttpServletResponse response) {// 生成唯一的Session IDString sessionId = UUID.randomUUID().toString();// 创建Cookie并设置有效期为1小时Cookie cookie = new Cookie(SESSION_COOKIE_NAME, sessionId);cookie.setPath("/");cookie.setMaxAge(60  60); // 1小时// 将Cookie添加到响应中response.addCookie(cookie);return "Cookie set with session ID: " + sessionId;}@GetMapping("/get-cookie")public String getCookie(HttpServletRequest request) {// 从请求中获取CookieCookie[] cookies = request.getCookies();if (cookies != null) {for (Cookie cookie : cookies) {if (cookie.getName().equals(SESSION_COOKIE_NAME)) {return "Session ID from Cookie: " + cookie.getValue();}}}return "No session ID found in Cookie.";}
}

说明:
通过set-cookie的方法,将必要的用户信息保存到response中,返回给浏览器。浏览器会自动将response中这些需要设置cookie的信息保存到cookie中,之后对相同的域进行请求,就会自动携带这些cookie信息。

(2)、Session绑定(Nginx IP绑定策略)

实现方式
通过Nginx的ip_hash指令,将来自同一IP的请求始终路由到同一台后端服务器。这样可以确保用户的Session信息保存在同一台服务器上。

优点:

  • 简单实现:配置简单,只需在Nginx中设置ip_hash即可。
  • 性能较好:由于Session数据直接存储在本地内存中,访问速度较快。

缺点:

  • 单点故障:如果某一台服务器宕机,该服务器上的Session信息将会丢失,导致用户需要重新登录或重新创建Session。
  • 负载不均衡:由于同一个IP的请求总是路由到同一台服务器,可能会导致某些服务器的负载过高,而其他服务器闲置。
  • 不适合动态IP:如果用户的IP地址频繁变化(如移动设备),可能会导致Session丢失或错误的Session分配。
  • 扩展性差:随着用户数量的增加,单台服务器的压力会越来越大,难以进行水平扩展。

适用场景:

  • 小型应用:适合用户量较小、服务器数量有限的应用场景。
  • 内部系统:对于内部系统或企业内部网,用户的IP地址相对固定,可以考虑使用这种方式。

示例:(nginx配置)

http {upstream backend {ip_hash;   使用IP哈希算法,确保同一IP的请求总是路由到同一台服务器server 192.168.1.1:8080;server 192.168.1.2:8080;}server {listen 80;location / {proxy_pass http://backend;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}}
}

说明:
nginx配置ip_hash后,每一次经过nginx代理的请求。nginx都会将请求的ip代理到固定的服务Ip上(实际是请求ip通过哈希算法映射的结果)。这种方法保证了来自同一IP的请求始终会路由到同一台后端服务器上。

(3)、Session同步(Tomcat内置Session同步)

实现方式

  • Tomcat提供了内置的Session复制机制,可以通过集群中的多台服务器之间同步Session数据。
    常见的同步方式包括:
    • 内存复制:每台服务器将Session数据复制到其他服务器的内存中。
    • Delta复制:只复制Session中发生变化的部分,减少同步的数据量。
    • 文件存储:将Session数据写入共享文件系统,所有服务器从该文件系统读取Session数据。

优点:

  • 高可用性:即使某一台服务器宕机,其他服务器仍然可以访问到用户的Session信息,避免了Session丢失。
  • 自动同步:无需额外开发,Tomcat内置了Session同步功能,配置相对简单。

缺点:

  • 同步延迟:Session同步可能会产生一定的延迟,尤其是在网络状况不佳或Session数据较大时,影响用户体验。
  • 性能开销:每次请求都会触发Session同步操作,增加了服务器之间的通信开销,降低了整体性能。
  • 扩展性有限:随着集群规模的扩大,Session同步的复杂性和开销也会增加,可能导致性能瓶颈。
  • 资源浪费:所有服务器都需要存储相同的Session数据,占用了大量的内存资源。

适用场景:

  • 中小型应用:适合用户量适中、服务器数量不多的应用场景。
  • 对Session同步要求不高的应用:如果Session数据更新频率较低,且对同步延迟不敏感,可以考虑使用这种方式。

(4)、Session共享(Redis等缓存中间件)

实现方式

  • 将Session数据存储在独立的缓存中间件(如Redis、Memcached)中,所有服务器都可以通过访问该中间件来获取和更新Session数据。常见的实现方式包括:
    • Redis作为Session存储:将Session数据以键值对的形式存储在Redis中,使用唯一的Session ID作为键。
    • Spring Session:Spring框架提供了Spring Session模块,可以轻松集成Redis作为Session存储。

优点:

  • 高可用性:Session数据集中存储在Redis中,所有服务器都可以访问,避免了单点故障。即使某一台服务器宕机,其他服务器仍然可以继续使用用户的Session。
  • 高性能:Redis是基于内存的NoSQL数据库,具有极高的读写性能,能够快速响应Session请求。
  • 易于扩展:Redis支持集群模式,可以根据需要水平扩展,满足大规模应用的需求。
  • 灵活性强:可以结合Redis的过期时间、持久化等功能,灵活管理Session的生命周期。
  • 减轻服务器压力:Session数据不再存储在服务器内存中,减少了服务器的内存占用,提升了服务器的性能。

缺点:

  • 依赖外部服务:需要额外部署和维护Redis等缓存中间件,增加了系统的复杂性。
  • 网络延迟:虽然Redis的性能很高,但仍然存在网络延迟,尤其是在跨机房或跨区域部署时,可能会影响Session的读取速度。
  • 数据一致性问题:如果Redis集群出现故障或网络分区,可能会导致Session数据的一致性问题。

适用场景:

  • 大型分布式应用:适合用户量大、服务器数量多的分布式应用,尤其是需要高可用性和水平扩展的场景。
  • 高并发应用:适合对性能要求较高的应用,Redis的高效读写能力可以应对大量并发请求。
  • 微服务架构:在微服务架构中,多个服务实例可以共享同一个Redis实例,方便统一管理Session。
Redis实现session共享示例代码

第一步:导入依赖

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

第二步:配置文件

spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=
spring.redis.database=0
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=500
spring.redis.pool.min-idle=0
spring.redis.timeout=500spring.session.store-type=redis    #使用Redis作为Session存储
spring.session.timeout=1800s       #Session超时时间

第三步:配置类
需要注解启用RedisSession。同时配置Redis的序列化方式。

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
import org.springframework.session.web.http.HttpSessionIdResolver;@Configuration
@EnableRedisHttpSession        // 启用Redis session
public class SessionConfig {// 可以自定义session id解析器,这里我们使用header解析器@Beanpublic HttpSessionIdResolver httpSessionIdResolver() {return HeaderHttpSessionIdResolver.xAuthToken();}@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);// 设置键的序列化器为 StringRedisSerializertemplate.setKeySerializer(new StringRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());// 设置值的序列化器为 Jackson2JsonRedisSerializerJackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper objectMapper = new ObjectMapper();jackson2JsonRedisSerializer.setObjectMapper(objectMapper);template.setValueSerializer(jackson2JsonRedisSerializer);template.setHashValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();return template;}
}

第四步:测试类

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;@Controller
@RequestMapping("/session")
public class SessionController {@GetMapping("/set")@ResponseBodypublic String setSessionAttribute(HttpServletRequest request) {HttpSession session = request.getSession();session.setAttribute("username", "John Doe");return "Session attribute 'username' set to 'John Doe'.";}@GetMapping("/get")@ResponseBodypublic String getSessionAttribute(HttpServletRequest request) {HttpSession session = request.getSession(false);if (session != null) {String username = (String) session.getAttribute("username");return "Session attribute 'username': " + username;} else {return "No session found.";}}
}

第五步:测试验证
当调用get方法后,查看Redis可以发现已经存在session信息了。
在这里插入图片描述

3、四种实现session共享方案对比

在这里插入图片描述
如果使用分布式系统,用户量大,服务器数量多,且对高可用性和性能有较高要求*,强烈推荐使用Session共享(Redis等缓存中间件)*。Redis的高性能和可扩展性使其成为最合适的方案,尤其是在微服务架构中。

进一步优化:

  • 结合JWT:对于无状态的应用,可以考虑使用JWT(JSON Web Token)来替代传统的Session机制。
  • Redis集群:为了提高Redis的可用性和性能,可以使用Redis集群或哨兵模式(Sentinel)。
  • Session压缩:如果Session数据较大,可以考虑对Session数据进行压缩后再存储到Redis中。
  • TTL设置:合理设置Session的过期时间(TTL),避免长时间未使用的Session占用过多资源。

http://www.ppmy.cn/devtools/145962.html

相关文章

SpringAI人工智能开发框架005---SpringAI文本转语音_语音转文本_音频翻译程序接口编写_英文音频翻译_中文音频翻译_指定模型

可以看到SpringAi中关于音频的API帮助文档可以去看一下. 可以看到帮助文档. 这部分功能就是把声音变成文本,以及把文本变成声音. 去创建一个项目 然后修改一下,仓库,引入 sring ai的仓库 然后指定一下版本,这里要用java 17的版本. 然后这里用的api-key 这个key, 这里配置到…

自动化测试框架playwright 常见问题和解决方案

自动化课程已经讲完了playwright框架&#xff0c;很多同学跃跃欲试&#xff0c;所谓实践出真知&#xff0c;这不在实践中就要到了一些问题&#xff0c;小编也给大家整理出来了&#xff0c;送个有需要的同学&#xff0c;记得点赞收藏哦~~ 01安装问题 问题描述&#xff1a; 在安…

自动屏蔽频繁访问IP,提升服务器安全:实战脚本解析

在当前的数字化时代&#xff0c;服务器安全成为了一个不可忽视的重要环节。无论是企业网站还是个人博客&#xff0c;都面临着来自各方的潜在威胁。其中&#xff0c;恶意访问和频繁登录尝试更是让管理员头疼不已。本文将为大家介绍如何通过脚本自动屏蔽频繁访问和尝试SSH登录的I…

QT调用Sqlite数据库

QT设计UI界面&#xff0c;后台访问数据库&#xff0c;实现数据库数据的增删改查。 零售商店系统 数据库表&#xff1a; 分别是顾客表&#xff0c;订单详情表&#xff0c;订单表&#xff0c;商品表 表内字段详情如下&#xff1a; 在QT的Pro文件中添加sql&#xff0c;然后添加头…

ElementUI 的 form 表单校验

文章目录 需求分析 需求 分析 <el-form:model"form"status-icon:rules"rules"ref"formRef"label-width"150px"class"customForm"size"small"><el-form-itemlabel"姓名&#xff1a;"prop"…

Kibana安装教程——Linux

Kibana安装教程——Linux 一、安装 下载安装包&#xff1a; 官网下载地址&#xff1a;https://www.elastic.co/cn/downloads/kibana 上传包到linux 切换到安装目录下 解压&#xff1a;tar -zxvf kibana-7.17.1-linux-x86_64.tar.gz 重命名安装文件夹 mv kibana-7.17.1-linux-x…

云原生周刊:利用 eBPF 增强 K8s

开源项目推荐 Slurm-operator Slurm-operator 是一个高效可扩展的框架&#xff0c;用于在 K8s 环境中部署和运行 Slurm 工作负载。 它结合了 Slurm 的可靠性和 Kubernetes 的灵活性&#xff0c;支持快速部署 Slurm 集群、动态扩展 HPC 工作负载&#xff0c;并提供高度灵活的定…

【每日学点鸿蒙知识】长时任务、profiler allocation、事件订阅、getTagInfo、NativeWindow

1、HarmonyOS长时任务报错&#xff1f; 按照官方文档开启长时任务&#xff1a; startContinuousTask() {let wantAgentInfo: wantAgent.WantAgentInfo {// 点击通知后&#xff0c;将要执行的动作列表// 添加需要被拉起应用的bundleName和abilityNamewants: [{bundleName: &q…