news 2026/4/16 19:29:02

Agent 服务底座学习笔记(三):MySQL、Redis 与异步任务到底该怎么配合

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent 服务底座学习笔记(三):MySQL、Redis 与异步任务到底该怎么配合

前言

到了这一部分,真正在从“会写接口”走向“理解后端系统”。

真正有系统感的后端服务,除了搭接口以外,往往还要解决下面这些问题:

  • 哪些数据需要长期保存?
  • 哪些数据只是临时状态,变化很快?
  • 哪些任务不适合让用户一直同步等待?
  • 提交任务之后,系统如何告诉前端当前进度?
  • 如果任务失败了,如何知道它失败在哪一步?

这些问题一出现,后端开发就不再只是“写几个接口”那么简单了,而会开始进入更真实的系统设计层面。

所以这篇文章不只写“数据库怎么连、Redis 怎么用、异步任务怎么起”,而是想从更完整的后端视角,把这三个部分的分工和协作讲清楚。


一、为什么后端服务不能什么都同步做完

这部分内容如果只从技术名词看,很容易觉得它们彼此没有关系:

  • MySQL
  • Redis
  • 异步任务

但如果从真实服务场景出发,这三者其实是在回应同一个问题:当一个服务开始变得更真实之后,数据和长任务应该如何组织?

我们可以先想一个很常见的场景,假设你写了一个 AI/Agent 服务,用户发来一个请求,服务要做的事情可能包括:

  • 记录这次请求内容
  • 调模型生成结果
  • 处理文件
  • 记录调用过程
  • 可能还要执行一段比较长的后续流程

如果这些事情都硬塞在一次请求里同步完成,会怎样?最直观的几个问题是:

  • 用户要一直等
  • 接口响应时间会很长
  • 超时风险变高
  • 中间过程不可见
  • 失败后不好排查
  • 结果和状态不好管理

这时候你就会发现:后端服务不能只靠“接口函数里把事情做完”这一种模式。

必须开始区分几类东西:

  1. 要长期保留的数据。

    比如交互记录、调用记录、任务执行痕迹,这些东西不能请求结束就消失。

  2. 服务运行过程中的状态数据。

    比如某个任务当前是排队中、执行中还是失败了,这种数据变化快,但很重要。

  3. 执行时间比较长的任务。

    这类任务不适合一直卡住主请求,而更适合拆成“先提交,再查询”。

从这个角度重新看 MySQL、Redis 和异步任务时,它们的角色就会一下子清楚很多:

  • MySQL 更适合处理长期结构化数据

  • Redis 更适合处理快速变化的状态数据

  • 异步任务模型更适合处理长流程执行

所以这部分内容真正学的不是三个工具,而是:一个后端系统如何把“历史数据”“当前状态”“长任务执行”分开管理。


二、为什么有些数据适合放数据库,而不是放内存里就算了

很多人一开始做项目时,会下意识地把“数据存起来”理解成一件很简单的事,比如先放在内存里,或者先随便写个列表、字典保存一下。在 demo 阶段,这样当然能跑。但只要你开始稍微认真一点做服务,就会发现有些数据天然就不适合只存在内存里。

比如下面这些数据:

  • 用户发过什么请求
  • 系统回复过什么
  • 某次模型调用用了什么参数
  • 某次任务执行到了第几步
  • 某次任务失败时的错误信息

这类数据有一个共同点:它们不只是“当下处理要用”,而是以后还可能要查、要追踪、要分析。

1. 数据库更适合承载“历史沉淀”

例如,一条交互记录很适合长成这样:

class InteractionRecord: id: int trace_id: str interaction_type: str prompt: str response_preview: str created_at: datetime

这类数据的特点非常典型:

  • 结构相对稳定
  • 字段清晰
  • 希望长期保存
  • 后续可能需要查询历史

如果这些数据只放在内存里,一旦服务重启,记录就全没了。而一旦放进数据库,你就可以做很多后续事情:

  • 查最近 20 条交互
  • 按 trace id 定位某次请求
  • 看某类交互出现得多不多
  • 后续做分析和可视化

2. 数据库里不一定只存“业务主数据”

很多人刚接触数据库时,会觉得数据库主要就是存用户表、订单表、商品表这种“主业务数据”。但在后端服务里,数据库也很适合存一些“过程型记录”。

例如,除了交互记录,还可以记录:

  • LLM 调用记录
  • Agent 执行步骤记录
  • 文档元信息
  • 用户记忆信息

这类数据虽然不一定直接给用户看,但它们对排查、分析、复盘非常有价值。

3. Repository 层到底在解决什么问题

当项目开始接数据库时,另一个很容易踩的坑就是:路由层或 service 层直接到处写 SQL。短期看似乎很方便,但后面会出现几个问题:

  • SQL 到处散
  • 数据访问方式不统一
  • 业务逻辑和数据访问耦在一起
  • 改动和排查都变麻烦

