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根据给定文件返回文件名和扩展名的方法
Mar 27 Python
python开发之thread实现布朗运动的方法
Nov 11 Python
python文件的md5加密方法
Apr 06 Python
python 根据pid杀死相应进程的方法
Jan 16 Python
python+django加载静态网页模板解析
Dec 12 Python
用TensorFlow实现多类支持向量机的示例代码
Apr 28 Python
在python下读取并展示raw格式的图片实例
Jan 24 Python
对python3.4 字符串转16进制的实例详解
Jun 12 Python
python实现beta分布概率密度函数的方法
Jul 08 Python
Python -m参数原理及使用方法解析
Aug 21 Python
python中lower函数实现方法及用法讲解
Dec 23 Python
Python线程池与GIL全局锁实现抽奖小案例
Apr 13 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去除字符串换行符示例分享
2014/02/13 PHP
使用JavaScript创建新样式表和新样式规则
2016/06/14 PHP
利用PHPExcel实现Excel文件的写入和读取
2017/04/26 PHP
Laravel开启跨域请求的方法
2019/10/13 PHP
Div自动滚动到末尾的代码
2008/10/26 Javascript
jquery select 设置默认选中的示例代码
2014/02/07 Javascript
让html页面不缓存js的实现方法
2014/10/31 Javascript
javascript设计模式之中介者模式Mediator
2014/12/30 Javascript
BootStrap智能表单实战系列(六)表单编辑页面的数据绑定
2016/06/13 Javascript
javascript简单实现等比例缩小图片的方法
2016/07/27 Javascript
学习vue.js计算属性
2016/12/03 Javascript
js select下拉联动 更具级联性!
2020/04/17 Javascript
nodejs之koa2请求示例(GET,POST)
2018/08/07 NodeJs
vue项目移动端实现ip输入框问题
2019/03/19 Javascript
js实现贪吃蛇游戏(简易版)
2020/09/29 Javascript
[03:57]2016完美“圣”典风云人物:rOtk专访
2016/12/09 DOTA
[13:25]VP vs VICI (BO3)
2018/06/07 DOTA
详解在Python的Django框架中创建模板库的方法
2015/07/20 Python
总结python爬虫抓站的实用技巧
2016/08/09 Python
R vs. Python 数据分析中谁与争锋?
2017/10/18 Python
python中时间模块的基本使用教程
2019/05/14 Python
DJANGO-URL反向解析REVERSE实例讲解
2019/10/25 Python
Python3和PyCharm安装与环境配置【图文教程】
2020/02/14 Python
将 Ubuntu 16 和 18 上的 python 升级到最新 python3.8 的方法教程
2020/03/11 Python
python 30行代码实现蚂蚁森林自动偷能量
2021/02/08 Python
阿迪达斯法国官方网站:adidas法国
2018/03/20 全球购物
PHP面试题及答案二
2015/05/23 面试题
几个Linux面试题笔试题
2012/12/01 面试题
水利学院求职自荐书
2014/02/01 职场文书
《满井游记》教学反思
2014/02/26 职场文书
2015新年寄语大全
2014/12/08 职场文书
服务员岗位职责
2015/02/03 职场文书
2015年大学生工作总结
2015/04/21 职场文书
分析mysql中一条SQL查询语句是如何执行的
2021/06/21 MySQL
go语言使用Casbin实现角色的权限控制
2021/06/26 Golang
关于springboot配置druid数据源不生效问题(踩坑记)
2021/09/25 Java/Android