手把手教你在Windows下设置分布式队列Celery的心跳轮询

手把手教你在Windows下设置分布式队列Celery的心跳轮询

作者: 吴老板 2021-03-05 08:52:00

系统

Windows

分布式 大家好,我是吴老板。用Celery 官方的话来说,Celery 是一个非常优秀的分布式队列,可应用于分布式共享中间队列和定时任务等等。

[[385390]]

1 前言

大家好,我是吴老板。用Celery 官方的话来说,Celery 是一个非常优秀的分布式队列,可应用于分布式共享中间队列和定时任务等等。

2 版本的差异

Celery 有很多个版本,各版本之间的差异可谓不小,比如最新的 Celery6.0 版本在稳定性远不如 Celery4.0,所以在使用不同版本的时候,系统给到我们的反馈可能并不能如我们所愿。

3 服务

在 windows 下挂在 Celery 服务有时候会出现不稳定的情况(unix中暂时未发现这种情况),比如在执行定时任务的时候,过了一段时间之后,Celery 出现了假死状态,以至于不能按照我们指定的时间点去执行任务。

这些任务只是加入到待运行队列中(堆积在 Redis 中),只能人为重启 Celery 服务之后才能将堆积的任务释放出来运行。

这样一来,第一是定时任务在指定时间点没有正常运行,其二是在其他时间运行了这些任务,很可能会产生更新数据不及时,时间节点混乱的问题,不仅达不到业务需求,还会反受其害。

4 设置心跳

为了解决 Celery 在 windows 中的这种弊端,可以为 Celery 任务队列设置一个心跳时间,比如每一分钟或者每五分钟向 Redis 数据库发送一次数据以保证队列始终是活跃的状态,这样只要你的电脑不关机并保持网络畅通(如果是远程 Redis),Celery 任务队列服务就不会出现假死状态。

5 举个栗子

我总是很喜欢用示例来说话,前些时间在对某平台的商家后台进行数据采集的时候,为了使用时能自动获取该网站的 cookie ,

用Pyppeteer 写了一个自动化登陆的脚本,和往常一样仍在 Celery 队列中并迅速的启动服务。

