python 多并发竞争微信token刷新问题的解决方案
来源:互联网 发布:美即黑面膜怎么样 知乎 编辑:程序博客网 时间:2024/06/11 02:23
看日志:
正常时候的日志: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-A2017-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 : DEBUG2017-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 : DEBUG2017-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-gQlYr8A2017-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 Pythonroot 8888 8882 0 10:23 ? 00:00:00 uwsgi
可是我的uwsgi配置如下:
[uwsgi]processes = 1vhost = falseplugins = pythonsocket = 127.0.0.1:xxxxmaster = trueenable-threads = trueworkers = 1wsgi-file = /data/xxxx/wxtoken/wxtoken/wsgi.pychdir = /data/xxxx/wxtokenhome=/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-8import loggingimport cPickle as pickleimport redisredis_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_datadef 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_tokendef 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: 印象笔记负责过来的内容排版怎么这么难看呀。
以上
阅读全文
0 0
- python 多并发竞争微信token刷新问题的解决方案
- 微信公众平台token认证问题及解决方案
- 定时刷新获取八爪鱼token(获取微信token也是一样的道理)
- 微信TOKEN总是验证失败的解决方案。
- java-并发-解决锁竞争的问题
- Redis的并发竞争问题如何解决
- 微信公众平台Token验证失败问题的解决
- 微信对接token验证失败的问题
- 关于微信公众号服务器设置token的问题
- 关于微信公众号服务器设置token的问题
- retrofit 刷新token并发处理
- 并发问题的解决方案
- 微信公众平台 接口设置出现“你的服务器没有正确响应Token验证,请阅读消息接口使用指南”解决方案 Python
- 微信Token验证失败原因及解决方案
- {html5} 微信刷新问题
- 自己的服务器通过微信公众号Token验证测试的代码(Python版)
- 微信平台的token安全验证
- 微信Token验证代码的实现
- Product of Array Except Self
- [Java--面试]--阿里巴巴面试2016
- 二分查找,你真的懂吗
- 欢迎使用CSDN-markdown编辑器
- 图像识别
- python 多并发竞争微信token刷新问题的解决方案
- JAVA 时间日期
- pygame编写飞机大战(8)-碰撞检测
- sizeof 函数
- 编写多线程并发的测试类
- nginx 499 status
- 科比-一个烙印在篮球上的传奇
- [SDOI2009]学校食堂Dining 洛谷p2157
- 软件测试-接口测试(怎样设计接口测试用例)