所以更合理的方式通常是:把数据库访问逻辑收口到 repository 层。

比如一条“写入交互记录”的逻辑,可以长成这样:

class InteractionRepository: async def add_record( self, trace_id: str, interaction_type: str, prompt: str, response_preview: str, ) -> None: await connection.execute( """ INSERT INTO interaction_records ( trace_id, interaction_type, prompt, response_preview, created_at ) VALUES (?, ?, ?, ?, ?) """, (trace_id, interaction_type, prompt, response_preview, utcnow()), ) await connection.commit()

这里最重要的不是 SQL 本身,而是分层思路

  • service 层只关心“我要记录一次交互”
  • repository 层负责“怎么把它写进数据库”

这个边界一旦建立起来,整个服务会清楚很多。所以这一部分真正要学会的是:持久化数据应该有专门的数据访问层,而不是在业务代码里到处乱写。


三、为什么不是所有数据都应该进 MySQL:Redis 的角色到底是什么

理解了数据库之后,很多人会自然问一个问题:“既然数据库也能存数据,那 Redis 到底多出来是干什么的?”

答案并不在于“Redis 更快”这么简单,而在于:不同数据的性质不同。

数据库更适合“长期结构化数据”,而 Redis 更适合“变化快、查询方式简单、主要关注当前值”的数据。

而任务状态,正是最典型的例子:

1. 任务状态为什么更像“看板数据”

假设你有一个后台任务,它的状态可能会这样变化:

  • queued
  • running
  • waiting_tool
  • success
  • failed

你会发现,这类数据和交互记录非常不一样。交互记录更像“历史档案”:

  • 写进去了就留着
  • 后面偶尔查
  • 重点是沉淀历史

而任务状态更像“当前看板”:

  • 更新很频繁
  • 查询通常按 task id 直接查
  • 更关心此刻状态
  • 更像运行时数据

所以这时候 Redis 的角色就很清楚了:它不是数据库的替代品,而是状态管理的好位置。

2. 一个最小任务状态存储是什么样

例如可以设计一个任务后端抽象:

class TaskBackend(ABC): async def create(self) -> TaskRecord: ... async def get(self, task_id: str) -> TaskRecord | None: ... async def update(self, record: TaskRecord) -> None: ...

然后 Redis 版本的实现会更像这样:

class RedisTaskBackend(TaskBackend): def __init__(self, redis_url: str): self.client = Redis.from_url(redis_url, decode_responses=True) @staticmethod def _key(task_id: str) -> str: return f"task:{task_id}" async def get(self, task_id: str) -> TaskRecord | None: raw = await self.client.get(self._key(task_id)) if raw is None: return None return TaskRecord.model_validate_json(raw) async def update(self, record: TaskRecord) -> None: await self.client.set(self._key(record.task_id), record.model_dump_json())

这段代码背后的设计非常直观:

  • 任务按task:{task_id}这样的 key 保存
  • 每次查询都可以直接按 key 拿
  • 每次状态更新都可以直接覆盖最新快照

四、异步任务模型真正重要的不是“后台跑”,而是“状态可描述”

很多人第一次接触异步任务时,会把它理解成:“把某个函数放后台执行一下。”

但真正重要的问题其实是:当一个任务被放到后台执行后,你如何知道它现在怎么样了?
也就是说,异步任务系统真正的核心,不只是“异步”,而是:

  • 任务有身份
  • 任务有状态
  • 任务有进度
  • 任务有结果
  • 任务有失败信息

1. 一个任务记录应该至少长什么样

from datetime import datetime from typing import Literal from pydantic import BaseModel, Field TaskStatus = Literal["queued", "running", "waiting_tool", "success", "failed"] class TaskStep(BaseModel): step_no: int title: str status: Literal["running", "success", "failed"] detail: str started_at: datetime finished_at: datetime | None = None duration_ms: int | None = None class TaskRecord(BaseModel): task_id: str status: TaskStatus trace_id: str | None = None current_step: int = 0 provider: str | None = None result: str | None = None result_data: dict | None = None error: str | None = None steps: list[TaskStep] = Field(default_factory=list) created_at: datetime updated_at: datetime

可以看到,一条任务记录不只是“有个 id”,它还明确描述了:

  • 当前状态
  • 当前跑到第几步
  • 结果是什么
  • 错误是什么
  • 每一步做了什么
  • 创建和更新时间

这让我真正开始理解:后台任务最重要的不是“任务已经丢出去了”,而是“任务过程是可见的”。

2. 为什么长任务不能强塞在一次请求里

异步任务之所以存在,通常不是因为大家喜欢把事情搞复杂,而是因为有些任务天然不适合同步做完。

