0x00 又换了一次评论系统
本站的评论区又换了。
从 Artalk 换到 Twikoo,再从 Twikoo 换回 Artalk。评论系统在博客底部安安静静待着,读者可能没什么感觉,对我这个维护者来说却折腾了好几轮。
这次回 Artalk,起因很简单。我受够 MongoDB 了。
当然,事情还得从头说。2026 年 4 月,我放弃了 Artalk,主要担心它的上游维护状态。2024 年 Artalk 发布了 v2.9.1,之后一直没有新的正式版本。仓库里每年都还有提交,可用户真正能拿到的稳定版本长期停在那里,Issues 和 Pull Requests 也越堆越多。
那时的 Twikoo 看起来更有希望。项目持续更新,社区教程多,遇到问题也比较容易搜到答案。于是我把评论数据导过去,重新接入了 Twikoo。
这个选择在当时完全说得通。后来让我失望的重点,落在了它的数据库上。
0x01 我为什么开始讨厌 Twikoo 的 MongoDB
试用了三个月,我发现我开始讨厌 Twikoo 的 MongoDB。
为几条评论单独养一套数据库
Twikoo 的部署文档提供了多种方式。可以使用 LokiJS,也可以通过 MONGODB_URI 连接 MongoDB。实际部署时,我选择了 MongoDB。那时候觉得数据库独立出来更稳,后面才发现自己给博客评论区找了一个长期室友。
我的博客评论量不大。评论、回复、用户和页面,加起来远远没有到需要 MongoDB 出场的程度。Artalk 用 SQLite 就能解决,普通的 PostgreSQL 也足够。到了 Twikoo 这里,我却需要额外考虑 MongoDB 的运行、连接、备份和恢复。
评论服务本身已经够小了,数据库反倒变成了需要单独照顾的部分。它多占一些资源,多一组配置,多一份日志,也多一个以后升级时要确认的组件。
我每次看到 MongoDB 连接字符串,都觉得这件事开始变得过于隆重。一个博客评论框,至于吗?
数据能保存,搬家却变麻烦
熟悉我的人都知道,我很在意自托管服务的数据能不能带走。
Artalk 默认使用 SQLite。停掉容器,复制数据目录,数据库就跟着走了。想看数据,可以用 SQLite 工具直接打开。想备份,复制文件或用数据库工具导出都行。它朴素,至少没有太多惊喜。
MongoDB 当然也支持备份。mongodump 和 mongorestore 都能用,MongoDB Atlas 也提供了导出方式。问题在于,我得记住 MongoDB 的工具、版本、连接参数和恢复流程。拿到一份 BSON 文件时,我还得先想办法把它还原成适合检查和迁移的内容。
如果使用 Atlas,事情还会再多一些。账号、网络访问列表、连接字符串、免费额度和平台限制,都得记着。MongoDB Atlas 对大型项目可能很方便,放在我的博客评论区里却显得特别笨重。
我想保存的是评论数据,结果还得顺便维护一套 MongoDB 的生活起居。****,越想越烦。
MongoDB 没有给我的评论区带来对应的收益
MongoDB 适合很多场景,我也没打算写一篇「MongoDB 一无是处」的文章。它能存 Twikoo 的数据,运行起来也能满足评论需求。
可「能用」与「适合我」差得很远。
评论天然有关系。评论属于页面,回复指向父评论,用户和评论互相关联,站点下面还有页面。我的需求很明确,没有复杂的文档结构,也没有高并发和大规模扩展。MongoDB 提供的灵活性,我基本用不上,它带来的维护工作却全部落到了我头上。
这就是我最失望的地方。Twikoo 解决了评论展示,却让我对评论数据的管理越来越不舒服。
初创公司要的是「一周上线 MVP」,没人关心半年后的数据腐烂。由于缺乏 Schema 约束,随着时间推移,同一集合中的文档结构可能变得五花八门。所以我认为 MongoDB 应该和 MySQL 一样丢进垃圾堆。
同时在当前大环境下最不好受的一点:
MongoDB 为了追求性能,会尽可能地占用内存作为缓存。你一个 JSON 文件夹伪装成的数据库吃我这么多内存是干啥?你觉得内存很便宜?
0x02 Twikoo 一直更新,但这件事救不了我的想法
我得承认,Twikoo 持续更新确实是我当初选择它的重要原因。
相比 Artalk 长时间没有新正式版本,Twikoo 的开发状态更让人安心。版本在发,文档有人维护,遇到问题也能看到项目继续处理。这些优点都是真的。
但项目更新积极,无法改变底层存储方式给我带来的麻烦。每次升级,我还得关注 Node.js 依赖、服务端变化和 MongoDB 数据。更新本身偶尔带来配置调整,这些只能算小问题。真正让我想离开的,始终是 MongoDB。
所以我没有因为 Twikoo 更新太快而离开它。恰恰相反,我很感谢它在那段时间持续维护。只是用了一阵之后,我发现自己更在意评论数据的简单、可见和可搬运。
0x03 Artalk 上游也有自己的问题
回到 Artalk,并不代表上游的问题消失了。
Artalk 在 2024 年 9 月发布 v2.9.1 后,长期没有新的正式版本。仓库每年仍然有提交,开发也没有完全停止,可对普通使用者来说,稳定版本停在旧版本依然会带来不安。上游还有不少积压的 Issues 和 PR,很多问题等不到一个明确的版本节点。
截至本文写作时,上游仓库有 79 个未关闭 Issue 和 27 个未合并 PR。这个数字会变化,文章发布后也可能很快过时,但它能说明当时的状态。
我当初因此离开 Artalk,很合理。现在又回来,也得正视这件事。
我最后做的决定是自己维护一个 fork。与其等一个不确定的正式版本,不如把我关心的修复、构建和镜像放到自己能控制的地方。维护成本确实会落到我身上,至少这份成本是透明的。
0x04 我的 Artalk fork 改了什么
代码放在 cnb/pwsh/Artalk。它仍然是 Artalk,配置方式和大部分用法都能沿用上游。Fork 的重点放在维护和发布,另外合并了一批实际会影响运行稳定性的修复。
发布链路放到了 CNB
我的版本把检查、夜间构建、正式发布、npm 包和 Docker 镜像放到了 CNB。镜像地址是:
docker.cnb.cool/pw.sh.cn/artalk:latest
这样做的好处很具体。代码提交后可以跑 Go 测试、前端构建和代码检查。需要发布时,我可以自己生成镜像,不用等上游发版。
这也意味着我要自己承担维护责任。镜像能不能正常启动,前端资源是否匹配,数据迁移有没有问题,都需要我自己验证。自由和麻烦通常一起出现。
我合并的修复
目前 fork 里比较值得提的改动有这些:
- 修正缓存的条目计数,避免重复覆盖、重复删除和过期数据导致计数失真
- 避免服务运行时修改全局
time.Local,减少重启和并发请求之间的数据竞争 - 让用户数据合并使用同一个数据库事务,失败时回滚已经完成的部分更新
- 改进迁移文件的生命周期。上传文件限制大小,导入结束后清理,未使用的临时文件会自动过期
- 修复个人资料链接校验、UA 平台版本缺失和可选评论列表页面等问题
- 更新存在高危漏洞的 JWT 依赖
这些改动没有给评论框增加什么花哨功能。它们解决的是另一类问题:后台少出一点莫名其妙的错,数据迁移少留一点垃圾,服务运行久一点也别自己制造竞争条件。
Fork 也远未达到「所有问题都解决」的程度,使用前建议看看 提交记录 和 发布页面。
0x05 从 Twikoo 搬回评论
Twikoo 的历史评论可以通过 Artalk 的迁移工具导入。我的流程大致如下:
- 从 Twikoo 管理面板导出评论 JSON。
- 打开 Artransfer,选择 Twikoo 格式并上传 JSON。
- 下载转换后的
.artrans文件。 - 部署 Artalk,创建管理员。
- 在 Artalk 控制中心的迁移页面导入
.artrans。 - 抽查评论数量、回复关系、页面路径、昵称和时间。
- 确认数据没问题后,再切换博客前端。
迁移时要重点看 pageKey。Twikoo 里保存的页面地址可能是完整 URL,Artalk 前端却可能只使用路径。两边的格式对不上,评论就会跑到另一篇页面下面,或者看起来像消失了。
我建议先在临时实例导入一遍。原始 JSON、.artrans 文件和数据库备份都留着,确认新实例正常后再停旧服务。
0x06 用 Docker Compose 部署我的版本
下面的方案使用 SQLite 保存数据,OpenResty 负责 HTTPS。需要一台安装 Docker 的服务器、一个已经解析的域名。
先创建目录:
mkdir -p /opt/artalk/data
cd /opt/artalk
创建 compose.yml:
services:
artalk:
container_name: artalk
image: docker.cnb.cool/pw.sh.cn/artalk:latest # 或者 nightly
restart: unless-stopped
ports:
- "127.0.0.1:23366:23366"
volumes:
- ./data:/data
environment:
TZ: Asia/Shanghai
ATK_LOCALE: zh-CN
ATK_SITE_DEFAULT: 你的站点名称
ATK_SITE_URL: https://blog.example.com
ATK_SITE_DEFAULT 要和前端初始化时的 site 保持一致。ATK_SITE_URL 填博客地址,别填评论服务地址。
端口只绑定到 127.0.0.1,公网访问交给 OpenResty。
启动容器:
docker compose up -d
docker compose logs -f artalk
创建管理员(一定要先启动):
docker compose exec artalk artalk admin
按照提示填写管理员信息。之后给评论服务配置一个域名,例如 artalk.example.com。
OpenResty 反向代理
server {
listen 80;
listen [::]:80;
server_name artalk.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
检查并加载配置:
openresty -t
openresty -s reload
HTTPS 证书按自己的方式申请。正式接入前,先确认 https://artalk.example.com 能打开,浏览器也没有证书和混合内容错误。
如果前面还有 CDN,不要无条件信任访客传来的 X-Forwarded-For。源站应该只接受来自可信反向代理的真实 IP 请求头。
接入评论区
Artalk 镜像里包含匹配的前端资源,可以从评论服务域名加载:
<link
href="https://artalk.example.com/dist/Artalk.css"
rel="stylesheet"
/>
<script src="https://artalk.example.com/dist/Artalk.js"></script>
<div id="Comments"></div>
<script>
Artalk.init({
el: "#Comments",
server: "https://artalk.example.com",
site: "你的站点名称",
pageKey: location.pathname,
pageTitle: document.title,
darkMode: "auto",
});
</script>
前端资源和后端来自同一个镜像,版本更容易保持一致。使用 Astro、Swup 或其他页面切换功能时,切换页面前要销毁旧的 Artalk 实例,否则评论区可能重复挂载,或者继续显示上一篇文章的评论。
0x07 备份和升级
Compose 里挂载的 ./data 就是最重要的数据目录。升级前先备份:
cd /opt/artalk
tar -czf "artalk-data-$(date +%Y%m%d-%H%M%S).tar.gz" data
拉取新镜像并重建:
docker compose pull
docker compose up -d
docker compose logs --tail=100 artalk
除了数据目录,我还建议定期从控制中心导出 Artrans。数据库备份适合完整恢复,Artrans 适合迁移和抽查,两种文件各留一份,心里踏实很多。
0x08 写在最后
我当初从 Artalk 换到 Twikoo,是因为担心 Artalk 上游不再认真维护。Twikoo 的持续更新确实吸引了我,这个判断也没有错。
后来我发现,项目更新积极只是选择评论系统时的一项指标。对我自己的博客来说,MongoDB 才是每天都在提醒我「这套东西有点重」的存在。
为了一个小型评论区维护 MongoDB,备份要管,恢复要管,连接要管,搬家还要重新想一遍。它能工作,可我每次碰到它都不开心。
Artalk 上游依然有版本和维护方面的缺口,所以我选择自己维护 fork。现在评论数据落在 SQLite 里,镜像由我自己构建,数据还能通过 Artrans 导出。以后如果又遇到问题,我至少知道该去哪里改。
这次换回来,核心理由就一句话:Twikoo 的更新让我放心过,MongoDB 却让我一直不爽。