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

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

  •  
  •   UserNameisNull · 37 天前 · 1840 次点击
    这是一个创建于 37 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景: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
        1
    optional   37 天前
    多个实例请求微信接口前先判断下 redis 里的 token ? 这是最简单的吧。
    搞个 pub/sub 就有点多余了。
        2
    xuanbg   37 天前
    分布式锁,更新的时候加锁就行了
        3
    julyclyde   37 天前
    具体到你这个案例,其实解决方法是只更新不删除
        4
    zisway   37 天前 via Android
    可以提前刷新,不依赖 redis 过期。存储时,保存 key 和 key 的创建时间。判断创建时间,是否进行提前更新。
    更新时拿获取到的创建时间去更新。如果时间一致,则去 wx 获取 key 更新,否则说明被别的实例更新过了。
        5
    Vegetable   37 天前   ♥ 3
    建议开发者使用中控服务器统一获取和刷新 access_token,其他业务逻辑服务器所使用的 access_token 均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致 access_token 覆盖而影响业务;
        6
    Vegetable   37 天前
    还有一句

    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   37 天前
    赞同 5 楼的
        8
    UserNameisNull   37 天前
    @Vegetable 中控服务器应该是实例吧,如果多实例,又会出现上面的提到的问题。
    单实例要保证高可用,高稳定
        9
    UserNameisNull   37 天前
    @optional
    肯定判断了的。
    比如 redis 中 token-1 正常没到过期时间,
    A 实例 get token-1, B 实例 get token-1,
    A 发现 token-1 已过期,A 会更新。然后 B 也发现 token-1 过期,B 也更新了,就会导致重复更新。
        10
    UserNameisNull   37 天前
    @xuanbg
    一样的问题,
    比如 redis 中 token-1 正常没到过期时间,
    A 实例 get token-1, B 实例 get token-1,
    A 发现 token-1 已过期,获取分布式锁,A 会更新。
    然后 B 也发现 token-1 过期,等待获取锁,得到锁,B 也更新了,就会导致重复更新。
        11
    mango88   37 天前
    用分布式锁呗,三个实例,谁持有锁,谁更新 access_token
        12
    mango88   37 天前   ♥ 1
    三个实例 ,尝试 set 同一个 key, value 为实例 id ;
    set 成功直接跟新 access_token,
    set 失败判断 , 判断 id 是否属于自己的实例,属于自己就更新 token,并刷新 expire time
        13
    UserNameisNull   37 天前
    @mango88 感谢提供思路
        14
    hdbzsgm   37 天前
    @UserNameisNull #10 仔细走一下分布式锁的逻辑 比如 B 得到锁之后 要不要先查询当前 token 是否过期的 再去执行更新逻辑 当然靠时间是不靠谱的 需要一个全局自增的 key ps: 请不要使用 redlock 方案
        15
    WuMingyu   37 天前
    “就会遇到 A 实例拿到一个 token,B 实例把 token 更新了,就会导致 A 实例报错。”为啥会报错呢,本身新老 token 是可以共存一段时间的
        16
    Dganzh   37 天前
    @UserNameisNull
    > 然后 B 也发现 token-1 过期,等待获取锁,得到锁,B 也更新了,就会导致重复更新。
    B 不要去更新,因为没有直接获得锁说明已经有人获得了,这就说明已经有人去更新了,其他人只需静静等待 access_token 更新即可
        17
    xuanbg   37 天前
    @UserNameisNull 双检锁了解一下。B 获取到锁不是直接去更新,而是先检查一下 Token 是否可用。
        18
    676529483   37 天前
    赞同 5 楼,用锁会影响性能,只需要后台启一个进程,专门负责刷新 key,其他只负责取就行了
        19
    MrYELiex   37 天前
    分布式锁并不能解决这个问题 因为 token 并不止在 redis 中 微信那边也有
    如果多个实例去刷新 token 那么后请求的会覆盖其他同时请求的实例刚生成的 token 这才是导致报错的原因

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

    1. 读取数据库 的 access_token 判断 (是否需要刷新)刷新失败就 出错并结束 成功就储存到数据库
    2. 请求微信的 api
    3. 如果提示 access_token 失效了就再读取下数据库 如果 再读取的 access_token 和 老的 access_token 相同 就 出错并结束
        32
    useben   36 天前
    最简单,最稳的方案:redis 集群存 access_token,lvs+多实例专门负责请求刷新 access_token 和提供获取 access_token 接口,其他的请求接口就行了。
        33
    conn4575   36 天前 via Android
    5 楼正解,所有要求全局统一的 token,包括 session 都应该使用中控服务的思想,redis 只是存储层的事情
        34
    Kerwin1202   36 天前 via iPhone
    赞同 5 楼,几台机器,专门用一台机器一个站点刷新 token 的
        35
    Ianchen   36 天前
    赞同 5 楼的做法. 实际还是分布式问题, 如果想避免, 则考虑下刷新 access token 的工作给开一个队列消息, 然后无论是 zk, kafka, rabbitmq 等, 都会自己寻找一个可用有效的节点去刷新. 有时候可以用消息队列来规避掉分布式的问题
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2857 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 29ms · UTC 11:22 · PVG 19:22 · LAX 03:22 · JFK 06:22
    ♥ Do have faith in what you're doing.