背景:redis
放了微信的access_token
,缓存时间是expire_time - 200s
,当有新的token
生成,旧的就会失效。
服务有多个实例,取到access_token
后请求微信接口,微信接口报token
失效的错误,就从redis
中删除 key 并更新为最新的。
就会遇到 A 实例拿到一个token
,B 实例把token
更新了,就会导致 A 实例报错。
有大佬可以提供一个思路吗?
1
optional 2019-10-14 10:33:01 +08:00
多个实例请求微信接口前先判断下 redis 里的 token ? 这是最简单的吧。
搞个 pub/sub 就有点多余了。 |
2
xuanbg 2019-10-14 10:45:29 +08:00
分布式锁,更新的时候加锁就行了
|
3
julyclyde 2019-10-14 10:50:24 +08:00
具体到你这个案例,其实解决方法是只更新不删除
|
4
zisway 2019-10-14 10:53:07 +08:00 via Android
可以提前刷新,不依赖 redis 过期。存储时,保存 key 和 key 的创建时间。判断创建时间,是否进行提前更新。
更新时拿获取到的创建时间去更新。如果时间一致,则去 wx 获取 key 更新,否则说明被别的实例更新过了。 |
5
Vegetable 2019-10-14 10:53:46 +08:00 3
建议开发者使用中控服务器统一获取和刷新 access_token,其他业务逻辑服务器所使用的 access_token 均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致 access_token 覆盖而影响业务;
|
6
Vegetable 2019-10-14 10:55:01 +08:00
还有一句
access_token 的有效期通过返回的 expire_in 来传达,目前是 7200 秒之内的值,中控服务器需要根据这个有效时间提前去刷新。在刷新过程中,中控服务器可对外继续输出的老 access_token,此时公众平台后台会保证在 5 分钟内,新老 access_token 都可用,这保证了第三方业务的平滑过渡; https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html |
7
IMCA1024 2019-10-14 11:35:56 +08:00
赞同 5 楼的
|
8
UserNameisNull OP @Vegetable 中控服务器应该是实例吧,如果多实例,又会出现上面的提到的问题。
单实例要保证高可用,高稳定 |
9
UserNameisNull OP @optional
肯定判断了的。 比如 redis 中 token-1 正常没到过期时间, A 实例 get token-1, B 实例 get token-1, A 发现 token-1 已过期,A 会更新。然后 B 也发现 token-1 过期,B 也更新了,就会导致重复更新。 |
10
UserNameisNull OP @xuanbg
一样的问题, 比如 redis 中 token-1 正常没到过期时间, A 实例 get token-1, B 实例 get token-1, A 发现 token-1 已过期,获取分布式锁,A 会更新。 然后 B 也发现 token-1 过期,等待获取锁,得到锁,B 也更新了,就会导致重复更新。 |
11
mango88 2019-10-14 13:00:45 +08:00
用分布式锁呗,三个实例,谁持有锁,谁更新 access_token
|
12
mango88 2019-10-14 13:05:29 +08:00 1
三个实例 ,尝试 set 同一个 key, value 为实例 id ;
set 成功直接跟新 access_token, set 失败判断 , 判断 id 是否属于自己的实例,属于自己就更新 token,并刷新 expire time |
13
UserNameisNull OP @mango88 感谢提供思路
|
14
hdbzsgm 2019-10-14 13:18:30 +08:00
@UserNameisNull #10 仔细走一下分布式锁的逻辑 比如 B 得到锁之后 要不要先查询当前 token 是否过期的 再去执行更新逻辑 当然靠时间是不靠谱的 需要一个全局自增的 key ps: 请不要使用 redlock 方案
|
15
WuMingyu 2019-10-14 13:50:31 +08:00
“就会遇到 A 实例拿到一个 token,B 实例把 token 更新了,就会导致 A 实例报错。”为啥会报错呢,本身新老 token 是可以共存一段时间的
|
16
Dganzh 2019-10-14 14:21:15 +08:00
@UserNameisNull
> 然后 B 也发现 token-1 过期,等待获取锁,得到锁,B 也更新了,就会导致重复更新。 B 不要去更新,因为没有直接获得锁说明已经有人获得了,这就说明已经有人去更新了,其他人只需静静等待 access_token 更新即可 |
17
xuanbg 2019-10-14 14:38:14 +08:00
@UserNameisNull 双检锁了解一下。B 获取到锁不是直接去更新,而是先检查一下 Token 是否可用。
|
18
676529483 2019-10-14 14:43:17 +08:00
赞同 5 楼,用锁会影响性能,只需要后台启一个进程,专门负责刷新 key,其他只负责取就行了
|
19
MrYELiex 2019-10-14 14:58:33 +08:00
分布式锁并不能解决这个问题 因为 token 并不止在 redis 中 微信那边也有
如果多个实例去刷新 token 那么后请求的会覆盖其他同时请求的实例刚生成的 token 这才是导致报错的原因 因此中控服务器维护这些会过期的秘钥是最好的选择 |
20
dot2017 2019-10-14 14:59:59 +08:00
本地缓存 key 没问题,需要补一个 version 键就行。拿的时候先判断 version 全局是否一致。每更新一次 key,version+1
|
21
hero2040407 2019-10-14 15:05:08 +08:00
不是每次请求都去重新拿一次?那就每个实例都在固定时间来更新 access_token
|
22
lihongjie0209 2019-10-14 15:06:58 +08:00
最简单的就是中控服务器, 其他的方案都有问题, 要不实现困难, 要不无法保证正确性
|
23
UserNameisNull OP 中控服务器肯定是单实例,无法做到高可用,怎么办呢?
单实例怎么情况下 重新部署 或者 重新发布 就会导致服务中断,或者两个实例并存一段时间。 |
24
b821025551b 2019-10-14 15:17:49 +08:00
@WuMingyu #15 你这边共存有什么用,微信那里只有一个是有效的。
|
25
UserNameisNull OP @b821025551b
微信文档说了 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html,新的出来,老的还能用 5 分钟。 问题是 有 A,B,C 三个服务,同时更新,必然有个服务的 Token 是失效的。 |
26
b821025551b 2019-10-14 15:25:06 +08:00
@UserNameisNull #25 这是对你这边对开发对建议,并不是微信那边的机制,token 就是只能有一个是有效的
|
27
killergun 2019-10-14 16:04:17 +08:00
access_token 推荐有固定服务去刷新,刷新之后旧的 access_token 在几分钟内还是有效的,这样就不会有问题了
|
28
b821025551b 2019-10-14 17:05:37 +08:00
@UserNameisNull #25 刚摸鱼差点被发现,重新说:
文档里提到的这个 5 分钟,是给你这边开发的建议,建议去保留这 5 分钟;而微信的 token 机制压根不存在这 5 分钟,新的出来,老的必定失效,不存在什么并存的。 |
29
leegoo 2019-10-14 20:53:15 +08:00
@b821025551b 以前我开发过,好像并不会直接失效,有一个平滑过渡时间
|
30
liukangxu 2019-10-14 21:08:36 +08:00
白纸黑字的官方文档不相信,还能怎么办呢,无非是浪费自己和他人的时间罢了
|
31
lianyue 2019-10-14 21:22:03 +08:00
其实啊 加个重试就好了 因为 就算没刷新 access_token 也可能用户取消授权 导致失效
1. 读取数据库 的 access_token 判断 (是否需要刷新)刷新失败就 出错并结束 成功就储存到数据库 2. 请求微信的 api 3. 如果提示 access_token 失效了就再读取下数据库 如果 再读取的 access_token 和 老的 access_token 相同 就 出错并结束 |
32
useben 2019-10-14 23:08:30 +08:00
最简单,最稳的方案:redis 集群存 access_token,lvs+多实例专门负责请求刷新 access_token 和提供获取 access_token 接口,其他的请求接口就行了。
|
33
conn4575 2019-10-15 08:33:11 +08:00 via Android
5 楼正解,所有要求全局统一的 token,包括 session 都应该使用中控服务的思想,redis 只是存储层的事情
|
34
Kerwin1202 2019-10-15 09:00:01 +08:00 via iPhone
赞同 5 楼,几台机器,专门用一台机器一个站点刷新 token 的
|
35
Ianchen 2019-10-15 09:14:02 +08:00
赞同 5 楼的做法. 实际还是分布式问题, 如果想避免, 则考虑下刷新 access token 的工作给开一个队列消息, 然后无论是 zk, kafka, rabbitmq 等, 都会自己寻找一个可用有效的节点去刷新. 有时候可以用消息队列来规避掉分布式的问题
|