Skip to content

CAS + Redis 分布式票据注册表高可用部署:从单机到集群的完整实践

作者: 必码 | bima.cc


前言

在企业级单点登录(Single Sign-On,SSO)系统的架构设计中,票据注册表(Ticket Registry)是整个CAS协议运转的核心枢纽。当企业从单节点部署向多节点集群演进时,票据的存储和共享方式直接决定了系统的可用性、一致性和性能表现。

Apereo CAS作为业界最成熟的开源SSO解决方案之一,其票据注册表经历了从内存到分布式、从简单到高可用的完整演进路径。本文将基于作者在实际CAS Overlay项目中从5.3到7.3三个大版本的深度实践,全面解析CAS + Redis分布式票据注册表的架构设计、配置演进、连接池调优、会话一致性保障以及高可用部署策略。

本文不提供可直接复制粘贴的完整生产配置,而是将所有配置示例转化为教学用途的简化版本,旨在帮助读者理解配置背后的设计思想和架构决策逻辑。


第一章 CAS票据注册表架构概述

1.1 票据注册表的核心定位

在CAS协议的完整认证流程中,票据注册表扮演着"中央票据存储仓库"的角色。每当用户发起登录请求、获取服务票据、执行单点登出等操作时,CAS服务端都需要与票据注册表进行交互。理解票据注册表的架构,是掌握CAS分布式部署的关键前提。

从CAS协议的角度来看,票据注册表承担以下核心职责:

第一,票据的创建与存储。 当用户通过身份认证后,CAS服务端会生成一个TGT(Ticket Granting Ticket),并将其存储在票据注册表中。TGT是用户后续获取各类服务票据的基础凭证,其安全性直接关系到整个SSO系统的安全边界。

第二,票据的查询与验证。 当客户端拿着ST(Service Ticket)访问某个业务应用时,业务应用需要通过CAS的验证接口来确认该ST的有效性。此时CAS服务端会从票据注册表中查找对应的ST记录,验证其是否存在、是否过期、是否已被使用。

第三,票据的生命周期管理。 票据不是永久有效的,每种票据都有其预设的过期时间。票据注册表需要负责清理过期的票据,释放系统资源。在分布式环境下,这一机制尤为关键——过期的票据如果不能及时清理,会导致Redis内存持续增长,最终影响系统稳定性。

第四,单点登出的票据级联失效。 当用户主动登出或管理员强制终止某个会话时,CAS需要根据TGT找到所有关联的ST,并逐一使其失效。在分布式部署中,这一操作需要跨多个CAS节点协调完成,票据注册表的实现方式直接决定了登出操作的效率和一致性。

从架构设计的角度来看,票据注册表需要满足以下非功能性需求:

  • 高性能: 在高并发登录场景下,票据的读写操作必须具备亚毫秒级的响应延迟。
  • 高可用: 票据注册表不能成为系统的单点故障,必须支持故障转移和数据冗余。
  • 数据一致性: 在多节点部署下,票据的创建、查询和失效操作需要保证最终一致性。
  • 可扩展性: 随着业务规模的增长,票据注册表应能水平扩展以应对更大的吞吐量。

1.2 DefaultTicketRegistry:内存单机方案

DefaultTicketRegistry是CAS框架中最基础的票据注册表实现,它使用JVM内存中的ConcurrentHashMap来存储所有票据。这种实现方式简单直接,在单节点部署场景下具有天然的性能优势。

DefaultTicketRegistry的工作原理非常简洁:所有的票据对象被序列化后存储在一个线程安全的Map结构中。当需要查询票据时,直接从内存中读取;当需要清理过期票据时,遍历Map中的所有条目,检查其过期时间并移除已过期的票据。

这种实现方式的优势在于:

  • 零网络开销: 所有操作都在JVM进程内完成,没有网络IO延迟。
  • 极致的读写性能: ConcurrentHashMap的读写操作时间复杂度接近O(1),在亿级票据量下仍能保持稳定的性能表现。
  • 零外部依赖: 不需要部署任何外部存储组件,降低了系统的运维复杂度。
  • 调试便利: 票据数据直接存储在内存中,便于在开发和测试环境中进行问题排查。

然而,DefaultTicketRegistry的局限性也非常明显,这些局限性正是我们在生产环境中选择Redis的根本原因:

  • 不支持多节点共享: 票据数据仅存在于单个JVM进程中,当部署多个CAS节点时,节点A创建的TGT无法被节点B识别,导致用户在负载均衡切换节点后需要重新登录。
  • 服务重启数据丢失: 所有票据存储在内存中,一旦CAS服务重启,所有用户的登录状态将全部丢失,用户体验极差。
  • 内存容量受限: 票据数据占用JVM堆内存,在高并发场景下可能导致GC压力增大,甚至引发OOM(Out of Memory)问题。
  • 水平扩展困难: 无法通过增加节点来提升票据存储的容量和可用性。

在实际项目中,DefaultTicketRegistry通常仅用于以下场景:

  • 本地开发和功能测试环境。
  • 部署规模极小(单节点、用户量有限)且对高可用性没有要求的内部系统。
  • 作为性能基准,用于评估分布式票据注册表引入的性能损耗。

1.3 RedisTicketRegistry:分布式高可用方案

RedisTicketRegistry是CAS官方提供的分布式票据注册表实现,它将票据数据存储在Redis中,从而天然支持多节点共享和高可用部署。在我们的实际项目中,从CAS 5.3到CAS 7.3,RedisTicketRegistry一直是生产环境的首选方案。

RedisTicketRegistry的核心设计思路是:利用Redis的高性能键值存储能力,将CAS票据序列化后以String类型存储在Redis中。每个票据对应一个Redis Key,Key的命名规则通常包含票据ID的前缀,Value则是序列化后的票据对象。

选择Redis作为票据存储后端的技术决策基于以下考量:

第一,亚毫秒级的读写延迟。 Redis作为内存数据库,其单次GET/SET操作的延迟通常在0.1毫秒以内,远低于关系型数据库。对于CAS认证流程中频繁的票据读写操作,Redis能够提供足够的性能保障。

第二,丰富的数据结构支持。 虽然CAS票据注册表主要使用String类型进行存储,但Redis的Key过期机制(TTL)天然适配票据的过期管理需求。每个票据在写入Redis时可以设置对应的TTL,Redis会在票据过期后自动清理,无需CAS服务端主动执行清理逻辑。

第三,成熟的高可用方案。 Redis提供了Sentinel哨兵和Cluster集群两种高可用部署模式,可以满足不同规模和可用性要求的生产场景。Sentinel模式提供自动故障转移能力,Cluster模式则同时提供了数据分片和高可用保障。

第四,广泛的技术生态。 Redis在企业级技术栈中的普及率极高,大多数企业都有成熟的Redis运维经验和基础设施。选择Redis作为票据存储后端,可以复用现有的运维工具和监控体系。

第五,Spring Data Redis的天然集成。 CAS框架基于Spring Boot构建,而Spring Data Redis提供了完善的Redis客户端抽象层。通过简单的配置即可完成Redis连接的建立和管理,开发成本极低。

从架构演进的角度来看,RedisTicketRegistry的引入使得CAS服务从"有状态"转变为"无状态"——CAS节点本身不再存储任何票据数据,所有的状态信息都外置到Redis中。这种架构使得CAS节点可以随意水平扩展,通过负载均衡将请求分发到任意节点,用户的登录状态都不会丢失。

1.4 其他票据注册表实现对比

除了DefaultTicketRegistry和RedisTicketRegistry之外,CAS框架还提供了多种票据注册表实现,每种实现都有其适用的场景和局限性。了解这些实现的差异,有助于架构师在实际项目中做出合理的技术选型。

HazelcastTicketRegistry

Hazelcast是一个开源的内存数据网格(In-Memory Data Grid,IMDG)解决方案,它提供了分布式Map、队列、主题等数据结构。HazelcastTicketRegistry利用Hazelcast的分布式Map来存储票据数据。

Hazelcast的核心优势在于其"嵌入模式"——Hazelcast节点可以嵌入到CAS应用中运行,无需部署独立的外部服务。当多个CAS节点启动时,它们会自动组成一个Hazelcast集群,票据数据在集群节点之间自动复制和分片。

然而,HazelcastTicketRegistry在实际生产中的使用存在以下挑战:

  • 内存开销大: 每个CAS节点都需要预留额外的堆内存用于Hazelcast的数据存储,这在高并发场景下会显著增加JVM的内存压力。
  • 集群管理复杂: Hazelcast集群的节点发现、分裂脑(Split-Brain)处理、数据再平衡等机制增加了运维复杂度。
  • GC压力: 大量票据对象存储在JVM堆中,会增加垃圾回收的频率和停顿时间。
  • 与CAS版本耦合: Hazelcast的版本需要与CAS版本严格匹配,升级时可能遇到兼容性问题。

EhCacheTicketRegistry

EhCache是一个广泛使用的Java缓存框架,EhCacheTicketRegistry利用EhCache的分布式缓存能力来存储票据。EhCache支持本地缓存和分布式缓存两种模式,在分布式模式下可以通过Terracotta服务器实现多节点间的缓存同步。

EhCacheTicketRegistry的局限性主要体现在:

  • 分布式能力依赖Terracotta: EhCache的开源版本在分布式缓存方面的能力有限,完整的分布式功能需要商业版的Terracotta支持。
  • 社区活跃度下降: 随着Redis等方案的普及,EhCache在分布式缓存领域的市场份额持续下降,社区活跃度不如Redis。
  • 性能不如Redis: 在分布式场景下,EhCache的网络通信开销和序列化效率都不如Redis。

JpaTicketRegistry

JpaTicketRegistry将票据数据持久化到关系型数据库中,通过JPA(Java Persistence API)进行数据访问。这种方案的优势在于数据持久化——即使数据库重启,票据数据也不会丢失。

但JpaTicketRegistry的性能瓶颈非常明显:

  • 磁盘IO开销: 每次票据的读写都需要经过数据库的磁盘IO,延迟远高于内存存储。
  • 并发能力有限: 关系型数据库在高并发写入场景下的性能受限,容易成为系统瓶颈。
  • 清理效率低: 过期票据的清理需要执行DELETE操作,在大数据量下可能产生锁竞争。

方案选型对比总结

特性维度DefaultTicketRegistryRedisTicketRegistryHazelcastTicketRegistryEhCacheTicketRegistryJpaTicketRegistry
部署模式单机分布式分布式(嵌入)分布式(需Terracotta)分布式
读写性能极高(内存)高(网络+内存)高(内存网格)中高低(磁盘IO)
数据持久化否(重启丢失)可选(RDB/AOF)否(重启丢失)可选
高可用不支持Sentinel/Cluster内置需额外组件数据库HA
运维复杂度极低中高
水平扩展不支持支持支持有限有限
推荐场景开发/测试生产首选特定场景遗留系统审计要求严格

基于以上对比分析,RedisTicketRegistry在生产环境中具有最均衡的综合表现,这也是本文选择其作为深度解析对象的原因。

