简易版celery的实现
最近学习了下,celery源码,看了一点点皮毛后,自己动手写了个简易的celery,通过redis作为broker,没有复杂的路由匹配规则,队列和任务之间一个直接匹配的简易规则。这里对项目简单的记录下。
项目目录结构
接下来挨个介绍下这几个文件:
- simple 是celery类所在位置,具体实现了celery的启动,加载配置文件,任务装饰器;
- utils 下base是任务类的实现,任务的发布方法,任务绑定celery实例方法;
- redisbase 是连接redis数据的类,主要是往队列里插入数据;
- example 是我自己写的用例
Celery类
# -*- coding: utf-8 -*-
'''
simplecelery @2019 03 26
author xuxiaolong
redis 作为broker
redis 作为结果返回result_backend
'''
from time import sleep
from utils.base import BaseTask
import json
import multiprocessing
from importlib import import_module
from utils.redisbase import RedisHelper
class Celery(object):
def __init__(self, name):
self.name = name
self.queuedic = dict()
self._task = dict()
def start(self):
'''
启动方法
从redis list 中获取message ,并找到对应的任务实例去执行,通过调用task.runtask()方法执行
'''
#_redis = RedisHelper(self.host,self.port,self.db,self.password)
#为每一个队列开启一个进程,执行对应的任务
queue = set([v["queue"] for v in self.taskdic.values()])
# pool = multiprocessing.Pool(processes=len(queue))
def runloop(queue):
_redis = RedisHelper(host=self.host, port=self.port, db=self.db, password=self.password)
while True:
retjson = _redis.Lpop(queue)
print retjson
if retjson is None:
sleep(5) #加了个休眠,避免terminal 刷的太快
continue
message = json.loads(retjson)
#print message
#print self.taskdic
for fun in self.queuedic[queue]:
#print message["name"] == fun.split(".")[-1:][0]
if message["name"] == fun.split(".")[-1:][0]:
print self._task[message["name"].encode('utf-8')](*message["args"],**message["kwargs"])
for q in queue:
runloop(q)
#pool.apply_async(runloop(q))
#pool.close()
#pool.join()
def config_from_object(self, include=None):
'''获取基本的配置信息'''
self.config = import_module(include)
# redis_url 配置redis地址信息
redis,hostpass, portdb = self.config.redis_url.split(':')
self.password, self.host = hostpass.split('@')
self.port, self.db = portdb.split('/')
# celery_route配置路由信息
self.taskdic = self.config.celery_route
for k,v in self.taskdic.items():
if v["queue"] not in self.queuedic.keys():
self.queuedic[v["queue"]] = [k]
else:
self.queuedic[v["queue"]].append(k)
#任务装饰器
def task(self,*args,**kwargs):
#print args,kwargs
def create_inner_task():
def create_tasks(func):
ret = self.create_task_fromfun(func)
return ret
return create_tasks
return create_inner_task()
#通过函数创建任务对象
def create_task_fromfun(self,func):
tasks = type(func.__name__, (BaseTask,), dict({'name':func.__name__,'run': staticmethod(func)}))()
#print isinstance(tasks,BaseTask)
if tasks.name not in self._task:
self._task[tasks.name] = tasks.run
tasks.bind(self)
return tasks
这里task装饰器的,装饰的是任务函数,原理是,将任务函数,转换成一个任务对象的实例,也就是BaseTask,并把方法体,赋给BaseTask的run方法,利用的是type元类的来实现的,
tasks = type(func.name, (BaseTask,), dict({‘name’:func.name,‘run’: staticmethod(func)}))()
这里有个小地方需要注意下,任务函数需要转成非绑定的方法,也就是通过staticmethod方法,装成静态方法;
start 方法,可以理解成监听函数,是一个无限循环,不断的从配置的队列中读取数据,然后,交给tasks去执行,也就是调用我们上面说的,run方法
任务类
# -*- coding: utf-8 -*-
import json
from redisbase import RedisHelper
class BaseTask(object):
'''
任务类的基类,所有任务的拓展都继承此类
'''
def runtask(self,*args,**kwargs):
'''
任务执行的方法
'''
_redis = RedisHelper(host=self._app.host, port=self._app.port, db=self._app.db, password=self._app.password)
messagedic = dict()
messagedic["name"] = self.name
messagedic["args"] = args
messagedic["kwargs"] = kwargs
print self.run(*args,**kwargs)
for k,v in self._app.taskdic.items():
if k.split(".")[-1:][0] == self.name:
print _redis.Lpush(v["queue"],json.dumps(messagedic))
def bind(self,app):
'''
绑定app实例到task
'''
self._app = app
runtask 是我们的任务的发布函数,通过在客户端调用,往指定队列插入数据,服务端读取执行
redisbase 就不介绍了,没啥可说的,就是操作数据库的
具体怎么让项目跑起来
exampe 是示例 :
celery.py文件代码:
from simple import Celery
app = Celery("test")
# 加载配置
app.config_from_object('example.config')
@app.task()
def add(x,y):
#print(x+y)
return x+y
if __name__ == '__main__':
app.start()
这里实例化我们的celery,并且实现具体的任务函数,记得一定要加上task装饰器,和celery一样 的,
celery.py 是在服务端执行的,它会去具体执行任务;
config.py
是配置文件,安装这个规则写就行,示例如下:
redis_url=“redis//?****:8004/0”
celery_route={‘example.celery.add’:{‘queue’:‘addqueue’}}
run.py:
from example.celery import add
if __name__ == '__main__':
print(add.runtask(1,2))
这是在客户端运行的,具体的任务发布,通过runtask来发布
完整的代码可以去github上下载: https://github.com/shabbyboy/simplecelery.git