零成本搭建 LLM 轮询服务

方案概览 为了给 side project 提供稳定的 LLM 接口,可以将三款免费服务拼成一套“轻量后端”——数据库用 Aiven 免费 MySQL,推理服务部署在 Hugging Face Space,API 网关由开源的 gpt-load 负责。最终实现了多密钥轮询和降级控制,不再担心某个 Key 被限速。 整体架构: 客户端请求 │ ├─> Hugging Face Space (gpt-load Docker 容器) │ └─ 读取 Aiven MySQL 中的密钥池 └─< LLM 响应(轮询后的结果) 第一步:申请 Aiven MySQL https://aiven.io/ Aiven可以免费部署一台1核1G内存数据库 注册 Aiven 账号后,新建一个 MySQL 服务,免费套餐即可。 进入 Service 页面,记下主机、端口、用户名等连接信息。 这些连接信息建议整理进 .env 文件中的 DATABASE_DSN,容器启动时即可直接读取。 第二步:在 Hugging Face 部署 gpt-load https://huggingface.co/ https://github.com/tbphp/gpt-load 新建一个 Docker Space,镜像选择公共的 ghcr.io/tbphp/gpt-load:latest。 在仓库中上传自己的 Dockerfile 与启动脚本。 ...

2025年8月3日 · 1 分钟 · 205 字

团队 CI/CD 实战流程

环境与分支映射 我们的流水线围绕两类环境搭建: develop 对应测试与联调环境,合并即自动部署。 master 搭配语义化版本标签,用于生产发布。 这样可以保证开发节奏稳定,同时让发布动作有明确的入口。 一次功能上线的节奏 从 develop 开分支:命名遵循 feat/日期-功能点 的格式,例如 feat/20251010-user-login。 本地提交:保持小步快跑,每次 commit 只包含一个意图。 推送与创建 PR:目标分支为 develop,PR 创建后自动触发 CI。 CI 阶段的检查项: - 构建项目(Build) - 代码质量 / Lint - 单元测试 任何一步失败都会阻断合并,这样评审者能聚焦在业务逻辑上。 合并 develop 后的 CD PR 通过评审并执行 “Rebase and merge” 后,自动化流程会: 构建镜像(例如 my-app:dev)。 推送至镜像仓库。 通过 SSH 或脚本部署到测试服务器。 几分钟后就能在联调环境验证实际效果。如果有多服务联动,可以在这里完成前后端互测。 面向生产的发布流程 当测试环境验收完毕,管理员会: 将 develop 合并入 master,并使用 “Squash and merge” 保持整洁历史。 在最新 commit 上创建语义化标签,比如 v1.2.0。 推送标签到远端,触发生产流水线。 流水线脚本会读取标签并执行: git checkout master git pull origin master git tag -a v1.2.0 -m "Release v1.2.0" git push origin v1.2.0 随后: ...

2024年4月25日 · 1 分钟 · 99 字

使用 Nexus 搭建 Maven 私服

为什么自建私服 当团队规模增大,频繁依赖外部 Maven 中央仓库会导致构建迟缓。搭建 Nexus 私服能够: 统一缓存依赖,减少开发者本地下载时间。 对内部制品做版本管控与权限隔离。 支持代理国内镜像,规避网络波动。 安装 Nexus 3 下载 Nexus 3.7 以上版本(需要 JDK 17)。 解压后重点关注两个目录: nexus-3.77.1-01/ # 主程序 sonatype-work/nexus3/ # 数据与配置 核心命令: ./nexus start # 启动 ./nexus stop # 停止 ./nexus status # 查看状态 初始密码位于 sonatype-work/nexus3/admin.password,首次登录后系统会要求修改并删除该文件。 首次登录与端口设置 编辑 nexus-3.77.1-01/etc/nexus-default.properties 可以更改默认端口(示例使用 8090)。随后访问 http://localhost:8090,用 admin + 初始密码登陆后台。 配置代理与仓库 在 Repositories 模块中: 为 maven-central 这类 Proxy 仓库改用阿里云镜像地址 http://maven.aliyun.com/nexus/content/groups/public/。 新建一个 Hosted 仓库(例如 levon-release)存放内部制品。 新建一个 Group 仓库(例如 levon-public),将 Proxy 与 Hosted 仓库组合,统一对外提供地址。 ...

2024年3月15日 · 1 分钟 · 126 字

SPI