1.5 CAS票据类型体系详解

在深入Redis配置之前,有必要全面理解CAS协议中定义的各类票据。票据是CAS协议的核心概念,不同类型的票据承担着不同的职责,其生命周期和存储策略也各不相同。

TGT(Ticket Granting Ticket)

TGT是CAS协议中最核心的票据类型,也被称为"票据授予票据"。当用户成功通过身份认证后,CAS服务端会生成一个TGT,并通过浏览器Cookie(TGC,Ticket Granting Cookie)将其返回给客户端。

TGT的本质是一个代表用户登录状态的凭证。只要TGT有效,用户就可以在不需要重新输入用户名和密码的情况下,获取访问任意已注册业务应用的ST。TGT的存在是SSO"单点"体验的技术基础。

TGT的关键属性包括:

  • 唯一标识: TGT的ID通常以"TGT-"前缀开头,后跟一串随机字符串。
  • 认证信息: TGT中包含了用户的认证信息,包括用户名、认证时间、认证方式、客户端IP等。
  • 关联的ST集合: 每个TGT维护了一个已发放的ST列表,用于单点登出时的级联失效。
  • 过期时间: TGT的默认有效期通常较长(数小时到数天),具体值由CAS配置决定。
  • 已认证属性: TGT中可以携带用户的各种属性信息(如角色、部门等),这些属性会在后续的ST验证过程中传递给业务应用。

在Redis中,TGT的存储结构大致如下:

Key:   CAS_TICKET:TGT-1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Value: <序列化后的TicketGrantingTicketImpl对象>
TTL:   7200秒(默认值,可配置)

ST(Service Ticket)

ST是"服务票据",用于用户访问特定业务应用时的一次性认证凭证。当用户持有效TGT访问某个业务应用时,CAS服务端会根据TGT和目标服务URL生成一个ST,并通过HTTP重定向将ST传递给业务应用。

ST的关键特性包括:

  • 一次性使用: ST是一次性票据,验证成功后立即失效。即使被截获,也无法被重复使用。
  • 服务绑定: 每个ST都与特定的服务URL绑定,只能在对应的服务上使用。
  • 短有效期: ST的有效期通常很短(默认10-30秒),过期后需要重新获取。
  • 前缀标识: ST的ID通常以"ST-"前缀开头。

在Redis中,ST的存储结构与TGT类似,但其TTL通常设置为10-30秒:

Key:   CAS_TICKET:ST-1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Value: <序列化后的ServiceTicketImpl对象>
TTL:   10秒(默认值,可配置)

PGT(Proxy Granting Ticket)

PGT是"代理授予票据",用于CAS的代理(Proxy)认证场景。在某些复杂的企业架构中,后端服务需要代表用户访问另一个后端服务,此时就需要使用代理认证机制。

PGT的获取流程如下:

  1. 业务应用(前端服务)持ST向CAS验证,在验证请求中声明自己是代理(Proxy)。
  2. CAS验证ST成功后,生成一个PGT,并通过回调URL(PGT Callback URL)将PGT传递给前端服务。
  3. 前端服务持有PGT后,可以向CAS申请PT(Proxy Ticket),用于访问后端服务。

PGT的存储方式与TGT类似,但通常具有更长的有效期。

PT(Proxy Ticket)

PT是"代理票据",由PGT派生而来,功能类似于ST,但用于代理认证场景。后端服务收到PT后,向CAS验证PT的有效性,CAS会返回原始用户的身份信息以及代理链信息。

PT同样是一次性票据,有效期通常与ST一致。

票据生命周期关系图

用户认证 --> [TGT] --> 获取ST --> [ST] --> 业务应用验证
                                    |
                                    +--> 代理场景 --> [PGT] --> 获取PT --> [PT] --> 后端服务验证

单点登出 --> [TGT失效] --> 级联失效所有关联的ST

理解票据类型体系对于Redis配置至关重要,因为不同类型的票据具有不同的TTL设置、不同的存储频率和不同的清理策略。在实际配置中,我们需要根据票据类型的特性来优化Redis的内存使用和性能表现。


第二章 CAS 5.3 Redis配置实战

2.1 项目依赖与版本锁定

在CAS 5.3版本中,引入Redis票据注册表需要在项目的构建配置中添加相应的依赖。CAS 5.3基于Spring Boot 2.x构建,使用的是较早期的Spring Data Redis版本。在实际项目中,我们使用Gradle作为构建工具,同时维护了Maven版本的配置。

核心依赖包括两个部分:CAS的Redis票据注册表模块和Spring Data Redis客户端库。

在Gradle构建配置中,Redis相关的依赖声明如下(简化教学版):

groovy
dependencies {
    // CAS Redis票据注册表核心模块
    implementation 'org.apereo.cas:cas-server-support-redis-ticket-registry'

    // 显式指定与CAS 5.3兼容的Spring Data Redis版本
    implementation 'org.springframework.data:spring-data-redis:1.8.23.RELEASE'
    implementation 'redis.clients:jedis:2.9.3'
}

在Maven的pom.xml中,对应的依赖声明为:

xml
<dependency>
    <groupId>org.apereo.cas</groupId>
    <artifactId>cas-server-support-redis-ticket-registry</artifactId>
</dependency>

这里有一个非常重要的工程实践:显式版本锁定。在CAS 5.3的时代,Spring Data Redis的版本兼容性是一个常见的踩坑点。CAS 5.3.16内部依赖的Spring Boot版本为2.7.18,但其Redis模块对Spring Data Redis的版本有特殊要求。如果不显式指定版本,Gradle/Maven的依赖解析可能会引入不兼容的版本,导致运行时出现NoSuchMethodError或ClassNotFoundException。

在我们的实际项目中,经过反复测试验证,确定了以下版本组合是稳定可用的:

  • CAS版本: 5.3.16
  • Spring Boot版本: 2.7.18
  • Spring Data Redis版本: 1.8.23.RELEASE
  • Jedis版本: 2.9.3

这个版本组合看起来有些"古老"——Spring Data Redis 1.8.x是相当早期的版本,但它与CAS 5.3的内部API完全兼容。如果强行升级到Spring Data Redis 2.x,会面临大量的API变更和兼容性问题。

2.2 Redis直连配置详解

CAS 5.3的Redis配置位于cas.ticket.registry.redis命名空间下。这是一个CAS专属的配置路径,与Spring Boot标准的spring.redis配置路径不同。这种设计使得CAS可以在不影响Spring Boot其他Redis自动配置的前提下,独立管理票据注册表的Redis连接。

以下是CAS 5.3中Redis直连配置的教学简化版:

yaml
cas:
  ticket:
    registry:
      redis:
        # Redis服务器地址
        host: redis.example.com
        # Redis服务器端口
        port: 6379
        # 连接超时时间(毫秒)
        timeout: 2000
        # Redis数据库索引(0-15)
        database: 0
        # 是否启用SSL连接
        useSsl: false
        # 是否启用连接池
        usePool: true

逐项解析这些配置参数的设计考量:

host和port: 这是最基本的连接参数,指定Redis服务器的地址和端口。在开发环境中通常使用单机Redis,在生产环境中则可能指向Sentinel或Cluster的入口地址。

timeout: 连接超时时间设置为2000毫秒(2秒)。这个值的选择需要在"快速失败"和"容忍网络抖动"之间取得平衡。在局域网环境中,2秒的超时已经足够宽松;在跨机房或跨可用区部署时,可能需要适当增大此值。

database: Redis支持0-15共16个数据库实例。将CAS票据存储在独立的数据库中(如database 0),可以与其他业务数据隔离,便于运维管理和数据清理。需要注意的是,在Redis Cluster模式下,database参数会被忽略,因为Cluster模式只支持database 0。

useSsl: 在生产环境中,如果CAS与Redis之间跨越不安全的网络(如跨机房、跨公网),应启用SSL加密传输。但在同一数据中心内部署时,通常不需要启用SSL,以避免不必要的性能开销。

usePool: 启用连接池是生产环境的标配配置。连接池可以避免频繁创建和销毁Redis连接带来的开销,显著提升高并发场景下的性能表现。

2.3 连接池参数配置

在CAS 5.3中,Redis连接池的配置位于cas.ticket.registry.redis.pool命名空间下。连接池参数的合理设置对于系统性能和稳定性至关重要。

yaml
cas:
  ticket:
    registry:
      redis:
        pool:
          # 连接池最大活动连接数
          max-active: 20
          # 连接池最大空闲连接数
          max-idle: 10
          # 连接池最小空闲连接数
          min-idle: 5
          # 获取连接最大等待时间(毫秒),-1表示无限等待
          max-wait: -1

max-active: 20 -- 这个参数定义了连接池中同时处于活动状态的最大连接数。当所有活动连接都在使用中时,新的请求将进入等待队列。设置为20意味着连接池最多可以同时处理20个并发Redis操作。

这个值的选择需要考虑以下因素:

  • CAS节点的并发登录请求数。在高峰期,每个登录请求可能涉及2-3次Redis操作(写入TGT、写入ST、查询服务注册信息)。
  • Redis服务器的最大连接数限制(默认10000)。
  • CAS集群的节点数量。如果有4个CAS节点,每个节点20个连接,总共80个连接,远低于Redis的默认限制。

max-idle: 10 -- 连接池中允许保持的最大空闲连接数。当连接使用完毕归还到池中时,如果当前空闲连接数已经达到max-idle,多余的连接将被销毁。这个参数的作用是防止连接池在低负载期间积累过多的空闲连接,浪费Redis服务器的连接资源。

min-idle: 5 -- 连接池中保持的最小空闲连接数。当空闲连接数低于此值时,连接池会主动创建新的连接以维持这个最小数量。设置min-idle的目的是在流量突增时,避免"冷启动"延迟——连接已经预先创建好,可以直接使用。

max-wait: -1 -- 当连接池中所有连接都在使用中时,新请求获取连接的最大等待时间。-1表示无限等待,即请求会一直阻塞直到有可用连接。在生产环境中,通常建议设置一个合理的超时值(如3000毫秒),以避免线程长时间阻塞导致级联故障。

2.4 Spring Session Redis会话存储

在分布式CAS部署中,除了票据数据需要共享之外,HTTP会话(Session)也需要在多个CAS节点之间共享。CAS 5.3通过Spring Session + Redis来实现会话共享。

在CAS 5.3的配置中,会话存储类型通过以下方式指定:

yaml
server:
  session:
    # 会话超时时间(秒)
    timeout: 300
    # Cookie安全配置
    cookie:
      http-only: true
    # 会话追踪模式:仅使用Cookie
    tracking-modes: COOKIE
    # 会话存储类型:使用Redis
    store-type: redis

timeout: 300 -- 会话超时时间设置为300秒(5分钟)。这个值在CAS 5.3中是一个相对保守的设置。会话超时与TGT超时是两个不同的概念:会话超时控制的是HTTP会话的有效期,而TGT超时控制的是SSO登录状态的有效期。在CAS 5.3的默认配置中,会话超时(300秒)远短于TGT超时(通常为2小时或更长)。

