Heruos || Rainux || 花酒锄作田

为何而活? 要到哪去? 该做什么? 在做什么?

FastAPI 执行异步定时任务:基于数据库的分布式锁实现

前言 在 FastAPI 应用中执行定时任务,通常可以选择 celery,但 Celery 相对重量级,且需要依赖 Redis 或 RabbitMQ 等消息队列。当服务规模较小,且原本未使用 Redis 或 RabbitMQ 时,仅为定时任务功能引入这些依赖会增加额外的运维成本。 ...

2026年2月8日 · 9 分钟 · Rainux He

SQLAlchemy 中使用 UPSERT

前言 SQLite 和 PostgreSQL 都支持 UPSERT 操作,即"有则更新,无则新增"。冲突列必须有唯一约束。 语法: PostgreSQL: INSERT ... ON CONFLICT (column) DO UPDATE/NOTHING SQLite: INSERT ... ON CONFLICT(column) DO UPDATE/NOTHING。注意括号位置 场景 PostgreSQL SQLite 说明 基本 UPSERT ON CONFLICT (col) DO UPDATE SET ... ON CONFLICT(col) DO UPDATE SET ... 括号位置略有不同 冲突忽略 ON CONFLICT (col) DO NOTHING ON CONFLICT(col) DO NOTHING 相同 引用新值 EXCLUDED.col excluded.col PostgreSQL 大写,SQLite 小写 返回结果 RETURNING * RETURNING * 相同 条件更新 WHERE condition 不支持 WHERE SQLite 限制 注意事项 冲突列必须有唯一约束 PostgreSQL 和 SQLite 的语法相似,但仍有细微差别。使用原生 SQL 时需要注意。 SQLite 在 UPSERT 时不支持 WHERE 子句,需要改用 CASE 表达式或应用层过滤。 SQLite 3.35+ 版本才支持 RETURNING EXCLUDED 和 RETURNING EXCLUDED EXCLUDED 表示冲突时被拦截的新值。 ...

2026年2月8日 · 6 分钟 · Rainux He

MCP官方Go SDK尝鲜

前言 此前在 MCP 官网就注意到官方提供了 Go SDK,近期由于在 Python 环境下开发 MCP Server 有点"审美疲劳",因此决定使用 Go 语言尝尝鲜。 从个人实际体验来看,Go 语言在并发处理方面确实具有显著优势:无需纠结于同步阻塞、异步事件循环、多进程多线程通信等复杂的并发问题,goroutine 一把梭哈。同时,Go 语言的部署也非常便捷,编译后生成的静态二进制文件具有良好的可移植性,可以在不同环境中直接运行。 然而,这种便利性也伴随着一定的代价。相较于 Python,使用 Go 语言实现 MCP 功能相对复杂一些,开发效率略低。这就是软件工程中的经典权衡了:运行成本与开发成本往往难以兼得,需要根据具体场景进行取舍。 MCP 协议简介 可能都耳熟能详了,但以防还有不熟悉的朋友,先简单介绍下MCP Model Context Protocol (MCP) 是一种标准化的协议,旨在为 AI 模型提供统一的工具调用接口。通过 MCP,开发者可以将各种工具、服务和数据源暴露给 AI 模型,使其能够执行超出基础语言模型能力范围的操作。MCP 支持多种传输协议,包括 HTTP 和 Stdio,为不同场景下的集成提供了灵活性。 一个简单的 MCP Server 示例 MCP 官方 Go SDK 在定义工具(Tool)时,要求明确指定输入参数和输出结果的数据结构。对于功能较为简单的工具,也可以直接使用 any 类型。以下是一个完整的 MCP Server 示例,提供了三个实用工具: getCurrentDatetime:获取当前时间,返回 RFC3339 格式(2006-01-02T15:04:05Z07:00)的时间戳字符串。由于不需要输入参数,因此参数类型定义为 any,输出同样使用 any 类型。 getComputerStatus:获取当前系统的关键信息,包括 CPU 使用率、内存使用情况、系统版本等。该工具接受一个 CPUSampleTime 参数,对应的输入结构体为 GetComputerStatusIn,输出结构体为 GetComputerStatusOut(Go SDK 的示例中通常采用 xxxIn 和 xxxOut 的命名约定来区分工具的输入输出结构体)。 ...

