V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
UserNameisNull
V2EX  ›  程序员

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

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

    背景: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   ·   实用小工具   ·   1067 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 18:32 · PVG 02:32 · LAX 10:32 · JFK 13:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.