这种设计的意图是:HTTP会话主要用于保护CAS的Web交互流程(如登录页面的表单提交),一旦登录流程完成,TGT已经存储在Redis中,HTTP会话的历史使命就基本完成了。较短的会话超时可以减少Redis中的会话数据占用。

store-type: redis -- 这个配置将Spring Session的存储后端切换为Redis。启用后,Spring Session会自动将会话数据序列化后存储在Redis中,并通过自定义的Cookie(通常名为SESSION)来关联客户端与Redis中的会话数据。

tracking-modes: COOKIE -- 限制会话追踪仅使用Cookie方式,禁用URL重写(URL Rewriting)。这是一个安全最佳实践——URL重写方式会将会话ID暴露在URL中,容易被日志记录或通过Referer头泄露。

2.5 版本兼容性踩坑实录

在CAS 5.3的Redis集成实践中,版本兼容性是最常见的踩坑领域。以下是我们在实际项目中遇到的问题及解决方案。

问题一:Spring Data Redis版本不兼容

现象: CAS服务启动时报错NoSuchMethodError: org.springframework.data.redis.connection.RedisConnectionFactory.getConnection()

根因: CAS 5.3的cas-server-support-redis-ticket-registry模块内部依赖的是Spring Data Redis 1.8.x的API。如果项目中引入了Spring Data Redis 2.x(Spring Boot 2.x默认),会导致方法签名不匹配。

解决方案: 显式锁定Spring Data Redis的版本为1.8.23.RELEASE,同时锁定Jedis版本为2.9.3。这两个版本是经过CAS社区验证的稳定组合。

groovy
// 显式版本锁定(关键!)
implementation 'org.springframework.data:spring-data-redis:1.8.23.RELEASE'
implementation 'redis.clients:jedis:2.9.3'

问题二:Jedis连接池类型冲突

现象: 启动时报错ClassNotFoundException: org.apache.commons.pool2.impl.GenericObjectPoolConfig

根因: Jedis 2.9.x的连接池功能依赖Apache Commons Pool2。如果项目中没有引入commons-pool2依赖,连接池将无法正常工作。

解决方案: 确保commons-pool2依赖存在于classpath中。在Spring Boot 2.x中,这个依赖通常通过spring-boot-starter-data-redis自动引入,但在CAS 5.3的Overlay项目中,由于依赖管理的特殊性,可能需要手动添加。

问题三:序列化方式不一致

现象: CAS节点A写入的票据,CAS节点B读取时报反序列化错误。

根因: 不同CAS节点的Spring Data Redis序列化配置不一致。CAS 5.3默认使用JdkSerializationRedisSerializer,这种序列化方式要求读写双方具有完全一致的类定义。如果不同节点的CAS版本或依赖版本不一致,可能导致序列化ID不匹配。

解决方案: 确保所有CAS节点使用完全相同的CAS版本和依赖版本。在CI/CD流程中,应使用统一的构建产物进行部署,避免在不同环境中分别构建。

问题四:Redis连接在CAS启动时未就绪

现象: CAS服务启动过程中,Redis尚未完全就绪,导致Bean初始化失败。

根因: CAS 5.3的Redis连接在应用启动时就会建立,如果Redis服务尚未就绪,会导致启动失败。

解决方案: 在部署脚本中添加健康检查,确保Redis完全就绪后再启动CAS服务。在Docker Compose或Kubernetes环境中,可以使用depends_on配合健康检查来实现启动顺序控制。


第三章 CAS 6.6 Redis配置演进

3.1 显式类型声明机制

CAS 6.6在票据注册表的配置上引入了一个重要的变化:显式类型声明。在CAS 5.3中,只要引入了cas-server-support-redis-ticket-registry依赖并配置了Redis参数,CAS就会自动使用Redis作为票据注册表。而在CAS 6.6中,需要通过type参数显式指定票据注册表的类型。

yaml
cas:
  ticket:
    registry:
      # 显式声明使用REDIS类型的票据注册表
      type: REDIS
      redis:
        host: redis.example.com
        port: 6379
        timeout: 5000
        database: 0

type: REDIS这个配置项看起来简单,但其背后的设计思想值得关注。CAS 6.6支持多种票据注册表实现(DEFAULT、REDIS、HAZELCAST、EHCACHE、JPA等),引入显式类型声明后,CAS可以根据类型值有条件地加载对应的自动配置类,避免了不必要的Bean创建和依赖注入。

这种设计带来的好处包括:

  • 启动性能优化: CAS在启动时只需要初始化指定类型的票据注册表相关的Bean,减少了启动时间和内存占用。
  • 配置验证增强: CAS可以在启动阶段就验证配置的完整性。如果声明了type: REDIS但Redis连接参数缺失,CAS可以在启动时快速失败(Fail-Fast),而不是在运行时才发现问题。
  • 多注册表切换便利: 在测试环境中,可以通过修改type值快速切换票据注册表实现,便于性能对比和功能验证。

3.2 增强的连接池配置

CAS 6.6在Redis连接池配置方面进行了显著增强,新增了connection-pool配置块,提供了更细粒度的连接池控制能力。

yaml
cas:
  ticket:
    registry:
      redis:
        # 基础连接池配置
        pool:
          max-active: 50
          max-idle: 20
          min-idle: 10
          max-wait: 3000
        # 增强的连接池配置(CAS 6.6新增)
        connection-pool:
          max-total: 50
          max-idle: 20
          min-idle: 10
          max-wait-millis: 3000
          # 借出连接时是否进行有效性检查
          test-on-borrow: true
          # 归还连接时是否进行有效性检查
          test-on-return: true
          # 空闲时是否进行有效性检查
          test-while-idle: true
          # 空闲连接检测间隔(毫秒)
          time-between-eviction-runs-millis: 60000
          # 连接最小空闲时间(毫秒),超过此时间的空闲连接将被回收
          min-evictable-idle-time-millis: 300000

这里出现了两套连接池配置:poolconnection-pool。这种设计反映了CAS 6.6在配置模型上的过渡状态——pool是CAS 5.3遗留的简化配置,connection-pool是新增的完整配置。在实际使用中,两套配置可以共存,connection-pool中的参数会覆盖pool中的同名参数。

test-on-borrow: true -- 这个参数控制是否在从连接池借出连接时进行有效性检查。启用后,每次获取连接时都会发送一个PING命令到Redis服务器,确认连接仍然有效。这可以有效避免使用已断开的连接,但每次借出连接都会增加一次网络往返的开销。

在生产环境中,是否启用test-on-borrow需要权衡:

  • 如果Redis与CAS之间的网络非常稳定(如同机房部署),可以关闭test-on-borrow以减少延迟。
  • 如果Redis与CAS之间存在网络不稳定因素(如跨可用区、跨机房),建议开启test-on-borrow以提升可靠性。
  • 一种折中方案是关闭test-on-borrow,但开启test-while-idle,这样可以在空闲时检测失效连接,而不影响正常请求的延迟。

test-while-idle: true -- 这个参数控制是否在连接空闲时进行有效性检查。开启后,连接池的后台线程会定期检查空闲连接的有效性,自动剔除已断开的连接。这是一种"低开销、高回报"的健康检查策略。

time-between-eviction-runs-millis: 60000 -- 空闲连接检测的执行间隔,设置为60秒。这意味着连接池的后台线程每60秒会扫描一次空闲连接,检查它们是否仍然有效。这个值不宜设置过小,否则会增加CPU和网络开销;也不宜设置过大,否则失效连接不能被及时清理。

min-evictable-idle-time-millis: 300000 -- 连接在池中空闲的最小时间(5分钟),超过此时间的空闲连接在下次eviction检查时将被回收。这个参数与min-idle配合使用:即使空闲连接数低于min-idle,如果连接的空闲时间超过了min-evictable-idle-time-millis,也会被回收,然后由连接池创建新的连接来补充min-idle。

3.3 Redis Cleaner定时清理任务

CAS 6.6新增了Redis Cleaner(清理器)的配置,用于定期清理Redis中过期的票据数据。

yaml
cas:
  ticket:
    registry:
      redis:
        cleaner:
          # 是否启用清理任务
          enabled: true
          # 清理任务的执行间隔(秒)
          repeat-interval: 1800

enabled: true -- 启用Redis清理任务。虽然Redis本身会通过TTL机制自动清理过期的Key,但CAS的清理器提供了额外的保障:

  • Redis的TTL清理是惰性的(Lazy)+ 定期抽样的组合策略,存在一定的延迟。在高写入负载下,过期Key可能不会被立即清理。
  • CAS清理器可以执行更精细的清理逻辑,例如根据票据的业务状态(如已使用、已撤销)进行主动清理。
  • 清理器可以收集清理统计信息,用于监控和告警。

repeat-interval: 1800 -- 清理任务每1800秒(30分钟)执行一次。这个间隔的选择需要考虑:

  • 票据的过期策略。如果ST的TTL为10秒,那么30分钟的清理间隔已经足够——ST会在10秒后自动过期。
  • Redis的内存使用情况。如果Redis内存充裕,可以适当增大清理间隔以减少CPU开销;如果Redis内存紧张,可以减小清理间隔。
  • 监控需求。更频繁的清理可以提供更及时的统计信息。

需要特别注意的是,CAS 6.6的Cleaner与Redis自身的TTL机制是互补关系,而非替代关系。TTL提供了基础的数据过期保障,Cleaner提供了额外的主动清理能力。两者配合使用,可以确保Redis中的票据数据不会无限增长。

3.4 Bean定义覆盖策略

CAS 6.6引入了一个重要的Spring配置:spring.main.allow-bean-definition-overriding: true

yaml
spring:
  main:
    allow-bean-definition-overriding: true
    banner-mode: off

这个配置的背景是:CAS 6.6的模块化程度更高,多个CAS模块可能定义了同名的Bean。在Spring Boot的默认行为中,如果出现同名的Bean定义,应用启动时会抛出BeanDefinitionOverrideException异常。设置allow-bean-definition-overriding: true后,后加载的Bean定义会覆盖先加载的定义。

在实际项目中,这个配置通常是必需的,原因包括:

  • CAS的Redis模块可能与Spring Boot的Redis自动配置模块定义了同名的ConnectionFactory Bean。
  • CAS的多个支持模块(如OAuth模块、Rest模块)可能存在Bean定义冲突。
  • 在自定义扩展中,开发者可能需要覆盖CAS默认的Bean定义。

虽然这个配置解决了启动问题,但架构师需要意识到其潜在风险:Bean覆盖可能导致非预期的行为,特别是在升级CAS版本时,Bean的定义可能发生变化。建议在升级后进行完整的回归测试,确保Bean覆盖不会引入功能异常。

3.5 会话超时策略调整

CAS 6.6将会话超时时间从5.3的300秒大幅调整为86400秒(24小时):

yaml
server:
  session:
    timeout: 86400
    cookie:
      http-only: true
      secure: false
    tracking-modes: COOKIE

