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

关于 token 穿透的问题 感觉 PHP 只能靠锁 Java 的话可以用 synchronized 来保证

  •  
  •   0576coder · 2018-09-25 17:22:35 +08:00 · 4238 次点击
    这是一个创建于 2286 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近项目上线 有点小流量 峰值差不多 600-800qps 吧 遇到了一个微信 accesstoken 穿透问题

    问题描述

    在并发高的时候 这时候缓存里面的 token 失效了 那么这时候我们会去请求微信的接口来刷这个 token 这时候因为有网络 io 然后各个请求得到的 accesstoken 互相覆盖 导致获取到的 token 不一定是最新有效的 token 所以就会陷入要不断去刷这个微信 token 的死循环当中 要是没及时发现 差不多要把微信 token 的当日获取次数用完

    目前我的做法 业务代码带上个参数可以直接请求微信接口去刷新这个 token 然后还有务去刷新这个 token


    目前暂时解决思路

    用 redis 的 setnx 锁 只有竞争到锁的才会去请求 accesstoken 然后在存到缓存里之后 再把锁释放掉 当然我会设置这个锁的最长过期时间的

    这样做了之后可以防止 token 穿透 但是比如 10 个并发请求 只有一个竞争到锁 其它的请求是不是要等待一会儿再去竞争锁 这样难道不会导致前端用户体验不好吗

    还有搜了下 搜到了二级缓存 可以缓存两个 key 防止穿透 但是具体实现的话 是不是还是要用到 setnx


    我搜了下 java 直接一个 synchronized 就能保证了- - 我是不是可以用 java 实现一个 token 中控

    38 条回复    2018-09-27 13:43:59 +08:00
    blindpirate
        1
    blindpirate  
       2018-09-25 17:27:12 +08:00
    语死早系列
    leoleoasd
        2
    leoleoasd  
       2018-09-25 17:29:40 +08:00
    缓存的 accesstoken 加上过期时间 快到过期时间了的时候每一个进程都有一定概率刷新 token
    leoleoasd
        3
    leoleoasd  
       2018-09-25 17:30:37 +08:00
    或者 php 跑一个后台进程 定时刷新 token
    linauror
        4
    linauror  
       2018-09-25 17:36:27 +08:00
    计划任务定时执行刷新
    liuxu
        5
    liuxu  
       2018-09-25 17:37:35 +08:00   ❤️ 3
    synchronized 也是锁啊。。。
    allanzhuo
        6
    allanzhuo  
       2018-09-25 17:39:05 +08:00
    setnx 的方法应该是一种比较常用的方法了吧,感觉楼主说的这个已经很完善了,只不过可以给竞争失败的用户一个友好的提示。还有种办法就是起个线程主动刷新即将过期的缓存。
    FreeEx
        7
    FreeEx  
       2018-09-25 17:39:13 +08:00 via Android   ❤️ 1
    如果你的应用是单节点的话可以使用 java 的 ReentrantReadWriteLock
    ,在 api 文档中搜索这个类会有一个 CacheData 的例子,可以完美解决你说的这个问题。
    mortonnex
        8
    mortonnex  
       2018-09-25 17:39:42 +08:00
    "峰值差不多 600-800qps"

    楼主的项目日 pv 是千万级的?
    0576coder
        9
    0576coder  
    OP
       2018-09-25 17:41:23 +08:00
    @mortonnex
    日 pv 刚百万左右
    0576coder
        10
    0576coder  
    OP
       2018-09-25 17:42:19 +08:00
    在你计划任务刷新的时候 假如有正常请求呢
    mortonnex
        11
    mortonnex  
       2018-09-25 17:43:11 +08:00
    @0576coder 那么你们的接口响应速度还是很快了
    loveCoding
        12
    loveCoding  
       2018-09-25 17:46:22 +08:00
    获取 token 时加锁可行
    loveCoding
        13
    loveCoding  
       2018-09-25 17:48:41 +08:00
    @0576coder #10 并不是刷新时就清除 token ,而是获取到了后覆盖原值, 刷新 token 要比 token 过期时间小
    yushiro
        14
    yushiro  
       2018-09-25 17:52:41 +08:00 via iPhone
    重新申请 accesstoken 专门分配一个线程或者进程去操作,这样就没并发的问题了。
    janxin
        15
    janxin  
       2018-09-25 17:57:56 +08:00
    所以 synchronized 是怎么实现的呢?
    baiy
        16
    baiy  
       2018-09-25 18:01:50 +08:00
    #3 三楼正解 业务仅读取 token, token 的维护交给其他程序负责 ,我记得微信的 token 过期时间是 7200, 起一个计划任务每 3600 主动刷新 token
    0576coder
        17
    0576coder  
    OP
       2018-09-25 18:23:35 +08:00
    @janxin
    Java 的 synchronized 也是一种线程互斥机制而已
    一个大佬告诉我的
    sampeng
        18
    sampeng  
       2018-09-25 18:37:24 +08:00 via iPhone
    synchronized 和 redis 的锁是一回事?后者处理分布式一致性问题。前者只处理本机一致性问题。两码事好吗
    mmdsun
        19
    mmdsun  
       2018-09-25 18:39:41 +08:00 via Android
    Redis 锁是 cas。Java synchronized 比较重量级吧
    fcten
        20
    fcten  
       2018-09-25 18:42:35 +08:00
    另起一个定时任务定期刷新 token 不行吗。。
    0576coder
        21
    0576coder  
    OP
       2018-09-25 19:05:38 +08:00
    @sampeng
    你 redis 分布式锁用 setnx?
    一般一个 token 中控台单机就够了 业务方自己缓存 过期了再来像中控台请求呀
    sampeng
        22
    sampeng  
       2018-09-25 19:24:04 +08:00
    @0576coder 轻量业务 setnx 做锁蛮好使的。。。lz 这个需求怎么搞都没错倒是。。。是我肯定像楼上说的。专门起一个线程或者小的脚本,定时重新申请一下。。。

    百万 pv。可能得两台机器吧。不然跪一台怎么搞?

    setnx 很符合需求啊。。用 synchronized。。不是不可以。但从两台机器来看。互相并不知道锁的存在。。
    sampeng
        23
    sampeng  
       2018-09-25 19:25:10 +08:00
    @0576coder 当然。一台机器,随便搞。。没啥区别
    pynix
        24
    pynix  
       2018-09-25 19:30:34 +08:00
    synchronized 只在进程内管用,,,,,多机玩完。。
    0576coder
        25
    0576coder  
    OP
       2018-09-25 19:38:24 +08:00
    @sampeng
    我现在跑 php-fpm 的在 3 台负载
    然后有一台专门跑队列 定时任务的
    xmh51
        26
    xmh51  
       2018-09-25 19:42:46 +08:00
    简单的做法 加 redis 分布式锁 java 实现是 redisson php 估计也有实现
    xmh51
        27
    xmh51  
       2018-09-25 19:43:46 +08:00
    这个甚至用 mysql 也能支撑的啊。因为刷新 token 的节点穿透缓存后调用量并不高。
    xuanbg
        28
    xuanbg  
       2018-09-25 21:21:02 +08:00
    这个需求一个双检锁就好了,不需要太过麻烦。如果是分布式环境,就要用分布式锁,否则锁不住。
    codespots
        29
    codespots  
       2018-09-25 23:42:14 +08:00
    你这个情况,Java 的 synchronized 锁不适应的,synchronized 只是单机进程之间的。你现在的情况更需要的是分布式锁。
    blackboom
        30
    blackboom  
       2018-09-26 00:05:35 +08:00 via Android
    这个地方不能单点,否则后果难以想象,分布式不但要考虑锁,还要考虑任务抢占和任务失败的情况,同时也要保证 RETRY 和强制刷新。

    并发量小的情况下可以考虑 redis 和 mysql

    并发量较大的时候 redis 或者 mysql 就会成为瓶颈,这个时候要考虑本地缓存。
    ca1123
        31
    ca1123  
       2018-09-26 01:07:55 +08:00
    你弄个信号量来表示 token 操作过程的状态呗。这是经典的并发问题嘛。
    shiny
        32
    shiny  
       2018-09-26 01:16:49 +08:00
    #4 的定时刷新不就解决了,方案越复杂越要小心可靠度
    GGGG430
        33
    GGGG430  
       2018-09-26 07:50:59 +08:00 via iPhone
    楼主的 token 中控台接口还需加个参数,当调用方发现 token 失效时(也就是微信返回 40001 )你需要加上这个参数请求中控台直接刷新缓存
    AngryPanda
        34
    AngryPanda  
       2018-09-26 09:18:20 +08:00 via Android
    让一个进程去做专门的事情。
    spkinger
        35
    spkinger  
       2018-09-26 09:43:02 +08:00
    每个 token 都是有有效期的,可以在有效期到期前就去加锁并刷新,没获取到锁的依然去使用旧 token,这样可以保证无缝过度。如果能做成单独的服务更好,便于排查问题。
    changepll
        36
    changepll  
       2018-09-26 10:04:29 +08:00
    微信文档中也提及到这个 token 生成的方式
    最佳实践是服务器开一个定时任务去生成. 业务只管使用, 不负责生成
    yc8332
        37
    yc8332  
       2018-09-26 16:41:56 +08:00
    缓存还在 token 过期?不同环境请求重新生成 token 了吧。。。单起一个进程负责 token 的维护啊,定时维护就好了。cron 最简单。。加锁也可以。但是加锁你所有请求都要 hold,能 hold 住吗
    0576coder
        38
    0576coder  
    OP
       2018-09-27 13:43:59 +08:00
    @changepll
    是的 我想复杂了 其实我主要不想靠定时任务 想做个中控台 不过先这样业务方只管取 只有在定时任务里才会刷新这个 token 目前代码改了之后平稳运行了两天了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   930 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 22:38 · PVG 06:38 · LAX 14:38 · JFK 17:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.