共计 1193 个字符,预计需要花费 3 分钟才能阅读完成。
问题背景:当 Token 遇上并发
开发中经常遇到这种情况:用户快速切换界面时,突然弹出多个登录失效提示。查看日志会发现大量 401 错误集中爆发——这就是典型的并发 Token 失效问题。当多个请求同时检测到 Token 过期时,可能触发多次刷新请求,导致服务器拒绝服务或新 Token 被覆盖。

原理拆解:Token 的生命周期
- 双 Token 机制:Access Token(短期有效)和 Refresh Token(长期有效)配合工作。前者用于业务请求,后者专用于 Token 刷新
- 多线程竞争场景:当三个线程同时检测到 Token 过期时,如果没有防护措施,可能出现:
- 线程 A 开始刷新 Token
- 线程 B 也发起刷新请求
- 线程 C 使用已失效的旧 Token
- 可见性问题:刷新后的新 Token 可能无法立即被其他线程感知
解决方案大比拼
方案对比表
| 方案 | 优点 | 缺点 |
|---|---|---|
| 同步锁 | 实现简单 | 性能瓶颈 |
| 双重检查锁 | 减少锁竞争 | 实现复杂度高 |
| 消息队列 | 完全解耦 | 架构复杂度高 |
OkHttp 拦截器实现(Kotlin 版)
class TokenInterceptor : Interceptor {
@Volatile private var isRefreshing = false
private val lock = Any()
override fun intercept(chain: Interceptor.Chain): Response {val request = chain.request()
var response = proceedWithCurrentToken(chain, request)
// Token 过期时自动刷新
if (response.code == 401) {synchronized(lock) {if (isRefreshing) {lock.wait() // 等待刷新完成
return proceedWithNewToken(chain, request)
} else {
isRefreshing = true
try {refreshTokenSync()
lock.notifyAll()} finally {isRefreshing = false}
response = proceedWithNewToken(chain, request)
}
}
}
return response
}
private fun refreshTokenSync() {// 实际刷新逻辑...}
}
生产环境避坑指南
- 刷新死锁:避免在刷新 Token 的请求中再次触发刷新
- 时钟漂移:客户端与服务端时间不同步导致提前判定 Token 失效
- 心跳间隔:建议设置为 Token 有效期的 1 /3
性能优化建议
- 本地缓存 Token 时采用
SharedPreferences的apply()异步写入 - 网络请求失败时采用指数退避重试策略
- 对非关键路径请求可降级处理
思考题
在微服务架构下,当用户同时在手机和网页端登录时,如何保证多个设备的 Token 刷新不会互相覆盖?这个问题的解决方案可能会涉及到分布式锁的设计 …
正文完