例如:

  • 长文本生成
  • 文件解析
  • 索引构建
  • 多步骤工具调用
  • 后台批量处理

如果这些流程都放在一次请求里同步做完,典型问题就是:

  • 用户一直等
  • 接口很容易超时
  • 失败时很难表达中间状态
  • 后续重试和排查都很别扭

所以更合理的方式通常是:

  1. 先提交任务
  2. 返回 task id
  3. 后台继续执行
  4. 前端或调用方再查询状态

普通同步接口更像:

  • 请求进来
  • 立刻做完
  • 返回最终结果

任务型接口更像:

  • 请求进来
  • 服务先接单
  • 返回“我已经收到了”
  • 后续再查执行结果

这也是为什么任务提交接口通常会返回:202 Accepted。它表达的不是“已经做完了”,而是:你的请求我接收了,但真正执行还在继续。

3. 一个最小任务提交流程是什么样

class TaskService: async def submit_chat_task(self, payload: ChatRequest) -> TaskRecord: record = await self.task_backend.create() record.trace_id = uuid4().hex await self.task_backend.update(record) asyncio.create_task(self._run_chat_task(record.task_id, payload)) return record

这几行代码虽然不长,但已经把异步任务的核心流程说明白了:

  1. 先创建任务记录。
  2. 给这条任务一个 trace_id。
  3. 把任务状态写回存储层。
  4. 后台启动真正执行逻辑。
  5. 当前请求先返回 task 记录。

4. 任务执行时为什么状态流转特别重要

再看后台执行部分:

async def _run_chat_task(self, task_id: str, payload: ChatRequest) -> None: record = await self.task_backend.get(task_id) if record is None: return record.status = "running" await self.task_backend.update(record) try: response = await self.agent_runner.run(...) record.status = "success" record.result = response.reply record.result_data = response.structured_data except Exception as exc: record.status = "failed" record.error = str(exc) await self.task_backend.update(record)

这段代码想表达的是,一条任务不是“有或没有”这么简单,而是会经历状态变化:

  • 刚提交时是queued
  • 真正开始跑时变成running
  • 成功结束时变成success
  • 出错时变成failed

如果中间还涉及多步骤过程,甚至可以进一步细化成:

  • 当前跑到第几步
  • 这一步成功还是失败
  • 每一步花了多久

所以任务系统真正难的地方是:怎么把任务从黑盒,变成一个可被查询和理解的状态机。


五、把 MySQL、Redis 和异步任务重新串起来:它们到底是怎么协作的

可以想象一个比较真实的场景:用户发起了一个比较长的聊天或处理请求,系统不想让他一直同步等待,于是采用任务模式。

整个过程大概是这样:

1. 前端提交任务

接口接收到请求后,不是直接同步把全部逻辑做完,而是先调用任务服务创建一条任务记录。

这时任务后端会生成:

  • task_id
  • 初始状态queued
  • 创建时间

然后接口立即返回:

  • task_id
    • 当前状态
  • trace_id

前端拿到这些数据后,就知道这件事已经被系统接收了。

2. 任务状态放进 Redis 或内存后端

这时最关键的不是把最终结果立刻算出来,而是:先让这条任务变得可查。

所以任务状态会先进入任务后端。如果是本地练习环境,可能存内存;如果更接近真实环境,就更适合存 Redis。

这时 Redis 承担的角色就是:“让我随时能按 task id 查到当前状态。”

3. 后台开始真正执行任务

后台协程开始跑真正的业务流程,一开始会把状态从queued改成running

如果任务执行过程中有多个步骤,还会不断更新:

  • 当前第几步
  • 这一步是否成功
  • 中间输出摘要

这让任务变得不再是“只知道还没结束”,而是“知道它进行到哪了”。

4. 过程型结果沉淀进数据库

执行过程中,如果有值得长期保留的信息,例如:

  • 交互摘要
  • 模型调用记录
  • 步骤记录
  • 文档处理记录

这些就更适合落进数据库,这些数据不是为了“当前状态查询”,而是为了:

  • 历史追踪
  • 排查分析
  • 后续复盘

所以这里 MySQL / SQLite 的角色更像:“把这次任务执行过程里值得长期留下来的信息沉淀下来。”

5. 任务结束后状态回写

任务成功了,就把状态更新成success,并把结果写进任务记录。
任务失败了,就把状态更新成failed,并带上错误信息。

此时前端再去查任务状态,就能看到:

  • 已完成还是失败
  • 最终结果是什么
  • 是否有错误信息

这样,一个完整的“提交任务 -> 后台执行 -> 查询状态”的闭环就形成了。

所以你会发现,这三者根本不是割裂的:

  • 数据库负责沉淀历史
  • Redis 负责保存当前状态
  • 异步任务负责组织长流程执行