为什么是 SPI 做框架开发时,经常需要让外部团队在不改源码的情况下插入自定义逻辑。Java 的 Service Provider Interface(SPI)正是解决这一痛点的经典方案:框架只暴露接口,具体实现由使用者提供,双方通过约定的加载机制解耦。 下图展示了 SPI 的角色关系: 框架接口 --> 服务提供者实现 --> 应用在运行时按需调用 | | | API 定义 resources/META-INF/services ServiceLoader 按需加载 框架负责定义接口与约定,提供者将实现类打包好,ServiceLoader 在运行时装配,两端互不感知彼此细节。 SPI 与 API 的区别 API:框架写好实现,并通过方法开放给调用方使用。 SPI:框架只制定契约,由外部提供实现,框架在运行时回调实现。 因此 SPI 更适合“我要在某个节点上让别人接管”的场景,比如数据库驱动、日志实现、路由策略等。 Java 里的落地步骤 定义接口:在框架模块中写出需要开放的契约。 制定注册文件:在 resources/META-INF/services 目录下,新建以接口全限定名命名的文件,内容是实现类的全限定名。 加载实现:使用 ServiceLoader.load(Interface.class) 遍历可用实现。 按需调用:框架可以根据配置挑选实现,或全部加载做聚合处理。 代码示例: ServiceLoader<MyExtension> loader = ServiceLoader.load(MyExtension.class); for (MyExtension extension : loader) { extension.handle(request); } 这种方式天然支持多实现并存,也可以搭配权重、标签等策略做更细粒度的路由。 经验分享 约定胜于配置:接口命名尽量描述清楚输入输出,让实现方知道该承担的责任。 尽早校验实现:在框架启动阶段做初始化测试,确保缺失依赖时能及时报错。 限制加载范围:按需使用自定义的 ClassLoader,避免整个应用 ClassPath 都被扫描,启动速度更可控。 配合缓存:ServiceLoader 支持懒加载,常驻的服务实现可以二次封装成缓存,减少重复实例化。 适用与注意事项 SPI 并非银弹。它适用于以下场景: ...

2023年7月19日 · 1 分钟 · 82 字

Git 代理配置

使用代理的动机 内网或跨境访问 Git 仓库时,常会遇到延迟高、连接失败、TLS 握手超时等问题。通过为 Git 配置代理,可以复用团队的跳板机、单机 Clash、SS/SSR、V2Ray、企业 HTTP Proxy 等网络能力,加速 git clone、git fetch 等操作。 快速启用 HTTP/HTTPS 代理 最常见的做法是直接写入 Git 配置: # 全局配置(~/.gitconfig) git config --global http.proxy http://127.0.0.1:7890 git config --global https.proxy http://127.0.0.1:7890 如果是单个仓库,去掉 --global,在项目根目录执行即可: # 仅对当前仓库生效 git config http.proxy http://127.0.0.1:7890 git config https.proxy http://127.0.0.1:7890 可借助 git config --global -l | grep proxy 快速确认配置。 配置 SOCKS 代理 与 HTTP/HTTPS 配置类似,只是需要显式声明 socks5: # Clash / V2Ray 等提供 SOCKS 端口时 port=7891 git config --global http.proxy socks5://127.0.0.1:$port git config --global https.proxy socks5://127.0.0.1:$port 部分代理工具会同时提供 HTTP 和 SOCKS 端口,可根据需要选择。 ...

2022年4月19日 · 1 分钟 · 207 字

Git协作常用基础操作(2)

Rebase 与历史整理 交互式 rebase 用于梳理一组提交的顺序、合并或拆分: git rebase -i HEAD~5 常见指令含义: pick:保留提交 reword:修改提交说明 squash:将多条提交压缩为一条 edit:暂停以修改内容 解决冲突后继续 git status # 查看冲突 git add <resolved-file> git rebase --continue git rebase --abort # 放弃本次 rebase 在公共分支谨慎使用 对已推送的分支执行 rebase 会改写历史。如果需要,对团队成员提前同步,并使用 git push --force-with-lease,减少误覆盖他人提交的风险。 变基 + 合并的组合拳 git pull --rebase:获取远程提交并把本地提交“平铺”在其后,减少无意义的合并节点。 git rebase origin/master:在功能分支上更新 master 的最新代码。 git merge --no-ff:保留合并节点,便于追踪特性分支的历史。 Bisect:二分定位缺陷 当某个时间段引入了 bug,可用 git bisect 快速锁定问题提交。 git bisect start git bisect bad HEAD # 标记当前为坏提交 git bisect good v1.1.0 # 标记已知正常的版本 随后按提示运行测试: ...

2022年4月14日 · 2 分钟 · 276 字

Git协作常用基础操作(1)

配置与巡检 新环境初次使用前,建议逐层检查配置来源,避免混入旧账户信息: git config --system --list # 系统级配置 git config --global --list # 当前用户配置 git config --local --list # 仓库内配置 git config --list --show-origin # 查看值来源 设置签名信息与常用别名: git config --global user.name "Team Member" git config --global user.email "[email protected]" git config --global core.editor "code --wait" git config --global alias.st status .gitignore_global 用于跨项目忽略临时文件: git config --global core.excludesfile ~/.gitignore_global echo '.DS_Store' >> ~/.gitignore_global 工作区到提交的路径 掌握工作区 → 暂存区 → 本地仓库的跃迁方式: git status -sb # 简明状态 git add <file> # 添加单个文件 git add . # 批量添加 git restore --staged <file> # 从暂存区撤回 git restore <file> # 丢弃工作区修改 commit规范 Conventional Commits 风格 ...