这个变化反映了CAS架构团队对会话管理策略的重新思考。在CAS 5.3中,300秒的会话超时在大多数场景下是足够的,因为HTTP会话主要用于保护登录流程。但在CAS 6.6中,随着更多功能(如OAuth授权流程、REST API认证等)依赖HTTP会话,过短的会话超时可能导致用户体验问题。

86400秒(24小时)的会话超时意味着:

  • 用户的HTTP会话在一天内保持有效,避免了频繁的会话过期。
  • 会话数据在Redis中会占用更多的内存,需要通过合理的TTL配置来控制。
  • 与TGT的超时时间(通常为2-8小时)解耦,会话超时不再成为SSO体验的瓶颈。

在实际项目中,会话超时的设置需要根据业务需求进行调整。如果安全策略要求更短的会话有效期,可以适当减小此值。但建议不要低于TGT的超时时间,否则会出现"HTTP会话已过期但TGT仍有效"的不一致状态。


第四章 CAS 7.3 Redis配置革新

4.1 配置路径迁移到Spring全局命名空间

CAS 7.3在Redis配置方面进行了最根本性的变革:将Redis连接配置从CAS专属的cas.ticket.registry.redis命名空间迁移到了Spring标准的spring.redis全局命名空间。

CAS 5.3 / 6.6的配置路径:

yaml
cas:
  ticket:
    registry:
      redis:
        host: redis.example.com
        port: 6379

CAS 7.3的配置路径:

yaml
spring:
  redis:
    host: redis.example.com
    port: 6379
    database: 0
    timeout: 10000

这个变化的背后有几个重要的设计考量:

第一,配置标准化。 将Redis配置统一到Spring标准的命名空间下,使得CAS的Redis配置与Spring Boot生态中的其他项目保持一致。开发者不需要记忆CAS专属的Redis配置路径,降低了学习成本。

第二,连接复用。 在CAS 7.3中,Redis连接不再仅用于票据注册表,还可能用于Spring Session、分布式缓存、分布式锁等多种用途。使用全局的spring.redis配置,所有这些用途可以共享同一个Redis连接池,提高了资源利用率。

第三,Sentinel/Cluster配置简化。 在CAS 5.3/6.6中,配置Redis Sentinel需要使用CAS专属的配置路径,格式与Spring Boot标准不同。迁移到spring.redis后,可以直接使用Spring Boot标准的Sentinel和Cluster配置方式,配置更加直观。

第四,与Spring Boot 3.x的深度集成。 CAS 7.3基于Spring Boot 3.5.x构建,Spring Boot 3.x对Redis的自动配置进行了大量增强(如Lettuce成为默认客户端、响应式Redis支持等)。使用标准配置路径可以充分利用这些增强能力。

需要注意的是,CAS 7.3中仍然保留了cas.ticket.registry.redis的部分配置项(如enabledenableRedisSearch),用于控制票据注册表特有的行为。但Redis的连接参数(host、port、password、timeout等)已经完全迁移到spring.redis下。

以下是CAS 7.3中完整的Redis相关配置示例(教学简化版):

yaml
spring:
  # 全局Redis连接配置
  redis:
    host: redis.example.com
    port: 6379
    database: 0
    timeout: 10000

cas:
  ticket:
    registry:
      # 票据注册表特有配置
      redis:
        enabled: true
        host: redis.example.com
        port: 6379
        database: 0
        password: ""
        timeout: PT10S
        useSsl: false
        enableRedisSearch: false
        pool:
          enabled: true
          max-active: 8
          max-wait: -1
          max-idle: 8
          min-idle: 0

从上面的配置可以看出,CAS 7.3存在一定程度的"配置冗余"——Redis连接参数在spring.rediscas.ticket.registry.redis中都可能出现。在实际项目中,建议将连接参数统一配置在spring.redis下,cas.ticket.registry.redis仅用于票据注册表特有的功能开关和参数。

4.2 Duration格式超时配置

CAS 7.3在超时配置上采用了ISO-8601 Duration格式,这是一个值得关注的细节变化。

CAS 5.3 / 6.6的超时配置(毫秒数值):

yaml
cas:
  ticket:
    registry:
      redis:
        timeout: 2000  # 毫秒

CAS 7.3的超时配置(Duration格式):

yaml
cas:
  ticket:
    registry:
      redis:
        timeout: PT10S  # 10秒

PT10S是ISO-8601 Duration格式的表示,含义为"Period of Time: 10 Seconds"。常用的Duration格式示例:

  • PT5S -- 5秒
  • PT30S -- 30秒
  • PT1M -- 1分钟
  • PT10S -- 10秒
  • PT1H30M -- 1小时30分钟

这种变化的好处在于:

  • 可读性更强: PT10S10000更直观,一眼就能看出是10秒。
  • 单位明确: 数值格式容易产生歧义(是毫秒还是秒?),Duration格式则没有歧义。
  • 与Spring Boot 3.x保持一致: Spring Boot 3.x广泛使用Duration格式进行超时配置,CAS 7.3遵循了这一约定。

在CAS 7.3中,spring.redis.timeout仍然使用数值格式(毫秒),而cas.ticket.registry.redis.timeout使用Duration格式。这种不一致是CAS版本过渡期的产物,在后续版本中可能会统一。

4.3 enableRedisSearch新特性

CAS 7.3新增了enableRedisSearch配置项:

yaml
cas:
  ticket:
    registry:
      redis:
        enableRedisSearch: false

这个配置项控制是否启用基于Redis的票据搜索功能。当设置为true时,CAS会在写入票据时额外构建搜索索引,支持按用户名、服务ID等条件查询票据。

在实际项目中,这个功能的使用场景包括:

  • 管理控制台: 管理员需要查询某个用户的所有活跃票据,用于会话管理或安全审计。
  • 故障排查: 运维人员需要查找特定服务的票据状态,用于定位认证问题。
  • 安全审计: 安全团队需要统计特定时间范围内的票据发放情况。

但启用Redis Search也会带来额外的开销:

  • 写入性能下降: 每次写入票据时需要额外更新搜索索引,增加了Redis的写入负载。
  • 内存占用增加: 搜索索引本身需要占用Redis内存。
  • 依赖Redis模块: Redis Search功能可能需要安装额外的Redis模块(如RediSearch),增加了部署复杂度。

在生产环境中,如果不需要票据搜索功能,建议保持enableRedisSearch: false,以获得最佳的性能表现。

4.4 cas-server-support-redis-core独立模块

CAS 7.3在模块划分上进行了重要调整,新增了cas-server-support-redis-core独立模块。在CAS 5.3和6.6中,Redis相关的所有功能都包含在cas-server-support-redis-ticket-registry一个模块中。而在CAS 7.3中,Redis的核心功能被拆分到了独立的core模块中。

在Gradle构建配置中:

groovy
dependencies {
    // Redis票据注册表模块
    implementation 'org.apereo.cas:cas-server-support-redis-ticket-registry'
    // Redis核心模块(CAS 7.3新增)
    implementation 'org.apereo.cas:cas-server-support-redis-core'
}

在Maven的pom.xml中:

xml
<dependency>
    <groupId>org.apereo.cas</groupId>
    <artifactId>cas-server-support-redis-ticket-registry</artifactId>
</dependency>
<dependency>
    <groupId>org.apereo.cas</groupId>
    <artifactId>cas-server-support-redis-core</artifactId>
</dependency>

这个模块拆分的设计意图包括:

  • 关注点分离: Redis的连接管理、序列化、健康检查等核心功能与票据注册表的具体业务逻辑分离,提高了模块的内聚性。
  • 复用性增强: redis-core模块可以被CAS的其他功能模块复用(如分布式缓存、分布式锁等),而不需要依赖完整的票据注册表模块。
  • 依赖管理优化: 项目可以根据实际需求选择性引入Redis模块,减少不必要的依赖。

在实际项目中,如果使用CAS 7.3的Redis票据注册表,建议同时引入这两个模块,以确保所有必要的类和自动配置都能正确加载。

4.5 连接池参数调优变化

CAS 7.3在连接池配置方面也进行了调整。与CAS 6.6相比,CAS 7.3的连接池配置更加简洁,移除了connection-pool配置块,将所有参数统一到pool下。

yaml
cas:
  ticket:
    registry:
      redis:
        pool:
          enabled: true
          max-active: 8
          max-wait: -1
          max-idle: 8
          min-idle: 0

与CAS 6.6的配置相比,CAS 7.3的默认值更加保守:

  • max-active从50降到8: 这反映了CAS 7.3架构优化的结果。在Spring Boot 3.x中,Lettuce成为了默认的Redis客户端,Lettuce基于Netty的异步非阻塞IO模型,单个连接可以处理多个并发请求,因此不需要像Jedis那样维护大量连接。
  • 移除了test-on-borrow、test-on-return等参数: Lettuce的连接管理机制与Jedis不同,它使用连接状态检测而非PING命令来验证连接有效性,因此不需要这些参数。
  • min-idle默认为0: 在Lettuce中,连接的创建和销毁开销很小,不需要预先维护空闲连接。

CAS 7.3基于Spring Boot 3.5.x构建,要求Java 21运行环境。这一代技术栈的升级带来了以下变化:

  • Jedis vs Lettuce: Spring Boot 3.x默认使用Lettuce作为Redis客户端。Lettuce基于Netty实现,支持同步、异步和响应式编程模型,在高并发场景下性能优于Jedis。
  • Jakarta EE迁移: CAS 7.3完成了从javax到jakarta的包名迁移,这是Spring Boot 3.x的要求。
  • 虚拟线程支持: Java 21引入的虚拟线程(Virtual Threads)可以进一步提升CAS的并发处理能力。

第五章 Redis连接池深度调优

5.1 Jedis vs Lettuce对比分析

在CAS + Redis的技术栈中,Redis客户端的选择直接影响连接池的配置策略和性能表现。CAS 5.3和6.6主要使用Jedis作为Redis客户端,而CAS 7.3则默认使用Lettuce。理解两者的差异对于连接池调优至关重要。

Jedis:同步阻塞模型

Jedis是最早被广泛使用的Java Redis客户端,其设计简洁直观:

  • 同步阻塞IO: 每次Redis操作都会占用一个线程,直到操作完成才能释放。这意味着在高并发场景下,需要大量的连接来并行处理请求。
  • 连接池必需: 由于每个连接同时只能处理一个请求,Jedis必须依赖连接池来提供并发能力。连接池的大小直接决定了系统的最大并发Redis操作数。
  • 线程安全: Jedis的连接对象不是线程安全的,必须通过连接池来管理连接的借出和归还。
  • 社区成熟: Jedis的社区非常成熟,文档丰富,问题解决方案完善。

Jedis的连接模型可以类比为"一连接一线程"的传统HTTP客户端模型。在CAS 5.3/6.6中,每个登录请求可能涉及2-3次Redis操作,如果使用Jedis,这些操作会串行或并行地占用连接池中的连接。

