【高并发】Celery + Redis异步任务队列方案提高OCR任务时的并发
线程池处理OCR仍然会阻塞请求的原因主要有以下几点,以及为什么Celery+Redis是更好的解决方案:
1. 线程池的阻塞本质
-
请求-响应周期未分离:即使使用线程池,HTTP请求仍需要等待线程池任务完成才能返回响应。当所有线程都繁忙时,新请求必须等待空闲线程。
-
共享进程资源:线程池与Web服务器共享同一进程的内存和CPU资源,大量OCR任务会挤占Web服务的资源。
-
Python的GIL限制:Python的全局解释器锁(GIL)导致多线程无法真正并行执行CPU密集型任务(如OCR),线程切换反而增加开销。
2. 线程池的典型问题场景
问题场景 | 表现 | Celery+Redis的解决方案 |
---|---|---|
高并发请求 | 线程池满后新请求被阻塞,用户长时间等待 | 请求快速入队后立即返回,任务异步处理 |
长任务处理 | 一个耗时OCR任务占用线程,其他短任务被阻塞 | 任务分发到独立Worker,不阻塞Web服务 |
资源竞争 | OCR占用CPU/内存导致Web服务响应变慢 | Worker运行在独立进程/节点,资源隔离 |
任务状态跟踪 | 线程池难以提供任务进度查询 | 通过Redis存储任务状态,支持实时查询 |
3. Celery+Redis的优势
(1) 真正的异步解耦
# 线程池方式(伪代码)
def handle_request(request):with thread_pool.submit(process_ocr): # 阻塞直到线程可用return HttpResponse("Processing...") # 用户仍需等待# Celery方式
def handle_request(request):task = process_ocr.delay() # 立即返回任务IDreturn JsonResponse({"task_id": task.id}) # 客户端轮询状态
(2) 资源隔离
-
Web服务:专注快速响应HTTP请求
-
Worker节点:独立进程/容器处理OCR,可横向扩展
(3) 可靠性保障
-
任务持久化:Redis存储任务队列,服务重启后不丢失
-
重试机制:Celery支持自动重试失败任务
@shared_task(bind=True, retry_backoff=True)
def process_ocr(self):try:# OCR逻辑except Exception as e:self.retry(exc=e) # 自动重试
(4) 状态监控
# 客户端查询状态(JavaScript示例)
function checkStatus(taskId) {fetch(`/task-status/?task_id=${taskId}`).then(res => res.json()).then(data => {if (data.status === "SUCCESS") {// 更新UI显示结果} else {setTimeout(checkStatus, 1000); // 继续轮询}});
}
4. 性能对比数据
指标 | 线程池方案 | Celery+Redis方案 |
---|---|---|
请求响应时间 | 500ms~30s(依赖任务耗时) | <50ms(立即返回任务ID) |
最大并发处理能力 | 受限于线程池大小(如20线程) | 理论上无限(通过扩展Worker) |
资源利用率 | Web与OCR竞争资源 | 资源隔离,独立扩展 |
失败恢复 | 任务丢失 | 任务持久化,支持重试 |
5. 何时线程池仍适用?
线程池适合:
-
短耗时任务(<100ms)
-
低并发场景
-
简单内存队列需求
但对于OCR这种CPU密集型+长耗时+高并发场景,Celery+Redis是更专业的选择。
总结建议
-
立即切换:将OCR任务迁移到Celery,Web服务只负责快速接收请求和返回任务ID。
-
部署优化:
-
使用独立Redis实例作为Broker
-
Worker节点配置专用高CPU机器
-
通过
flower
监控任务状态
-
-
客户端适配:
-
改为异步状态轮询机制
-
添加任务进度条UI
-
通过这种架构升级,你的系统将能轻松应对高并发OCR请求,同时保持Web服务的响应速度。
关键问题:同步处理 OCR 会阻塞 Web 请求
❌ 传统同步架构(无 Celery)
OCR处理Web服务器用户OCR处理Web服务器用户提交图片(HTTP请求)同步调用OCR处理(CPU密集型)耗时5秒完成返回结果(用户等待5秒)
-
问题:OCR 处理期间,Web 服务器线程被完全占用,无法响应其他请求(高并发时崩溃)。
✅ 解决方案:Celery + Redis 异步任务队列
1. 架构分工
2. 异步处理流程(时序图)
核心优势对比
场景 | 同步处理 | Celery + Redis 异步 |
---|---|---|
Web 响应速度 | 阻塞(5秒) | 立即返回(0.1秒) |
服务器资源占用 | 线程被占用(无法处理其他请求) | Web 线程立即释放(高并发可用) |
任务处理 | 实时处理,失败影响用户体验 | 后台重试,可靠性高 |
扩展性 | 难以水平扩展 | 可增加 Celery Worker 横向扩展 |
图解关键点
-
解耦 Web 与 OCR
-
Web 服务器仅负责接收请求和返回状态,不执行耗时操作。
-
OCR 任务交给 Celery Worker 在后台处理。
-
-
Redis 的作用
-
任务队列:存储待处理的 OCR 任务(先进先出)。
-
结果缓存:保存任务状态(完成/失败)和识别结果。
-
-
用户感知优化
-
通过轮询或 WebSocket 获取结果,避免长时间阻塞。
-
from celery import Celery
import os
from dotenv import load_dotenvload_dotenv() # 加载 .env 文件中的环境变量# 动态确定项目根目录,确保 Celery Worker 能找到模块
# 假设 celery_app.py 在项目根目录
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
GOT_ROOT = os.path.abspath(os.path.join(PROJECT_ROOT, 'utils/GOT_OCR/GOT_OCR_master'))import sys
if PROJECT_ROOT not in sys.path:sys.path.insert(0, PROJECT_ROOT)
if GOT_ROOT not in sys.path: # 确保 GOT_OCR 也能被找到sys.path.insert(0, GOT_ROOT)# Celery 应用实例
# 'resume_processor' 是应用名,可以自定义
# broker 和 backend URL 从环境变量读取,如果未设置则使用默认值
celery_app = Celery('tasks',broker='redis://localhost:6379/0',backend='redis://localhost:6379/0')# 指定任务模块的导入路径
celery_app.conf.imports = ['app.tasks.resume_tasks']celery_app.conf.update(task_serializer='json',result_serializer='json',accept_content=['json'],timezone='Asia/Shanghai', # 根据您的时区设置enable_utc=False, # 如果时区已设置,通常utc可以设为false# task_track_started=True, # 如果需要 PENDING -> STARTED 状态# worker_send_task_events=True, # 如果使用 Flower 等监控工具# worker_prefetch_multiplier=1, # 对于长任务,通常设置为1,避免worker一次取太多任务# task_acks_late = True, # 如果任务失败了,消息会重新入队(需要幂等性保证)worker_pool_restarts=True, # 允许工作池重启worker_max_tasks_per_child=1, # 每个子进程只处理一个任务
)if __name__ == '__main__':# 可以通过 python celery_app.py worker -l info 启动 worker (不常用)# 更常见的是使用命令行: celery -A celery_app.celery_app worker -l infocelery_app.start()
作用:
-
broker='redis://...'
:Celery 用 Redis 作为消息队列(存放待处理的 OCR 任务)。 -
backend='redis://...'
:Celery 用 Redis 存储任务结果(如 OCR 识别后的文本)。 -
如果不需要定时任务,可以忽略
beat_schedule
。
from celery_app import celery_app # 导入 Celery 应用实例
from app.processing import processing_logic
import os
import shutil
import tempfile
import logging# 确保任务模块也能获取到正确的 logger
logger = processing_logic.setup_logging() # 或者 logging.getLogger(__name__)@celery_app.task(bind=True, name="process_resumes_from_pdfs") # bind=True 使任务函数可以访问 self (任务实例)
def process_resumes_task(self,pdf_input_dir: str, # API保存PDF文件的临时目录persistent_vector_store_path: str, # 此知识库的向量库最终存储路径persistent_db_path: str, # 此知识库的结构化数据DB最终存储路径persistent_json_dir: str # 此知识库的JSON文件最终存储路径):"""Celery 任务:处理上传的PDF文件,进行OCR、内容提取、存入数据库和向量库。"""task_id = self.request.idlogger.info(f"[{task_id}] Celery Task process_resumes_task started.")logger.info(f"[{task_id}] Args: pdf_input_dir='{pdf_input_dir}', persistent_vs_path='{persistent_vector_store_path}', persistent_db_path='{persistent_db_path}'")# 从 persistent_vector_store_path 中提取知识库ID# persistent_vector_store_path 格式为: KNOWLEDGE_BASES_FOLDER/knowledge_id/All/vector_storeknowledge_id = persistent_vector_store_path.split(os.sep)[-3] # 获取倒数第三个部分作为knowledge_idknowledge_base_folder = os.path.join(os.path.dirname(os.path.dirname(persistent_vector_store_path)), '..')temp_pdf_dir = os.path.join(knowledge_base_folder, "temp_pdf")# 为当前任务创建一个独立的临时工作目录,用于存放中间文件(MD, JSON)# 这样可以避免不同任务间文件冲突,也便于任务结束时清理task_run_temp_dir = tempfile.mkdtemp(prefix=f"celery_task_{task_id}_")logger.info(f"[{task_id}] Created task-specific temporary directory: {task_run_temp_dir}")try:# 更新任务状态为 "进行中",并可以传递一些元数据self.update_state(state='PROGRESS', meta={'current_step': 'Starting PDF to Markdown conversion'})# 调用核心处理流水线result_summary = processing_logic.full_processing_pipeline_for_celery(pdf_input_dir=pdf_input_dir,persistent_vector_store_path=persistent_vector_store_path,persistent_db_path=persistent_db_path,persistent_json_dir=persistent_json_dir,task_temp_dir=task_run_temp_dir, # 传递任务的临时工作目录task_id=str(task_id))logger.info(f"[{task_id}] Processing pipeline finished. Result: {result_summary}")# 清理temp_pdf目录if os.path.exists(temp_pdf_dir):try:# 清空目录中的所有文件,但保留目录本身for file_name in os.listdir(temp_pdf_dir):file_path = os.path.join(temp_pdf_dir, file_name)if os.path.isfile(file_path):os.remove(file_path)logger.info(f"[{task_id}] Successfully cleaned up temp_pdf directory for knowledge_id {knowledge_id}: {temp_pdf_dir}")except Exception as e_clean:logger.error(f"[{task_id}] Error cleaning up temp_pdf directory {temp_pdf_dir}: {e_clean}", exc_info=True)# 任务成功完成,返回结果摘要# Celery 会将这个返回值存储在结果后端 (Redis)return result_summary # 这个字典必须是 JSON 可序列化的except Exception as e:logger.error(f"[{task_id}] Unhandled exception in process_resumes_task: {e}", exc_info=True)# 发生异常时,Celery 会自动将任务状态标记为 FAILURE,并记录异常信息# 可以选择性地更新状态并包含自定义错误信息# self.update_state(state='FAILURE', meta={'exc_type': type(e).__name__, 'exc_message': str(e), 'traceback': traceback.format_exc()})raise # 重新抛出异常,确保 Celery 正确处理失败状态finally:# 清理任务的临时工作目录if os.path.exists(task_run_temp_dir):try:shutil.rmtree(task_run_temp_dir)logger.info(f"[{task_id}] Successfully cleaned up temporary directory: {task_run_temp_dir}")except Exception as e_clean:logger.error(f"[{task_id}] Error cleaning up temporary directory {task_run_temp_dir}: {e_clean}", exc_info=True)# 清理 API 上传的 PDF 目录 (pdf_input_dir)if pdf_input_dir and os.path.exists(pdf_input_dir) and pdf_input_dir.startswith(os.getenv("PDF_UPLOAD_BASE_DIR", "/tmp/")):try:shutil.rmtree(pdf_input_dir)logger.info(f"[{task_id}] Successfully cleaned up PDF input directory: {pdf_input_dir}")except Exception as e_clean_api_dir:logger.error(f"[{task_id}] Error cleaning up PDF input directory {pdf_input_dir}: {e_clean_api_dir}", exc_info=True)# 清理 temp_pdf 目录if os.path.exists(temp_pdf_dir):try:# 清空目录中的所有文件,但保留目录本身for file_name in os.listdir(temp_pdf_dir):file_path = os.path.join(temp_pdf_dir, file_name)if os.path.isfile(file_path):os.remove(file_path)logger.info(f"[{task_id}] Successfully cleaned up temp_pdf directory for knowledge_id {knowledge_id}: {temp_pdf_dir}")except Exception as e_clean_temp:logger.error(f"[{task_id}] Error cleaning up temp_pdf directory {temp_pdf_dir}: {e_clean_temp}", exc_info=True)
这个 tasks.py
文件是 Celery 异步任务的核心定义文件,在 Celery + Redis
架构中扮演 异步任务执行器 的角色。以下是它的具体作用和关键设计解析:
1. 核心作用总结
功能 | 说明 |
---|---|
定义异步任务 | 将耗时的 PDF 处理(OCR/向量化/存储)封装为 Celery 任务,避免阻塞 Web 请求。 |
任务状态管理 | 通过 self.update_state() 实时更新任务进度,支持前端轮询。 |
资源隔离与清理 | 为每个任务创建独立临时目录,确保并发安全,并在完成后自动清理。 |
错误处理与日志 | 捕获异常并记录详细日志,便于排查问题。 |
Redis 消息队列集成 | 通过 @celery_app.task 将任务注册到 Redis 队列,供 Worker 消费。 |
2. 代码逐模块解析
(1)任务定义装饰器
@celery_app.task(bind=True, name="process_resumes_from_pdfs")
-
bind=True
允许任务访问self
(即任务实例),从而调用self.update_state()
或self.retry()
等方法。 -
name="process_resumes_from_pdfs"
显式指定任务名称,避免模块路径变化导致的问题。
(2)任务参数设计
def process_resumes_task(self, pdf_input_dir, persistent_vector_store_path, persistent_db_path, persistent_json_dir):
-
输入参数:
-
pdf_input_dir
:临时存储用户上传 PDF 的目录。 -
persistent_*_path
:最终存储向量库、数据库、JSON 的路径。
-
-
设计意图:
明确分离 临时输入 和 持久化输出,符合异步任务的“数据隔离”原则。
(3)任务生命周期管理
① 初始化阶段
task_id = self.request.id # 获取 Celery 自动生成的任务ID
task_run_temp_dir = tempfile.mkdtemp(prefix=f"celery_task_{task_id}_") # 创建任务专属临时目录
-
task_id
:唯一标识任务,用于日志和状态跟踪。 -
独立临时目录:避免多任务并发时的文件冲突(如同时处理 PDF 生成中间文件)。
② 执行阶段
self.update_state(state='PROGRESS', meta={'current_step': 'Starting PDF to Markdown conversion'})
result_summary = processing_logic.full_processing_pipeline_for_celery(...)
-
进度更新:通过
update_state
通知前端当前步骤(如 OCR 转换中)。 -
核心逻辑:调用
processing_logic
模块处理 PDF,生成结构化数据并存储。
③ 清理阶段(finally
块)
shutil.rmtree(task_run_temp_dir) # 删除任务临时目录
shutil.rmtree(pdf_input_dir) # 清理用户上传的原始PDF目录
-
资源释放:确保临时文件不会堆积,避免磁盘空间泄漏。
(4)异常处理
except Exception as e:logger.error(f"[{task_id}] Unhandled exception: {e}", exc_info=True)raise # 重新抛出异常,触发 Celery 的 FAILURE 状态
-
日志记录:详细错误信息 + 堆栈跟踪(
exc_info=True
)。 -
状态同步:Celery 会自动将任务标记为
FAILURE
,前端可通过task_id
查询失败原因。
(5)结果返回
return result_summary # 必须是 JSON 可序列化的字典
-
存储到 Redis:Celery 将返回值存入 Redis Backend,供后续查询。
3. 在整体架构中的协作流程
4. 关键设计亮点
(1)资源隔离
-
每个任务拥有 独立的临时目录(
task_run_temp_dir
),避免并发冲突。 -
通过
knowledge_id
分离不同知识库的数据存储路径。
(2)可观测性
-
日志跟踪:每条日志包含
task_id
,便于分布式排查问题。 -
进度通知:
update_state
让前端实时显示当前步骤(如“OCR 中”)。
(3)健壮性
-
异常捕获:即使任务失败,也能保证临时资源被清理。
-
幂等性设计:重复处理同一
knowledge_id
不会导致数据错乱(依赖持久化路径的唯一性)。
总结
这个 tasks.py
是 Celery + Redis 异步架构的核心枢纽:
-
定义任务:将同步阻塞操作转化为异步任务。
-
管理状态:通过 Redis 实现任务进度跟踪和结果存储。
-
资源治理:确保临时文件安全创建和清理。
-
错误隔离:单个任务失败不影响其他任务或系统稳定性。
#!/bin/bash# (可选) 激活虚拟环境
# source /path/to/your/venv/bin/activate# 设置必要的环境变量 (如果.env文件不可靠或未被celery_app.py加载)
# export OPENAI_API_KEY="your_openai_key"
# export LLF_KNOWLEDGE_BASES_FOLDER="/full/path/to/your_source_database.db"
# ... 其他环境变量 ...# 将项目根目录添加到PYTHONPATH,确保Celery能找到所有模块
# 假设此脚本在项目根目录运行
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
export PYTHONPATH=$SCRIPT_DIR:$PYTHONPATHecho "PYTHONPATH is set to: $PYTHONPATH"
echo "Current directory: $(pwd)"
echo "Starting Celery worker..."# -A celery_app.celery_app: 指向 Celery 应用实例 (celery_app.py 文件中的 celery_app 对象)
# -l INFO: 日志级别
# -c 1: 并发worker进程数。对于CPU密集型且内部已使用多进程的任务,通常设为1或较小值。
# 如果您的任务主要是IO密集型,可以适当调高。
# --pool=prefork: Celery 的默认执行池模型。
# 对于调试,可以使用 --pool=solo,这样任务会在主进程中串行执行。
# -Q <queue_name>: (可选) 如果您定义了特定的队列,可以在这里指定worker监听哪个队列。
# --hostname=worker1@%h: (可选) 自定义worker名称
export PYTHONPATH=/data/hyq/code/llf/yuanshenqidong/backend:$PYTHONPATH# 设置 tokenizers 并行处理
export TOKENIZERS_PARALLELISM=false# 使用 solo 池来避免 fork 相关问题
celery -A celery_app worker --loglevel=info --pool=solo
这是一个用于 启动 Celery Worker 的 Bash 脚本,主要目的是配置环境并启动 Celery 工作进程来处理异步任务(比如你之前提到的 OCR/PDF 处理任务)。以下是逐行解析:
1. 脚本功能概述
这个脚本的作用是:
-
设置必要的环境变量(如 Python 路径、Tokenizers 并行配置等)。
-
启动 Celery Worker,指定日志级别、并发模式等参数。
-
适配 CPU/IO 密集型任务,通过参数调整优化性能。
2. 关键代码解析
(1)设置 PYTHONPATH
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
export PYTHONPATH=$SCRIPT_DIR:$PYTHONPATH
-
作用:将脚本所在目录(项目根目录)添加到
PYTHONPATH
,确保 Celery 能正确找到项目模块(如celery_app
、tasks.py
)。 -
示例:
如果脚本路径是/project/start_worker.sh
,则PYTHONPATH
会包含/project
,使得import celery_app
能成功。
(2)环境变量配置
export TOKENIZERS_PARALLELISM=false
-
作用:禁用 Hugging Face Tokenizers 的并行处理,避免与 Celery 的多进程冲突(尤其是使用
--pool=prefork
时)。 -
背景:某些 NLP 库(如 Transformers)默认启用多线程,可能与 Celery 的并发模型冲突。
(3)启动 Celery Worker
celery -A celery_app worker --loglevel=info --pool=solo
参数 | 作用 |
---|---|
-A celery_app | 指定 Celery 应用实例(celery_app.py 中定义的 celery_app 对象)。 |
--loglevel=info | 设置日志级别为 INFO ,显示任务执行状态和错误信息。 |
--pool=solo | 使用单进程模式(solo 池),适合调试或 CPU 密集型任务。 |
3. 参数扩展说明
(1)并发模式选择(--pool
)
池类型 | 适用场景 | 示例命令 |
---|---|---|
solo | 单进程,避免多进程问题(调试或简单任务)。 | --pool=solo |
prefork | 多进程(默认),适合 CPU 密集型任务。 | --pool=prefork --concurrency=4 |
gevent | 协程,适合 IO 密集型任务(如 HTTP 请求)。 | --pool=gevent --concurrency=100 |
(2)其他常用参数
参数 | 说明 |
---|---|
-Q queue_name | 指定监听的队列(需与 celery_app.conf.task_queues 配合使用)。 |
--concurrency=4 | 设置 Worker 并发数(默认是 CPU 核心数)。 |
--hostname=worker1@%h | 自定义 Worker 名称(用于监控和区分多个 Worker)。 |
4. 典型使用场景
场景 1:调试任务
# 单进程运行,日志更清晰
celery -A celery_app worker --loglevel=debug --pool=solo
场景 2:生产环境(CPU 密集型)
# 4 个进程并发,使用 prefork 池
celery -A celery_app worker --loglevel=info --pool=prefork --concurrency=4
场景 3:生产环境(IO 密集型)
# 100 个协程,适合大量 HTTP/DB 请求
celery -A celery_app worker --loglevel=info --pool=gevent --concurrency=100
5. 与你的 Celery + Redis 架构的关系
-
任务流转:
-
Web 服务通过
tasks.process_resumes_task.delay()
将任务发送到 Redis。 -
此脚本启动的 Worker 从 Redis 拉取任务并执行。
-
-
日志关联:
Worker 的日志会显示任务 ID(如[task_id] Processing...
),与tasks.py
中的日志对应。 -
资源隔离:
如果任务涉及 GPU 或大量内存,建议使用--concurrency=1
避免资源竞争。
6. 可能遇到的问题
问题 1:ImportError: No module named 'celery_app'
-
原因:
PYTHONPATH
未正确设置,Celery 找不到模块。 -
解决:确保脚本在项目根目录运行,或手动指定路径:
export PYTHONPATH=/path/to/project_root:$PYTHONPATH
问题 2:Worker 卡死或无响应
-
原因:任务代码有阻塞操作(如死锁)或资源耗尽。
-
解决:
-
使用
--pool=solo
调试任务逻辑。 -
限制并发数(
--concurrency=1
)。
-
总结
-
这个脚本是 Celery Worker 的启动器,核心是通过参数控制并发模型和日志。
-
关键配置:
-
PYTHONPATH
:确保模块可导入。 -
--pool
:选择适合任务类型的并发模式。 -
TOKENIZERS_PARALLELISM=false
:避免 NLP 库与 Celery 的冲突。
-
-
扩展性:可通过参数适配不同场景(如增加并发数、多队列优先级)。
Redis 和 Celery 的协作流程
1. Redis 的作用(端口 6379
)
-
Redis 的角色:
-
只是一个 消息代理(Broker) 和 结果存储(Backend),不执行任何任务逻辑(包括 OCR)。
-
它仅负责:
-
接收 Celery 发布的任务消息(存储到队列)。
-
将任务分发给 Celery Worker。
-
存储任务执行结果(如果配置了 Redis 作为 Backend)。
-
-
-
端口
6379
:-
这是 Redis 服务的默认端口,Celery 通过
redis://localhost:6379/0
连接到 Redis。 -
数字
0
表示使用 Redis 的 第 0 号数据库(Redis 支持多数据库,默认 16 个)。
-
2. 完整任务流程
关键步骤说明:
-
Web 发布任务:
-
调用
task.delay()
时,Celery 将任务序列化后发送到 Redis 的队列(默认键为celery
)。 -
Redis 只存储消息,不关心内容(消息包含函数名、参数等)。
-
-
Worker 消费任务:
-
Celery Worker 主动从 Redis 拉取任务消息。
-
Worker 在自己的进程中 调用 Python 函数(如
process_ocr
)执行 OCR 逻辑。
-
-
结果存储:
-
如果配置了
backend='redis://...'
,Worker 会将结果写回 Redis(键如celery-task-meta-<task_id>
)。 -
Redis 依然只是存储数据,不参与计算。
-
3. 常见误解澄清
误解 1:Redis 执行 OCR 任务
-
错误:认为 Redis 会解析任务内容并执行 OCR。
-
事实:Redis 只是消息中转站,实际执行者是 Celery Worker 中的 Python 代码。
误解 2:端口 6379
处理业务逻辑
-
错误:认为
6379
端口“运行”OCR。 -
事实:
6379
仅用于通信,任务在 Worker 的进程中执行。
误解 3:Redis 需要高性能 CPU
-
错误:为加速 OCR 任务,提升 Redis 的 CPU 资源。
-
事实:Redis 的瓶颈通常是内存和网络 I/O,OCR 的 CPU 压力在 Worker 上。
邮局与快递员(最经典比喻)
-
Redis(邮局):
-
你(Web服务器)把包裹(任务)送到邮局(Redis),邮局只是暂存包裹,不负责处理包裹内容。
-
每个包裹上有地址(队列名称)和收件人(任务函数名)。
-
-
Celery Worker(快递员):
-
快递员从邮局取包裹,按地址派送(执行任务)。
-
快递员自己拆包裹、处理内容(如OCR识别),最后把回执(结果)放回邮局。
-
-
关键点:
-
邮局(Redis)只负责中转,快递员(Worker)才是干活的人。
-
如果快递员太多(并发过高),邮局可能拥堵,但邮局本身不参与搬运。
-
相关文章:

【高并发】Celery + Redis异步任务队列方案提高OCR任务时的并发
线程池处理OCR仍然会阻塞请求的原因主要有以下几点,以及为什么CeleryRedis是更好的解决方案: 1. 线程池的阻塞本质 请求-响应周期未分离:即使使用线程池,HTTP请求仍需要等待线程池任务完成才能返回响应。当所有线程都繁忙时&#…...

2025数维杯数学建模竞赛B题完整参考论文(共38页)(含模型、代码、数据)
2025数维杯数学建模竞赛B题完整参考论文 目录 摘要 一、问题重述 二、问题分析 三、模型假设 四、定义与符号说明 五、 模型建立与求解 5.1问题1 5.1.1问题1思路分析 5.1.2问题1模型建立 5.1.3问题1求解结果 5.2问题2 5.2.1问题2思路分析 5.2.2问题2…...
C#黑魔法:鸭子类型(Duck Typing)
C#黑魔法:鸭子类型(Duck Typing) 如果它走起路来像鸭子,叫起来像鸭子,那么它就是鸭子。 鸭子类型,主要应用于动态语言类型,比如JS、Python等,核心理念为:关注对象的行为(方法或属性…...

AI数据分析中的伪需求场景:现状、挑战与突破路径
在当今企业数字化转型浪潮中,AI数据分析产品如雨后春笋般涌现,但其中存在大量"伪需求场景"——看似创新实则难以落地的功能设计。本文将从技术限制、用户体验和商业价值三个维度,系统分析AI数据分析产品中常见的伪场景现象…...
大尺寸PCB如何重塑通信与新能源产业格局
在5G通信基站与新能源电站的机房内,一块块面积超过600mm600mm的PCB板正悄然推动着技术革命。作为电子设备的核心载体,大尺寸PCB凭借其高密度集成与复杂工艺,成为通信、能源等领域的“隐形功臣”。以猎板PCB为代表的厂商,凭借宽幅曝…...

base64与图片的转换和预览(高阶玩法)
1.完整的功能描述 功能概述 这是一个网页工具,支持用户输入不同格式的图片数据或上传本地图片文件,对图片进行预览、转换为多种格式,并支持导出不同格式的图片数据。 输入方式 1. 文本输入 :用户可以输入 Data URL、公网图片 UR…...

AI客服问答自动生成文章(基于deepseek实现)
小编一直在用AI做网站平台文章的润色或者二创。一直有一个想法,在自己网站加一个AI智能客服,通过文心或者deepseek来智能回答网友提出的问题,这样就能减少很多人工回复的麻烦,提高互动效率。 开发背景 其实很多网友提出的问题非…...
Langchain、RAG、Agent相关
ChatBot-销售型机器人 优化点:把相似度低于10条的请求Query打印出来。 RAG 类型:RAG、Latent RAG(产生一个回答,再用回答进行召回)、Logit RAG、Speculative RAG 个人感觉RAG召回可以分成3种:一种是que…...

Spring Web MVC基础理论和使用
目录 什么是MVC 什么是SpringMVC SpringMVC基础使用 建立连接 RequestMapping介绍 请求 传递参数 传递对象 参数重命名 传递数组 传递JSON数据 获取URL中参数 上传文件 获取Cookie/Session 获取Header 响应 返回静态页面 RestController和Controller的区别 返…...

课程审核流程揭秘:确保内容合规与用户体验
业务流程 为什么课程审核通过才可以发布呢? 这样做为了防止课程信息有违规情况,课程信息不完善对网站用户体验也不好,课程审核不仅起到监督作用,也是 帮助教学机构规范使用平台的手段。 如果流程复杂用工作流 说明如下ÿ…...

Mac电脑,idea突然文件都展示成了文本格式,导致ts,tsx文件都不能正常加载或提示异常,解决方案详细说明如下
有一天使用clean my mac软件清理电脑 突然发现idea出现了文件都以文本格式展示,如图所示 然后就卸载,计划重新安装,安装了好几个版本,并且setting->file types怎么设置都展示不对,考虑是否idea没卸载干净ÿ…...

HarmonyOS开发-组件市场
1. HarmonyOS开发-组件市场 HarmonyOS NEXT开源组件市场是一个独立的插件,需通过DevEco Studio进行安装,可以点击下载,无需解压,直接通过zip进行安装,具体安装和使用方法可参考HarmonyOsNEXT组件市场使用说明。Harmony…...
【Python 列表(List)】
Python 中的列表(List)是最常用、最灵活的有序数据集合,支持动态增删改查操作。以下是列表的核心知识点: 一、基础特性 有序性:元素按插入顺序存储可变性:支持增删改操作允许重复:可存储重复元…...

vison transformer vit 论文阅读
An Image is Worth 16x16 Words 20年的论文看成10年的哈斯我了 [2010.11929] 一张图像胜过 16x16 个单词:用于大规模图像识别的转换器 --- [2010.11929] An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale 为什么transformer好训练&am…...
图像移动图像归类代码
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 图像移动图像归类代码 import os import shutilvalue_dir rJ:\IDM_data\cls_chinese_medicine\cls_chinese_medicine\traintrain_dir rJ:\IDM_data\cls_chinese_medicine\c…...
Day 15 训练
Day 15 对鸢尾花数据集进行处理,特征可视化,贝叶斯优化随机森林,Shap解释1. 导入必要的库2. 设置中文字体3. 加载数据集4. 查看数据5. 数据准备6. 贝叶斯优化随机森林7. 评估结果8. 绘制箱形图9. 绘制特征相关性热力图10. SHAP模型解释总结 对…...
Vue Router 3 使用详解:从零构建嵌套路由页面
Vue Router 是 Vue.js 官方的路由管理器,常用于构建单页面应用(SPA)。本文将手把手带你完成 vue-router3.6.5 的基本配置,并实现一个带有嵌套路由的页面结构。本文适用于 Vue 2.x 项目 一、安装 vue-router3.6.5 npm install vue…...

物理服务器紧急救援:CentOS系统密码重置全流程实战指南
前言 在企业IT运维实践中,物理服务器密码丢失是典型的"低概率高风险"事件。某金融科技公司曾因核心服务器密码遗失导致业务中断36小时,直接损失超过800万元。这起真实案例揭示了系统密码管理的关键性——当承载重要业务的物理服务器遭遇密码丢…...
第J7周:对于ResNeXt-50算法的思考
目录 思考 一、代码功能分析 1. 构建 shortcut 分支(残差连接的旁路) 2. 主路径的第一层卷积(11) 4. 主路径的第三层卷积(11) 5. 残差连接 激活函数 二、问题分析总结:残差结构中通道数不一致的…...
华为云短信接入实现示例
1)构建Springboot项目 2) 添加依赖 <dependency><groupId>com.huawei.apigateway</groupId><artifactId>java-sdk-core</artifactId><version>3.2.4</version> </dependency> 3) 配置文件 huaweiyun:sms:url:…...

Linux系统下使用Kafka和Zookeeper
Apache Kafka 是一个分布式流处理平台,最初由 LinkedIn 开发,后来成为 Apache 软件基金会的顶级项目。它具有高吞吐量、可扩展性、持久性、容错性等特点,主要用于处理实时数据流。 Linux系统下使用Kafka 1.安装 Java Kafka 和 Zookeeper 都是基于 Java 开发的,所以需要先…...

Unity按钮事件冒泡
今天unity写程序时,我做了一个透明按钮,没图片,只绑了点击事件,把子对象文字组件也删了,空留一个透明按钮,此时运行时点击按钮是没有反应的,网上的教程说必须指定target graphic(目标…...

指令图像编辑模型:ICEdit-MoE-LoRA
ICEdit-MoE-LoRA 一、研究背景与目标 In-Context Edit 是一种新颖的基于指令的图像编辑方法,旨在实现与现有最佳方法相当甚至更优的编辑效果。传统图像编辑技术在处理复杂指令时存在一定局限性,尤其是在多轮编辑任务中,结果的准确性和连贯性…...
第十六届蓝桥杯 2025 C/C++B组第一轮省赛 全部题解(未完结)
目录 前言: 试题A:移动距离 试题C:可分解的正整数 试题D:产值调整 试题E:画展布置 前言: 我参加的是第一轮省赛,说实话第一次参加还是比较紧张的,真到考场上看啥都想打暴力&…...

捌拾叁- 量子傅里叶变换
1. 前言 最近公司地震,现在稍微有点时间继续学习。 看了几个算法,都说是基于 量子傅里叶变换 ,好,就是他了 Quantum Fourier。 2. 傅里叶变换 大学是学通信的,对于傅里叶变换还是有所理解的。其实就是基于一个 时域…...

2.在Openharmony写hello world
原文链接:https://kashima19960.github.io/2025/03/21/openharmony/2.在Openharmony写hello%20world/ 前言 Openharmony 的第一个官方例程的是教你在Hi3861上编写hello world程序,这个例程相当简单编写 Hello World”程序,而且步骤也很省略&…...

STM32外设-串口UART
STM32外设-串口UART 一,串口简介二,串口基础概念1,什么是同步和异步/UART与USART对比2,串行与并行3,波特率 (Baud Rate)4,数据帧 (Data Frame)5,TX 和 RX 三,硬件连接1,u…...
计算机视觉与深度学习 | 视觉+激光雷达+惯惯性SLAM算法汇总(原理,公式,代码)
视觉+激光雷达+惯性 1. LVI-SAM(Laser-Visual-Inertial SLAM)2. MMF-LVINS(Multi-Modal Feature-based LVINS)3. FAST-LIVO(Fast LiDAR-Inertial-Visual Odometry)4. CamVox5. Coco-LIC6. FAST-LIO27. SLICT(Surfel-Based LiDAR-Inertial Mapping)总结与对比以下是对视…...

MCU存储系统架构解析
今天和大家分享一下MCU存储器层次结构的设计思路。这种分层存储架构通过整合不同特性的存储单元,能够很好地平衡性能与成本需求。 首先是寄存器层,它直接集成在CPU内核里,速度最快(纳秒级),但容量比较小&a…...
使用mindie部署qwen2_vl分析视频
说明 使用mindie1.0部署qwen2_vl_72b模型,可以用来分析图片了。现在想分析视频。看了下mindie文档,支持视频分析的。 错误 错误1:没安装pyav http返回 Error code: 422 - {error: Messages token length must be in (0, 1048576], but g…...