Python使用 Beanstalkd 做异步任务处理的方法

使用 Beanstalkd 作为消息队列服务,然后结合 Python 的装饰器语法实现一个简单的异步任务处理工具.

最终效果

定义任务:

from xxxxx.job_queue import JobQueue

queue = JobQueue()

@queue.task('task_tube_one')
def task_one(arg1, arg2, arg3):
 # do task

提交任务:

task_one.put(arg1="a", arg2="b", arg3="c")

然后就可以由后台的 work 线程去执行这些任务了。

实现过程

1、了解 Beanstalk Server

Beanstalk is a simple, fast work queue. https://github.com/kr/beanstalkd

Beanstalk 是一个 C 语言实现的消息队列服务。 它提供了通用的接口,最初设计的目的是通过异步运行耗时的任务来减少大量Web应用程序中的页面延迟。针对不同的语言,有不同的 Beanstalkd Client 实现。 Python 里就有 beanstalkc 等。我就是利用 beanstalkc 来作为与 beanstalkd server 通信的工具。

2、任务异步执行实现原理

beanstalkd 只能进行字符串的任务调度。为了让程序支持提交函数和参数,然后由woker执行函数并携带参数。需要一个中间层来将函数与传递的参数注册。

实现主要包括3个部分:

Subscriber: 负责将函数注册到 beanstalk 的一个tube上,实现很简单,注册函数名和函数本身的对应关系。(也就意味着同一个分组(tube)下不能有相同函数名存在)。数据存储在类变量里。

class Subscriber(object):
 FUN_MAP = defaultdict(dict)

 def __init__(self, func, tube):
  logger.info('register func:{} to tube:{}.'.format(func.__name__, tube))
  Subscriber.FUN_MAP[tube][func.__name__] = func

JobQueue: 方便将一个普通函数转换为具有 Putter 能力的装饰器

class JobQueue(object):
 @classmethod
 def task(cls, tube):
  def wrapper(func):
   Subscriber(func, tube)
   return Putter(func, tube)

  return wrapper

Putter: 将函数名、函数参数、指定的分组组合为一个对象,然后 json 序列化为字符串,最后通过 beanstalkc 推送到beanstalkd 队列。

class Putter(object):
 def __init__(self, func, tube):
  self.func = func
  self.tube = tube

 # 直接调用返回
 def __call__(self, *args, **kwargs):
  return self.func(*args, **kwargs)

 # 推给离线队列
 def put(self, **kwargs):
  args = {
   'func_name': self.func.__name__,
   'tube': self.tube,
   'kwargs': kwargs
  }
  logger.info('put job:{} to queue'.format(args))
  beanstalk = beanstalkc.Connection(host=BEANSTALK_CONFIG['host'], port=BEANSTALK_CONFIG['port'])
  try:
   beanstalk.use(self.tube)
   job_id = beanstalk.put(json.dumps(args))
   return job_id
  finally:
   beanstalk.close()

Worker: 从 beanstalkd 队列中取出字符串,然后通过 json.loads 反序列化为对象,获得 函数名、参数和tube。最后从 Subscriber 中获得 函数名对应的函数代码,然后传递参数执行函数。

class Worker(object):
 worker_id = 0

 def __init__(self, tubes):
  self.beanstalk = beanstalkc.Connection(host=BEANSTALK_CONFIG['host'], port=BEANSTALK_CONFIG['port'])
  self.tubes = tubes
  self.reserve_timeout = 20
  self.timeout_limit = 1000
  self.kick_period = 600
  self.signal_shutdown = False
  self.release_delay = 0
  self.age = 0
  self.signal_shutdown = False
  signal.signal(signal.SIGTERM, lambda signum, frame: self.graceful_shutdown())
  Worker.worker_id += 1
  import_module_by_str('pear.web.controllers.controller_crawler')

 def subscribe(self):
  if isinstance(self.tubes, list):
   for tube in self.tubes:
    if tube not in Subscriber.FUN_MAP.keys():
     logger.error('tube:{} not register!'.format(tube))
     continue
    self.beanstalk.watch(tube)
  else:
   if self.tubes not in Subscriber.FUN_MAP.keys():
    logger.error('tube:{} not register!'.format(self.tubes))
    return
   self.beanstalk.watch(self.tubes)

 def run(self):
  self.subscribe()
  while True:
   if self.signal_shutdown:
    break
   if self.signal_shutdown:
    logger.info("graceful shutdown")
    break
   job = self.beanstalk.reserve(timeout=self.reserve_timeout) # 阻塞获取任务,最长等待 timeout
   if not job:
    continue
   try:
    self.on_job(job)
    self.delete_job(job)
   except beanstalkc.CommandFailed as e:
    logger.warning(e, exc_info=1)
   except Exception as e:
    logger.error(e)
    kicks = job.stats()['kicks']
    if kicks < 3:
     self.bury_job(job)
    else:
     message = json.loads(job.body)
     logger.error("Kicks reach max. Delete the job", extra={'body': message})
     self.delete_job(job)

 @classmethod
 def on_job(cls, job):
  start = time.time()
  msg = json.loads(job.body)
  logger.info(msg)
  tube = msg.get('tube')
  func_name = msg.get('func_name')
  try:
   func = Subscriber.FUN_MAP[tube][func_name]
   kwargs = msg.get('kwargs')
   func(**kwargs)
   logger.info(u'{}-{}'.format(func, kwargs))
  except Exception as e:
   logger.error(e.message, exc_info=True)
  cost = time.time() - start
  logger.info('{} cost {}s'.format(func_name, cost))

 @classmethod
 def delete_job(cls, job):
  try:
   job.delete()
  except beanstalkc.CommandFailed as e:
   logger.warning(e, exc_info=1)

 @classmethod
 def bury_job(cls, job):
  try:
   job.bury()
  except beanstalkc.CommandFailed as e:
   logger.warning(e, exc_info=1)

 def graceful_shutdown(self):
  self.signal_shutdown = True

