python 多并发竞争微信token刷新问题的解决方案

2023-11-13

看日志:
正常时候的日志:
2017-09-24 07:35:30,723 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 07:35:31,342 views.py[line:24] [INFO]  【获取token】
2017-09-24 07:35:31,343 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 07:35:35,156 views.py[line:24] [INFO]  【获取token】
2017-09-24 07:35:35,157 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 07:35:40,285 views.py[line:24] [INFO]  【获取token】
2017-09-24 07:35:40,286 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 07:35:52,522 views.py[line:24] [INFO]  【获取token】
2017-09-24 07:35:52,523 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 07:35:52,523 views.py[line:51] [INFO]  【重置Token】 getToken中竟然拿到了过期的token,Ok...!
2017-09-24 07:35:52,524 tools.py[line:45] [INFO]  【生成新的token】
2017-09-24 07:35:52,934 tools.py[line:66] [INFO]  token=uX-Ss9sgpfcK5fAmxOomQevy4FZTQXB_FX6G0JjoNWGjws5ZJtK-QVXLcgXLooIcN4zutB8KehLQPV-0ZR3BhiD31jOy77M_d306XlIxqlbMrBuYYyrQg4xFHvNJW8MPSCAhABAWGE, expire_at=expire: 2017-09-24 09:35:52, ticket=kgt8ON7yVITDhtdwci0qeYKnTxnRCJqsQusUs77nYwUaOBEr--EY31LjMYstkPp15zQ0KTyT84KANjsx2UEu-A
2017-09-24 07:35:52,935 views.py[line:61] [INFO]  写到redis中...
2017-09-24 07:35:52,935 views.py[line:65] [INFO]  写到文件中...
2017-09-24 07:36:11,051 views.py[line:24] [INFO]  【获取token】
2017-09-24 07:36:11,052 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 07:36:27,335 views.py[line:24] [INFO]  【获取token】
2017-09-24 07:36:27,335 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 07:36:28,813 views.py[line:24] [INFO]  【获取token】
2017-09-24 07:36:28,814 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 07:36:32,783 views.py[line:24] [INFO]  【获取token】

错误时候的日志:
2017-09-24 09:35:48,320 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 09:35:48,992 views.py[line:24] [INFO]  【获取token】
2017-09-24 09:35:48,993 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 09:35:51,360 views.py[line:24] [INFO]  【获取token】
2017-09-24 09:35:51,361 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 09:35:51,814 views.py[line:24] [INFO]  【获取token】
2017-09-24 09:35:51,814 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 09:35:53,318 views.py[line:24] [INFO]  【获取token】
2017-09-24 09:35:53,319 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 09:35:53,319 views.py[line:51] [INFO]  【重置Token】 getToken中竟然拿到了过期的token,Ok...!
2017-09-24 09:35:53,319 tools.py[line:45] [INFO]  【生成新的token】
2017-09-24 09:52:03,673 tools.py[line:32] [INFO]  Current log level is : DEBUG
2017-09-24 09:52:03,796 MyCache.py[line:17] [INFO]  ===>redis畅通,切换到缓存模式!cache_flag = TRUE.
2017-09-24 09:52:03,797 wsgi.py[line:22] [INFO]  【初始化一个token】
2017-09-24 09:52:03,797 tools.py[line:45] [INFO]  【生成新的token】
2017-09-24 09:52:05,620 tools.py[line:32] [INFO]  Current log level is : DEBUG
2017-09-24 09:52:05,645 MyCache.py[line:17] [INFO]  ===>redis畅通,切换到缓存模式!cache_flag = TRUE.
2017-09-24 09:52:05,646 wsgi.py[line:22] [INFO]  【初始化一个token】
2017-09-24 09:52:05,646 tools.py[line:45] [INFO]  【生成新的token】
2017-09-24 09:52:08,796 tools.py[line:66] [INFO]  token=CSgJq-aPPLUYr_RoFbljb_Dia42HtEgQj77g55TWW1sVAIuOEvn5jjMOPwohmaTBQ73SDjBx2L1L0AifX0QNH3Rxvsb7YRlomapkypc9J7tVBnqo4w_izu-JWXN0Fs5XWZChAFAADG, expire_at=expire: 2017-09-24 11:52:04, ticket=kgt8ON7yVITDhtdwci0qeYKnTxnRCJqsQusUs77nYwVrkjNNRqAVnEJhMznAJIRjvn93qY1duo-sEO-gQlYr8A
2017-09-24 09:52:08,796 wsgi.py[line:25] [INFO]  写到文件中...
2017-09-24 09:52:08,797 wsgi.py[line:29] [INFO]  写到redis中...
2017-09-24 09:52:09,458 views.py[line:24] [INFO]  【获取token】
2017-09-24 09:52:09,460 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 09:52:09,462 views.py[line:24] [INFO]  【获取token】
2017-09-24 09:52:09,463 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 09:52:11,236 views.py[line:24] [INFO]  【获取token】
2017-09-24 09:52:11,237 views.py[line:34] [INFO]  GetToken from Redis.
2017-09-24 09:52:11,280 views.py[line:24] [INFO]  【获取token】

