Python的Flask框架应用调用Redis队列数据的方法


Posted in Python onJune 06, 2016

任务异步化
打开浏览器,输入地址,按下回车,打开了页面。于是一个HTTP请求(request)就由客户端发送到服务器,服务器处理请求,返回响应(response)内容。

我们每天都在浏览网页,发送大大小小的请求给服务器。有时候,服务器接到了请求,会发现他也需要给另外的服务器发送请求,或者服务器也需要做另外一些事情,于是最初们发送的请求就被阻塞了,也就是要等待服务器完成其他的事情。

更多的时候,服务器做的额外事情,并不需要客户端等待,这时候就可以把这些额外的事情异步去做。从事异步任务的工具有很多。主要原理还是处理通知消息,针对通知消息通常采取是队列结构。生产和消费消息进行通信和业务实现。

生产消费与队列
上述异步任务的实现,可以抽象为生产者消费模型。如同一个餐馆,厨师在做饭,吃货在吃饭。如果厨师做了很多,暂时卖不完,厨师就会休息;如果客户很多,厨师马不停蹄的忙碌,客户则需要慢慢等待。实现生产者和消费者的方式用很多,下面使用Python标准库Queue写个小例子:

import random
import time
from Queue import Queue
from threading import Thread

queue = Queue(10)

class Producer(Thread):
  def run(self):
    while True:
      elem = random.randrange(9)
      queue.put(elem)
      print "厨师 {} 做了 {} 饭 --- 还剩 {} 饭没卖完".format(self.name, elem, queue.qsize())
      time.sleep(random.random())

class Consumer(Thread):
  def run(self):
    while True:
      elem = queue.get()
      print "吃货{} 吃了 {} 饭 --- 还有 {} 饭可以吃".format(self.name, elem, queue.qsize())
      time.sleep(random.random())

def main():
  for i in range(3):
    p = Producer()
    p.start()
  for i in range(2):
    c = Consumer()
    c.start()

if __name__ == '__main__':
  main()

大概输出如下:

厨师 Thread-1 做了 1 饭 --- 还剩 1 饭没卖完
厨师 Thread-2 做了 8 饭 --- 还剩 2 饭没卖完
厨师 Thread-3 做了 3 饭 --- 还剩 3 饭没卖完
吃货Thread-4 吃了 1 饭 --- 还有 2 饭可以吃
吃货Thread-5 吃了 8 饭 --- 还有 1 饭可以吃
吃货Thread-4 吃了 3 饭 --- 还有 0 饭可以吃
厨师 Thread-1 做了 0 饭 --- 还剩 1 饭没卖完
厨师 Thread-2 做了 0 饭 --- 还剩 2 饭没卖完
厨师 Thread-1 做了 1 饭 --- 还剩 3 饭没卖完
厨师 Thread-1 做了 1 饭 --- 还剩 4 饭没卖完
吃货Thread-4 吃了 0 饭 --- 还有 3 饭可以吃
厨师 Thread-3 做了 3 饭 --- 还剩 4 饭没卖完
吃货Thread-5 吃了 0 饭 --- 还有 3 饭可以吃
吃货Thread-5 吃了 1 饭 --- 还有 2 饭可以吃
厨师 Thread-2 做了 8 饭 --- 还剩 3 饭没卖完
厨师 Thread-2 做了 8 饭 --- 还剩 4 饭没卖完

Redis 队列
Python内置了一个好用的队列结构。我们也可以是用redis实现类似的操作。并做一个简单的异步任务。

Redis提供了两种方式来作消息队列。一个是使用生产者消费模式模式,另外一个方法就是发布订阅者模式。前者会让一个或者多个客户端监听消息队列,一旦消息到达,消费者马上消费,谁先抢到算谁的,如果队列里没有消息,则消费者继续监听。后者也是一个或多个客户端订阅消息频道,只要发布者发布消息,所有订阅者都能收到消息,订阅者都是ping的。

生产消费模式
主要使用了redis提供的blpop获取队列数据,如果队列没有数据则阻塞等待,也就是监听。

import redis

class Task(object):
  def __init__(self):
    self.rcon = redis.StrictRedis(host='localhost', db=5)
    self.queue = 'task:prodcons:queue'

  def listen_task(self):
    while True:
      task = self.rcon.blpop(self.queue, 0)[1]
      print "Task get", task

if __name__ == '__main__':
  print 'listen task queue'
  Task().listen_task()

发布订阅模式
使用redis的pubsub功能,订阅者订阅频道,发布者发布消息到频道了,频道就是一个消息队列。

import redis


class Task(object):

  def __init__(self):
    self.rcon = redis.StrictRedis(host='localhost', db=5)
    self.ps = self.rcon.pubsub()
    self.ps.subscribe('task:pubsub:channel')

  def listen_task(self):
    for i in self.ps.listen():
      if i['type'] == 'message':
        print "Task get", i['data']

if __name__ == '__main__':
  print 'listen task channel'
  Task().listen_task()

Flask 入口
我们分别实现了两种异步任务的后端服务,直接启动他们,就能监听redis队列或频道的消息了。简单的测试如下:

import redis
import random
import logging
from flask import Flask, redirect

app = Flask(__name__)

