Latest

三台机器部署 ClickHouse 高可用集群实战记录

本文是一份可发布版部署记录。真实 IP、域名、账号、密码、下载链接、业务目录名、机器唯一标识等敏感信息已经替换为占位符。命令中的 <...> 需要按自己的环境替换。 目标与拓扑 这次目标是用三台数据节点部署一套 ClickHouse 高可用集群,拓扑采用: 1 shard x 3 replicas 含义是:集群只有一个逻辑分片,三台机器都保存同一份数据的完整副本。任意一台数据节点宕机时,只要 ClickHouse Keeper 仍然有多数派,剩余节点仍可继续提供读写服务。 规划节点如下: 主机名示例地址角色ch-01<ch-01-ip>ClickHouse Server + ClickHouse Keeperch-02<ch-02-ip>ClickHouse Server + ClickHouse Keeperch-03<ch-03-ip&

By ladydd

折腾记(二):接入火山引擎实时语音 API,家庭语音助手体验直接拉满

接上篇 上一篇用全开源组件(Whisper + Hermes + Edge-TTS)搭了个语音助手,能跑,但体验就是"能用"二字: * 中文识别只有 70 分,方言基本歇菜 * 英文唤醒词"Alexa"喊着别扭 * 说完到回复要等 4-8 秒 * 它说话的时候你插不了嘴 这些问题靠堆开源组件很难根治。于是我去试了火山引擎(字节跳动)的语音服务,结果直接换了条路。 这篇分两段:先讲怎么用火山引擎的 ASR/TTS 替换掉开源组件(小改),再讲怎么上端到端实时语音模型(大改)。 第一段:先把 ASR 和 TTS 换成火山引擎 为什么换 我用豆包输入法的时候发现它语音识别准得离谱。一查,豆包用的就是字节自家的火山引擎 Seed-ASR。开通后有免费额度(

By ladydd

折腾记(一):用全开源组件给家里搭一个语音助手,对接自己的 Hermes Agent

起因 事情是从一块 ESP32-S3 开发板开始的。 我手上有一块 Seeed Studio XIAO ESP32-S3 Sense,带摄像头和麦克风。最初的想法很美好:用这块板子做一个无线语音终端,对着它说话,连到我服务器上跑的 Hermes Agent(一个自托管的 AI agent),让它回答我。 但折腾到一半我突然意识到一件事:我的麦克风、音响、服务器全在家里,为什么要绕一圈用 ESP32?直接把麦克风和音响插到服务器上不就行了? ESP32 那条路(做无线拾音终端)当然也有价值,但那是"为了学嵌入式而学",不是解决问题的最短路径。于是这个项目就从"嵌入式项目"变成了"在服务器上拼一个语音助手"。这篇就记录后者。 教训零:先想清楚你要解决的是什么问题。很多时候最优解比你最初设想的简单得多。 目标

By ladydd

Kiro 的三种代理设置方法:本地、服务端、Remote

作为kiro的骨灰级用户,这篇是我自己折腾 Kiro / Kiro Remote / Ubuntu Server 代理问题后的复盘。 核心不是“怎么配一个代理”,而是先判断:到底是谁在访问外网? 谁访问外网,代理就要配给谁。 0. 先说结论 Kiro 相关代理大概分三类: 场景真正访问外网的进程在哪里代理应该配在哪里本地 KiroWindows / Mac 本机本机 Clash / Proxifier / 系统代理服务端 Kiro / CLIUbuntu Server 上的 shell、CLI、node、kiro 进程Ubuntu 的环境变量,比如 HTTP_PROXY / HTTPS_PROXYKiro Remote远程 Ubuntu 上的 ~/.kiro-server 和 extensionHost远程 Ubuntu 的 Kiro Server

By ladydd

三岁孩子发烧、咳嗽、腹泻这几天:一次家庭应对复盘

这篇不是医学建议,只是记录一次三岁孩子生病期间,作为家长从慌乱到逐渐冷静的全过程。 真正重要的不是“我用了什么神药”,而是:怎么观察,什么时候去医院,怎么避免因为焦虑而乱加药。 一、事情开始:周天第一次发烧 这次孩子生病是从周天开始的。 一开始是发烧。和以前不一样的是,以前孩子很多时候发一次烧,退下来之后状态就慢慢好了。但这一次有点拉扯:用了几次退烧药,体温退下去之后又反复,孩子状态也不是特别稳定。 当时最让我焦虑的不是单纯发烧,而是后面出现了比较明显的咳嗽,尤其是痰音很重。 那种声音对家长来说很折磨。听起来像是喉咙里、气管里全是痰,孩子又不会像大人一样把痰咳出来。于是我开始担心:是不是肺炎?是不是细菌感染?是不是要用抗生素? 后来回头看,这种焦虑很正常,但也最容易导致家长乱判断、乱加药。 二、第一次去医院:急性支气管炎 第一次去医院,医生判断是急性支气管炎。 做了血常规和甲乙流咽拭子。 血常规整体并不吓人:白细胞正常,中性粒细胞没有明显升高,CRP 只是轻度升高。这个结果至少说明一件事:不像典型的严重细菌感染。 甲流、

By ladydd

AI Skill 平台的基础设施设计:Gateway、用户管理、上游代理

背景 上一篇讲了 Skill 本身的设计——用户侧 Skill 包、CLI 层、服务端、展示层。但 Skill 后端不是孤立运行的,它需要一套基础设施来解决:谁能调、调多少、花多少钱、上游怎么保护。 这篇讲的是 Skill 后端之外的那些"不性感但不能没有"的系统。 整体拓扑 用户(CLI) │ │ HTTP + API Key ▼ ┌─────────────────────────────────────────────────────┐ │ Gateway(Go) │ │ 鉴权 → 余额检查 → 用户限流 → 路由转发 │ │ │ │ ┌──────────────────────────────────┐ │ │ │ 用户服务(Python/FastAPI)

By ladydd

我的 AI Skill 架构设计:让 Agent 可靠地完成复杂任务

背景 我做了一套面向 AI Agent 的垂直业务 Skill。 一开始我以为自己只是在做一个“复杂一点的 Skill”。但系统跑通之后,我发现真正有意思的不是业务本身,而是背后的架构问题: 怎么让一个 Agent 可靠地完成一个多步骤、多数据源、需要用户确认、还要生成可交付成果的复杂任务? 如果只是让 Agent 调一个 API,这件事并不难。 难的是: * 任务不是一步完成,而是很多步骤串起来; * 每一步会产生大量中间数据; * 中间需要用户确认,不能完全自动跑到底; * 外部 API 有凭据、限流、异步任务、失败重试; * 最终结果不能只停留在对话窗口里,而要能保存、分享、复查。 这已经不是“给 Agent 一个工具”那么简单了。 更准确地说,我做的是一个: Agent-facing vertical application package

By ladydd

Harness、Skills、Workflow——三个概念的关系和我的选择

Harness、Skills、Workflow:三个概念的关系,以及我现在的选择 做了几个比较重的 AI Skill 之后,我开始重新思考一个问题: 我现在做的东西,到底算 Skill、Workflow,还是 Harness? 一开始我把这三个概念放在同一条线上比较,后来发现这样容易混乱。更准确地说,它们不是同一层级的东西。 * Workflow 是一种确定性流程编排方式。 * Skill 是一种能力封装形态。 * Harness 是一套能力运行与治理框架。 它们不是三选一,也不是谁比谁高级,而是解决不同层级的问题。一个成熟的 Agent 系统里,三者甚至可以同时存在。 一、Workflow:确定性流程编排 Workflow 是最传统、最稳定的工程抽象。 它的核心不是“每一步都完全没有判断”,而是: 流程控制权主要在代码或流程引擎手里。 也就是说,开发者提前定义好: * 有哪些步骤; * 每一步输入是什么; * 每一步输出是什么; * 失败怎么重试; * 哪些分支可以走;

By ladydd

滑动窗口限流:我们在 API Gateway 里是怎么做的

一个真实项目的限流实现记录。不用 Redis,不用令牌桶,纯内存 Go 实现,200 行代码搞定 per-user 限流。 背景 我们有一个多租户的 API Gateway(Go + Gin),后面挂了 4 个业务后端。每个用户通过 API Key 鉴权,不同用户有不同的请求频率限制(RPM,Requests Per Minute)。 需求很简单: * 每个用户独立限流,互不影响 * 限额从数据库读,管理员随时可调 * 超限返回 429,不排队不等待 * 不引入 Redis(单机部署,没必要) 为什么选滑动窗口 常见的限流算法有四种: 算法 优点 缺点 固定窗口 实现最简单 窗口边界有突发问题 滑动窗口

By ladydd

DNS 扩展篇:从会配记录,到真正能排查 DNS 问题

上一篇聊完普通解析、泛域名解析、A 记录、TXT 记录、普通证书和通配证书之后,我对 DNS 的理解已经比以前清楚很多了。 以前我对 DNS 的理解基本停留在: 域名 -> IP 也就是: A 记录 后来才发现,DNS 不只是“把域名解析成 IP”。 它还承担了: 命名 寻址 验证 策略 委派 缓存 排障 这些角色。 这篇算是 DNS 的扩展篇,整理一下接下来还值得补的内容。 目标不是背概念,而是让自己以后遇到 DNS、证书、域名、Caddy、Cloudflare、阿里云解析这些问题时,能知道从哪里下手排查。 一、DNS 不只是

By ladydd

从只懂 A 记录,到重新理解 DNS:普通解析、泛域名和证书验证

最近在折腾 frp、Caddy、内网穿透和域名分流的时候,我突然发现一个问题: 我以前对 DNS 的理解其实挺片面的。 以前我理解 DNS,大概就是: 域名 -> IP 比如: weini.xin -> 服务器 IP www.weini.xin -> 服务器 IP 也就是说,我过去主要只知道 A 记录。 A 记录确实是 DNS 里最常见、最直观的一种记录,但这次深入折腾之后,我发现 DNS 远不只是“域名转 IP”这么简单。 它其实同时参与了: 域名寻址 服务入口命名 泛域名管理 证书验证

By ladydd

自己把自己关在门外:一次 UFW 把 SSH 锁死的排查记录

这次踩了一个挺经典的坑:我在云服务器上折腾 frp 的时候,顺手开了 UFW 防火墙,结果把自己 SSH 入口给挡住了。 服务器没死,服务也没坏,但我就是连不上。 最后发现:不是服务器出问题,是我自己把自己关在了门外。 事情背景 我有一台云服务器,上面跑了一些服务: Caddy / Nginx Docker frp 一些 Go / Python / Node / Bun 服务 最近在折腾 frp,用来做内网穿透和服务分流。 因为涉及端口暴露,我当时想着:“要不顺手把 UFW 开起来,做一层本机防火墙?” 这个想法本身没错,但问题是:我这是一台云服务器。 云服务器本来就有一层安全组,比如阿里云安全组、腾讯云安全组、AWS Security Group 之类的。公网入口本来就可以在云后台控制。 结果我在云安全组之外,

By ladydd

Linux

从 frp 暴露端口,到泛域名 + Caddy,再到 frp Host 分流:一次内网穿透架构的升级

最近我在折腾一个很实际的问题: 我有一台内网机器,上面跑着很多服务,每个服务都有自己的端口。现在我想把其中某些服务暴露到公网,让外部可以访问。 这个需求很常见。 比如我家里或者局域网里有一台 Ubuntu 机器,上面跑着: MCP 服务:8120 API 服务:8122 测试后台:8080 某个 Go 服务:9000 某个 Python 服务:7860 这些服务本身都在内网里,外部是访问不到的。 如果机器没有公网 IP,或者路由器不方便做端口映射,那传统方式就很麻烦。于是我用了 frp。 一开始我对 frp 的理解还比较简单: 它就是一个内网穿透工具,可以把内网端口暴露到公网服务器上。 但真正跑通之后,我对它的理解一步一步升级了。 一、frp 的核心逻辑:公网服务器帮内网机器“代收连接” frp 的结构其实很清晰: 外部用户

By ladydd

部署运维

内网穿透实战:用 frp 把本地服务暴露到公网

记录一次从零开始部署 frp 的完整过程,包括原理理解、架构设计、踩坑排查,以及最终跑通的全流程。 背景 我有一台内网服务器,上面跑着一些本地服务(监听在 8900 端口)。同时我有一台云服务器,有公网 IP,上面已经用 Caddy 跑了一堆服务。 需求很简单:让公网能访问到我内网服务器上的服务。 方案:frp(Fast Reverse Proxy)—— 一个专注于内网穿透的反向代理工具。 frp 是什么 frp 由两个组件构成: * frps(server):部署在有公网 IP 的服务器上 * frpc(client):部署在内网机器上 它的工作原理: 1. frps 在公网服务器上启动,监听一个"控制端口"(默认 7000),等待客户端连接

By ladydd

MCP

传统 SaaS 转向 AI 时代,我目前的一点理解:先把数据能力变成 Agent 可调用的基础设施

最近我一直在思考一个问题:传统 SaaS 到底应该怎么转向 AI? 一开始很容易想到的方向是:给原来的系统加一个 AI 助手。 比如在页面右下角放一个聊天框,让用户可以问数据、生成报告、总结内容、解释指标。这个当然有价值,但我现在越来越觉得,这只是比较表层的一种转型。 真正的变化,可能不是“在 SaaS 里面加 AI”,而是 SaaS 本身的能力形态发生变化。 过去的 SaaS,核心是给人使用。 人登录系统,看页面、点按钮、筛选数据、导出报表、判断问题,然后再去做决策。数据库是给 Web 页面供数的,后端 API 是给前端页面服务的,整个产品的中心是“人如何操作软件”。 但 AI 时代,尤其是 Agent 逐渐发展之后,

By ladydd

Python

对 Python 应用场景的一次重新思考:FastAPI、协程、线程、数据库与任务系统边界

最近在重新设计一个任务系统时,我顺便把自己对 Python,尤其是 CPython 应用场景的理解重新梳理了一遍。 这次讨论的背景是一个典型的异步任务服务: 上游提交任务 API 立即返回 task_id 后台 worker 慢慢执行 用户通过 task_id 查询任务状态 任务主要是 LLM 调用、图片下载、外部 HTTP 请求这类 I/O 型工作。 一开始关注的是队列、Redis、PostgreSQL、worker 并发控制这些问题。但聊到后面,其实更核心的问题变成了: Python 到底应该放在什么位置? 哪些并发适合 Python? 哪些并发不要硬塞给 Python? FastAPI、协程、线程、数据库之间应该怎么分工? 这篇文章就是这次思考的整理。 一、我不想抛弃 Python,

By ladydd

Go

Go 和 Python 的并发模型对比:进程、线程、协程、并发和并行到底怎么理解?

最近我在写 worker 任务系统的时候,重新理解了一遍 Python 和 Go 的并发差异。 以前写 Python,多 worker 经常要考虑: 多进程怎么管理? 日志会不会串? 一个 worker 崩了怎么办? 怎么吃满多核心? 后来换成 Go,发现一个进程里开多个 goroutine worker 就很自然: go worker(1) go worker(2) go worker(3) go worker(4) 日志也好管,状态也好管,而且单进程还能利用多个 CPU 核心。 一开始很容易误会成: Python 不行,Go 行 但更准确的理解应该是: Python 和

By ladydd

Go

Python 进程和 Go 进程的区别:为什么 Go 单进程多 worker 用起来更爽?

最近我在做 worker 任务系统的时候,突然意识到一个很关键的问题: 以前写 Python,多 worker 的时候经常要小心日志串、文件切割乱、时间不好管理。 但是换成 Go 以后,一个进程里开多个 goroutine worker,反而可以比较自然地写到同一个日志文件里。 一开始我以为这是“Python 和 Go 写日志能力不一样”,后来想明白了,核心不是日志本身,而是: Python 常见 worker 模型:多进程 Go 常见 worker 模型:单进程 + 多 goroutine 这背后其实是两个语言在并发模型上的巨大差异。 一、进程、线程、goroutine 先分清楚 先把几个概念捋一下。 进程:操作系统分配资源的单位 线程:CPU 调度执行的基本单位

By ladydd

Go

从 Redis + Channel 到 Redis Stream:一次 Go 任务队列方案的重新理解

在讨论 PostgreSQL queue 方案之后,我又回头看了一下之前自己设想过的 Go + Redis 任务架构。 最早我脑子里的方案大概是: POST 创建任务 Redis 存状态 Go goroutine 直接处理 进程内 channel 控制并发 这个方案很直观,也很符合 Go 的写法。 用户请求进来后,API 生成一个 task_id,把任务状态写进 Redis,然后把任务塞进 Go 的 channel,由后台 goroutine 消费执行。并发控制就靠一个固定大小的 channel 或 worker pool,比如最多 20 个 goroutine 同时执行任务。 这个设计在单进程里确实很舒服。 但当我开始思考多进程、多容器、

By ladydd

架构

Just Use Postgres:用一个数据库吃掉你架构里 80% 的中间件

"What can't Postgres do?" —— 这两年技术圈最流行的一句话 "Use one database, but use it really well." —— Hacker News 上一条 2k+ 赞的评论 写在最前面:一张让人窒息的架构图 先看一张图,看看你眼熟不: [客户端] │ ▼ [API Gateway] ┌─────────────────┼─────────────────┐ ▼ ▼ ▼ [业务服务] [认证服务] [搜索服务] │ │ │ ├──→ MySQL ├──→ Redis ├──→ Elast

By ladydd

架构

从 Redis 到 PostgreSQL:一次需求驱动的任务系统架构升级

最近我对一个 LLM 任务服务做了一次比较完整的架构升级。 这个服务本身并不复杂:上游提交任务,我返回一个 task_id,后续上游再通过 task_id 查询任务状态和结果。任务的执行内容主要是调用 LLM 或相关多模态接口,单个任务耗时通常在几十秒以内。 最开始的实现是比较常见的组合: FastAPI + Redis + Docker API 接到请求后生成任务 ID,把状态写入 Redis,然后由后台逻辑继续执行任务。这个方案在早期足够轻量,开发速度也很快。 但随着需求继续演进,我开始更明确地追求几个能力: 1. 上游可以瞬间提交大量任务 2. API 必须快速返回 task_id 3. 真正执行的任务数量必须全局可控 4. 任务状态要可查询、可恢复、可追踪 5. worker 崩溃后任务不能永久卡死 6. 未来希望减少组件数量,尽量 all

By ladydd

MCP

JSON-RPC / MCP 为什么看起来很像“固定一个 POST 接口”?

理解 MCP、JSON-RPC、Streamable HTTP 的过程中,有一个很自然的联想: 这不就是固定一个 POST 接口,然后传入不同 JSON,根据请求体里的字段返回不同响应吗? 这个直觉是对的。 尤其是当 MCP 走 Streamable HTTP 的时候,表面上看确实很像: POST /mcp 然后请求体里传: { "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "search", "arguments"

By ladydd

MCP

从 MCP 到 JSON-RPC,再到 Streamable HTTP:理清 Agent 工具调用里的协议关系

做 MCP 相关项目时,经常会遇到几个词: MCP JSON-RPC Transport stdio SSE Streamable HTTP 这些词经常一起出现,如果不把层次拆开,很容易混在一起。 这篇文章尝试从工程开发的角度,把它们之间的关系梳理清楚。重点不是翻译标准文档,而是建立一套比较稳定的理解模型。 一、先分清三层:MCP、JSON-RPC、Transport 可以先记住这句话: MCP 负责:Agent 能调用什么能力 JSON-RPC 负责:调用消息长什么样 Transport 负责:消息怎么传过去 也就是: MCP = 协议语义层 JSON-RPC = 消息格式层 Transport = 传输层 更直观一点: MCP: 我要调用工具、读取资源、获取 prompt JSON-RPC: method 是什么,

By ladydd

AI

vLLM 四卡部署 Embedding 模型实战:离线部署、Nginx 负载均衡、FastAPI 256D 网关与 systemd 自启

最近把一套 Embedding 服务完整落地了一遍:4 张显卡分别启动 vLLM 实例,用 Nginx 做统一入口和故障切换,再在上层挂一个 FastAPI 网关,把原始向量统一裁剪成 256 维并归一化,最终形成一套比较完整、可直接对外提供服务的 Embedding 架构。 这篇文章把整个过程完整整理一下,包含环境准备、离线模型部署、多卡启动、Nginx 配置、systemd 开机自启,以及业务网关设计。 整套架构的目标很明确: * 提供标准 HTTP Embeddings API * 支持四卡并行 * 支持统一入口与负载均衡 * 单实例故障时自动 failover * 支持开机自启 * 保留日志,便于运维与统计 一、整体架构 先看整体结构: Client │ ▼ FastAPI Gateway(8681) ← 推荐对外入口 │ ▼ Nginx(

By ladydd
陕公网安备61011302002223号 | 陕ICP备2025083092号