写上面代码的时候,发现一个问题:

通过 Subscriber 注册函数名和函数本身的对应关系,是在一个Python解释器,也就是在一个进程里运行的,而 Worker 又是异步在另外的进程运行,怎么样才能让 Worker 也能拿到和 Putter 一样的 Subscriber。最后发现通过 Python 的装饰器机制可以解决这个问题。

就是这句解决了 Subscriber 的问题

import_module_by_str('pear.web.controllers.controller_crawler')
# import_module_by_str 的实现
def import_module_by_str(module_name):
 if isinstance(module_name, unicode):
  module_name = str(module_name)
 __import__(module_name)

执行 import_module_by_str 时, 会调用 __import__ 动态加载类和函数。将使用了 JobQueue 的函数所在模块加载到内存之后。当 运行 Woker 时,Python 解释器就会先执行 @修饰的装饰器代码,也就会把 Subscriber 中的对应关系加载到内存。

实际使用可以看 https://github.com/jiyangg/Pear/blob/master/pear/jobs/job_queue.py

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

您可能感兴趣的文章:

  • Python的Tornado框架的异步任务与AsyncHTTPClient
  • Python环境下安装使用异步任务队列包Celery的基础教程
  • python异步任务队列示例
(0)

相关推荐

  • Python环境下安装使用异步任务队列包Celery的基础教程

    1.简介 celery(芹菜)是一个异步任务队列/基于分布式消息传递的作业队列.它侧重于实时操作,但对调度支持也很好. celery用于生产系统每天处理数以百万计的任务. celery是用Python编写的,但该协议可以在任何语言实现.它也可以与其他语言通过webhooks实现. 建议的消息代理RabbitMQ的,但提供有限支持Redis, Beanstalk, MongoDB, CouchDB, ,和数据库(使用SQLAlchemy的或Django的 ORM) . celery是易于集成Dja

  • python异步任务队列示例

    很多场景为了不阻塞,都需要异步回调机制.这是一个简单的例子,大家参考使用吧 复制代码 代码如下: #!/usr/bin/env python# -*- coding: UTF-8 -*- import loggingimport queueimport threading def func_a(a, b):    return a + b def func_b():    pass def func_c(a, b, c):    return a, b, c # 异步任务队列_task_queu

  • Python的Tornado框架的异步任务与AsyncHTTPClient

    高性能服务器Tornado Python的web框架名目繁多,各有千秋.正如光荣属于希腊,伟大属于罗马.Python的优雅结合WSGI的设计,让web框架接口实现千秋一统.WSGI 把应用(Application)和服务器(Server)结合起来.Django 和 Flask 都可以结合 gunicon 搭建部署应用. 与 django 和 flask 不一样,tornado 既可以是 wsgi 应用,也可以是 wsgi 服务.当然,选择tornado更多的考量源于其单进程单线程异步IO的网络模

  • Python使用 Beanstalkd 做异步任务处理的方法

    使用 Beanstalkd 作为消息队列服务,然后结合 Python 的装饰器语法实现一个简单的异步任务处理工具. 最终效果 定义任务: from xxxxx.job_queue import JobQueue queue = JobQueue() @queue.task('task_tube_one') def task_one(arg1, arg2, arg3): # do task 提交任务: task_one.put(arg1="a", arg2="b",

  • Python通过select实现异步IO的方法

    本文实例讲述了Python通过select实现异步IO的方法.分享给大家供大家参考.具体如下: 在Python中使用select与poll比起在C中使用简单得多.select函数的参数是3个列表,包含整数文件描述符,或者带有可返回文件描述符的fileno()方法对象.第一个参数是需要等待输入的对象,第二个指定等待输出的对象,第三个参数指定异常情况的对象.第四个参数则为设置超时时间,是一个浮点数.指定以秒为单位的超时值.select函数将会返回一组文件描述符,包括输入,输出以及异常. 在linux

  • Python通过poll实现异步IO的方法

    本文实例讲述了Python通过poll实现异步IO的方法.分享给大家供大家参考.具体分析如下: 在使用poll()后返回轮询对象,该对象支持以下方法: pollObj.register(fd,[,eventmask])第一个参数是注册新的文件描述符fd,fd要么是一个整数文件描述符,要么可以带有一个获取文件描述符的fileno()方法的对象.eventmask是一些按位或标记,这些标记指示要处理的事件. POLLIN:       用于读取数据 POLLPRI:      用于读取紧急数据 PO

  • Python使用LDAP做用户认证的方法

    LDAP(Light Directory Access Portocol)是轻量目录访问协议,基于X.500标准,支持TCP/IP. LDAP目录以树状的层次结构来存储数据.每个目录记录都有标识名(Distinguished Name,简称DN),用来读取单个记录, 一般是这样的: cn=username,ou=people,dc=test,dc=com 几个关键字的含义如下: base dn:LDAP目录树的最顶部,也就是树的根,是上面的dc=test,dc=com部分,一般使用公司的域名,也

  • Python使用百度api做人脸对比的方法

    安装SDK: pip install baidu-aip 如果在pycharm里也可以在setting----Project Interpreter---右边绿色加号,输入baidu,安装baidu-aip 入门代码: 先去百度AI开放平台注册一个账号,然后开通人脸识别,免费的 http://ai.baidu.com/tech/face 之后把得到的Api key secretkey 填进去. from aip import AipFace """ 你的 APPID AK S

  • Python中requests做接口测试的方法

    一.介绍 Requests是一个很实用的Python HTTP客户端库,编写爬虫和测试服务器响应数据时经常会用到,Requests是Python语言的第三方的库,专门用于发送HTTP请求 二.前提 pip install requests 三.get的请求 3.1 GET无参请求 r = requests.get('http://www.baidu.com') 3.2 GET传参 payload = {'key1': 'value1', 'key2': 'value2', 'key3': Non

  • python基于celery实现异步任务周期任务定时任务

    这篇文章主要介绍了python基于celery实现异步任务周期任务定时任务,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 hello, 小伙伴们, 好久不更新了,这一次带来的是celery在python中的应用以及设置异步任务周期任务和定时任务的步骤,希望能给入坑的你带来些许帮助. 首先是对celery的介绍,Celery其实是一个专注于实时处理和调度任务的分布式任务队列,同时提供操作和维护分布式系统所需要的全部数据, 因此可以用它提供的接口快

  • Python用来做Web开发的优势有哪些

    Python是世界上最受欢迎的编码语言之一,在80年代后期首次受到ABC和Modula-3语言的影响而被概念化.从1991年的第一个版本到成为开源项目的2.0版本,它已经走了很长一段路,直到今天,它正在聚集一个庞大且专业的社区,该社区正在不断改进技术. 目前而言一些顶级公司在其技术堆栈中使用Python(例如Instagram) Facebook旗下的一个图片社交网站 Python适合从简单到复杂的各种Web项目.它广泛用于旅行,医疗保健,交通运输,金融等不同领域,用于Web开发和软件测试,脚本

  • JVM优先级线程池做任务队列的实现方法

    前言 我们都知道 web 服务的工作大多是接受 http 请求,并返回处理后的结果.服务器接受的每一个请求又可以看是一个任务.一般而言这些请求任务会根据请求的先后有序处理,如果请求任务的处理比较耗时,往往就需要排队了.而同时不同的任务直接可能会存在一些优先级的变化,这时候就需要引入任务队列并进行管理了.可以做任务队列的东西有很多,Java 自带的线程池,以及其他的消息中间件都可以. 同步与异步 这个问题在之前已经提过很多次了,有些任务是需要请求后立即返回结果的,而有的则不需要.设想一下你下单购物

  • python 中的 asyncio 异步协程

    目录 一.定义协程 二.运行协程 三.协程回调 四.运行多个协程 五.run_forever 六.多协程中关闭run_forever 一.定义协程 asyncio 执行的任务,称为协程,但是Asyncio 并不能带来真正的并行 Python 的多线程因为 GIL(全局解释器锁)的存在,也不能带来真正的并行 import asyncio # 通过 async 定义一个协程 async def task(): print('这是一个协程') # 判断是否是一个协程,返回True print(asyn

随机推荐