rcon = redis.StrictRedis(host='localhost', db=5)
prodcons_queue = 'task:prodcons:queue'
pubsub_channel = 'task:pubsub:channel'

@app.route('/')
def index():

  html = """
<br>
<center><h3>Redis Message Queue</h3>
<br>
<a href="/prodcons">生产消费者模式</a>
<br>
<br>
<a href="/pubsub">发布订阅者模式</a>
</center>
"""
  return html


@app.route('/prodcons')
def prodcons():
  elem = random.randrange(10)
  rcon.lpush(prodcons_queue, elem)
  logging.info("lpush {} -- {}".format(prodcons_queue, elem))
  return redirect('/')

@app.route('/pubsub')
def pubsub():
  ps = rcon.pubsub()
  ps.subscribe(pubsub_channel)
  elem = random.randrange(10)
  rcon.publish(pubsub_channel, elem)
  return redirect('/')

if __name__ == '__main__':
  app.run(debug=True)

启动脚本,使用

siege -c10 -r 5 http://127.0.0.1:5000/prodcons
siege -c10 -r 5 http://127.0.0.1:5000/pubsub

可以分别在监听的脚本输入中看到异步消息。在异步的任务中,可以执行一些耗时间的操作,当然目前这些做法并不知道异步的执行结果,如果需要知道异步的执行结果,可以考虑设计协程任务或者使用一些工具如RQ或者celery等。

Python 相关文章推荐
python计算一个序列的平均值的方法
Jul 11 Python
[原创]教女朋友学Python3(二)简单的输入输出及内置函数查看
Nov 30 Python
Python排序搜索基本算法之归并排序实例分析
Dec 08 Python
Python操作MongoDB数据库的方法示例
Jan 04 Python
python模块导入的细节详解
Dec 10 Python
浅谈Python 递归算法指归
Aug 22 Python
tensorflow2.0与tensorflow1.0的性能区别介绍
Feb 07 Python
python中线程和进程有何区别
Jun 17 Python
keras实现theano和tensorflow训练的模型相互转换
Jun 19 Python
如何使用python写截屏小工具
Sep 29 Python
使用Python Tkinter实现剪刀石头布小游戏功能
Oct 23 Python
Python借助with语句实现代码段只执行有限次
Mar 23 Python
Python第三方库的安装方法总结
Jun 06 #Python
在Python程序和Flask框架中使用SQLAlchemy的教程
Jun 06 #Python
Python的socket模块源码中的一些实现要点分析
Jun 06 #Python
深入浅析python定时杀进程
Jun 06 #Python
深入理解python函数递归和生成器
Jun 06 #Python
python下调用pytesseract识别某网站验证码的实现方法
Jun 06 #Python
浅析AST抽象语法树及Python代码实现
Jun 06 #Python
You might like
用PHP编程开发“虚拟域名”系统
2006/10/09 PHP
上传多个文件的PHP脚本
2006/11/26 PHP
php面向对象全攻略 (十四) php5接口技术
2009/09/30 PHP
php ckeditor上传图片文件名乱码解决方法
2013/11/15 PHP
Nginx环境下PHP flush失效的解决方法
2016/10/19 PHP
利用PHPStorm如何开发Laravel应用详解
2017/08/30 PHP
PHP cURL获取微信公众号access_token的实例
2018/04/28 PHP
php curl优化下载微信头像的方法总结
2018/09/07 PHP
jQuery的观察者模式详解
2014/12/22 Javascript
jQuery+css实现的换页标签栏效果
2016/01/27 Javascript
总结JavaScript设计模式编程中的享元模式使用
2016/05/21 Javascript
Bootstrap table 定制提示语的加载过程
2017/02/20 Javascript
基于AngularJS实现表单验证功能
2017/07/28 Javascript
如何在 JavaScript 中更好地利用数组
2018/09/27 Javascript
vue实现购物车结算功能
2020/06/18 Javascript
JavaScript this指向相关原理及实例解析
2020/07/10 Javascript
[03:02]安得倚天剑,跨海斩长鲸——中国军团出征DOTA2国际邀请赛
2018/08/14 DOTA
Python序列之list和tuple常用方法以及注意事项
2015/01/09 Python
python操作redis的方法
2015/07/07 Python
Python基于pygame实现的弹力球效果(附源码)
2015/11/11 Python
Python实现发送QQ邮件的封装
2017/07/14 Python
PyQt5通信机制 信号与槽详解
2019/08/07 Python
python 如何实现遗传算法
2020/09/22 Python
Python+OpenCV检测灯光亮点的实现方法
2020/11/02 Python
HTML5调用手机发短信和打电话功能
2020/04/29 HTML / CSS
美国滑板店:Tactics
2020/11/08 全球购物
销售简历自我评价
2014/01/24 职场文书
工程承包协议书
2014/04/22 职场文书
给学校建议书范文
2014/05/13 职场文书
综治维稳工作承诺书
2014/08/30 职场文书
MySQL 角色(role)功能介绍
2021/04/24 MySQL
解析Java异步之call future
2021/06/14 Java/Android
Anaconda配置各版本Pytorch的实现
2021/08/07 Python
如何通过一篇文章了解Python中的生成器
2022/04/02 Python
Redis特殊数据类型bitmap位图
2022/06/01 Redis
python如何将mat文件转为png
2022/07/15 Python