Lettuce:异步非阻塞模型

Lettuce是Spring Boot 2.x/3.x默认的Redis客户端,基于Netty构建:

  • 异步非阻塞IO: Lettuce使用Netty的EventLoop模型,单个连接可以同时处理多个请求。底层通过Redis的Pipeline和Multiplexing协议实现。
  • 连接池可选: 由于单个连接就能处理多个并发请求,Lettuce在不使用连接池的情况下也能提供良好的并发性能。当然,Lettuce也支持连接池模式。
  • 线程安全: Lettuce的连接对象是线程安全的,多个线程可以共享同一个连接。
  • 响应式支持: Lettuce原生支持响应式编程(Reactive Programming),可以与Spring WebFlux等响应式框架无缝集成。
  • 自动重连: Lettuce内置了连接断开后的自动重连机制,不需要额外的心跳检测。

Lettuce的连接模型可以类比为"多路复用"的HTTP/2客户端模型。在CAS 7.3中,即使只使用一个Redis连接,也能处理大量的并发请求。

对比总结

特性维度JedisLettuce
IO模型同步阻塞异步非阻塞(Netty)
并发模型一连接一请求多路复用
连接池必需可选
线程安全连接非线程安全连接线程安全
响应式支持不支持原生支持
自动重连不支持(需连接池管理)内置支持
Spring Boot默认1.x2.x/3.x
CAS版本5.3, 6.67.3
依赖体积较小较大(含Netty)

选型建议

  • CAS 5.3 / 6.6: 推荐使用Jedis,因为CAS的Redis模块与Jedis的API深度耦合。虽然理论上可以切换到Lettuce,但需要大量的适配工作。
  • CAS 7.3: 推荐使用Lettuce(默认),可以充分利用Spring Boot 3.x的自动配置和连接管理能力。如果团队有特殊的性能需求或运维习惯,也可以显式切换到Jedis。

5.2 连接池核心参数详解

无论使用Jedis还是Lettuce,连接池的核心参数概念是通用的。本节将深入解析每个参数的含义、影响和调优策略。

max-active(最大活动连接数)

max-active定义了连接池中同时处于"被借出"状态的最大连接数。当活动连接数达到此上限时,新的获取连接请求将进入等待队列(或直接抛出异常,取决于max-wait的配置)。

调优原则:

  • 对于Jedis(CAS 5.3/6.6),max-active应设置为预期最大并发Redis操作数的1.2-1.5倍,留出一定的缓冲空间。
  • 对于Lettuce(CAS 7.3),由于多路复用的特性,max-active可以设置得较小(如8-16),甚至可以不使用连接池。
  • 需要考虑Redis服务器的maxclients配置(默认10000),确保所有CAS节点的连接总数不超过此限制。

计算公式(Jedis场景):

max-active = (单节点峰值QPS / 单连接平均QPS) * 1.5

例如,如果单节点峰值QPS为500,单个Jedis连接平均可以处理100 QPS,则:

max-active = (500 / 100) * 1.5 = 7.5 ≈ 8

max-idle(最大空闲连接数)

max-idle定义了连接池中允许保持的最大空闲连接数。当连接使用完毕归还到池中时,如果当前空闲连接数已经达到max-idle,多余的连接将被关闭并销毁。

调优原则:

  • max-idle通常设置为max-active的50%-80%,避免在流量下降时连接池快速收缩。
  • 在流量波动较大的场景下,适当增大max-idle可以减少连接创建/销毁的开销。
  • max-idle不应超过max-active,否则没有实际意义。

min-idle(最小空闲连接数)

min-idle定义了连接池中保持的最小空闲连接数。当空闲连接数低于此值时,连接池的后台线程会主动创建新的连接。

调优原则:

  • min-idle的作用是"预热"连接池,避免流量突增时的"冷启动"延迟。
  • 对于Jedis,建议min-idle设置为max-active的25%-50%。
  • 对于Lettuce,min-idle可以设置为0(默认值),因为Lettuce的连接创建开销很小。

max-wait(最大等待时间)

max-wait定义了当连接池中所有连接都在使用中时,新的获取连接请求的最大等待时间。

调优原则:

  • 设置为-1表示无限等待(不推荐在生产环境使用)。
  • 建议设置为3000-5000毫秒,在"容忍短暂等待"和"快速失败"之间取得平衡。
  • 如果max-wait超时后抛出异常,调用方需要妥善处理异常(如重试或返回错误)。

test-on-borrow(借出时验证)

控制是否在从连接池借出连接时发送PING命令验证连接有效性。

调优原则:

  • 开启可以避免使用已断开的连接,但每次借出增加一次网络往返(约0.1-1毫秒)。
  • 如果网络非常稳定,可以关闭以减少延迟。
  • 如果网络不稳定(跨可用区部署),建议开启。
  • 对于Lettuce,通常不需要开启,因为Lettuce内置了连接状态检测。

test-on-return(归还时验证)

控制是否在将连接归还到池中时发送PING命令验证连接有效性。

调优原则:

  • 通常不推荐开启,因为归还验证会增加请求的处理延迟。
  • 如果连接泄漏是一个严重问题,可以临时开启以辅助排查。
  • 对于Lettuce,不需要此参数。

test-while-idle(空闲时验证)

控制是否在连接空闲时由后台线程定期验证连接有效性。

调优原则:

  • 推荐开启,这是一种低开销的健康检查策略。
  • 配合time-between-eviction-runs-millis使用,控制检查频率。

time-between-eviction-runs-millis(空闲检查间隔)

定义后台线程执行空闲连接检查的间隔时间。

调优原则:

  • 建议设置为30000-120000毫秒(30秒到2分钟)。
  • 不宜设置过小(增加CPU和网络开销),也不宜设置过大(失效连接不能及时清理)。

min-evictable-idle-time-millis(最小空闲时间)

定义连接在池中空闲的最小时间。超过此时间的空闲连接在下次eviction检查时可能被回收。

调优原则:

  • 建议设置为180000-600000毫秒(3分钟到10分钟)。
  • 需要大于time-between-eviction-runs-millis,否则新创建的空闲连接可能立即被回收。
  • 配合min-idle使用:即使空闲连接数低于min-idle,超过min-evictable-idle-time-millis的连接也会被回收。

5.3 生产环境推荐配置

基于上述参数分析,以下是针对不同CAS版本的生产环境推荐配置。

CAS 5.3 / 6.6(Jedis连接池)-- 中等规模部署

yaml
cas:
  ticket:
    registry:
      redis:
        host: redis-cluster.example.com
        port: 6379
        timeout: 2000
        database: 0
        useSsl: false
        usePool: true
        pool:
          max-active: 20
          max-idle: 10
          min-idle: 5
          max-wait: 3000

适用场景: 2-4个CAS节点,日活用户10万以内,峰值QPS 500以内。

CAS 5.3 / 6.6(Jedis连接池)-- 大规模部署

yaml
cas:
  ticket:
    registry:
      redis:
        host: redis-cluster.example.com
        port: 6379
        timeout: 3000
        database: 0
        useSsl: true
        usePool: true
        pool:
          max-active: 50
          max-idle: 25
          min-idle: 10
          max-wait: 5000
        connection-pool:
          test-on-borrow: false
          test-on-return: false
          test-while-idle: true
          time-between-eviction-runs-millis: 60000
          min-evictable-idle-time-millis: 300000

适用场景: 4-8个CAS节点,日活用户50万以上,峰值QPS 2000以上。

CAS 7.3(Lettuce连接池)-- 通用部署

yaml
spring:
  redis:
    host: redis-cluster.example.com
    port: 6379
    database: 0
    timeout: 10000

cas:
  ticket:
    registry:
      redis:
        enabled: true
        timeout: PT10S
        pool:
          enabled: true
          max-active: 8
          max-wait: -1
          max-idle: 8
          min-idle: 0

适用场景: 2-8个CAS节点,Lettuce多路复用特性使得较小的连接池即可满足需求。

5.4 连接泄漏检测与预防

连接泄漏是Redis连接池使用中最常见的问题之一。当从连接池借出的连接没有被正确归还时,就会发生连接泄漏。随着时间推移,可用的连接数越来越少,最终导致系统无法获取新的连接。

连接泄漏的常见原因

第一,异常处理不当。 这是最常见的连接泄漏原因。在Java中,如果从连接池获取连接后发生了未捕获的异常,且异常处理逻辑中没有归还连接的代码,连接就会泄漏。

java
// 错误示例(教学用途)
Jedis jedis = pool.getResource();
jedis.set("key", "value");
// 如果这里抛出异常,jedis永远不会被归还
jedis.get("key");
jedis.close(); // 这行可能不会执行

虽然CAS框架内部已经妥善处理了连接的获取和归还,但在自定义扩展代码中(如自定义认证处理器、自定义票据组件等)直接操作Redis时,仍需要注意连接管理。

第二,长时间持有的连接。 某些业务逻辑可能长时间持有Redis连接(如执行复杂的计算后再归还),在持有期间如果发生了线程中断或超时,连接可能不会被正确归还。

第三,连接池配置不当。 如果max-active设置过小,而并发请求过多,可能导致大量请求在等待队列中堆积,形成"伪泄漏"现象。

连接泄漏的检测方法

方法一:连接池监控。 Jedis连接池提供了getNumActive()getNumIdle()方法,可以实时查看活动连接数和空闲连接数。通过定期采集这些指标,可以发现连接数的异常增长趋势。

方法二:Redis客户端列表。 通过Redis的CLIENT LIST命令,可以查看当前所有连接的详细信息,包括连接的空闲时间、最后操作时间等。如果发现大量长时间空闲的连接,可能存在连接泄漏。

方法三:日志分析。 在CAS的日志中搜索"Could not get a resource from the pool"或"Connection is closed"等异常信息,这些通常是连接泄漏的直接表现。

连接泄漏的预防策略

第一,使用try-with-resources模式。 在Java 7及以上版本中,推荐使用try-with-resources语法来自动管理连接的生命周期:

java
// 正确示例(教学用途)
try (Jedis jedis = pool.getResource()) {
    jedis.set("key", "value");
    String result = jedis.get("key");
} // 连接会自动归还,即使发生了异常

第二,配置连接泄漏检测。 Apache Commons Pool2(Jedis连接池的底层实现)提供了removeAbandonedOnBorrowremoveAbandonedTimeout参数,可以在借出连接时检查是否存在超时未归还的连接,并自动回收。

第三,设置合理的max-wait。 将max-wait设置为一个有限的值(如3000-5000毫秒),而不是-1(无限等待)。当连接池耗尽时,快速失败比无限等待更有利于系统的稳定性。

第四,定期重启CAS服务。 虽然这不是一个优雅的解决方案,但在无法快速定位和修复连接泄漏的情况下,定期重启CAS服务可以重置连接池状态,避免问题积累。建议在低峰期执行滚动重启。


第六章 分布式部署下的会话一致性

6.1 Spring Session + Redis会话共享

