请教一个问题,多实例同时删除 key 怎么解决 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
UserNameisNull
V2EX    程序员

请教一个问题,多实例同时删除 key 怎么解决

  • &nbp;
  •   UserNameisNull 2019-10-14 10:27:39 +08:00 3720 次点击
    这是一个创建于 2203 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景:redis放了微信的access_token,缓存时间是expire_time - 200s,当有新的token生成,旧的就会失效。

    服务有多个实例,取到access_token后请求微信接口,微信接口报token失效的错误,就从redis中删除 key 并更新为最新的。

    就会遇到 A 实例拿到一个token,B 实例把token更新了,就会导致 A 实例报错。

    有大佬可以提供一个思路吗?

    35 条回复    2019-10-15 09:14:02 +08:00
    optional
        1
    optional  
       2019-10-14 10:33:01 +08:00
    多个实例请求微信接口前先判断下 redis 里的 token ? 这是最简单的吧。
    搞个 pub/sub 就有点多余了。
    xuanbg
        2
    xuanbg  
       2019-10-14 10:45:29 +08:00
    分布式锁,更新的时候加锁就行了
    julyclyde
        3
    julyclyde  
       2019-10-14 10:50:24 +08:00
    具体到你这个案例,其实解决方法是只更新不删除
    zisway
        4
    zisway  
       2019-10-14 10:53:07 +08:00 via Android
    可以提前刷新,不依赖 redis 过期。存储时,保存 key 和 key 的创建时间。判断创建时间,是否进行提前更新。
    更新时拿获取到的创建时间去更新。如果时间一致,则去 wx 获取 key 更新,否则说明被别的实例更新过了。
    Vegetable
        5
    Vegetable  
       2019-10-14 10:53:46 +08:00   3
    建议开发者使用中控服务器统一获取和刷新 access_token,其他业务逻辑服务器所使用的 access_token 均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致 access_token 覆盖而影响业务;
    Vegetable
        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
    IMCA1024
        7
    IMCA1024  
       2019-10-14 11:35:56 +08:00
    赞同 5 楼的
    UserNameisNull
        8
    UserNameisNull  
    OP
       2019-10-14 12:53:47 +08:00
    @Vegetable 中控服务器应该是实例吧,如果多实例,又会出现上面的提到的问题。
    单实例要保证高可用,高稳定
    UserNameisNull
        9
    UserNameisNull  
    OP
       2019-10-14 12:56:08 +08:00
    @optional
    肯定判断了的。
    比如 redis 中 token-1 正常没到过期时间,
    A 实例 get token-1, B 实例 get token-1,
    A 发现 token-1 已过期,A 会更新。然后 B 也发现 token-1 过期,B 也更新了,就会导致重复更新。
    UserNameisNull
        10
    UserNameisNull  
    OP
       2019-10-14 12:57:56 +08:00
    @xuanbg
    一样的问题,
    比如 redis 中 token-1 正常没到过期时间,
    A 实例 get token-1, B 实例 get token-1,
    A 发现 token-1 已过期,获取分布式锁,A 会更新。
    然后 B 也发现 token-1 过期,等待获取锁,得到锁,B 也更新了,就会导致重复更新。
    mango88
        11
    mango88  
       2019-10-14 13:00:45 +08:00
    用分布式锁呗,三个实例,谁持有锁,谁更新 access_token
    mango88
        12
    mango88  
       2019-10-14 13:05:29 +08:00   1
    三个实例 ,尝试 set 同一个 key, value 为实例 id ;
    set 成功直接跟新 access_token,
    set 失败判断 , 判断 id 是否属于自己的实例,属于自己就更新 token,并刷新 expire time
    UserNameisNull
        13
    UserNameisNull  
    OP
       2019-10-14 13:14:57 +08:00
    @mango88 感谢提供思路
    hdbzsgm
        14
    hdbzsgm  
       2019-10-14 13:18:30 +08:00
    @UserNameisNull #10 仔细走一下分布式锁的逻辑 比如 B 得到锁之后 要不要先查询当前 token 是否过期的 再去执行更新逻辑 当然靠时间是不靠谱的 需要一个全局自增的 key ps: 请不要使用 redlock 方案
    WuMingyu
        15
    WuMingyu  
       2019-10-14 13:50:31 +08:00
    “就会遇到 A 实例拿到一个 token,B 实例把 token 更新了,就会导致 A 实例报错。”为啥会报错呢,本身新老 token 是可以共存一段时间的
    Dganzh
        16
    Dganzh  
       2019-10-14 14:21:15 +08:00
    @UserNameisNull
    > 然后 B 也发现 token-1 过期,等待获取锁,得到锁,B 也更新了,就会导致重复更新。
    B 不要去更新,因为没有直接获得锁说明已经有人获得了,这就说明已经有人去更新了,其他人只需静静等待 access_token 更新即可
    xuanbg
        17
    xuanbg  
       2019-10-14 14:38:14 +08:00
    @UserNameisNull 双检锁了解一下。B 获取到锁不是直接去更新,而是先检查一下 Token 是否可用。
    676529483
        18
    676529483  
       2019-10-14 14:43:17 +08:00
    赞同 5 楼,用锁会影响性能,只需要后台启一个进程,专门负责刷新 key,其他只负责取就行了
    MrYELiex
        19
    MrYELiex  
       2019-10-14 14:58:33 +08:00
    分布式锁并不能解决这个问题 因为 token 并不止在 redis 中 微信那边也有
    如果多个实例去刷新 token 那么后请求的会覆盖其他同时请求的实例刚生成的 token 这才是导致报错的原因

    因此中控服务器维护这些会过期的秘钥是最好的选择
    dot2017
        20
    dot2017  
       2019-10-14 14:59:59 +08:00
    本地缓存 key 没问题,需要补一个 version 键就行。拿的时候先判断 version 全局是否一致。每更新一次 key,version+1
    hero2040407
        21
    hero2040407  
       2019-10-14 15:05:08 +08:00
    不是每次请求都去重新拿一次?那就每个实例都在固定时间来更新 access_token
    lihongjie0209
        22
    lihongjie0209  
       2019-10-14 15:06:58 +08:00
    最简单的就是中控服务器, 其他的方案都有问题, 要不实现困难, 要不无法保证正确性
    UserNameisNull
        23
    UserNameisNull  
    OP
       2019-10-14 15:13:48 +08:00
    中控服务器肯定是单实例,无法做到高可用,怎么办呢?
    单实例怎么情况下 重新部署 或者 重新发布 就会导致服务中断,或者两个实例并存一段时间。
    b821025551b
        24
    b821025551b  
       2019-10-14 15:17:49 +08:00
    @WuMingyu #15 你这边共存有什么用,微信那里只有一个是有效的。
    UserNameisNull
        25
    UserNameisNull  
    OP
       2019-10-14 15:21:15 +08:00
    @b821025551b
    微信文档说了 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html,新的出来,老的还能用 5 分钟。
    问题是 有 A,B,C 三个服务,同时更新,必然有个服务的 Token 是失效的。
    b821025551b
        26
    b821025551b  
       2019-10-14 15:25:06 +08:00
    @UserNameisNull #25 这是对你这边对开发对建议,并不是微信那边的机制,token 就是只能有一个是有效的
    killergun
        27
    killergun  
       2019-10-14 16:04:17 +08:00
    access_token 推荐有固定服务去刷新,刷新之后旧的 access_token 在几分钟内还是有效的,这样就不会有问题了
    b821025551b
        28
    b821025551b  
       2019-10-14 17:05:37 +08:00
    @UserNameisNull #25 刚摸鱼差点被发现,重新说:
    文档里提到的这个 5 分钟,是给你这边开发的建议,建议去保留这 5 分钟;而微信的 token 机制压根不存在这 5 分钟,新的出来,老的必定失效,不存在什么并存的。
    leegoo
        29
    leegoo  
       2019-10-14 20:53:15 +08:00
    @b821025551b 以前我开发过,好像并不会直接失效,有一个平滑过渡时间
    liukangxu
        30
    liukangxu  
       2019-10-14 21:08:36 +08:00
    白纸黑字的官方文档不相信,还能怎么办呢,无非是浪费自己和他人的时间罢了
    lianyue
        31
    lianyue  
       2019-10-14 21:22:03 +08:00
    其实啊 加个重试就好了 因为 就算没刷新 access_token 也可能用户取消授权 导致失效

    1. 读取数据库 的 access_token 判断 (是否需要刷新)刷新失败就 出错并结束 成功就储存到数据库
    2. 请求微信的 api
    3. 如果提示 access_token 失效了就再读取下数据库 如果 再读取的 access_token 和 老的 access_token 相同 就 出错并结束
    useben
        32
    useben  
       2019-10-14 23:08:30 +08:00
    最简单,最稳的方案:redis 集群存 access_token,lvs+多实例专门负责请求刷新 access_token 和提供获取 access_token 接口,其他的请求接口就行了。
    conn4575
        33
    conn4575  
       2019-10-15 08:33:11 +08:00 via Android
    5 楼正解,所有要求全局统一的 token,包括 session 都应该使用中控服务的思想,redis 只是存储层的事情
    Kerwin1202
        34
    Kerwin1202  
       2019-10-15 09:00:01 +08:00 via iPhone
    赞同 5 楼,几台机器,专门用一台机器一个站点刷新 token 的
    Ianchen
        35
    Ianchen  
       2019-10-15 09:14:02 +08:00
    赞同 5 楼的做法. 实际还是分布式问题, 如果想避免, 则考虑下刷新 access token 的工作给开一个队列消息, 然后无论是 zk, kafka, rabbitmq 等, 都会自己寻找一个可用有效的节点去刷新. 有时候可以用消息队列来规避掉分布式的问题
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2554 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 05:35 PVG 13:35 LAX 22:35 JFK 01:35
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86