以下1,2,3,4... 是我的思考过程。。
1、
明显感觉到,在切换token的那一瞬间,正常情况下,是一个用户来请求,然后就完美度过这个切换token情况。
但是如果那一瞬间是3个用户来请求,则有问题啦。。
2、
为什么会冒出这句话:Current log level is : DEBUG   ===>redis畅通,切换到缓存模式!cache_flag = TRUE.
这是项目启动的时候才会说的话。而且还是重启两次?
后来发现是因为我用supervisor手动重启了wxtoken这个项目才打印这个日志(尴尬),然而为啥子是两次呢,是因为uwsgi就开启了两个进程。
root      8882 13690  0 10:23 ?        00:00:00 uwsgi /data/xxxx/wxtoken/uwsgi.ini --plugin Python
root      8888  8882  0 10:23 ?        00:00:00 uwsgi
可是我的uwsgi配置如下:
[uwsgi]
processes = 1
vhost = false
plugins = python
socket = 127.0.0.1:xxxx
master = true
enable-threads = true
workers = 1
wsgi-file = /data/xxxx/wxtoken/wxtoken/wsgi.py
chdir = /data/xxxx/wxtoken
home=/data/python_venv/wxtoken_venv/
listen=1024
workers=1 并且 processes =1, 就是单进程呀,为啥子有2个呢?
哦哦哦,原来是因为master=true,会有一个master进程+单个子进程=2个进程。爸爸管理n个孩子,如果kill爸爸就是杀了所有孩子。
先让master=false。因为我就是要单个进程即可。
附上uwsgi.ini参数说明(当然有些和我的配置出入,比如home就是程序运行的python环境目录):
socket:uwsgi监听的socket,可以为socket文件或ip地址+端口号(如0.0.0.0:9000),取决于nginx中upstream的设置
processes:同时启动uwsgi进程的个数,这个进程与nginx中的workers是不一样的,uwsgi中的每个进程每次只能处理一个请求(进程越多可以同时处理的请求越多),nginx采用的异步非阻塞的方式来处理请求的,每个进程可以接受处理多个请求。
chdir:在app加载前切换到当前目录
pythonpath:给PYTHONPATH 增加一个目录(或者一个egg),最多可以使用该选项64次。
module:加载指定的python WSGI模块(模块路径必须在PYTHONPATH里)
master:相当于master=true,启动一个master进程来管理其他进程,以上述配置为例,其中的4个uwsgi进程都是这个master进程的子进程,如果kill这个master进程,相当于重启所有的uwsgi进程
pidfile:在失去权限前,将master的pid写到当前文件中
daemonize:使进程在后台运行,并将日志打到指定的日志文件或者udp
3、
回到我的错误日志:
2017-09-24 09:35:53,319 tools.py[line:45] [INFO]  【生成新的token】
2017-09-24 09:52:03,673 tools.py[line:32] [INFO]  Current log level is : DEBUG
两句话差了快20分钟,在生成新的token这里就一直挂着了呢。我大概知道是网络请求有问题,要不把urllib改成request吧。
改为python更加推荐的requests库,加入超时参数,加入https不验证参数(有些时候验证https会报SSL错误,麻烦得紧)
# wp = urllib.urlopen(url)
# ret = json.loads(wp.read())
r = requests.get(url, timeout=3, verify=False)
ret = r.json()

以及