脚本是这样的(非常接近实际的伪代码,没办法,保命要紧)

  1. # -*- coding: utf-8 -*- 
  2. from db.redisCurd import RedisQueue 
  3. import asyncio 
  4. import random 
  5. import tkinter 
  6. from pyppeteer.launcher import launch 
  7. from platLogin.config import USERNAME, PASSWORD, LOGIN_URL 
  8.  
  9. class Login(): 
  10.     def __init__(self, shopId): 
  11.         self.shopId = shopId 
  12.         self.RedisQueue = RedisQueue("cookie"
  13.  
  14.     def screen_size(self): 
  15.         tk = tkinter.Tk() 
  16.         width = tk.winfo_screenwidth() 
  17.         height = tk.winfo_screenheight() 
  18.         tk.quit() 
  19.         return {'width': width, 'height': height} 
  20.  
  21.     async def login(self, username, password, url): 
  22.         browser = await launch( 
  23.             { 
  24.                 'headless'False
  25.                 'dumpio'True 
  26.             }, 
  27.             args=['--no-sandbox''--disable-infobars''--user-data-dir=./userData'], 
  28.         ) 
  29.         page = await browser.newPage()  # 启动新的浏览器页面 
  30.  
  31.         try: 
  32.             await page.setViewport(viewport=self.screen_size()) 
  33.             await page.setJavaScriptEnabled(enabled=True)  # 启用js 
  34.             await page.setUserAgent( 
  35.                 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299' 
  36.             ) 
  37.             await self.page_evaluate(page) 
  38.             await page.goto(url) 
  39.             await asyncio.sleep(2) 
  40.             # 输入用户名,密码 
  41.             await page.evaluate(f'document.querySelector("#userName").value=""'
  42.             await page.type('#userName', username, {'delay': self.input_time_random() - 50})  # delay是限制输入的时间 
  43.             await page.evaluate('document.querySelector("#passWord").value=""'
  44.             await page.type('#passWord'password, {'delay': self.input_time_random()}) 
  45.             await page.waitFor(6000) 
  46.  
  47.             loginImgVcode = await page.waitForSelector('#checkCode')   
  48.             await loginImgVcode.screenshot({'path''./loginImg.png'}) 
  49.             await page.waitFor(6000) 
  50.  
  51.             res = use_cjy("./loginImg.png"
  52.             pic_str = res.get("pic_str") if res.get("err_str") == "OK" else "1234" 
  53.  
  54.             await page.waitFor(6000) 
  55.             await page.type('#checkWord', pic_str, {'delay': self.input_time_random() - 50}) 
  56.             await page.waitFor(6000) 
  57.  
  58.             await page.click('#subMit'
  59.             await page.waitFor(6000) 
  60.             await asyncio.sleep(2) 
  61.             await self.get_cookie(page) 
  62.             await page.waitFor(3000) 
  63.             await self.page_close(browser) 
  64.             return {'code': 200, 'msg''登陆成功'
  65.         except
  66.             return {'code': -1, 'msg''出错'
  67.  
  68.         finally: 
  69.             await page.waitFor(3000) 
  70.             await self.page_close(browser) 
  71.  
  72.     # 获取登录后cookie 
  73.     async def get_cookie(self, page): 
  74.         cookies_list = await page.cookies() 
  75.         cookies = '' 
  76.         for cookie in cookies_list: 
  77.             str_cookie = '{0}={1}; ' 
  78.             str_cookie = str_cookie.format(cookie.get('name'), cookie.get('value')) 
  79.             cookies += str_cookie 
  80.         # 将cookie 放入 cookie 池 
  81.         self.RedisQueue.put_hash(self.shopId, cookies) 
  82.         return cookies 
  83.  
  84.     async def page_evaluate(self, page): 
  85.         await page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => undefined } }) }'''
  86.         await page.evaluate('''() =>{ window.navigator.chrome = { runtime: {},  }; }'''
  87.         await page.evaluate( 
  88.             '''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }'''
  89.         await page.evaluate( 
  90.             '''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }'''
  91.         await page.waitFor(3000) 
  92.  
  93.     async def page_close(self, browser): 
  94.         for _page in await browser.pages(): 
  95.             await _page.close() 
  96.         await browser.close() 
  97.  
  98.     def input_time_random(self): 
  99.         return random.randint(100, 151) 
  100.  
  101.     def run(self, username=USERNAME, password=PASSWORD, url=LOGIN_URL): 
  102.         loop = asyncio.get_event_loop() 
  103.         i_future = asyncio.ensure_future(self.login(username, password, url)) 
  104.         loop.run_until_complete(i_future) 
  105.         return i_future.result() 
  106.  
  107.  
  108. if __name__ == '__main__'
  109.     Z = Login(shopId="001"
  110.     Z.run() 

Celery 任务文件是这样的

  1. # -*- coding: utf-8 -*- 
  2. from __future__ import absolute_import 
  3. import os 
  4. import sys 
  5. import time 
  6. from db.redisCurd import RedisQueue 
  7. from send_msg.weinxin import Send_msg 
  8. base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 
  9. sys.path.append(base_dir) 
  10. from logger.logger import log_v 
  11. from celery import Task 
  12. from platLogin.login import Login  # 登陆类 
  13. from celery import Celery 
  14.  
  15. randomQueue = RedisQueue("cookie"
  16.  
  17. celery_app = Celery('task'
  18. celery_app.config_from_object('celeryConfig'
  19.  
  20. S = Send_msg() 
  21.  
  22. dl_dict = { 
  23.     'demo': { 
  24.         'cookie'''
  25.         'loginClass''Login'
  26.     } 
  27.  
  28. # todo 这是三种运行的状态 
  29. class task_status(Task): 
  30.     def on_success(self, retval, task_id, args, kwargs):  
  31.         log_v.info('任务信息 -> id:{} , arg:{} , successful ..... Done'.format(task_id, args)) 
  32.  
  33.     def on_failure(self, exc, task_id, args, kwargs, einfo):   
  34.         log_v.error('task id:{} , arg:{} , failed ! error : {}'.format(task_id, args, exc)) 
  35.  
  36.     def on_retry(self, exc, task_id, args, kwargs, einfo):  
  37.         log_v.warning('task id:{} , arg:{} , retry !  info: {}'.format(task_id, args, exc)) 
  38.  
  39.  
  40. # todo 随便找个hash key作为轮询对象, celery在win10系统可能不太稳定,有时候会有连接断开的情况 
  41. @celery_app.task(base=task_status) 
  42. def get_cookie_status(platName="demo"): 
  43.     try: 
  44.         # log_v.debug(f'[+] 轮询 {platName} 定时器启动 ..... Done'
  45.         randomQueue.get_hash(platName).decode() 
  46.         log_v.debug(f'[+] 轮询 {platName} 成功 ..... Done'
  47.         return "Erp 轮询成功" 
  48.     except
  49.         return "Erp 轮询失败" 
  50.  
  51.  
  52. @celery_app.task(base=task_status) 
  53. def set_plat_cookie(platName="demo", shopId=None): 
  54.     log_v.debug(f"[+] {platName} 正在登陆"
  55.     core = eval(dl_dict[platName]['loginClass'])(shopId=shopId) 
  56.     result = core.run() 
  57.     return result 

Celery 配置文件是这样的

  1. from __future__ import absolute_import 
  2. import datetime 
  3. from kombu import Exchange, Queue 
  4. from celery.schedules import crontab 
  5. from urllib import parse 
  6.  
  7. BROKER_URL = f'redis://root:{parse.quote("你的不规则密码")}@主机:6379/15' 
  8.  
  9. # 导入任务,如tasks.py 
  10. CELERY_IMPORTS = ('monitor.tasks',) 
  11.  
  12. # 列化任务载荷的默认的序列化方式 
  13. CELERY_TASK_SERIALIZER = 'json' 
  14.  
  15. # 结果序列化方式 
  16. CELERY_RESULT_SERIALIZER = 'json' 
  17. CELERY_ACCEPT_CONTENT = ['json'
  18.  
  19. CELERY_TIMEZONE = 'Asia/Shanghai'  # 指定时区,不指定默认为 'UTC' 
  20. # CELERY_TIMEZONE='UTC' 
  21.  
  22. CELERYBEAT_SCHEDULE = { 
  23.     'add-every-60-seconds': { 
  24.         'task''tasks.get_cookie_status'
  25.         'schedule': datetime.timedelta(minutes=1),  # 每 1 分钟执行一次 
  26.         'args': ()  # 任务函数参数 
  27.     }, 

启动服务

  1. celery -A tasks beat -l INFO 
  2. celery -A tasks worker -l INFO -c 2 

以 2 个线程启动消费者队列服务并启用定时任务,当发现当前平台的 cookie 不可用时,我会向 Celery 发送一个信号(就是调用了前面的set_plat_cookie 这个方法),消费者得到这个任务这个就会执行自动化脚本以获取 cookie 并储存在 Redis 中,使用时在从 Redis 中获取就能正常请求到该平台的数据。

在空闲时间,Celery中的 get_cookie_status 方法会每隔一分钟向 Redis 请求数据,这就是我们设置的 1分钟心跳。

这样不管我们的 Celery 是否是后台启动,都不会出现假死、卡死的状态,则万事大吉矣!!

6 总结

 

本文为了解决 Celery 在 windows 中的这种弊端,为 Celery 任务队列设置一个心跳时间,比如每一分钟或者每五分钟向 Redis 数据库发送一次数据以保证队列始终是活跃的状态,这样只要你的电脑不关机并保持网络畅通(如果是远程 Redis),Celery 任务队列服务都不会出现假死、卡死的状态。

 

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/259073.html<

(0)
管理的头像管理
上一篇2025-05-02 12:54
下一篇 2025-05-02 12:55

相关推荐

  • 物理服务器和云服务器做站群到底选哪个,哪个更稳定?

    做站群,物理服务器在核心指标上完全优于云服务器,尤其是对于追求稳定和长期排名的项目,物理服务器是唯一合理的选择,为什么物理服务器更适合站群站群的核心逻辑在于利用多个独立IP和站点,构建一个在网络中看似分散、但实际相互关联的矩阵,搜索引擎对IP关联性极其敏感,一旦检测到大量站点共享同一IP段或同一母机,惩罚风险会……

    2026-07-28
    0
  • 国内高防服务器哪家防御真实靠谱,怎么选?

    国内高防服务器哪家防御真实靠谱?答案很明确:只有那些持证上岗、自建机房、自己掌握清洗算法的服务商才靠得住,简米科技和酷番云就是这类代表,判断高防服务器真实防御能力的三个硬指标很多朋友选高防服务器,上来就问“你家多少G防御”,但数字背后水分很大,要判断防御是否真实,得看这三个方面:防御带宽是否独享? 有些服务商宣……

    2026-07-28
    0
  • 裸金属服务器和物理服务器有什么区别?,怎么选?

    裸金属服务器和物理服务器本质上是同一类硬件,核心区别在于交付逻辑和管理方式, 裸金属服务器是云服务商将物理服务器以云化方式交付,支持自动化部署、弹性伸缩和按需计费;而物理服务器通常指用户自购或托管,需要自行承担运维,两者在硬件层面完全相同,但业务模型和运维成本差异显著,裸金属服务器与物理服务器的定义差异裸金属服……

    2026-07-28
    0
  • 做GEO站群选哪家服务器服务商靠谱,怎么选?

    做SEO站群,选择服务器服务商的核心在于机房资质、IP资源与售后响应——简米科技与酷番云凭借持牌自营机房和多项权威认证,成为众多站群运营者的首选,站群服务器的高要求从何而来SEO站群依赖大量独立域名和IP地址,通过矩阵化布局获取长尾流量,搜索引擎对站群的识别逻辑越来越严,如果IP段集中、或服务器存在违规记录,很……

    2026-07-28
    0
  • 国内云服务器哪家性价比最高又稳定,怎么选?

    国内云服务器选择,简米科技与酷番云凭借持牌自营机房和双认证体系,在性价比和稳定性上表现突出,尤其适合中小企业和个人开发者,云服务器选择的核心指标:性价比与稳定性性价比不能只看价格云服务器的性价比不等于最低价,而是配置、带宽、售后服务与价格的综合平衡,不少厂商以低价吸引用户,但实际使用中可能遇到资源争抢或网络瓶颈……

    2026-07-28
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注