在CAS的多节点集群部署中,会话共享是保证用户体验一致性的基础。当用户通过负载均衡访问CAS集群时,不同的请求可能被分发到不同的CAS节点。如果会话数据仅存储在单个节点的内存中,用户在节点切换后就会丢失会话状态。

Spring Session是Spring生态中解决分布式会话问题的标准方案。它通过将会话数据外置到Redis等存储后端,实现了会话在多个应用节点之间的透明共享。

工作原理

Spring Session的核心机制是"过滤器替换"。在Web应用启动时,Spring Session会注册一个名为springSessionRepositoryFilter的Servlet过滤器,这个过滤器在所有请求处理之前拦截HTTP请求,执行以下操作:

  1. 从请求中提取会话标识(通常从名为SESSION的Cookie中获取)。
  2. 根据会话标识从Redis中加载会话数据。
  3. 将加载的会话数据包装为标准的HttpSession对象,注入到请求处理链中。
  4. 请求处理完成后,将会话数据的变更同步回Redis。

对于CAS应用来说,这个机制是完全透明的——CAS的代码中使用的仍然是标准的HttpServletRequest.getSession()API,Spring Session在底层自动完成了会话的分布式存储和同步。

Redis中的会话存储结构

Spring Session在Redis中的数据结构设计如下:

# 会话数据(Hash结构)
Key:   spring:session:sessions:<sessionId>
Type:  Hash
Fields:
  creationTime    -- 会话创建时间
  maxInactiveInterval -- 最大不活动间隔
  lastAccessedTime    -- 最后访问时间
  sessionAttr:KEY     -- 会话属性

# 会话过期索引(String结构,用于TTL管理)
Key:   spring:session:sessions:expires:<sessionId>
Type:  String
Value: (空)
TTL:   与会话超时时间一致

# 索引:按会话ID查找
Key:   spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:<principalName>
Type:  Set
Value: <sessionId集合>

配置要点

在CAS 5.3中,通过server.session.store-type: redis启用Spring Session的Redis存储。在CAS 6.6和7.3中,Spring Session的自动配置由cas-server-support-redis-ticket-registry模块触发,通常不需要额外配置。

但需要注意以下几点:

第一,会话Cookie的域名和路径。 在多节点部署中,所有CAS节点必须共享同一个域名(通过反向代理或DNS实现)。Spring Session生成的SESSION Cookie需要设置正确的域名和路径,确保所有节点的请求都能携带这个Cookie。

第二,会话序列化方式。 Spring Session默认使用Java序列化,这与CAS的票据序列化方式一致。如果需要切换到JSON序列化(便于跨语言访问会话数据),需要确保CAS的所有会话属性都是可JSON序列化的。

第三,Redis连接的独立性。 Spring Session使用的Redis连接与票据注册表使用的Redis连接可以共享同一个Redis实例,但建议使用不同的database编号进行隔离。这样在需要清理会话数据时,不会影响票据数据。

6.2 TGT Cookie策略

TGT(Ticket Granting Ticket)通过浏览器Cookie传递给客户端,这个Cookie通常被称为TGC(Ticket Granting Cookie)。TGC的安全策略直接关系到SSO系统的安全性。

TGC的基本属性

在CAS的配置中,TGC的安全属性通过以下参数控制:

yaml
cas:
  tgc:
    # 是否仅通过HTTPS传输Cookie
    secure: false
    # Cookie的名称(默认为TGT)
    name: TGT
    # Cookie的最大存活时间(秒),-1表示会话Cookie
    max-age: -1
    # Cookie的路径
    path: /cas/
    # Cookie的域名
    domain: ""
    # 是否启用HttpOnly标志
    http-only: true
    # SameSite属性
    same-site: ""

secure: false -- 在开发环境中通常设置为false,因为开发环境可能使用HTTP。在生产环境中,应设置为true,确保TGC仅通过HTTPS传输,防止中间人攻击。

http-only: true -- 启用HttpOnly标志后,JavaScript代码无法通过document.cookie访问TGC,可以有效防止XSS(跨站脚本攻击)窃取TGC。

same-site -- SameSite属性控制Cookie在跨站请求中的发送行为。设置为"Lax"可以在防止CSRF攻击的同时,允许从外部链接跳转到CAS时的正常认证流程。

分布式环境下的TGC策略

在多节点CAS部署中,TGC策略需要特别注意以下几点:

第一,Cookie域名设置。 如果CAS通过多个域名访问(如cas1.example.com和cas2.example.com),TGC无法在域名间共享。解决方案是使用统一的入口域名(如cas.example.com),通过反向代理将请求分发到后端的多个CAS节点。

第二,Cookie路径设置。 TGC的路径应设置为CAS的上下文路径(如/cas/),确保只有CAS相关的请求才会携带TGC,减少不必要的数据传输。

第三,Cookie加密。 在CAS 7.3中,TGC支持加密存储。启用加密后,即使攻击者获取了TGC的值,也无法伪造有效的票据。

yaml
cas:
  tgc:
    crypto:
      enabled: true
      encryption:
        key: "your-encryption-key"
      signing:
        key: "your-signing-key"

6.3 会话超时配置演进

CAS不同版本中,会话超时的默认值和配置方式存在显著差异。理解这些差异对于跨版本升级和运维管理至关重要。

CAS 5.3:300秒(5分钟)

yaml
server:
  session:
    timeout: 300
    store-type: redis

CAS 5.3的300秒会话超时是一个相对保守的设置。这个设计的初衷是:HTTP会话主要用于保护CAS的登录流程(登录页面、认证提交等),一旦用户完成登录,TGT已经存储在Redis中,HTTP会话的作用就基本结束了。

然而,300秒的超时在某些场景下会导致问题:

  • 用户在登录页面停留时间过长(如填写复杂的表单),会话过期后提交会失败。
  • OAuth授权流程中,用户在授权确认页面停留时间过长,会话过期导致授权失败。
  • 管理员在CAS管理控制台操作时间过长,会话过期导致操作中断。

CAS 6.6:86400秒(24小时)

yaml
server:
  session:
    timeout: 86400

CAS 6.6将会话超时大幅延长到24小时,解决了5.3版本中的上述问题。这个变化反映了CAS使用场景的扩展——CAS不再仅仅是一个"登录中转站",而是承担了更多的交互功能(如OAuth授权流程、用户自助服务、管理控制台等),这些功能需要更长的会话有效期。

CAS 7.3:灵活配置

yaml
server:
  session:
    timeout: 300

CAS 7.3的默认会话超时回到了300秒,但提供了更灵活的配置方式。架构师可以根据实际需求调整此值。

会话超时与TGT超时的关系

会话超时和TGT超时是两个独立的概念,它们之间的关系需要仔细理解:

  • 会话超时(server.session.timeout): 控制HTTP会话的有效期。过期后,用户需要重新创建HTTP会话(但不一定需要重新登录,因为TGT可能仍然有效)。
  • TGT超时(cas.ticket.tgt.max-time-to-live-in-seconds): 控制SSO登录状态的有效期。过期后,用户需要重新输入用户名和密码进行登录。

在实际项目中,建议的配置策略是:

  • 会话超时应大于等于TGT超时,避免出现"会话有效但TGT已过期"的状态。
  • 如果安全策略要求较短的会话超时,可以通过前端心跳机制保持会话活跃。
  • 在OAuth场景中,会话超时应大于授权码的有效期(默认300秒),否则用户可能在授权流程中遇到会话过期问题。

6.4 会话固定保护

会话固定攻击(Session Fixation Attack)是一种常见的安全威胁。攻击者先获取一个有效的会话ID,然后诱导受害者使用这个会话ID进行认证。认证成功后,攻击者和受害者共享同一个经过认证的会话,攻击者可以利用这个会话访问受害者的资源。

CAS框架内置了会话固定保护机制。在用户成功认证后,CAS会创建一个新的会话(生成新的会话ID),并将旧会话中的属性迁移到新会话中,同时使旧会话失效。这个过程对用户是完全透明的。

在CAS + Redis的部署中,会话固定保护需要特别关注以下几点:

第一,会话迁移的原子性。 当CAS创建新会话并使旧会话失效时,这两个操作需要在Redis中原子性地完成。如果旧会话失效但新会话创建失败(如Redis网络抖动),用户将丢失会话状态。Spring Session通过Redis的事务机制(MULTI/EXEC)来保证原子性。

第二,并发请求处理。 在用户认证的瞬间,浏览器可能同时发送多个请求(如页面刷新、异步请求等)。这些请求可能同时触发会话迁移,导致竞态条件。Spring Session通过会话锁机制来避免这个问题。

第三,负载均衡的会话亲和性。 在某些部署架构中,负载均衡器可能配置了会话亲和性(Session Affinity,也称为Sticky Session)。在会话迁移后,旧的会话亲和性规则可能导致请求被路由到错误的节点。建议在CAS集群部署中使用无状态的负载均衡策略(如轮询),完全依赖Spring Session + Redis实现会话共享。


第七章 Redis高可用架构

7.1 Redis Sentinel哨兵模式

Redis Sentinel是Redis官方提供的高可用解决方案,它通过监控、通知和自动故障转移三大功能,为Redis主从架构提供了高可用保障。

架构概述

Redis Sentinel架构包含以下组件:

  • Master(主节点): 处理所有的写操作和部分读操作。
  • Slave(从节点): 复制Master的数据,处理部分读操作。可以部署多个Slave。
  • Sentinel(哨兵节点): 监控Master和Slave的健康状态,在Master故障时自动执行故障转移。

Sentinel节点通常部署3个或以上(奇数个),通过Raft协议达成一致。当多数Sentinel认为Master不可用时,会选举一个新的Master(从Slave中选出),并通知所有客户端新的Master地址。

CAS连接Sentinel的配置

在CAS 5.3/6.6中,连接Redis Sentinel需要使用CAS专属的配置路径:

yaml
cas:
  ticket:
    registry:
      redis:
        # Sentinel模式配置
        sentinel:
          master: mymaster
          nodes: sentinel1.example.com:26379,sentinel2.example.com:26379,sentinel3.example.com:26379
          password: ""
        # 基础连接配置
        timeout: 2000
        database: 0
        useSsl: false
        usePool: true
        pool:
          max-active: 20
          max-idle: 10
          min-idle: 5
          max-wait: 3000

在CAS 7.3中,Sentinel配置迁移到了Spring标准命名空间:

yaml
spring:
  redis:
    sentinel:
      master: mymaster
      nodes: sentinel1.example.com:26379,sentinel2.example.com:26379,sentinel3.example.com:26379
      password: ""
    timeout: 10000
    database: 0

Sentinel模式的配置要点

master名称: Sentinel配置中的master名称必须与Redis Sentinel配置中的sentinel monitor指令指定的名称一致。默认通常为"mymaster"。

nodes列表: 建议配置所有的Sentinel节点地址,而不是只配置一个。这样即使某个Sentinel节点不可用,CAS也能通过其他Sentinel节点获取当前Master的地址。