# jsapiTicketRequestData = {'type': 'jsapi', 'access_token': access_token}
# jsapiTicketRequestDataUrlencode = urllib.urlencode(jsapiTicketRequestData)
# jsapiTicketRequest = "https://api.weixin.qq.com/cgi-bin/ticket/getticket"
# jsapiTicketRequestGet = urllib2.Request(url=jsapiTicketRequest, data=jsapiTicketRequestDataUrlencode)
# jsapiTicketRequestGetData = urllib2.urlopen(jsapiTicketRequestGet)
# jsapiTicketRequestGetResult = jsapiTicketRequestGetData.read()
# ticket = json.loads(jsapiTicketRequestGetResult)['ticket']

payload = {'type': 'jsapi', 'access_token': access_token}
url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket"
r = requests.get(url, params=payload, timeout=3, verify=False)
content = r.json()
ticket = content['ticket']

4、
修改完毕之后,手动让redis里面的token失效,因为是pickle dumps过的(如果是json dumps的话,能直接修改呀),我还得用程序去修改,修改。测试通过。
# coding=utf-8
import logging
import cPickle as pickle

import redis
redis_client = redis.Redis(host='localhost', port=6379, db=1, password='xxx')

value = {
    'access_token': 'e4ZUdlQQGRknsN7UfaBruFBKhj8Kj5_6kq7MhlkHscz5DiQSlT0RzQdMs-woooa-FW7JXlAzjUVPen4xTJrgWz6AohKY6KhO3aaPFVVnVz2sW7ATrUUgQtyj-GPrO6iWNDOaAJATJU',
    'access_token_expires_at':'1501228165',
    'ticket': 'kgt8ON7yVITDhtdwci0qeYKnTxnRCJqsQusUs77nYwUL-QgOjkakVZKqMbEctIhcpt',
}
name = 'wx_access_token'
value = pickle.dumps(value)
redis_client.set(name, value)

5、
ok,最后一步,还没解决如下问题,如果失效的那一瞬间,同时有10个请求过来。程序会发生什么事呢?
排个号: 1,2,3,4,5,6,7,8,9,10
因为这10个兄弟失效了嘛,依次处理,处理3的那一瞬间,token恢复正常,也写进了redis。
可是4-10这7个兄弟还不知道呀,它们依旧会走完流程,也就是不断地刷新token,不断得使之前的token失效,并且重新写入redis,写7次。
那么如果有11-30很多其他人这个时候来访问,其实他们只会拿到刚刚被这7个兄弟弄失效的token,但是不会重新去刷新token,因为我判断的依据是超时时间是否超过2小时,哈哈哈。
所以如果不加锁的话,影响也许就是十几二十的用户吧。
6、
ab测试下,上面的想法,结果大体一致,发现影响用户可能只有2,3人。
ab.exe -n 1200 -c 20 http://xxx.com/getToken/

结果:
[root@iZ9458z0ss9Z wxtoken]# cat wxtoken.log | grep 生成新     
2017-09-24 11:17:29,917 10818-140683547019072-MainThread tools.py[line:46] [INFO]  【生成新的token】
2017-09-24 11:17:41,358 10818-140683547019072-uWSGIWorker1Core0 tools.py[line:46] [INFO]  【生成新的token】
[root@iZ9458z0ss9Z wxtoken]#

7、
也有人问我为啥子不加定时器,其实之前是加了的,apscheduler,但是偶尔会报一些奇奇怪怪的错误,要么就是项目没有报错了但是定时器也不工作,很让人烦躁,索性去掉了,毕竟又不是非要用定时器,用定时器也不是非要用这货。
其实我别的项目用到了更加靠谱的定时器:celery,但是我不想在这个简简单单的地方引入这么重型的哥们。
(当然如果哪一天项目重要程度升级,并发很高balabala,我就换celery咯。)
至于现在,我想用python自己去解决这个刷新token的事。
8、
加个锁吧。python的threading,condition。
大致代码如下:
def get_token_from_srouce():
    """
    从数据源获取token
    要么是缓存,要么是文件。。
    :return:
    """
    response_data = {}
    try:
        item = mycache.get('wx_access_token')
    except Exception, ex:
        logging.error(ex)
        item = None

    # 从redis拿
    if item:
        logging.info("GetToken from Redis.")
        dic = item
        response_data['access_token'] = dic['access_token']
        response_data['access_token_expires_at'] = dic['access_token_expires_at']
        response_data['ticket'] = dic['ticket']
    # 从文件中拿
    else:
        logging.info("GetToken from %s." % settings.accessTokenFile)
        with open(settings.accessTokenFile, 'r') as f:
            response_data['access_token'] = f.readline().strip('\n')
            response_data['access_token_expires_at'] = f.readline().strip('\n')
            response_data['ticket'] = f.readline().strip('\n')
    return response_data

