ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Colibri 自托管部署:本地优先归档与中文全文检索实战

Colibri 自托管部署:本地优先归档与中文全文检索实战 三年前我的浏览器书签里躺着四千多条链接真正回看过的不到百分之三两年前我开始把看到的好文章往各种云端笔记里塞结果一半的链接在半年后打开已经是 404。这件事让我意识到真正的问题不是我懒得整理而是我把内容托管在了别人的服务器上。后来我把整套个人阅读归档体系搬到了自己的机器上核心组件就是Colibri—— 一个轻量级、本地优先的开源自托管工具它把收藏、归档、全文检索这三件事压缩进了单进程加单文件数据库的极简架构里。这篇文章不打算复述官方文档里那些一行命令就能跑通的演示我想聊的是从一台干净的主机到一套能连续跑两年不崩、中文搜索秒出结果、数据随时能带走的生产级部署中间要跨过哪些坑。不管你是不懂 Linux 的新手还是已经自建过十来个服务的老玩家下面这些内容应该都能直接用。1. 为什么我要自己搭一套 Colibri需求拆解与选型逻辑1.1 从收藏夹墓地说起三个绕不开的真实痛点先说第一个痛点收藏这个动作的成本太低而回看的成本太高。浏览器自带的书签管理器本质上是个树状文件夹当你存到三百条以上时分类就成为负担于是所有新内容都往待整理里扔这个文件夹最终变成黑洞。我需要的是一个不需要提前分类、事后靠搜索就能定位的工具分类这件事应该交给索引而不是交给我。第二个痛点是平台的存续风险。我曾经用过的一个小众稍后读服务在毫无预警的情况下发邮件通知三个月后关停导出功能只给了一个残缺的 JSON图片附件全部失效。这不是个例任何一个免费服务的商业模型都撑不起长期归档这件事。归档类数据的生命周期通常是十年起步而互联网产品的平均寿命远低于这个数字这两者天生矛盾。第三个痛点是检索质量。很多工具只索引标题和摘要不索引正文你记得某篇文章里提过一句冷启动阶段的留存曲线但搜标题怎么都搜不到。全文检索不是锦上添花它是归档工具唯一真正重要的功能——存进去的东西如果找不出来等于没存。这三点叠加起来结论很简单数据得在我手里检索得在正文层面操作成本得低到我能随手按一下。1.2 为什么最后落在 Colibri 上四类方案的横向对比我把当时能想到的方案都试过一轮下面是真实对比结果。需要说明的是这里面有些取舍是个人偏好你完全可能得出不同结论但判断维度是通用的。方案类型数据归属中文全文检索运维成本长期可迁移性云端笔记类平台好零差导出格式受限浏览器书签同步平台无零中但只有链接纯书签管理自托管自己一般多只索引元数据中好Colibri 这类归档型工具自己好正文级 FTS低单进程无中间件好单文件数据库决定性的一点在于架构复杂度。Colibri 是单进程 单文件数据库的设计没有独立的消息队列没有需要单独调优的搜索集群整个数据目录拷走就是一次完整备份。对比之下我上一套自建方案跑着四个容器光升级顺序错一次就把索引搞坏了修了两小时。工具本身的运维复杂度应该和它的使用频率成正比一个每周只用几次的工具就不该需要每季度做一次架构维护。1.3 Colibri 的三条设计原则以及它们带来的实际好处理解一个工具的设计原则比记住它的命令更重要因为原则决定了你在遇到问题时该往哪个方向修。原则一本地优先。Colibri 的所有数据默认落在本机磁盘上网络只是可选的同步通道。这意味着断网可用、主机迁移只需要拷贝目录、不存在服务方跑路这个场景。代价是你得自己负责备份这一点后面会专门讲。原则二单文件存储。正文、元数据、索引都在一个数据库文件里常见实现是 SQLite 系。好处是事务一致性天然由数据库保证不会出现正文写进去了但索引没更新的中间态。代价是并发写入能力有限如果你的抓取任务非常密集需要控制写入节奏这一点在第四章会展开。原则三零外部中间件。没有 Redis、没有独立的检索引擎、没有需要单独部署的任务队列。这让资源占用可以压到很低——我实测在 1 核 1G 的小机器上跑空闲时内存占用在 80MB 上下抓取峰值也就 300MB 左右。对于个人工具而言不需要维护本身就是最重要的功能之一。2. 部署前的准备主机规格、目录规划与依赖梳理2.1 主机规格怎么定按每天抓 30 篇倒推一遍我不建议上来就买大机器这类工具的瓶颈不在 CPU 而在磁盘而且增长是可预测的。给你一个可以套用的估算方法。先算单篇占用。一篇普通技术文章的正文纯文本大约 3000 到 8000 字UTF-8 编码下折合 10KB 到 25KB。加上元数据、索引开销按 2 倍余量算一篇按 50KB 规划比较稳妥。如果文章带封面图而且你选择把图片也落盘我很推荐这么做因为原图链接失效是常态一张压缩后的图按 200KB 算。于是每天 30 篇、保留图片的情况下日均约 30 × 250KB ≈ 7.5MB一年不到 3GB。这个量级意味着磁盘起步 40GB 就非常宽裕但要留出备份空间建议 80GB 以上内存1GB 能跑2GB 舒服给数据库页缓存留出余量会明显提升搜索响应CPU单核足够抓取的正文提取是 IO 密集而非计算密集网络真正需要关注的是出站带宽和限速策略不是带宽大小注意不要用最低配的突发性能实例来做长期抓取。这类实例有 CPU 积分机制积分耗尽后性能会断崖式下降表现为抓取任务随机变慢非常难排查。这是我踩过的坑换成常规实例后问题直接消失。2.2 目录规划数据、备份、日志必须分家我见过太多人把数据目录和备份目录放在同一个磁盘上然后硬盘坏了一块两样一起没。目录规划的核心就一条让任何一次误操作最多只能伤到一类文件。推荐的结构是这样/srv/colibri/ ├── app/ # 程序本体与配置文件可随时重装 ├── data/ # 数据库与附件绝对不能丢 ├── backup/ # 备份产物最好指向另一块盘或远端 └── logs/ # 运行日志按周轮转允许丢弃配置上我强制自己遵守三条data/目录的属主必须和容器内运行用户一致后面有具体命令logs/做轮转单文件不超过 50MB保留 8 份backup/如果指向同盘至少要再做一层异地同步。另外建议把data/单独挂载成一个卷或分区这样将来迁移时umount一下就能整体搬走比tar打包几十 GB 要快得多。2.3 两条安装路线容器还是单二进制Colibri 通常提供两种分发形式我的建议是先容器、后二进制但原因和你想的可能不一样。选容器不是因为容器更先进而是因为它的依赖关系是封闭的。这类工具经常依赖特定版本的运行环境和特定的字符集、时区数据裸机安装时一个小小的 glibc 版本差异就可能导致运行时报奇怪的错。容器把这层不确定性吃掉了对新手尤其友好。但容器有两个真实的代价一是文件权限容易出问题宿主机用户 ID 和容器内用户 ID 不匹配时会出现容器能写、你在宿主机上删不掉的情况二是网络抓取场景下容器默认的 DNS 配置有时会比宿主机慢半拍。什么时候该切到二进制两种情况第一你的主机资源极小比如 512MB 内存容器运行时的固定开销占比过高第二你需要和主机上的其他脚本做深度集成比如用系统级的定时任务直接调用它的命令行。除此之外容器方案足够用很久。3. 从零跑起来Colibri 完整部署实操3.1 最小可运行配置与权限处理先把目录和权限做对这一步省下的时间比后面任何调优都值。下面这套命令假设你用 Docker 部署运行用户 ID 是 1000绝大多数云主机的默认普通用户。# 建立目录结构 sudo mkdir -p /srv/colibri/{app,data,backup,logs} # 关键一步把数据目录交给运行用户避免容器内写不进 sudo chown -R 1000:1000 /srv/colibri sudo chmod 750 /srv/colibri/data /srv/colibri/backup然后是编排文件。下面是精简后的结构字段名我按通用习惯写实际部署时对照你所用版本的文档核对一遍键名即可# /srv/colibri/app/docker-compose.yml services: colibri: image: colibri:latest container_name: colibri restart: unless-stopped user: 1000:1000 environment: - TZAsia/Shanghai - COLIBRI_DATA_DIR/data - COLIBRI_LOG_LEVELinfo # 首次启动用于初始化管理员账号初始化完成后建议移除 - COLIBRI_ADMIN_EMAILyouexample.com - COLIBRI_ADMIN_PASSWORD换成你自己的强密码 volumes: - /srv/colibri/data:/data - /srv/colibri/logs:/logs ports: # 只监听回环地址外部访问一律走前置网关 - 127.0.0.1:8080:8080 healthcheck: test: [CMD, wget, -qO-, http://127.0.0.1:8080/health] interval: 30s timeout: 5s retries: 3有几个点值得单独说明。restart: unless-stopped让服务在主机重启后自动拉起但不会在你手动停止后自己又跑起来这是长期无人值守场景下最合适的一档。端口绑定在127.0.0.1上意味着即使防火墙配置出问题外部也无法直接访问到这个端口必须经过前置网关多一道保险。健康检查看似可有可无但它能让你用一条docker ps就判断服务状态比翻日志快得多。启动命令和首次验证cd /srv/colibri/app docker compose up -d docker compose logs -f --tail50日志里出现监听端口的提示后用curl -I http://127.0.0.1:8080确认返回 200 或 302 即可。如果返回的是连接被拒绝九成是容器没起来如果是 403基本可以确定是目录权限问题回去检查chown那一步。3.2 HTTPS 与请求转发用 Caddy 三行搞定这一步的目标是让外部通过域名加 HTTPS 访问同时不让后端端口暴露。我强烈建议用 Caddy因为它自动申请和续期证书配置量小到可以背下来。Nginx 也能做但要自己处理证书续期的钩子长期维护成本更高。# /etc/caddy/Caddyfile read.example.com { encode gzip reverse_proxy 127.0.0.1:8080 header { Strict-Transport-Security max-age31536000; X-Content-Type-Options nosniff X-Frame-Options SAMEORIGIN } }重载配置sudo systemctl reload caddy。证书会在第一次访问时自动申请通常十秒内完成。这里有两个实战经验。第一先确认 80 和 443 端口的入站策略放行再去申请证书否则会触发申请频率限制同一域名短期内反复失败要等很久才能重试这是新手最常卡住的地方。第二如果你的归档内容包含需要登录才能看的页面务必开启访问控制不要让归档页面在公网上裸奔。注意把整个服务暴露到公网之前一定先确认鉴权已经生效。做法很简单用一个没登录过的浏览器无痕窗口访问域名如果直接能看到内容列表说明鉴权没开或者会话校验有问题这时候立刻把入站策略收回到你自己的常用地址段。3.3 初始化与账号安全三件必须做的事首次登录后有三件事我建议当天就做完因为拖延之后往往会一直拖下去。第一改掉初始化密码并启用两步验证。初始化密码写在编排文件里存在被误提交到版本库的风险。改完之后把编排文件里的相关环境变量删掉只保留一个空的占位注释。第二明确关闭开放注册。绝大多数自托管工具默认是第一个用户即管理员、之后关闭注册但也有版本默认允许注册务必去设置里确认一遍。这个开关如果不确认你的归档库在公网上就是公开可写的。第三把会话有效期调短一点。默认的三十天会话在个人设备上很方便但考虑到你的归档里可能有内部资料我通常调到 7 天配合移动端的指纹解锁体验损失很小。3.4 内容入口三件套订阅、插件与开放接口Colibri 的价值取决于入口是否顺畅。只靠手动粘贴链接坚持不过两周。我配置了三条入口覆盖了九成以上的使用场景。RSS 订阅是主力入口。把常看的几十个源加进去设置每小时拉取一次。这里有两条经验一是按源设置不同的拉取间隔更新频繁的新闻类源可以 30 分钟一次个人博客一周一次就够统一设成高频只会让你更快撞上对方的限速二是给每个源设置初始回溯条数首次订阅不要设为全部一个更新了十年的博客会瞬间灌进来几千篇既拖慢首次索引也淹没了你真正想看的内容我一般设 20 到 50 篇。浏览器扩展负责随手存。看到好文章点一下图标就归档这是降低动作成本的关键。如果你的主力浏览器没有现成扩展退而求其次可以用一个书签小工具bookmarklet把一段携带当前页面地址的请求挂到书签栏效果几乎一样。开放接口负责自动化。当你有脚本或自动化流程要写入内容时直接调用它的 HTTP 接口即可curl -X POST https://read.example.com/api/items \ -H Authorization: Bearer $COLIBRI_TOKEN \ -H Content-Type: application/json \ -d {url:https://example.com/post,tags:[longread],archive:true}令牌建议单独建一个不要复用登录会话这样将来要撤销某条自动化流程时只废掉那一个令牌就行不影响你日常登录。4. 让搜索真正好用中文分词与索引调优4.1 默认分词在中文上为什么容易翻车这是整篇文章里技术含量最高、也最容易被忽略的一节。Colibri 这类工具底层通常用 SQLite 的 FTS5 做全文索引而 FTS5 内置的分词器是为英文这类以空格分词的语言设计的。拿它去处理中文效果会非常糟糕。举个具体的例子。正文里有增量索引重建默认分词器会把这一整串连续汉字当成一个词元token。于是你用索引去搜搜不到用重建去搜也搜不到只有原样输入增量索引重建才可能命中。更糟的是标点和英文混排的情况比如用 FTS5 做检索可能被切成一堆奇怪的碎片。这就是很多人抱怨明明存过却搜不出来的根本原因——不是没存进去是索引结构不对。判断方法很简单归档几篇中文文章后故意搜一个你确定正文里出现过的双字词。如果搜不到就是分词配置的问题不用怀疑数据。4.2 两套可行方案trigram 与预分词各自适合谁解决中文字符串检索有两条路我把它们的特点和一些踩坑经验整理如下。维度trigram 方案预分词jieba 类方案原理把文本切成连续三字符片段建索引先用中文分词库切词再建索引索引体积明显偏大通常 2 到 3 倍接近原文体积双字词检索支持但两字查询要特殊处理原生支持模糊匹配天然支持子串匹配只能匹配完整词实现复杂度低改配置即可中需要在写入链路插入分词步骤适合场景想要即改即用、能接受磁盘开销数据量大、对索引体积敏感我最后选了 trigram主要原因是它不需要改动写入链路。预分词方案的问题在于分词库的词典会更新一旦你换了词典或者升级了版本历史数据必须全量重建索引几百 GB 的数据重建一次要跑好几个小时。trigram 没有这个问题索引和原文是一一对应的理论上的查询效果也更宽容——你记错一个字也能搜到。代价是磁盘。我实测同一批约 12000 篇文章正文纯文本约 380MB开 trigram 之后索引部分约 900MB。按现在硬盘的价格这个开销完全可以接受但你要在规划容量时把它算进去别等到磁盘满了才发现。提示如果只是想先验证效果不用改配置也能临时判断——很多版本的搜索界面支持精确短语模式。把你要搜的词用引号括起来再搜如果这样能搜到而普通搜索搜不到基本可以确认是分词粒度问题。4.3 索引重建步骤与实测数据确认要用 trigram 之后配置改动通常是在数据库层面对索引表重新定义分词器。这个操作会重建索引必须安排在不影响使用的时间段做并且动手之前先做一次完整备份这不是客套话。大致的流程如下具体语句请对照你所使用版本的说明调整表名# 第一步停下写入来源。停掉抓取任务和同步脚本避免重建期间有新数据写入 docker compose stop colibri # 第二步整库冷备一份作为回滚点 sqlite3 /srv/colibri/data/colibri.db .backup /srv/colibri/backup/pre-reindex.db # 第三步执行索引重建示意表名与列名以实际库结构为准 sqlite3 /srv/colibri/data/colibri.db SQL -- 移除旧的全文索引 DROP TABLE IF EXISTS items_fts; -- 用 trigram 分词器重建 CREATE VIRTUAL TABLE items_fts USING fts5( title, content, tokenizetrigram ); -- 从原始表回填数据 INSERT INTO items_fts(rowid, title, content) SELECT id, title, content FROM items; SQL # 第四步让索引体积回到健康状态 sqlite3 /srv/colibri/data/colibri.db PRAGMA optimize; VACUUM; # 第五步启动服务 docker compose start colibri下面是重建前后的实测对比数据同一台 2 核 2G 的机器同一批 12000 篇数据指标重建前默认分词重建后trigram数据库总大小约 1.1GB约 2.0GB双字词命中率抽样 50 次约 30%约 98%单次搜索响应30 到 60ms80 到 180ms重建耗时—约 14 分钟响应时间确实变长了但 180ms 对个人使用来说完全无感。这里有个可以优化的点给数据库设置合理的页缓存。在配置里把缓存调到 64MB 到 128MB 之间搜索响应能明显回落代价只是多一点内存占用。对于只有一两万篇的库这个调整的收益比升级 CPU 大得多。还有一个容易被忽略的细节重建之后建议跑一次PRAGMA optimize它会让查询计划器更新统计信息。刚重建完如果不做这一步你可能会看到某些复杂查询莫名其妙变慢其实是查询计划没选对索引。5. 长期维护备份、升级与数据导出5.1 备份策略热备、冷备、异地三层自托管最大的风险从来不是被攻击而是你自己的误操作。我见过最典型的案例是有人执行清理命令时写错了一个路径把数据目录连带删了而备份目录就在隔壁。我的做法是三层成本很低但覆盖了绝大多数故障场景。第一层是热备频率最高。用 SQLite 的在线备份能力在不停止服务的情况下每六小时导出一份保留最近 7 天#!/bin/bash # /srv/colibri/app/hot-backup.sh set -euo pipefail DATE$(date %Y%m%d-%H%M) DEST/srv/colibri/backup/hot mkdir -p $DEST sqlite3 /srv/colibri/data/colibri.db .backup $DEST/colibri-$DATE.db # 只保留最近 7 天 find $DEST -name colibri-*.db -mtime 7 -delete关键在于用.backup而不是直接cp。直接复制正在被写入的数据库文件可能拿到一个事务中间态的文件恢复时会报损坏。这是很多人第一次做备份时都会犯的错。第二层是冷备频率低但最重要。每周停服务一分钟把整个data/目录打包压缩。附件图片这类二进制文件也在这里热备只覆盖数据库是不够的。第三层是异地。用同步工具把冷备产物推到另一台机器或对象存储。这一步的意义在于防范单机级的灾难比如云服务商整块盘出问题。异地副本不用保留很多份最近四份就够。注意做完备份一定要验证一次恢复。把备份文件放到一个临时目录启动一个测试实例确认能正常打开、能搜索、附件能显示。没验证过的备份在真出事的时候往往靠不住。5.2 升级与回滚预案这类工具的更新频率通常不低修 bug 和小功能迭代都很勤。我的原则是不追最新只跟稳定版本且每次升级前留一个能一键退回的路径。具体流程是四步。第一看更新说明里有没有涉及数据库结构变更如果有这一版的升级就要更谨慎务必先做冷备。第二记录当前版本号把旧镜像打上标签保留在本地别急着删。第三升级后先验证三个核心动作能登录、能抓取一篇新文章、能搜到内容。第四观察一到两天确认后台任务没有堆积再清理旧镜像。回滚就是反向操作把编排文件里的版本号改回旧值docker compose up -d。如果升级过程中已经跑过数据库迁移回滚时要用升级前的那份冷备替换回去这也是为什么数据库结构变更的版本必须冷备。我踩过的一个坑是升级后忘了检查抓取任务的调度状态新版本把定时任务的配置格式改了结果抓取静默停了三天直到我发现内容列表好久没更新。现在我固定会在升级后的第二天早上看一眼任务列表和最近入库时间两秒钟的事能省掉很多麻烦。5.3 数据导出别把自己锁死在工具里这一点我很在意。任何工具都有可能被更好的工具替代如果你的数据无法干净地导出迁移成本就会大到让你将就着用。所以归档工具的选型标准里导出能力应该和搜索能力同等重要。Colibri 这类基于单文件数据库的工具在这件事上有天然优势数据就是一张 SQLite 表你可以直接用任意 SQL 客户端打开导出成 CSV、JSON、Markdown 都行。我给自己定了一个季度一次的演练随机抽 50 条记录导成 Markdown 文件确认标题、正文、标签、原始链接四要素完整。一个实用的技巧是导出时保留原始链接而不是只留正文。正文可能因为抓取时的解析问题有缺失但原始链接是唯一的溯源凭证。如果原页面还在你永远有机会重新抓一次。-- 导出近一年带标签的文章为 Markdown 需要的字段 SELECT id, title, url, created_at, group_concat(t.name, ,) AS tags FROM items i LEFT JOIN item_tags it ON it.item_id i.id LEFT JOIN tags t ON t.id it.tag_id WHERE i.created_at datetime(now, -1 year) GROUP BY i.id ORDER BY i.created_at DESC;6. 踩坑实录六个高频问题与排查路径6.1 抓取层的四个典型故障故障一抓回来只有导航和页脚正文是空的。这是最普遍的问题原因是目标页面用脚本动态渲染内容而抓取器拿到的是初始 HTML。排查方法是把抓取结果和浏览器里查看源代码的结果对比如果源代码里确实没有正文那就是渲染问题。解决办法是给这类来源配置专用的解析规则或者干脆改用带渲染能力的抓取通道只对少数几个站点启用避免整体速度被拖慢。故障二内容出现乱码。九成是编码声明缺失或错误。有些页面的 HTTP 头声明是 UTF-8但实际内容用了另一种编码工具按声明去解码就成了乱码。排查时先看乱码的形态如果是ä½ å¥½这种拉丁字母加符号的样式是 UTF-8 被当成单字节编码解了如果是????是编码转换过程中丢字符了。前者可以通过强制指定编码解决后者通常无解只能放弃这一篇。故障三图片全部失效。因为很多页面用了会过期的图片链接或者对非浏览器请求做了来源校验。解决办法是开启本地化存储把图片一起抓下来。这会显著增加磁盘占用建议只对长文和教程类内容开启新闻类可以不开。故障四某个来源突然全部抓取失败。先看日志里的状态码。如果是 429是频率限制把间隔调大如果是 403是来源校验可能需要补上常规的请求头如果是连接超时先确认是不是你自己机器的 DNS 或出站策略出了问题别急着改工具配置。6.2 服务层内存、磁盘与权限内存缓慢上涨。如果你用监控看到内存曲线是缓慢爬升然后不降先别急着判定是泄漏。全文检索会大量使用数据库页缓存这部分被计入进程内存是正常的。判断方法很直接连续观察一周如果曲线是涨到某个水平后就平了那是缓存如果一直涨到接近上限然后被系统杀掉重启那才是需要处理的问题。前者不用管后者可以先配置一个内存上限让服务在达到阈值前主动回收。磁盘悄悄写满。三个常见来源日志没有轮转、附件没有清理策略、索引膨胀后没做整理。建议配一条简单的磁盘告警超过 80% 就通知你。另外定期执行一次数据库整理VACUUM能回收不少空间但在大库上这个操作会锁库且耗时安排在低峰期。权限问题导致容器反复重启。典型表现是日志里反复出现无法写入的错误。根因是宿主机目录属主和容器运行用户不匹配。修法是先确认容器实际以哪个用户运行再统一宿主机目录属主。不要图省事把目录设成 777这在多用户主机上是个隐患。6.3 问题速查表把上面这些整理成一张表出问题时按顺序对号入座能省掉大部分翻日志的时间。现象最可能的原因第一步验证动作中文双字词搜不到分词器不支持中文用引号做短语搜索能搜到即确认抓取只有导航无正文页面动态渲染对比浏览器源代码确认正文是否在初始 HTML内容乱码编码声明与实际不符看乱码形态判断是哪种编码问题图片全部失效外链过期或来源校验开启附件本地化存储某源批量失败触发频率限制或来源校验看日志状态码429 就降频403 就补请求头容器反复重启目录权限不匹配检查宿主机属主与容器运行用户是否一致内存持续上涨不回落页缓存增长或真实泄漏观察一周曲线看是否在某水平趋平磁盘占用异常增长日志未轮转或索引膨胀检查日志目录大小执行一次数据库整理搜索响应突然变慢查询计划未更新执行一次数据库统计信息更新最后再分享一个排查习惯遇到问题时先把最近三天改过什么列出来。我遇到的绝大多数故障根因都在最近一次配置改动、系统更新或者新增的订阅源上。养成改动前留个备注、改动后立刻验证核心功能的习惯比你记住任何一条命令都有用。这套体系我跑了两年多中间换过一次主机、重建过一次索引、恢复过一次备份数据一直在。真正让它活下去的不是某个精妙的配置而是这些看起来啰嗦的小规矩。
返回列表