2022年4月13日 · 2 分钟 · 383 字

表驱动法

表驱动法是什么 表驱动法(Table-Driven Design)本质上是“以数据替代条件判断”。当规则数量不断增加、条件分支越来越难维护时,将规则整理到表中,再根据输入做查找,可以明显降低代码复杂度。 在业务系统中常见的例子包括:状态迁移表、权限矩阵、优惠券策略映射等。接下来用三个小程序演示这个思路。 案例一:石头剪刀布 传统写法往往是一串 if/else 或 switch。使用表驱动法,只需维护一份胜利映射: static const uint32_t WINNING_MOVES[] = { 0, // 占位,下标从 1 开始 SCISSORS, // ROCK 胜过 SCISSORS PAPER, // SCISSORS 胜过 PAPER ROCK // PAPER 胜过 ROCK }; bool player_wins(uint32_t player, uint32_t opponent) { if (player == opponent) { return false; // 平局视为未胜 } return WINNING_MOVES[player] == opponent; } 一旦要扩展玩法(例如加入“蜥蜴、斯波克”),只需补充映射表即可。 案例二:分数评级 查表法让分数转换成等级更直观: char make_grade(uint32_t score) { if (score > 100) { return 'X'; } static const char GRADES[] = { 'F','F','F','F','F','F','D','C','B','A','A' }; return GRADES[score / 10]; } 数组下标对应分段区间,使得规则一目了然。 案例三:月份天数 结合闰年判断,也可以把每月天数放进表里: uint32_t days_in_month(uint32_t year, uint32_t month) { static const uint32_t DAYS_COMMON_YEAR[] = { 31,28,31,30,31,30,31,31,30,31,30,31 }; static const uint32_t DAYS_LEAP_YEAR[] = { 31,29,31,30,31,30,31,31,30,31,30,31 }; bool leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); return leap ? DAYS_LEAP_YEAR[month - 1] : DAYS_COMMON_YEAR[month - 1]; } 通过两份数组快速选取结果,避免在每次判断中重复写闰年的逻辑。 使用心得 先画表,再写代码:把所有输入、输出、边界情况列出来,确认是否覆盖完整。 表结构易于维护:使用配置文件或数据库存储规则,支持非工程师的运营同事调整。 注意缓存与性能:大型表可以放在内存中,或搭配哈希索引来加速查询。 保持单一职责:逻辑层只负责查表,不要混入过多的业务判断。 适用场景 多条件组合导致的“条件爆炸”。 规则相对稳定,但需要频繁调整内容。 需要将规则交给业务人员维护。 总结 表驱动法不是新的概念,却是控制复杂度的可靠武器。将分支逻辑转化为数据结构,再通过查表解释业务规则,代码更短、更易测、更容易交接。 ...

2021年12月14日 · 1 分钟 · 153 字

Vim 上手指南与常用操作

为什么坚持使用 Vim 写脚本、改配置或连上服务器时,我更愿意使用 Vim。原因很简单:全部操作都围绕键盘展开,速度一旦练起来,效率远超图形化编辑器。这份笔记记录了我日常最常用的命令,帮助保持手感。 模式思维 Vim 的精髓在于不同模式: 普通模式(Normal):浏览、操作文本的默认模式。 插入模式(Insert):输入内容,按 i、a、o 等进入。 可视模式(Visual):选择文本,按 v(字符)、V(整行)或 Ctrl+v(块)。 命令行模式(Command-line):按 : 触发,执行保存、替换等命令。 牢记 “Esc 回到普通模式,再决定下一步” 的节奏,就不会迷路。 启动与保存 vim file.txt # 打开或新建文件 vim . # 浏览当前目录 常见命令: :w 保存 :q 退出 :wq 保存退出 :q! 不保存强退 光标移动 把方向键忘掉,用 hjkl 建立肌肉记忆: k h l j 扩展移动: w / b / e 单词粒度跳转 0 / ^ / $ 行首、文本首、行尾 <number>gg 跳到指定行 G / gg 文件末尾 / 头部 { / } 按段落跳转 Ctrl+f / Ctrl+b 翻页 Ctrl+e / Ctrl+y 向上/下滚动单行 编辑与撤销 i 在光标前插入 a 在光标后插入 o 另起一行 x 删除当前位置 r 替换当前字符 R 连续替换直到 Esc dd 删除整行(同时拷贝) dw 删除一个单词 u 撤销 Ctrl+r 重做 复制粘贴与可视模式 yy 复制整行 p 粘贴 vaw 选中一个单词 vab 选中括号内文本 vaB 选中大括号内文本 v< / v> 批量缩进或回退缩进 v~ / vU / vu 大小写转换 可视模式配合 = 可以快速对齐代码块。 ...

2021年6月13日 · 1 分钟 · 183 字