2026年2月7日 · 10 分钟 · Rainux He

Debian 13基于kubeadm和cilium部署单节点kubernetes

前言 在本地虚拟机环境中使用 kubeadm 搭建 Kubernetes 集群是学习和实验的理想选择。考虑到实际应用场景中可能存在的网络限制以及镜像构建需求,本文详细记录了在完全离线环境下部署单节点 Kubernetes 集群的完整过程。通过集成 Harbor 私有镜像仓库,所有 Kubernetes 组件镜像均从本地 Harbor 实例拉取,确保部署过程的可靠性和可重复性。 ...

2026年2月5日 · 8 分钟 · Rainux He

Flask - Tracking ID的设计

前言 在实际业务中,根据 tracking_id 追溯一条请求的完整处理路径是比较常见的需求。借助 Flask 自带的全局对象 g 以及钩子函数,可以很容易地为每条请求添加 tracking_id,并在日志中自动记录。 ...

2026年1月17日 · 7 分钟 · Rainux He

FastAPI - 在异步方法中调用同步方法

前言 在异步方法中直接调用同步方法会阻塞整个事件循环,导致应用在执行同步方法期间无法处理任何其他并发请求,严重影响服务的整体性能和响应能力。 为了解决这个问题,核心思路是将同步方法交给外部线程池或进程池执行,避免阻塞主事件循环。 方法 1:使用 asyncio.to_thread Python 3.9 及以后版本可以使用 asyncio.to_thread 方法,将同步函数运行在独立的线程中,并返回一个可供 await 的协程对象 import asyncio import time from fastapi import FastAPI app = FastAPI() def sync_task(name: str): time.sleep(2) return f"Hello {name}, sync task done!" @app.get("/async-call") async def async_endpoint(): result = await asyncio.to_thread(sync_task, "World") return {"message": result} 方法 2:直接定义同步路由 FastAPI 支持定义同步路由,FastAPI 会自动在一个外部线程池中运行该函数。不过出于代码整体设计和一致性的考虑,不建议在异步项目中混用同步路由。 方法 3:使用 run_in_threadpool FastAPI 基于 Starlette,而 Starlette 提供了一个工具函数 run_in_threadpool,这种方式类似于 asyncio.to_thread,在某些老版本的 FastAPI 或特定的 contextvars 传递场景下更常用。 from fastapi.concurrency import run_in_threadpool @app.get("/method3") async def starlette_endpoint(): result = await run_in_threadpool(sync_task, "Starlette") return {"message": result} 方法 4:使用进程池 对于 CPU 密集型任务,应该使用多进程 ProcessPoolExecutor 来处理 import concurrent.futures import math from fastapi import FastAPI app = FastAPI() # 创建一个全局进程池 executor = concurrent.futures.ProcessPoolExecutor() def cpu_intensive_calculation(n: int): # 模拟重度 CPU 计算 return sum(math.isqrt(i) for i in range(n)) @app.get("/cpu-bound-task") async def cpu_task(): loop = asyncio.get_running_loop() result = await loop.run_in_executor(executor, cpu_intensive_calculation, 10**7) return {"result": result}

2026年1月6日 · 1 分钟 · Rainux He

Mcp-01: 简介与概念

