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 相关文章推荐
利用Fn.py库在Python中进行函数式编程
Apr 22 Python
Python编程入门的一些基本知识
May 13 Python
Python中MySQLdb和torndb模块对MySQL的断连问题处理
Nov 09 Python
Python连接PostgreSQL数据库的方法
Nov 28 Python
python MysqlDb模块安装及其使用详解
Feb 23 Python
python中logging包的使用总结
Feb 28 Python
Python实现的爬取百度文库功能示例
Feb 16 Python
python实现移位加密和解密
Mar 22 Python
在Python中os.fork()产生子进程的例子
Aug 08 Python
详解python中index()、find()方法
Aug 29 Python
Django框架模板用法入门教程
Nov 04 Python
python 判断文件或文件夹是否存在
Mar 18 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
Parse正式发布开源PHP SDK
2014/08/11 PHP
php常用文件操作函数汇总
2014/11/22 PHP
使用 PHPStorm 开发 Laravel
2015/03/24 PHP
jquery打开直接跳到网页最下面、最低端实现代码
2013/04/22 Javascript
用客户端js实现带省略号的分页
2013/04/27 Javascript
使用jquery animate创建平滑滚动效果(可以是到顶部、到底部或指定地方)
2014/05/27 Javascript
Bootstrap媒体对象学习使用
2017/03/07 Javascript
当vue路由变化时,改变导航栏的样式方法
2018/08/22 Javascript
vue中使用heatmapjs的示例代码(结合百度地图)
2018/09/05 Javascript
通过JavaScript下载文件到本地的方法(单文件)
2019/03/17 Javascript
使用jQuery如何写一个含验证码的登录界面
2019/05/13 jQuery
countUp.js实现数字滚动效果
2019/10/18 Javascript
Vue2.0 实现页面缓存和不缓存的方式
2019/11/12 Javascript
使用Typescript和ES模块发布Node模块的方法
2020/05/25 Javascript
解决vue scoped scss 无效的问题
2020/09/04 Javascript
小程序自定义圆形进度条
2020/11/17 Javascript
[02:06]DOTA2英雄基础教程 暗影萨满
2013/12/16 DOTA
python实现删除文件与目录的方法
2014/11/10 Python
Python中列表和元组的使用方法和区别详解
2020/12/30 Python
Python 编码Basic Auth使用方法简单实例
2017/05/25 Python
Python实现句子翻译功能
2017/11/14 Python
分析Python读取文件时的路径问题
2018/02/11 Python
使用Django启动命令行及执行脚本的方法
2018/05/29 Python
解决Django生产环境无法加载静态文件问题的解决
2019/04/23 Python
Python新手如何理解循环加载模块
2020/05/29 Python
Python暴力破解Mysql数据的示例
2020/11/09 Python
CSS3的新特性介绍
2008/10/31 HTML / CSS
HTML5 audio标签使用js进行播放控制实例
2015/04/24 HTML / CSS
理肤泉加拿大官网:La Roche-Posay加拿大
2018/07/06 全球购物
中英文自我评价语句
2013/12/20 职场文书
告诉你怎样写创业计划书
2014/01/27 职场文书
安全伴我行演讲稿
2014/09/04 职场文书
生日答谢词
2015/01/05 职场文书
2015年设计师个人工作总结
2015/04/25 职场文书
2015社区健康教育工作总结
2015/05/20 职场文书
文案策划岗位个人自我评价(范文)
2019/08/08 职场文书