def set_token_to_soruce(dic):
    """
    把数据写入数据源
    :param dic:
    :return:
    """
    # 写到redis中
    logging.info("写到redis中...")
    mycache.set('wx_access_token', dic)

    # 写到文件中
    logging.info("写到文件中...")
    with open(settings.accessTokenFile, 'w') as tokenFile:
        tokenFile.write(dic['access_token'] + '\n')
        tokenFile.write(str(dic['access_token_expires_at']) + '\n')
        tokenFile.write(dic['ticket'] + '\n')

# 返回access_token
def get_token(request):
    logging.info(u"【获取token】")
    my_token_dic = get_token_from_srouce()

    # 如果获取的时间戳显示token过期,则reset一下
    expires_at = int(my_token_dic['access_token_expires_at'])
    now = int(time.time())
    if now > expires_at:
        logging.info(u"【重置Token】 getToken中竟然拿到了过期的token,Ok...!")

        settings.condition.acquire()

        # 双重判断
        my_token_dic = get_token_from_srouce()
        expires_at = int(my_token_dic['access_token_expires_at'])
        now = int(time.time())
        if now > expires_at:
            my_token_dic = get_new_token()
            set_token_to_soruce(my_token_dic)

        settings.condition.notify_all()
        settings.condition.release()

    # 顺便更新返回的信息
    response_data = {}
    response_data['access_token'] = my_token_dic['access_token']
    response_data['access_token_expires_at'] = my_token_dic['access_token_expires_at']
    response_data['ticket'] = my_token_dic['ticket']
    response_data['code'] = 1
    response_data['msg'] = 'Ok!'

    return HttpResponse(json.dumps(response_data), content_type="application/json")


总结: 
1、加个锁机制,保证在失效的那一瞬间,那并发的几十个请求都等着,而且必须是双重检查锁机呦。。
2、完美解决方案是定时器和getToken服务分离,定时器每个小时去刷一次token,getToken服务不管别的,来了就返回redis或者文件里面的value即可。
但我就不!
ps: 印象笔记负责过来的内容排版怎么这么难看呀。
以上
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python 多并发竞争微信token刷新问题的解决方案 的相关文章