前言 所有示例代码我都上传到 Git Repo 了,有需要的话可以直接 clone。https://github.com/rainuxhe/mcp-examples 简介 MCP(全称为Model Context Protocol,模型上下文协议)是一种面向大模型交互过程的通用上下文协议标准。其核心目标在于为模型构建一个结构化、可控、可扩展的语义执行环境,使语言模型能够在统一的上下文管理体系下进行任务调度、工具调用、资源协作与状态保持,从而突破传统Prompt Engineering在多轮交互、指令组合与行为稳定性方面的瓶颈。 在传统的大模型应用中,模型本身只能被动地接收输入、产生输出,要让它调用外部工具或访问自定义的上下文,就需要在代码里逐条写好 API 调用、认证、错误处理的逻辑,既繁琐又难以维护。MCP的初衷,就是将这些"上下文管理"和"工具调用"能力抽象成一个标准化的通信协议,让大模型应用只需关注"我想用什么资源",由专门的 MCP 服务端来真正执行调用、管理状态、返回结果。 MCP 官方GitHub 有一种说法是,传统的大模型应用叫做Prompt Engineering,而MCP出现后,大模型应用开发应该叫做Context Engineering。传统的提示工程常常依赖于简单的字符串拼接,这种方式有几个问题: 歧义性:模型可能难以区分哪些是指令,哪些是用户输入,哪些是检索到的数据。 提示注入风险:如果提示中包含恶意指令,例如ignore all previous instructions,模型可能被欺骗。 脆弱性:格式的微小变化(比如多一个换行符)都可能导致模型性能下降。 难以维护:当上下文变得更复杂时(例如,多个数据源、工具定义、历史消息),这种拼接方式会变得一团糟(亲身体验,塞了一堆历史消息后,模型的回答越拐越远) 核心概念 Tools(工具) 工具是AI模型可以调用以执行特定操作的函数。它们允许模型与外部系统交互,执行有副作用的操作,如: 调用API获取实时数据 查询或修改数据库 执行代码或脚本 发送邮件或消息 文件操作 工具由模型控制,这意味着AI决定是否以及何时使用它们。工具调用可能会产生副作用,其结果可以反馈到对话中。 Resources(资源) 资源是提供给模型的只读上下文单元(数据源)。它们可以是: 文件内容 数据库记录 API响应 知识库内容 资源由应用程序控制,托管方或开发人员决定公开哪些数据以及如何公开。读取资源没有副作用,类似于仅获取数据的GET请求。资源提供可在需要时注入模型上下文的内容(例如,在问答场景中检索到的文档)。 Prompts(提示模板) 提示模板是可重复使用的提示模板或指令,可以根据需要调用。它们由用户控制或由开发人员预定义。提示可能包含常见任务或指导性工作流程的模板(例如,代码审查模板或问答格式)。 提示模板的关键特性包括: 参数化:支持动态参数输入 资源整合:可嵌入资源上下文供模型参考 多轮交互:支持构建多轮对话流程 统一发现:通过标准接口注册和调用 Sampling(采样) 采样是工具与LLM交互以生成文本的机制。通过采样,工具可以请求LLM生成文本内容,例如生成诗歌、文章或其他文本内容。采样允许工具利用LLM的能力来创建内容,而不仅限于执行预定义的操作。 Elicitation(征询) 征询是一种允许工具向用户请求额外信息或确认的机制。当工具执行过程中需要更多信息才能继续执行时,可以使用征询功能与用户交互。这在处理需要用户确认或提供额外参数的操作时特别有用。 例如,在预订系统中,如果用户请求的日期已满,工具可以征询用户是否愿意选择其他日期。征询机制确保了工具可以在必要时暂停执行,等待用户输入,从而提供更好的用户体验。 征询的关键特性包括: 交互性:允许工具与用户进行双向沟通 验证:可以对用户输入进行验证,确保数据的正确性 可选性:用户可以选择接受、拒绝或取消征询请求 结构化:支持结构化数据输入,便于处理复杂信息 Roots(根) Root表示一次语义执行的起点,携带资源引用、执行目标、响应格式等信息,支持多并发执行流。它作为语义执行的基础输入结构,可以包含多个Prompt和工具,为模型提供完整的上下文环境。 Logging(日志记录) 日志记录是MCP中的一个重要功能,允许服务器和工具向客户端发送日志信息。通过日志记录,开发者可以跟踪工具执行过程、调试问题以及监控系统状态。MCP支持多种日志级别,包括调试(debug)、信息(info)、警告(warning)和错误(error)等。 Notifications(通知) 通知机制允许服务器向客户端发送实时更新信息,例如资源变更、工具列表更新等。通过通知,客户端可以及时了解服务器状态的变化,并相应地更新用户界面或执行其他操作。常见的通知类型包括资源更新通知、工具列表变更通知、提示列表变更通知等。 组件 MCP Server Server 是一个独立的程序或服务,它通过 MCP 协议向 MCP 客户端暴露特定的功能、工具或数据资源。 ...

2025年12月25日 · 1 分钟 · Rainux He