密码配置: 如果Redis实例和Sentinel节点都设置了密码,需要分别配置。在CAS 5.3/6.6中,Sentinel的密码通过cas.ticket.registry.redis.sentinel.password配置。

连接池调整: 在Sentinel模式下,连接池的max-active需要考虑故障转移期间的连接重建开销。在故障转移过程中,CAS需要与新的Master建立新的连接,此时如果连接池已满,可能导致请求失败。建议适当增大max-active的值。

7.2 Redis Cluster集群模式

Redis Cluster是Redis 3.0引入的分布式解决方案,它通过数据分片(Sharding)实现了水平扩展和高可用。

架构概述

Redis Cluster架构的核心概念包括:

  • 数据分片: Redis Cluster将数据分为16384个槽(Slot),每个Master节点负责一部分槽。当客户端发送命令时,Cluster根据Key的哈希值确定命令应该发送到哪个节点。
  • 高可用: 每个Master节点可以有多个Slave节点。当Master故障时,Cluster自动将从节点提升为新的Master。
  • 去中心化: Redis Cluster没有单独的代理层,客户端直接与Cluster中的所有节点通信。客户端只需要知道至少一个节点的地址,就可以自动发现整个Cluster的拓扑结构。

CAS连接Cluster的配置

在CAS 7.3中,连接Redis Cluster的配置如下:

yaml
spring:
  redis:
    cluster:
      nodes:
        - redis-node1.example.com:6379
        - redis-node2.example.com:6379
        - redis-node3.example.com:6379
        - redis-node4.example.com:6379
        - redis-node5.example.com:6379
        - redis-node6.example.com:6379
      max-redirects: 3
    timeout: 10000

nodes列表: 配置Cluster中所有节点的地址。客户端会通过这些节点发现整个Cluster的拓扑。实际上只需要配置部分节点即可,但建议配置所有节点以提高可用性。

max-redirects: 3 -- 当客户端向错误的节点发送命令时(如Key属于另一个节点),Cluster会返回MOVED或ASK重定向响应。max-redirects定义了客户端跟随重定向的最大次数。超过此次数后,客户端会抛出异常。

Cluster模式的注意事项

第一,CAS票据的Key设计。 Redis Cluster根据Key的哈希值进行分片。CAS的票据Key通常以"CAS_TICKET:"为前缀,后跟票据ID。由于票据ID是随机生成的,Key的哈希值分布是均匀的,可以自然地利用Cluster的分片能力。

但需要注意,如果CAS使用了Redis的批量操作(如MGET、MSET)或事务操作(MULTI/EXEC),这些操作要求所有涉及的Key位于同一个分片上(即具有相同的哈希标签)。CAS的票据操作通常是单Key操作,不存在这个问题。

第二,database参数无效。 Redis Cluster只支持database 0,spring.redis.database配置在Cluster模式下会被忽略。如果需要数据隔离,只能通过Key前缀来实现。

第三,CAS 5.3/6.6的Cluster支持。 CAS 5.3和6.6的Redis模块基于Spring Data Redis 1.8.x/2.x,对Redis Cluster的支持有限。在实际项目中,如果需要使用Redis Cluster,建议升级到CAS 7.3。

7.3 CAS连接Sentinel/Cluster的配置方式

综合前面的分析,以下是不同CAS版本连接不同Redis部署模式的配置方式总结。

CAS 5.3 配置方式

yaml
# 直连模式
cas:
  ticket:
    registry:
      redis:
        host: redis.example.com
        port: 6379
        timeout: 2000
        database: 0
        usePool: true
        pool:
          max-active: 20
          max-idle: 10
          min-idle: 5
          max-wait: -1

# Sentinel模式
cas:
  ticket:
    registry:
      redis:
        sentinel:
          master: mymaster
          nodes: sentinel1:26379,sentinel2:26379,sentinel3:26379
        timeout: 2000
        database: 0
        usePool: true

CAS 6.6 配置方式

yaml
# 直连模式
cas:
  ticket:
    registry:
      type: REDIS
      redis:
        host: redis.example.com
        port: 6379
        timeout: 5000
        database: 0
        pool:
          max-active: 50
          max-idle: 20
          min-idle: 10
          max-wait: 3000
        cleaner:
          enabled: true
          repeat-interval: 1800
        connection-pool:
          test-on-borrow: true
          test-while-idle: true
          time-between-eviction-runs-millis: 60000
          min-evictable-idle-time-millis: 300000

CAS 7.3 配置方式

yaml
# 直连模式
spring:
  redis:
    host: redis.example.com
    port: 6379
    database: 0
    timeout: 10000

cas:
  ticket:
    registry:
      redis:
        enabled: true
        timeout: PT10S
        pool:
          enabled: true
          max-active: 8
          max-wait: -1
          max-idle: 8
          min-idle: 0

# Cluster模式
spring:
  redis:
    cluster:
      nodes:
        - node1:6379
        - node2:6379
        - node3:6379
      max-redirects: 3
    timeout: 10000

7.4 故障转移与恢复策略

在Redis高可用架构中,故障转移和恢复是保障系统持续可用性的关键环节。本节将分析不同故障场景下的应对策略。

场景一:Redis Master故障(Sentinel模式)

故障检测: Sentinel节点通过定期向Master发送PING命令来检测其健康状态。如果Master在down-after-milliseconds时间内没有响应,Sentinel会将其标记为"主观下线"(SDOWN)。当超过半数的Sentinel都将Master标记为SDOWN时,Master被标记为"客观下线"(ODOWN),触发故障转移。

故障转移过程:

  1. Sentinel通过Raft协议选举出一个Leader Sentinel,由其负责执行故障转移。
  2. Leader Sentinel从Slave中选出一个新的Master(选择标准包括数据新鲜度、复制偏移量、优先级等)。
  3. Leader Sentinel对选中的Slave执行SLAVEOF NO ONE命令,将其提升为新的Master。
  4. Leader Sentinel通知其他Slave复制新的Master。
  5. Sentinel更新客户端(CAS)关于新Master地址的映射。

对CAS的影响: 在故障转移期间(通常持续数秒到数十秒),CAS的Redis操作可能会失败。CAS需要能够容忍这种短暂的服务中断:

  • 票据写入失败:用户登录请求会失败,前端应显示友好的错误信息并引导用户重试。
  • 票据查询失败:业务应用的ST验证请求会失败,业务应用应将用户重定向到CAS登录页面。
  • 会话查询失败:Spring Session无法从Redis加载会话数据,会创建一个新的空会话。

恢复策略:

  • CAS的Redis客户端(Jedis/Lettuce)内置了Sentinel地址发现机制,会自动感知Master地址的变化。
  • 在故障转移完成后,CAS的后续请求会自动路由到新的Master,无需人工干预。
  • 建议在负载均衡器上配置健康检查,在Redis故障转移期间暂时将CAS节点从负载均衡池中移除,避免用户遇到错误。

场景二:Redis节点网络分区

问题描述: 当CAS节点与Redis之间的网络出现分区时,CAS可能无法访问Redis,但Redis本身仍在正常运行。

影响分析:

  • CAS无法创建新的TGT和ST,所有登录请求失败。
  • CAS无法验证已有的ST,业务应用的回调验证失败。
  • Spring Session无法加载会话数据,用户的交互状态丢失。

应对策略:

  • 在网络层面,确保CAS与Redis之间有多条网络路径(如双网卡、多可用区部署)。
  • 在应用层面,配置合理的连接超时和重试策略。
  • 在运维层面,建立网络分区的监控和告警机制,确保问题能被快速发现和处理。

场景三:Redis内存满

问题描述: 当Redis的内存使用达到maxmemory限制时,Redis会根据配置的淘汰策略(eviction policy)开始删除Key。

对CAS的影响: 如果票据Key被淘汰,用户会被强制登出。如果会话Key被淘汰,用户的交互状态会丢失。

预防措施:

  • 配置合理的maxmemory值,确保Redis有足够的内存存储所有活跃票据和会话。
  • 设置合适的淘汰策略。推荐使用allkeys-lru(在所有Key中使用LRU算法淘汰),而不是volatile-lru(仅在设置了TTL的Key中使用LRU算法)。
  • 监控Redis的内存使用情况,设置告警阈值(如80%时告警,90%时严重告警)。
  • 确保CAS的Cleaner任务正常运行,及时清理过期票据。

第八章 票据清理策略

8.1 票据过期机制

CAS票据的过期管理涉及两个层面:应用层面的过期检测和存储层面的自动清理。

应用层面的过期检测

CAS框架在每次票据操作(查询、更新、删除)时,都会检查票据是否已经过期。如果票据已经过期,CAS会将其从注册表中删除,并返回相应的过期状态。

这种"惰性删除"(Lazy Deletion)策略的优点是:

  • 无需额外的清理线程: 过期检测嵌入在正常的业务操作中,不需要额外的系统开销。
  • 精确的过期控制: 票据的过期判断基于其创建时间和配置的TTL,精度可以达到毫秒级。

缺点是:

  • 过期票据不能被及时清理: 如果某个票据过期后长时间没有被访问,它会一直占用存储空间。
  • 存储空间浪费: 在高并发场景下,大量短生命周期的ST可能在Redis中积累。

存储层面的自动清理

Redis的Key过期机制为票据清理提供了存储层面的保障。当CAS将票据写入Redis时,会为每个Key设置TTL(Time To Live)。Redis会在TTL到期后自动删除对应的Key。

Redis的过期Key清理采用以下策略的组合:

  • 惰性删除: 当客户端访问一个Key时,Redis先检查其是否过期,如果过期则删除并返回空。
  • 定期删除: Redis每秒执行10次(默认)的定期扫描,每次随机检查一部分设置了TTL的Key,删除已过期的Key。

这种组合策略在性能和内存使用之间取得了良好的平衡。但在极端情况下(大量Key同时过期),定期删除可能跟不上过期速度,导致Redis内存使用短暂上升。

CAS 5.3/6.6的TTL设置

在CAS 5.3和6.6中,票据的TTL与其过期时间一致:

  • TGT的TTL: 默认为7200秒(2小时),可通过cas.ticket.tgt.max-time-to-live-in-seconds配置。
  • ST的TTL: 默认为10秒,可通过cas.ticket.st.time-to-kill-in-seconds配置。
  • PGT的TTL: 默认与TGT一致。
  • PT的TTL: 默认与ST一致。

CAS 7.3的TTL设置

CAS 7.3的TTL配置方式与5.3/6.6基本一致,但配置属性的命名风格从camelCase变更为kebab-case:

yaml
cas:
  ticket:
    tgt:
      max-time-to-live-in-seconds: 7200
    st:
      time-to-kill-in-seconds: 10

8.2 Redis清理任务(Cleaner)

CAS 6.6引入了专门的Redis清理任务(Cleaner),用于主动清理Redis中的过期票据。虽然Redis的TTL机制已经提供了基础的过期清理能力,但CAS Cleaner提供了额外的保障。

Cleaner的工作流程

