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中method的参数传递过程
Apr 02 Python
以Flask为例讲解Python的框架的使用方法
Apr 29 Python
深度定制Python的Flask框架开发环境的一些技巧总结
Jul 12 Python
安装Python和pygame及相应的环境变量配置(图文教程)
Jun 04 Python
Dlib+OpenCV深度学习人脸识别的方法示例
May 14 Python
linux下python中文乱码解决方案详解
Aug 28 Python
pytorch 求网络模型参数实例
Dec 30 Python
python中resample函数实现重采样和降采样代码
Feb 25 Python
Python 字符串池化的前提
Jul 03 Python
详解python爬取弹幕与数据分析
Nov 14 Python
学会用Python实现滑雪小游戏,再也不用去北海道啦
May 20 Python
一篇文章弄懂Python中的内建函数
Aug 07 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
Zerg剧情介绍
2020/03/14 星际争霸
PHP 采集程序中常用的函数
2009/12/09 PHP
防止用户利用PHP代码DOS造成用光网络带宽
2011/03/01 PHP
PHPExcel导出2003和2007的excel文档功能示例
2017/01/04 PHP
php制作圆形用户头像的实例_自定义封装类源代码
2017/09/18 PHP
Laravel统计一段时间间隔的数据方法
2019/10/09 PHP
Prototype Object对象 学习
2009/07/12 Javascript
window.navigate 与 window.location.href 的使用区别介绍
2013/09/21 Javascript
利用jquery操作Radio方法小结
2014/10/20 Javascript
JavaScript字符串常用的方法
2016/03/10 Javascript
js实现div模拟模态对话框展现URL内容
2016/05/27 Javascript
使用vs code开发Nodejs程序的使用方法
2017/09/21 NodeJs
JS设计模式之状态模式概念与用法分析
2018/02/05 Javascript
JS定义函数的几种常用方法小结
2019/05/23 Javascript
Vue实现数据表格合并列rowspan效果
2020/11/30 Javascript
js 实现watch监听数据变化的代码
2019/10/13 Javascript
Vue打包后访问静态资源路径问题
2019/11/08 Javascript
JavaScript简单编程实例学习
2020/02/14 Javascript
[04:22]DSPL第二期精彩集锦:残血反杀!
2014/12/10 DOTA
Python 模拟登陆的两种实现方法
2017/08/10 Python
Python3调用微信企业号API发送文本消息代码示例
2017/11/10 Python
python docx 中文字体设置的操作方法
2018/05/08 Python
对python tkinter窗口弹出置顶的方法详解
2019/06/14 Python
python cumsum函数的具体使用
2019/07/29 Python
Python用类实现扑克牌发牌的示例代码
2020/06/01 Python
如何在Windows中安装多个python解释器
2020/06/16 Python
Python抓包并解析json爬虫的完整实例代码
2020/11/03 Python
CSS3 border-image详解、应用及jQuery插件
2011/08/29 HTML / CSS
一款纯css3实现简单的checkbox复选框和radio单选框
2014/11/05 HTML / CSS
初中生学习生活的自我评价
2013/11/20 职场文书
教育学专业实习生的自我鉴定
2013/11/26 职场文书
商铺租赁意向书
2014/04/01 职场文书
经营目标管理责任书
2014/07/25 职场文书
单位授权委托书范本
2014/09/26 职场文书
公务员个人年终总结
2015/02/12 职场文书
2015年学校总务工作总结
2015/07/20 职场文书