美文网首页
优化并发获取微信token

优化并发获取微信token

作者: linjiajiam | 来源:发表于2018-12-06 17:46 被阅读0次
    背景:
    • 最近项目使用到了微信开发,获取微信token是必须可少的步骤。Redis是存储token的最佳方案,所以把获取token的方法写在了一个公用的方法内,如下:
    public class WeChatUtil {
    
        public static String APPID;
    
        public static String SECRET;
    
        private static String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
    
        @Value("${wechat.appid}")
        public void setAPPID(String APPID) {
            WeChatUtil.APPID = APPID;
        }
    
        @Value("${wechat.secret}")
        public void setSECRET(String SECRET) {
            WeChatUtil.SECRET = SECRET;
        }
    
        public static long EXPIRETIME = 7100;
    
       
        /**
         * 获取token
         * @return
         */
        public static String getWeixinToken(){
            //从redis中获取token
            String token = (String)RedisUtil.get("vbpWxToken");
            //token不存在或者已经过期,重新调用微信接口获取token
            if(StringUtils.isBlank(token)){
                try {
                    String response = HttpUtil.requestGet(String.format(TOKEN_URL, APPID, SECRET), null);
                    JSONObject jsonobject = JSONObject.parseObject(response);
                    //如果取得token,那么就返回
                    if(jsonobject.getString("access_token") != null){
                        token = jsonobject.getString("access_token");
                        //将token写入redis
                        RedisUtil.set("vbpWxToken", token, EXPIRETIME);
                    }
                    System.out.println(DateUtil.getCurrentFormatDate(null) +  ",获取新token:" + token);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return token;
        }
    }
    
    • 这个写法初看没问题,但是我们遇到一个BUG是当程序运行一段时间后,偶尔会出现redis中的token校验失败的问题。手工清除redis中的token后获取,可以正常运行一段时间,但是过一段时间问题又出现。思来想去,后来才发现有可能存在如下问题:
    • 举一种情况。假如token已经失效,在redis中已经不存在。此时两个线程1、2同时先后调用这个方法,线程1先调用了微信接口获取token,在线程1还没写入redis的时候,线程2也调用了微信的接口获取redis,然后会出现以下情况。


      image.png
    解决方案:
    1. 模拟单例双重检查
    • 这个解决方式模拟了单例中的双重检查,似乎没有问题。但是在分布式负载均衡的部署中不能用。因为synchronized是内存同步,分布式环境中每台机器的内存都是独立的。
    • 但是如果是在单机模式下,这个是方法是能用的。
        public static String getWeixinToken(){
            String token = (String)RedisUtil.get("vbpWxToken");
            //第一重检查
            if(StringUtils.isBlank(token)){
                //加锁
                synchronized (token){
                    //第二重检查
                    if(StringUtils.isBlank(token)){
                        try {
                            String response = HttpUtil.requestGet(String.format(TOKEN_URL, APPID, SECRET), null);
                            JSONObject jsonobject = JSONObject.parseObject(response);
                            //如果取得token,那么就返回
                            if(jsonobject.getString("access_token") != null){
                                token = jsonobject.getString("access_token");
                                RedisUtil.set("vbpWxToken", token, EXPIRETIME);
                            }
                            System.out.println(DateUtil.getCurrentFormatDate(null) +  ",获取新token:" + token);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
    
            return token;
        }
    
    2. 独立定时JOB刷新token
    • 我们最后采用的是一个独立的定时任务去刷新token,这种方法最简单,也最高效了。
      定时任务代码如下:
    @EnableScheduling
    @Component
    public class WeChatSchedule {
    
        private static String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
    
        /**
         * 刷新微信token
         * 第一次延迟1秒执行,当执行完后6500秒再执行
         */
        @Scheduled(initialDelay = 1000, fixedDelay = 6500*1000 )
        public void setWeixinToken(){
                try {
                    String response = HttpUtil.requestGet(String.format(TOKEN_URL, APPID, SECRET), null);
                    JSONObject jsonobject = JSONObject.parseObject(response);
                    //如果取得token,那么就写入redis
                    if (jsonobject.getString("access_token") != null) {
                        String token = jsonobject.getString("access_token");
                        RedisUtil.set("vbpWxToken", token, EXPIRETIME);
                        System.out.println(DateUtil.getCurrentFormatDate("yyyy-MM-dd HH:mm:ss") + "=====获取token成功");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
        }
    }
    
    • 获取token的方法变为单纯的读取redis
        public static String getWeixinToken(){
            String token = (String)RedisUtil.get("vbpWxToken");
            return token;
        }
    
    • 注意这个定时任务是写在一个单独的job工程中,单机模式部署。

    相关文章

      网友评论

          本文标题:优化并发获取微信token

          本文链接:https://www.haomeiwen.com/subject/jxgocqtx.html