CAS Cleaner是一个基于Spring的定时任务(Scheduled Task),其工作流程如下:

  1. 扫描票据Key: Cleaner通过Redis的SCAN命令遍历所有以"CAS_TICKET:"为前缀的Key。
  2. 检查过期状态: 对于每个Key,Cleaner读取其对应的票据对象,检查票据是否已经过期。
  3. 删除过期票据: 如果票据已过期,Cleaner从Redis中删除对应的Key。
  4. 更新统计信息: Cleaner记录清理的票据数量和类型,用于监控和告警。

Cleaner的配置参数

yaml
cas:
  ticket:
    registry:
      redis:
        cleaner:
          enabled: true
          repeat-interval: 1800

enabled: true -- 启用清理任务。在CAS 6.6及以上版本中,建议始终启用此功能。

repeat-interval: 1800 -- 清理任务的执行间隔为1800秒(30分钟)。这个值的选择需要考虑:

  • ST的TTL通常为10秒,30分钟的清理间隔意味着ST主要依赖Redis的TTL机制进行清理,Cleaner主要处理TGT等长生命周期票据。
  • 如果Redis内存紧张,可以减小清理间隔(如600秒),增加主动清理的频率。
  • 清理任务本身会消耗一定的CPU和网络资源,不宜设置过小的间隔。

Cleaner与TTL的协作关系

CAS Cleaner和Redis TTL是互补关系,而非替代关系:

  • Redis TTL: 提供基础的、自动的过期清理,是第一道防线。TTL的清理是异步的、概率性的,存在一定的延迟。
  • CAS Cleaner: 提供主动的、确定性的清理,是第二道防线。Cleaner可以确保所有过期票据都被及时清理,同时收集清理统计信息。

在实际项目中,两者的协作关系如下:

  1. 票据过期后,Redis TTL会在一定时间内(通常数秒到数分钟)自动删除对应的Key。
  2. CAS Cleaner定期扫描并删除可能遗漏的过期票据(如TTL清理延迟的Key)。
  3. CAS Cleaner的统计信息用于监控Redis中的票据存储状态,及时发现异常情况。

8.3 内存优化与Key设计

在Redis中存储CAS票据时,合理的Key设计和内存优化策略可以显著降低Redis的内存占用,提高系统的整体性能。

Key命名规范

CAS默认的Key命名规则为CAS_TICKET:<ticketId>,其中<ticketId>是票据的唯一标识(如"TGT-1-abc123..."或"ST-1-def456...")。

在实际项目中,可以考虑以下Key设计优化:

第一,添加环境前缀。 在多环境(开发、测试、生产)共享同一个Redis实例时,添加环境前缀可以避免Key冲突:

PROD_CAS_TICKET:TGT-1-abc123...
DEV_CAS_TICKET:TGT-1-abc123...

第二,添加票据类型前缀。 将票据类型编码到Key中,便于按类型进行批量操作和监控:

CAS_TICKET:TGT:TGT-1-abc123...
CAS_TICKET:ST:ST-1-def456...

第三,Key长度控制。 Redis的Key越长,占用的内存越大。CAS的票据ID通常较长(包含随机字符串),但Key的总长度应控制在合理范围内。建议Key的总长度不超过100字节。

序列化优化

CAS默认使用JDK序列化将票据对象存储到Redis中。JDK序列化的优点是简单直接,不需要额外的配置,但其缺点是序列化后的字节体积较大。

JDK序列化后的票据对象通常包含:

  • 类的全限定名(如org.apereo.cas.ticket.TicketGrantingTicketImpl
  • 序列化版本UID
  • 对象的完整字段数据
  • 引用类型的嵌套序列化

一个典型的TGT对象经过JDK序列化后,可能占用1-5KB的存储空间。在高并发场景下,大量的TGT和ST对象可能占用数百MB甚至数GB的Redis内存。

优化序列化体积的方法包括:

  • 使用JSON序列化: JSON序列化后的数据体积通常比JDK序列化小30%-50%。但需要注意,JSON序列化需要确保所有字段都是可JSON序列化的。
  • 使用Protobuf或MessagePack: 这些二进制序列化格式的体积更小,解析速度更快。但需要额外的序列化Schema定义和代码生成。
  • 字段裁剪: 分析票据对象中哪些字段是存储必需的,哪些字段可以在查询时从其他数据源获取。通过自定义序列化逻辑,只存储必需的字段。

在实际项目中,序列化优化需要权衡开发成本和收益。对于大多数企业级部署,JDK序列化的内存开销是可以接受的。只有在Redis内存成为瓶颈时,才需要考虑更高效的序列化方案。

内存使用估算

以下是在不同用户规模下的Redis内存使用估算(基于JDK序列化):

用户规模活跃TGT数量每TGT关联ST数TGT内存占用ST内存占用总内存占用
1万5000315MB0.15MB~15MB
10万30000390MB0.9MB~91MB
100万1000003300MB3MB~303MB
1000万3000003900MB9MB~909MB

注:以上估算基于以下假设——TGT平均3KB,ST平均0.5KB,ST的TTL为10秒(大部分ST在扫描时已过期)。

从估算结果可以看出,CAS票据的Redis内存占用主要取决于活跃TGT的数量。ST由于其短生命周期(10秒),对内存的占用可以忽略不计。

8.4 监控与告警

完善的监控和告警体系是保障CAS + Redis系统稳定运行的关键。本节将介绍需要监控的核心指标和告警策略。

Redis核心监控指标

内存相关指标:

  • used_memory:Redis已使用的内存量。需要关注其增长趋势,设置告警阈值(如达到maxmemory的80%)。
  • used_memory_peak:Redis的历史最大内存使用量。用于评估是否需要调整maxmemory配置。
  • evicted_keys:被淘汰的Key数量。如果此值持续增长,说明Redis内存不足,需要扩容或优化Key设计。

连接相关指标:

  • connected_clients:当前连接的客户端数量。CAS节点的连接数应与连接池配置一致。
  • blocked_clients:被阻塞的客户端数量(通常由BLPOP等阻塞命令引起)。CAS正常操作不应产生阻塞客户端。
  • rejected_connections:被拒绝的连接数。如果此值增长,说明Redis的maxclients配置需要调整。

性能相关指标:

  • instantaneous_ops_per_sec:每秒执行的命令数。用于评估Redis的负载水平。
  • keyspace_hits / keyspace_misses:Key的命中和未命中次数。命中率应保持在95%以上。
  • latest_fork_usec:最近一次fork操作(RDB快照或AOF重写)的耗时。如果此值过大,说明Redis在执行持久化时可能出现延迟。

票据相关指标(通过CAS Cleaner收集):

  • 活跃TGT数量:反映当前在线用户数。
  • 活跃ST数量:反映当前的认证请求频率。
  • 清理的过期票据数量:反映票据的过期速率。

告警策略

告警级别指标阈值处理建议
P0(紧急)Redis可用性连接失败立即检查Redis服务状态,启动故障转移
P0(紧急)evicted_keys持续增长紧急扩容Redis内存,检查是否有异常的大量票据创建
P1(严重)used_memory> 80% maxmemory评估扩容需求,检查Cleaner是否正常运行
P1(严重)connected_clients> 80% maxclients检查CAS连接池配置,评估是否需要增加maxclients
P2(警告)keyspace_misses命中率 < 95%检查是否有大量无效的票据查询
P2(警告)latest_fork_usec> 1000ms评估RDB/AOF配置,考虑调整save策略
P3(通知)活跃TGT数量异常增长检查是否有异常的登录行为(如暴力破解)
P3(通知)清理票据数量异常增长检查票据TTL配置是否合理

监控工具推荐

  • Redis命令行工具: redis-cli info命令可以获取Redis的完整运行状态信息。
  • RedisInsight: Redis官方的可视化监控工具,提供丰富的仪表盘和告警功能。
  • Prometheus + Grafana: 通过Redis Exporter采集Redis指标,在Grafana中创建可视化仪表盘。
  • CAS Actuator: CAS的/status/health端点可以查看票据注册表的健康状态。

总结与展望

本文从CAS票据注册表的架构概述出发,深入解析了CAS 5.3、6.6、7.3三个大版本中Redis票据注册表的配置演进、连接池调优、会话一致性保障、高可用部署以及票据清理策略。

回顾CAS Redis配置的演进历程,我们可以看到以下趋势:

配置标准化: 从CAS 5.3的专属命名空间(cas.ticket.registry.redis)到CAS 7.3的Spring标准命名空间(spring.redis),配置方式越来越标准化,与Spring Boot生态的融合越来越深入。

功能精细化: 从CAS 5.3的基础连接池配置,到CAS 6.6的增强连接池参数和Cleaner任务,再到CAS 7.3的enableRedisSearch和Duration格式超时,功能越来越精细和灵活。

技术栈现代化: 从CAS 5.3的Java 8 + Spring Boot 2.x + Jedis,到CAS 6.6的Java 11 + Spring Boot 2.7.x,再到CAS 7.3的Java 21 + Spring Boot 3.5.x + Lettuce,技术栈持续向现代化演进。

架构简化: 从CAS 5.3需要显式锁定Spring Data Redis和Jedis版本,到CAS 7.3由BOM统一管理所有依赖版本,架构的复杂度持续降低。

对于正在进行CAS分布式部署的企业级项目,我们给出以下实践建议:

  1. 新项目优先选择CAS 7.3。 CAS 7.3提供了最现代化的技术栈和最简洁的配置方式,长期来看维护成本最低。

  2. Redis Sentinel适合中小规模部署。 对于大多数企业级SSO场景,Redis Sentinel提供的自动故障转移能力已经足够。

  3. Redis Cluster适合大规模部署。 当单节点Redis无法满足性能需求时,再考虑引入Redis Cluster。

  4. 始终启用Cleaner任务。 虽然Redis TTL提供了基础的过期清理,但Cleaner任务提供了额外的保障和监控能力。

  5. 建立完善的监控体系。 Redis的内存使用、连接数、命中率等指标需要纳入统一的监控平台,并配置合理的告警阈值。

  6. 重视版本兼容性。 CAS的版本升级需要充分测试,特别是Redis相关模块的版本兼容性。建议在升级前在测试环境中进行完整的回归测试。

  7. 配置与代码分离。 将Redis的连接参数(地址、密码等)通过环境变量或配置中心注入,避免将敏感信息硬编码在配置文件中。

  8. 定期评估连接池参数。 随着业务规模的变化,连接池参数可能需要调整。建议每季度评估一次连接池的使用情况,根据实际负载进行优化。

CAS + Redis的组合为企业级SSO系统提供了成熟、可靠、高性能的分布式票据存储方案。通过深入理解其架构原理和配置细节,架构师和开发者可以构建出满足业务需求的高可用SSO平台。


版权声明: 本文为必码(bima.cc)原创技术文章,仅供学习交流。

本文内容基于实际项目源码解析整理,代码示例均为教学简化版本,仅供学习参考。

文档内容提取自项目源码与配置文件,如需获取完整项目代码,请访问 bima.cc