分布式任务调度在自动化购物系统中的应用:从定时任务到弹性调度的演进

简介: 本文介绍了一种基于Redis ZSET的分布式任务调度方案,用于高效管理数千个商品监控任务。相比原始多线程轮询和APScheduler方案,该架构通过集中式任务队列、Worker竞争消费与动态扩缩容,显著降低资源开销,将任务延迟控制在200ms内,兼具高并发、低延迟与弹性伸缩能力。(239字)

一、业务场景
在煤炉自动下单和雅虎代拍场景中,系统需要同时监控成千上万个用户设置的关键词和商品。每个监控任务都需要定期执行——搜索新上架的商品、检查价格变化、触发自动下单。
我最早实现的是一个简单的while True循环 + time.sleep()方案:
python
import timedef monitor_task(keyword, max_price): while True: items = search_mercari(keyword, max_price) for item in items: if match_condition(item): place_order(item) time.sleep(5) # 每5秒检查一次
当监控任务从10个增长到1000个的时候,这个方案彻底崩溃了——1000个线程同时运行,每个线程5秒一次请求,每秒就是200次请求,服务器CPU直接飙到100%。
二、APScheduler方案
我首先用APScheduler重构了任务调度:
python
from apscheduler.schedulers.background import BackgroundSchedulerfrom apscheduler.triggers.interval import IntervalTriggerscheduler = BackgroundScheduler()def add_monitor_task(keyword, max_price, user_id): scheduler.add_job( func=monitor_once, trigger=IntervalTrigger(seconds=5), args=[keyword, max_price, userid], id=f"monitor{userid}{keyword}", replace_existing=True )
APScheduler比手动循环好一些,但本质上还是每个任务独立调度。当任务数量达到几千个时,调度器本身的开销就很大了。
三、基于Redis ZSET的集中式任务调度
最终方案是把所有监控任务放在Redis的有序集合(ZSET)中,由一组Worker竞争消费:
python
import redisimport timeimport jsonclass TaskScheduler: def init(self): self.redis = redis.Redis(decode_responses=True) self.task_queue = "scheduler:tasks" self.running_key = "scheduler:running" def add_task(self, task_id, task_data, interval=5): """添加一个周期性任务""" # 计算下一次执行时间 next_run = time.time() + interval # 存储任务详情 detail_key = f"task:detail:{task_id}" self.redis.hset(detail_key, mapping={ 'data': json.dumps(task_data), 'interval': interval, 'last_run': '0' }) # 加入ZSET,score为下一次执行时间 self.redis.zadd(self.task_queue, {task_id: next_run}) def acquire_task(self, worker_id): """Worker获取一个待执行的任务""" # 获取score最小的任务(最早需要执行的) tasks = self.redis.zrange(self.task_queue, 0, 0, withscores=True) if not tasks: return None task_id, score = tasks[0] now = time.time() if score > now: # 还没到执行时间 return None # 尝试锁定这个任务(使用Lua脚本保证原子性) lock_script = """ local task_id = ARGV[1] local worker_id = ARGV[2] local now = tonumber(ARGV[3]) -- 从ZSET中移除 local removed = redis.call('ZREM', KEYS[1], task_id) if removed == 0 then return nil end -- 记录正在运行 redis.call('HSET', KEYS[2], task_id, worker_id) return 'ok' """ result = self.redis.eval( lock_script, 2, self.task_queue, self.running_key, task_id, worker_id, now ) if result is None: return None # 获取任务详情 detail_key = f"task:detail:{task_id}" task_data = self.redis.hgetall(detail_key) return { 'task_id': task_id, 'data': json.loads(task_data['data']), 'interval': int(task_data['interval']) } def complete_task(self, task_id): """任务执行完成,重新调度""" detail_key = f"task:detail:{task_id}" interval = int(self.redis.hget(detail_key, 'interval')) next_run = time.time() + interval # 更新最后执行时间 self.redis.hset(detail_key, 'last_run', str(time.time())) # 重新加入ZSET self.redis.zadd(self.task_queue, {task_id: next_run}) # 从运行中移除 self.redis.hdel(self.running_key, task_id)
Worker的实现:
python
import threadingimport timeclass Worker: def init(self, worker_id, scheduler): self.worker_id = worker_id self.scheduler = scheduler self.running = True def run(self): while self.running: task = self.scheduler.acquire_task(self.worker_id) if task: self._execute(task) else: time.sleep(0.1) # 没有任务时短暂休眠 def _execute(self, task): try: # 执行监控逻辑 items = search_mercari( task['data']['keyword'], task['data']['max_price'] ) for item in items: if match_condition(item, task['data']): place_order(item, task['data']['user_id']) except Exception as e: # 记录错误,但不影响任务重新调度 log_error(task['task_id'], str(e)) finally: self.scheduler.complete_task(task['task_id'])
四、弹性扩缩容
这套架构支持动态增减Worker——流量大的时候启动更多Worker,流量小的时候减少Worker:
python
class WorkerManager: def init(self, scheduler): self.scheduler = scheduler self.workers = [] self.min_workers = 2 self.max_workers = 20 def adjust_workers(self): # 根据队列长度调整Worker数量 queue_length = self.scheduler.redis.zcard(self.scheduler.task_queue) target = max(self.min_workers, min(self.max_workers, queuelength // 50 + 1)) while len(self.workers) < target: worker = Worker(f"worker{len(self.workers)}", self.scheduler) threading.Thread(target=worker.run, daemon=True).start() self.workers.append(worker)
五、总结
这套基于Redis ZSET的分布式任务调度方案,支撑了数千个煤炉监控任务的同时运行,任务延迟控制在200毫秒以内。核心经验是:不要把调度逻辑分散在各个任务中,而是集中管理、统一调度。

