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实现的tab文件操作类分享
Nov 20 Python
Python进阶之尾递归的用法实例
Jan 31 Python
python负载均衡的简单实现方法
Feb 04 Python
将Dataframe数据转化为ndarry数据的方法
Jun 28 Python
解决Python selenium get页面很慢时的问题
Jan 30 Python
python3应用windows api对后台程序窗口及桌面截图并保存的方法
Aug 27 Python
python 实现dict转json并保存文件
Dec 05 Python
opencv-python的RGB与BGR互转方式
Jun 02 Python
Python过滤掉numpy.array中非nan数据实例
Jun 08 Python
Python map及filter函数使用方法解析
Aug 06 Python
python 中关于pycharm选择运行环境的问题
Oct 31 Python
人工智能深度学习OpenAI baselines的使用方法
May 20 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 代码优化的42条建议 推荐
2009/09/25 PHP
PHP+MySQL 手工注入语句大全 推荐
2009/10/30 PHP
zend framework多模块多布局配置
2011/02/26 PHP
PHP+Ajax检测用户名或邮件注册时是否已经存在实例教程
2014/08/23 PHP
详解Yii2 定制表单输入字段的标签和样式
2017/01/04 PHP
PHP实现八皇后算法
2019/05/06 PHP
自动更新作用
2006/10/08 Javascript
JavaScript 异步调用框架 (Part 1 - 问题 &amp; 场景)
2009/08/03 Javascript
javascript XMLHttpRequest对象全面剖析
2010/04/24 Javascript
JavaScript 不支持 indexof 该如何解决
2016/03/30 Javascript
详解vue2路由vue-router配置(懒加载)
2017/04/08 Javascript
基于JavaScript实现的插入排序算法分析
2017/04/14 Javascript
javascript 数据存储的常用函数总结
2017/06/01 Javascript
AngularJS实现的select二级联动下拉菜单功能示例
2017/10/25 Javascript
Node+OCR实现图像文字识别功能
2018/11/26 Javascript
jQuery的ztree仿windows文件新建和拖拽功能的实现代码
2018/12/05 jQuery
Python中转换角度为弧度的radians()方法
2015/05/18 Python
python中字典(Dictionary)用法实例详解
2015/05/30 Python
Python文件读取的3种方法及路径转义
2015/06/21 Python
python 在指定范围内随机生成不重复的n个数实例
2019/01/28 Python
Python----数据预处理代码实例
2019/03/20 Python
python里dict变成list实例方法
2019/06/26 Python
python 实现的发送邮件模板【普通邮件、带附件、带图片邮件】
2019/07/06 Python
时尚的CSS3进度条效果
2012/02/22 HTML / CSS
香港交友网站:be2香港
2018/07/22 全球购物
c语言常见笔试题总结
2016/09/05 面试题
销售实习自我鉴定
2013/12/07 职场文书
给实习单位的感谢信
2014/02/01 职场文书
酒店值班经理的工作职责范本
2014/02/18 职场文书
预防传染病方案
2014/06/14 职场文书
装修活动策划方案
2014/08/27 职场文书
谢师宴邀请函
2015/02/02 职场文书
如何撰写出一份完美的商业计划书?
2019/07/12 职场文书
2019关于实习生工作安排及待遇的管理方案!
2019/07/16 职场文书
优胜劣汰,强者为王——读《鲁滨逊漂流记》有感
2019/08/15 职场文书
修改MySQL的数据库引擎为INNODB的方法
2021/05/26 MySQL