随机推荐

  • 【论文翻译-3】Attention U-Net: Learning Where to Look for the Pancreas

    Attention U Net Learning Where to Look for the Pancreas 阅读日期 2020年11月25日 Abstract 我们提出新型注意力门控 attention gate AG 模型用于医学成像
  • 恒玄BES2300XX系列常用接口(1)

    app ibrt ui pairing mode test 先回连手机 回连不上进才配对 app tws ibrt set access mode 设置访问模式 app ibrt if pairing mode refresh 断开手机 进
  • Python学习09:继承与多重继承

    本文学习Python的笔记 仅供参考 继承 熟悉C 的都应该了解继承了 简单的说一个类继承另外一个类 可以获得继承类的属性和方法 继承的类称为子类 被继承的类称为父类 比如说人类算是非常大的一个类了 如果按照职业分可能会分为学生类 教师类
  • 关于COM Surrogate已经停止工作的问题的处理

    之前在使用电脑的时候 老是弹出COM Surrogate已经停止工作的提示 我实在是忍受不了这种莫名奇妙的错误 于是上网查了一些资料 现在将这些资料总结一下 修复win7的COM Surrogate已经停止工作的问题 最近一个礼拜 每次打开
  • 五、51单片机控制矩阵按键

    1 矩阵按键 1 1 矩阵按键原理分析 这里矩阵按键为4 4的矩阵按键 1 矩阵按键横向和纵向分割 2 按键两端分别接不同的IO引脚 3 按键物理作用不变 按下按键两端接通 弹起按键两端断开 1 2 矩阵按键的工作过程 JP4接P3端口 J
  • sklearn库使用问题汇总

    20200813 引言 打印分类报告 问题 1 打印分类报告 在分类过程结束之后 需要反馈分类效果 使用的函数是classification report 1 函数的全部分信息如下 sklearn metrics classificatio
  • TypeError: can only concatenate str (not “list“) to str 报错

    报错如下 这里报错的意思的你的数据是个数组 这里我附上我的源码 import requests from lxml import etree import pymysql import re headers User Agent Mozil
  • vue-cli配置less变量的两种方式

    方式一 build文件配置 步骤1 安装包 npm install sass resources loader save dev 步骤二 build 的utils js配置 找到exports cssLoaders function opt
  • 计算机视觉(二)——HSV色彩分离及目标跟踪

    HSV是根据颜色的直观特性由A R Smith在1978年创造的一种颜色空间 也称六角锥体模型 这个模型中颜色的参数分别是 色调 H 饱和度 S 明度 V HSV比传统的RGB颜色空间更能准确的感知颜色 并仍保持在计算上的简单 HSV色彩分
  • win10连接filco蓝牙键盘

    参考站点 http tieba baidu com 操作步骤 1 确保PC端蓝牙驱动已经正常安装2 确保键盘的蓝牙已经解绑3 蓝牙图标上右键 选择 加入个人区域网 4 点击添加设备5 找到蓝牙键盘后 正常执行蓝牙配对连接动作
  • 测试理论学习(分类、流程、方法)

    一 软件测试 将实际结果与预期结果做对比 软件测试是使用人工或自动的手段来运行或测定某个软件系统的过程 其目的在于检验它是否满足规定的需求或弄清预期结果与实际结果之间的差别 二 软件测试发展历程 三阶段 证明软件是正确的 证明软件是错误的
  • 【C++入门到精通】 C++入门—命名空间

    前言 前面我们学习了C语言 并且知道了C语言的基础语法与用途 接下来一段时间我们会再来学习一下基于C语言并且根据C语言改造的一门新的语言 C 硕硕相信只要各位大佬们跟着我的博客看下去 肯定能有不少的收获 二话不说咱们要开车了 坐稳扶好呦 C
  • Nginx常见模块

    nginx常见模块 4 Nginx常见模块 4 1 四层访问控制 访问控制基于模块ngx http access module实现 可以通过匹配客户端源IP地址进行限制 环境配置 注意 如果能在防火墙设备控制 最好就不要在nginx上配置
  • 深度学习项目,使用python进行表情识别,pytorch应用

    文章目录 前言 一 深度学习是什么 二 数据的预处理 1 数据分类 2 代码 三 构建模型与训练 1 模型与代码 2 使用方法 四 实时识别 总结 前言 这个项目是以前课设用到的功能之一 参考了其他人的人的博客 自己做了一下整理 需要用到的
  • Intra ERC Scheme

    Iterative Method First initial the corrupted MB with neighboring MB information then use iterative techniques to conceal
  • 机器学习中梯度下降法和牛顿法的比较

    在机器学习的优化问题中 梯度下降法和牛顿法是常用的两种凸函数求极值的方法 他们都是为了求得目标函数的近似解 在逻辑斯蒂回归模型的参数求解中 一般用改良的梯度下降法 也可以用牛顿法 由于两种方法有些相似 我特地拿来简单地对比一下 下面的内容需
  • Linux日志分析工具之AWStats

    Linux日志分析工具之AWStats 博客主页 微笑的段嘉许博客主页 欢迎关注 点赞 收藏 留言 本文由微笑的段嘉许原创 CSDN首发时间 2023年2月3日 坚持和努力一定能换来诗与远方 作者水平很有限 如果发现错误 一定要及时告知作者
  • python单元测试之pytest

    前提 需要安装pytest和pytest html 安装pytest 在控制台输入 命令 pip install pytest 进行下载安装 安装pytest html 在控制台输入 命令 pip install pytest html 进
  • syntax error near unexpected token `newline'脚本无法执行

    问题描述 执行run sh脚本是报错 报错信息如下 data app information provider No such file or directory command not found run sh line 4 syntax
  • python 多并发竞争微信token刷新问题的解决方案

    看日志 正常时候的日志 2017 09 24 07 35 30 723 views py line 34 INFO GetToken from Redis 2017 09 24 07 35 31 342 views py line 24 I