这就是今天这部分内容真正要建立的系统感。


六、这一部分对后面学习 Agent / RAG / 任务系统有什么帮助

这一块非常重要,因为它决定了后面学更复杂能力时会不会乱。

比如:

1. 对 Agent 长流程执行很重要

Agent 很多时候并不是“一步出答案”,而是:

  • 调模型
  • 调工具
  • 走多步流程
  • 可能中间等待

这天然适合任务状态模型。

2. 对 RAG 文档处理很重要

文档解析、切分、索引构建往往不是瞬时完成的,很适合异步任务化处理。

3. 对系统排查很重要

一旦服务出了问题,你需要的不只是“接口报错了”,而是:

  • 这次请求有没有落记录
  • 任务跑到哪一步失败了
  • 状态什么时候从 running 变 failed

4. 对后端系统感非常重要

一个服务不是几个接口函数的集合,而是由:接口层、数据层、状态层、任务执行层共同组成的系统。


总结

这一部分最重要的,不是我学会了“怎么连数据库”“怎么用 Redis”“怎么起异步任务”,而是我开始真正理解:一个后端系统必须区分历史数据、当前状态和长任务执行。

以前我更容易把服务理解成:请求进来,代码跑完,结果返回。

但现在我会更自然地意识到:

  • 有些数据应该长期沉淀到数据库
  • 有些状态更适合放在 Redis 这样的状态存储里
  • 有些任务不该强塞在主请求里,而应该拆成提交和查询两段

这部分内容真正帮我建立起来的是一种“系统视角”:MySQL、Redis 和异步任务这一层,真正学的不是三个工具,而是一个后端系统如何管理历史、状态和长流程。


下一篇预告

下一篇我会继续复盘 Docker / Linux / Git / 排错这一层:为什么这些看起来不像“业务代码”的能力,反而是后端服务能否真正交付的关键部分。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 19:28:55

Source Han Serif CN 开源字体:专业设计零成本的终极解决方案

Source Han Serif CN 开源字体:专业设计零成本的终极解决方案 【免费下载链接】source-han-serif-ttf Source Han Serif TTF 项目地址: https://gitcode.com/gh_mirrors/so/source-han-serif-ttf 还在为商业项目寻找高质量中文字体而烦恼吗?Sourc…

作者头像 李华
网站建设 2026/4/16 19:27:59

Python自动化抢票脚本:3步搞定大麦网热门演出票务

Python自动化抢票脚本:3步搞定大麦网热门演出票务 【免费下载链接】Automatic_ticket_purchase 大麦网抢票脚本 项目地址: https://gitcode.com/GitHub_Trending/au/Automatic_ticket_purchase 还在为抢不到心仪演唱会门票而烦恼吗?当热门演出开票…

作者头像 李华
网站建设 2026/4/16 19:27:43

5分钟掌握RePKG:Wallpaper Engine资源提取与转换完整指南

5分钟掌握RePKG:Wallpaper Engine资源提取与转换完整指南 【免费下载链接】repkg Wallpaper engine PKG extractor/TEX to image converter 项目地址: https://gitcode.com/gh_mirrors/re/repkg RePKG是一款专为Wallpaper Engine用户设计的强大资源提取工具&…

作者头像 李华
网站建设 2026/4/16 19:27:41

AppPoet:基于LLM提示工程的安卓恶意软件检测

1 研究背景 1.1 问题 Android软件由于其来源特性容易受到hacker攻击 现有的基于传统机器学习的apk检测方法在挖掘软件行为语义信息方面存在局限性,且检测结果缺乏可解释性 1.2 传统机器学习方法 基于字符串 方法:将提取的特征表示成字符串序列&#…

作者头像 李华
网站建设 2026/4/16 19:21:13

嵌入式开发避坑:eMMC HS200/HS400模式下的Sampling Tuning到底怎么工作的?

嵌入式开发实战:eMMC HS200/HS400模式下Sampling Tuning机制深度解析 当你在凌晨三点的实验室里盯着示波器上跳动的波形,试图找出为什么嵌入式系统在高温环境下频繁出现eMMC读写错误时,Sampling Tuning机制可能正是那个被忽略的关键因素。本文…

作者头像 李华
网站建设 2026/4/16 19:20:06

CentOS 5.8下1核2G服务器搭建DNF私服全记录(附资源与常见启动失败排查)

CentOS 5.8下1核2G服务器搭建DNF私服全记录 在低配服务器上搭建游戏私服一直是技术爱好者热衷的挑战。当手头只有1核2G的云服务器,系统还是早已停止维护的CentOS 5.8时,整个过程就变成了一场与硬件限制的博弈。本文将详细记录如何在这样严苛的环境下&…

作者头像 李华