知识篇 | 中间件会话保持和会话共享有啥区别?
理解中间件的会话保持(Session Affinity / Sticky Session)和会话共享(Session Sharing / Distributed Session)对于构建可扩展、高可用的Web应用至关重要。它们解决了在集群/负载均衡环境下用户状态管理的问题,但采用了截然不同的思路。本文一起学习下。
1. 会话保持(Session Affinity / Sticky Session)
核心思想:确保来自同一个用户会话的所有请求,在会话有效期内,都被路由到同一个后端服务器实例上。
目标:让用户的会话数据(通常存储在单个服务器进程的内存中)在后续请求中仍然可以被访问到,因为请求总是到达最初创建会话的那台服务器。
实现方式(通常在负载均衡器/反向代理层配置):
(1)基于Cookie:插入Cookie: 负载均衡器在用户第一次请求时(或响应第一个响应时),在用户的浏览器中设置一个特殊的Cookie(如 `AWSALB`, `JSESSIONID` 在某些配置下,或自定义Cookie)。
识别Cookie: 后续请求中,浏览器会携带此Cookie。负载均衡器根据Cookie的值(通常包含目标服务器的标识信息)将请求路由到对应的服务器。
(2)基于源IP地址:(1)负载均衡器根据用户的源IP地址进行哈希计算,将同一IP来源的请求固定路由到某台服务器。这种方式在用户IP变化(如移动网络切换、使用大型NAT网关)或大量用户共享同一IP(如公司出口IP)时效果不佳,且不够灵活和安全,现在较少作为首选。
优点:
实现相对简单:负载均衡器配置即可,后端应用通常无需修改(保持将会话存在内存中的方式)。
性能:会话数据在服务器本地内存中,访问速度最快(零网络开销)。
缺点:
缺乏真正的容错性/高可用:
1.服务器故障:如果用户“粘住”的服务器宕机,该用户的会话数据将丢失(因为数据只存在那台服务器的内存里)。用户需要重新登录或重新开始会话。
2.服务器维护:重启或下线被粘住的服务器也会导致该服务器上所有会话丢失。
3.负载可能不均衡:如果某个用户的会话非常长或请求量特别大,会导致其“粘住”的服务器负载过高,而其他服务器负载较低。
4.扩展性限制:增加新服务器时,新会话会路由到新服务器,但已有会话仍然粘在旧服务器上。无法有效利用新服务器的资源来分担已有长会话的压力。
适用场景: 对会话丢失容忍度较高、会话持续时间较短、应用规模不大或对极致性能要求非常高且能接受宕机风险的应用。常用于临时性需求或作为快速解决方案。
2. 会话共享(Session Sharing / Distributed Session)
核心思想:将会话数据从单个Web服务器实例的内存中移出来,存储在一个独立、集中、所有服务器实例都能访问的外部存储服务中。
目标:让任何后端服务器实例都能处理用户的请求,并能访问和更新该用户的完整会话数据,实现真正的无状态(Stateless)应用服务器(服务器本身不保存会话状态)。
实现方式:
1)外部集中式存储:
a.内存数据库:最常用且性能最佳的选择。例如Redis (最流行)、Memcached。它们提供极高的读写速度和数据结构支持。
b.关系型数据库:如MySQL, PostgreSQL。易于实现但读写性能通常低于内存数据库,可能成为瓶颈。
c.NoSQL数据库: 如 MongoDB, Cassandra。提供灵活性和可扩展性,但可能不如Redis专为会话存储优化。
d.专用会话存储服务:某些云平台或中间件提供专用服务。
2)应用集成:
a.应用服务器配置一个会话管理器(Session Manager)插件或库。当应用需要读取或写入会话数据时(如 `request.getSession()` / `session.setAttribute()`),会话管理器会透明地与配置的外部存储服务交互,而不是操作本地内存。负载均衡器不需要配置会话保持,它可以采用任何负载均衡策略(如轮询、最少连接、随机等)。
优点:
1)真正的容错性与高可用:
2)服务器故障/维护: 任何服务器宕机或重启,用户的后续请求会被负载均衡器路由到其他健康的服务器。新服务器从共享存储中读取会话数据,用户会话不会中断或丢失(前提是共享存储本身是高可用的)。
3)存储高可用:Redis/Memcached等通常支持集群、主从复制、持久化(Redis)等机制来保证数据可靠性和服务可用性。
4)优秀的水平扩展性:可以轻松地增加或减少应用服务器实例。新服务器启动后立即能处理任何用户的请求,因为会话数据在共享存储中。负载可以均匀分布。
5)负载均衡更灵活:负载均衡器可以采用最优策略(如基于CPU/内存负载),无需考虑会话绑定。
缺点:
1)实现复杂度增加:需要引入、配置和维护外部存储服务(如Redis集群),并在应用中集成会话管理库。
2)性能开销:每次读写会话数据都需要网络请求到外部存储,相比本地内存访问有延迟(虽然Redis非常快,但仍有网络延迟)。需要优化网络连接(连接池)和序列化方式。
3)外部存储成为关键依赖:外部存储服务的性能、可用性和容量成为整个系统的关键点。必须确保其高可用和可扩展。
4)潜在的数据一致性:在高并发写入时,需要依赖存储服务自身提供的一致性保证(如Redis的单线程模型能保证强一致性)。
5)适用场景:对高可用性、容错性、可扩展性要求高的关键业务应用。是现代分布式Web应用(尤其是云原生应用)的推荐做法和主流趋势。几乎适用于所有需要会话管理的场景。
会话保持和会话共享的两者对照
特性 | 会话保持 (Sticky Session) | 会话共享 (Distributed Session) |
核心原理 | 固定用户请求到特定服务器 | 会话数据集中存储,任何服务器可访问 |
数据位置 | 后端服务器本地内存 | 外部共享存储 (如 Redis, Memcached, DB) |
容错性 | 差 (服务器宕机导致会话丢失) | 优 (服务器宕机不影响会话) |
扩展性 | 受限 (已有会话无法迁移) | 优 (轻松增减服务器) |
负载均衡 | 受限 (需基于会话绑定) | 灵活 (可采用最优策略) |
实现复杂度 | 简单 (主要在LB配置) | 较复杂 (需外部存储+应用集成) |
性能 | 极高 (本地内存访问) | 高 (有网络延迟,但Redis等很快) |
外部依赖 | 无 | 有 (必须依赖并维护高可用共享存储) |
典型用例 | 临时方案、短会话应用、性能敏感型场景 | 高可用要求系统、云原生应用、主流Web应用架构 |
如何选择?
1. 优先考虑会话共享: 对于现代应用,尤其是需要高可用性和弹性伸缩的云环境或微服务架构,会话共享是首选方案。虽然引入了Redis等外部依赖,但带来的容灾能力和扩展性优势是至关重要的。
2. 谨慎使用会话保持: 仅在会话丢失影响极小、会话非常短、性能要求极端苛刻(且愿意承担宕机风险)、或者作为向会话共享迁移的临时过渡方案时才考虑。基于Cookie的会话保持比基于IP的更好。
值得明确的是:
1)无状态设计是理想:终极目标是让应用服务器本身完全无状态(不存储任何用户会话数据)。所有状态都存储在客户端(如Token)或后端共享服务(数据库、缓存、对象存储)中。会话共享是实现应用层无状态的关键一步。
2)安全考虑:无论哪种方式,都要注意会话安全(如使用Secure/HttpOnly Cookie,会话超时,防止会话劫持和固定攻击)。
3)序列化:在会话共享中,存储在外部服务(如Redis)中的对象需要被序列化(如JSON, Java Serialization, Protocol Buffers)。选择高效、兼容性好的序列化方式很重要。
4)TTL管理: 共享存储中的会话数据需要设置合理的TTL(生存时间),避免无效数据无限期占用资源。Redis的自动过期特性非常适合此场景。
文章结束语:
会话保持虽实现简单,但其在容灾和扩展性上的致命缺陷使其在现代分布式架构中逐渐被淘汰。会话共享通过将会话状态外部化到专用存储(尤其是Redis)中,为构建真正弹性、高可用的应用提供了坚实的基础,是当前的主流和推荐实践。
在设计新系统时,应优先采用会话共享架构。
文章至此。