
企业基于Spring Boot + Vue开发了一套CRM(客户关系管理)系统,采用多租户架构,并通过Docker容器部署于香港数据中心的物理服务器上。由于业务拓展,系统主要服务于境内外客户,尤其是东南亚市场,对网络稳定性和多租户隔离性有较高要求。
系统上线初期运行正常,但近两周内用户频繁反馈“页面自动跳转登录页”、“操作中断”、“表单提交失败”等问题,严重影响客户体验和业务处理效率。
通过前端监控系统(Sentry)及用户反馈综合整理,主要问题表现如下:
- 登录成功后页面操作一段时间自动跳转回登录页;
- 部分租户用户在不同时间段频繁掉线;
- 前端请求提示401 Unauthorized或Request Timeout;
- 部分用户出现“两个页面分别登录两个账号,A账号会被踢出”的情况。
这些问题均具备偶发性和租户无关性,发生概率与并发量相关,表现出明显的Session丢失或中断连接的问题特征。
系统架构与部署信息
1. 部署结构
服务器地点:香港新世界电讯机房
物理配置:
- CPU:Intel Xeon Gold 5218
- 内存:128GB
- 存储:NVMe SSD 2TB
- 网络带宽:500Mbps
部署方式:Docker Compose 单节点部署
核心组件:
- Nginx 反向代理
- Spring Boot Web API(Tomcat embedded)
- Redis缓存(Session共享)
- MySQL多租户逻辑隔离
- Vue前端单页应用(SPA)
Session策略:Spring Session Redis(默认HttpSession同步至Redis)
初步排查路径
1. 网络链路检查
通过对境外用户访问路径的traceroute分析及CDN日志审查,未发现明显的丢包和高延迟问题。Ping测试显示香港主机至东南亚地区的平均延迟为60ms左右,处于正常范围。
2. Redis状态检查
使用redis-cli info命令查看内存与连接状态:
connected_clients: 512
used_memory_human: 450MB
blocked_clients: 0
keyspace_hits: 22450498
keyspace_misses: 12315
Redis本身无报错,内存未达瓶颈,连接数也未满。但进一步用MONITOR命令监听后,发现大量如下命令:
DEL spring:session:sessions:abc123xyz
DEL spring:session:sessions:expires:abc123xyz
Redis中Session频繁被删除,引起警觉。
3. 应用日志分析
Spring Boot日志中频繁出现如下内容:
o.s.s.web.session.HttpSessionEventPublisher - Session destroyed: abc123xyz
结合Redis日志,判断Session并非过期删除,而是显式销毁。
深入分析与实验验证
1. Session冲突定位
开发环境模拟多租户登录时,使用不同浏览器登录两个租户用户,发现其中一个账号频繁被踢出,Redis中的Session ID被删除。结合应用设置分析发现,系统采用如下Session配置:
server.servlet.session.cookie.name: JSESSIONID
server.servlet.session.timeout: 30m
且前端与后端均未区分不同租户的Cookie名称。
问题根源:所有租户共享相同的JSESSIONID,前端 SPA 页面请求携带的是统一Cookie,导致多个租户间Session覆盖,尤其在同一浏览器或同一IP中操作多租户账户时表现更明显。
2. Nginx代理配置审查
查看Nginx配置文件,发现代理配置如下:
proxy_pass http://crm_backend;
proxy_set_header Host $host;
proxy_set_header Cookie $http_cookie;
但未设置任何Session或租户隔离策略。也就是说,所有租户请求通过Nginx转发后,Redis中统一处理Session,进而产生冲突和误删。
解决方案与优化措施
1. Session命名隔离
调整Spring Session配置,按租户维度设置不同的Session命名空间,示例配置如下:
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("TENANT_SESSION_" + tenantIdResolver.resolve());
return serializer;
}
其中tenantIdResolver是一个线程内基于请求Header提取租户ID的组件。
2. Nginx增加租户标识透传
proxy_set_header X-Tenant-ID $http_x_tenant_id;
并确保前端每次请求携带此Header。这样后端能动态识别租户并初始化对应Session命名空间。
3. Redis过期策略调整
增加Redis中Session TTL清理阈值,避免误删除活跃Session:
spring.session.redis.flush-mode: on-save
spring.session.redis.namespace: crm_session
并设置合理的失效时间,避免Session被无效清理。
4. Docker资源限制与调度优化
发现高并发时Redis容器负载略高,调整docker-compose.yml资源限制:
redis:
deploy:
resources:
limits:
memory: 2g
cpus: '1.5'
确保Redis在高负载下仍能保持快速响应。
排查总结与经验教训
此次故障的根源在于多租户系统未对Session做区分管理,导致同源Cookie冲突,进而引发用户频繁掉线、Session失效等问题。通过系统性排查,我们确认了Session命名冲突是核心问题,并通过租户隔离、Nginx透传、Redis优化等措施彻底解决问题。
核心经验:
- 多租户架构下,需对Session、Token、Cookie等状态管理机制做命名隔离或作用域隔离;
- Redis作为Session存储时,需避免并发场景下的写覆盖;
- 前端、Nginx、中间件、后端需统一租户标识链路,避免上下游不一致;
- 日志系统应记录租户维度的Session生命周期,便于追踪。
后续计划
- 引入OpenTelemetry对用户请求链路做更细粒度监控;
- 增加Redis Sentinel机制,保障高可用;
- 准备将系统逐步迁移至Kubernetes平台,利用其原生的服务隔离和资源调度能力提升稳定性与可维护性。