目录
相关文章
|
3天前
|
人工智能 弹性计算 运维
|
1天前
|
缓存 人工智能 安全
GPT-5.6 Terra与GPT-5.5性能实测:成本减半后的跑分对比与快速迁移指南
GPT-5.6 Terra 的定价为每百万 token 输入 2.50/输出 15。GPT-5.5 则是 5/ 30。Terra 的每一项费率,包括 $0.25/M 的缓存读取,都恰好是 GPT-5.5 的一半,因此在任何工作负载组合下,Terra 都固定 便宜 2.0x。以每天 10 万次请求、3K token 提示词计算,大约是 Terra 每天 2,000,GPT−5.5每天 4,000,即每月约 60,000对 120,000。问题在于:OpenAI 没有发布任何针对 Terra 的编码基准。那个著名的 91.9% Terminal-Bench 数字是 Sol 在 Ul
|
24天前
|
Linux 程序员 数据格式
【2026最新】Notepad++下载、安装和使用一篇搞定(附中文版安装包)
Notepad++ 是一款免费开源、轻量高效的 Windows 文本编辑器,支持 C/Python/HTML 等 80+ 语言语法高亮、代码折叠、正则替换、编码转换及插件扩展,专为程序员与文本处理用户打造,完美替代系统记事本。(239字)
|
10天前
|
人工智能 缓存 安全
Claude Code 封号真实原因曝光,这次彻底不装了,直接针对国内开发者的账号下手?
Claude Code 封号潮背后:逆向扒出客户端隐写区域标记,Anthropic 政策收紧叠加 DeepSeek 7 月涨价,国产替代更紧迫。
|
19天前
|
存储 人工智能 监控
QoderWork完全指南:从入门到精通,把“AI实习生”变成你的全能工作搭档
阿里云2026年推出的桌面端AI工作助手QoderWork,不止聊天,更可动手干活:本地运行、安全可控,支持文件整理、数据分析、PPT生成、网页开发等;内置专家套件、多Agent协作与自定义Skills,让AI真正成为你身边的“AI实习生”。
|
15天前
|
人工智能 JSON 自然语言处理
让教学更智慧:用阿里云百炼工作流,自动生成中小学教材内容#小有可为#有温度的AI
通过可视化工作流编排,将大模型推理能力转化为标准化的教学内容生成引擎。教师只需输入教材标题和适用学段,即可自动获得结构完整、符合课程标准的章节内容,大幅降低备课门槛,助力教育资源均衡化。
502 127
|
8天前
|
人工智能 编解码 物联网
2026 最新Stable Diffusion 本地部署教程 下载安装使用详细图解(含官网安装包)
Stable Diffusion(SD)是2022年发布的开源文生图模型,由Stability AI等联合开发。支持文生图、图生图、局部重绘等,依托VAE降低算力需求,可在消费级显卡运行。本文提供秋葉aaaki制作的Windows整合包(含图形界面与插件),开箱即用,零配置启动。
|
10天前
|
人工智能 安全 程序员
终于,Claude Code 封号的原因被曝光了!竟然针对中国用户,植入隐形代码?!
通俗易懂地揭秘 Claude Code 封号的手段,分享一些自己对 AI 编程困境的思考,Codex、Cursor、DeepSeek、智谱 GLM、甚至是豆包,都有所行动了
516 1
|
18天前
|
人工智能 弹性计算 API
什么是 AlibabaCloud Agent Toolkit
Alibaba Cloud Agent Toolkit 是面向AI Agent的阿里云智能工具套件,集成OpenAPI、Terraform、CLI与文档能力,提供MCP插件、场景化Skills及执行审计机制,助AI准确查API、生成代码、规划架构、校验部署,实现安全、可靠、可追溯的云上智能运维。
453 2