Karakeep(Hoarder)架构解析:Next.js 前端 + SQLite 任务队列 + 多 Worker 异步流水线
2026/9/11 9:12:35 网站建设 项目流程

Karakeep(Hoarder)架构解析:Next.js 前端 + SQLite 任务队列 + 多 Worker 异步流水线

【免费下载链接】hoarderA self-hostable bookmark-everything app (links, notes and images) with AI-based automatic tagging and full text search项目地址: https://gitcode.com/GitHub_Trending/ho/hoarder

Karakeep 是一款可自托管的"收藏一切"应用(链接、笔记与图片),其系统架构采用"单体 Web 应用 + 异步任务队列 + 专用 Worker 工作进程"的分层设计:Web 应用负责数据持久化与用户交互,SQLite 任务队列承载所有后台作业,而 Crawling、OpenAI 推理与 Meilisearch 索引三类 Worker 分别完成内容抓取、自动打标签与全文检索建索引。阅读本文后,你将理解 Karakeep 从"保存一个链接"到"链接可被全文搜索"的完整数据流转链路,掌握其三大核心组件的职责边界、队列语义与部署形态,并能在源码层面定位每一步的实际实现。

架构总览:一条流水线上的三个角色

原版架构文档(docs/versioned_docs/version-v0.29.0/07-development/04-architecture.md)用三句话勾勒了系统骨架:

  • Web 应用(Webapp):基于 Next.js 构建,使用 SQLite 存储数据;
  • Worker 工作进程:从基于 SQLite 的任务队列中消费作业并执行,共有三种作业类型——抓取、OpenAI 推理、索引;
  • 三者协同:用户通过 Web 应用产生收藏行为,系统把重活(抓网页、跑 AI、建索引)异步下沉到队列,由 Worker 逐个消化。

这套设计与 Karakeep 的"收藏一切"定位高度契合:用户保存的内容形态多样(链接、笔记、图片),而链接需要爬取正文、生成截图、推断标签、全文检索,这些都属于耗时操作,绝不能阻塞用户界面。把 Web 应用与 Worker 分离,既保证了前端响应速度,也让每一类重活可以独立扩缩容。

核心组件一:Web 应用(Next.js + SQLite)

Web 应用是整个系统的入口,负责所有用户交互与数据落盘。文档明确它的两个技术要点:

  • Next.js:提供前端渲染、页面路由与 API 层。项目中的 Web 前端代码位于 apps/web,包含仪表盘(dashboard)、阅读器(reader)、设置(settings)等页面,并统一通过 tRPC 路由(packages/trpc/routers)暴露后端能力;
  • SQLite:作为主数据库,承载书签、链接、标签、列表等全部业务数据。数据库的 schema 定义在 packages/db/schema.ts,迁移脚本位于 packages/db/drizzle,通过 Drizzle ORM 访问。

从部署配置(docker/docker-compose.yml)可以看到,web服务默认将数据目录挂载为/dataDATA_DIR: /data),SQLite 数据库文件即存放于此。除了业务数据,任务队列本身也落在 SQLite 中,这正是下节要展开的关键设计。

核心组件二:基于 SQLite 的任务队列

架构文档特别强调 Worker 消费的是SQLite 任务队列,而不是 Redis 等独立中间件。这一点在源码中得到印证:

  • 队列抽象接口定义在 packages/shared/queueing.ts,包含Queue(enqueue/stats/ensureInit)与Runner(run/stop)等核心契约;
  • 默认的 SQLite 队列实现是 liteque 插件(packages/plugins/queue-liteque/src/index.ts):它在dataDir/queue.db中建表存任务(buildDBClient(path.join(serverConfig.dataDir, "queue.db"), ...)),并封装了createQueuecreateRunner与重试语义;
  • 所有队列在 packages/shared-server/src/queues.ts 中统一注册,每个队列都通过 Zod schema 校验作业载荷,并配置独立的numRetries重试次数与keepFailedJobs策略。

选用 SQLite 做队列意味着部署时无需额外引入 Redis,一个数据文件即可同时承载业务数据与后台任务,大幅降低了自托管门槛。队列插件的可替换性则由 packages/shared/plugins.ts 的插件机制保证——仓库中还提供了基于 Restate 的queue-restate插件(packages/plugins/queue-restate),满足需要更强队列能力(如分布式、持久化事件流)的部署场景。

核心组件三:Worker 工作进程

文档列出的三类作业在 apps/workers/index.ts 的workerBuilders中都有对应实现,每个 Worker 通过createRunner绑定到专属队列,并受concurrency(并发数)、pollIntervalMs(轮询间隔,默认 1000ms)与timeoutSecs(超时)三个运行参数约束。

1. Crawling Worker:无头 Chrome 抓取链接内容

抓取作业从link_crawler_queue队列消费,载荷结构为{ bookmarkId, runInference?, archiveFullPage?, storePdf? }(见 packages/shared-server/src/queues.ts)。其核心实现位于 apps/workers/workers/crawlerWorker.ts:

  • 无头 Chrome:Worker 使用部署在同一容器环境中的 headless Chrome(BROWSER_WEB_URL: http://chrome:9222)真实执行页面 JS、渲染截图,保证抓取到的是浏览器视角的完整内容,而不是简单 HTTP 拉取;
  • 内容探测与类型分发:抓取前先探测 URL 的 content-type 与元数据(getContentTypeAndMetadata)。若目标为 PDF 或图片,则转为资产书签处理(handleAsAssetBookmark),否则走完整网页抓取流程crawlAndParseUrl
  • 域名限流:通过checkDomainRateLimit对目标域名做限流控制,触发限流时抛出QueueRetryAfterError,让任务延时重试且不消耗重试次数(packages/shared/queueing.ts);
  • 抓取后接力:抓取成功的页面会继续入队后续作业(enqueuePostCrawlJobs)——包括 OpenAI 打标签/摘要、搜索索引重建、可选的视频下载以及crawled事件 webhook。

2. OpenAI Worker:AI 推断标签与摘要

推理作业从openai_queue队列消费,载荷为{ bookmarkId, type: "summarize" | "tag" }(packages/shared-server/src/queues.ts)。实现在 apps/workers/workers/inference/inferenceWorker.ts:

  • 通过InferenceClientFactory.build()(packages/shared/inference.ts)获取推理客户端,支持 OpenAI 兼容接口,因此可以接入各类大模型服务;
  • 作业成功后会把书签的taggingStatus/summarizationStatus标记为success,失败且重试耗尽时标记为failureattemptMarkStatus),这些状态字段由 Web 端展示;
  • 打标签(tagging)与摘要(summarize)分别实现在 apps/workers/workers/inference 目录下,是"AI 自动打标签"这一核心卖点的执行者。

3. Search Indexing Worker:Meilisearch 全文检索索引

索引作业从searching_indexing队列消费,载荷为{ bookmarkId, type: "index" | "delete" }(packages/shared-server/src/queues.ts)。实现在 apps/workers/workers/searchWorker.ts:

  • 从数据库读取书签及其关联的链接正文、笔记、摘要、标签,组装成BookmarkSearchDocument文档;
  • 通过searchClient.addDocuments写入 Meilisearch(默认部署在http://meilisearch:7700),删除书签时则调用deleteDocuments同步清理索引;
  • 为提高可靠性,首次执行使用批量写入(batch = job.runNumber === 0),重试时关闭批量、逐条写入。

得益于索引 Worker,Karakeep 的搜索可以覆盖正文全文、标题、标签、摘要与笔记,这是 Web 端全文检索功能(packages/shared/search.ts)的索引侧支撑。

从收藏到可搜索:一条完整的作业流水线

将三个 Worker 串起来,一次"保存链接"的完整生命周期是:

  1. 用户在 Web 应用保存链接,书签行写入 SQLite;
  2. Web 端将{ bookmarkId, ... }入队link_crawler_queue
  3. Crawling Worker 用无头 Chrome 抓取页面正文、截图与元数据,写入书签的关联资产;
  4. 抓取完成后入队openai_queue(打标签、摘要)与searching_indexing(重建索引),必要时入队视频下载队列;
  5. OpenAI Worker 生成标签与摘要回写数据库;
  6. Search Indexing Worker 把最新的正文、标签、摘要组装成文档写入 Meilisearch;
  7. 用户随后即可在 Web 端通过全文搜索秒级检索到该链接。

这条链路在crawlerWorker.tsenqueuePostCrawlJobs(apps/workers/workers/crawlerWorker.ts)中清晰可见,是理解整系统数据流的最佳起点。

部署形态:一个容器内三份职责

架构文档描述的组件在 docker/docker-compose.yml 中以三个服务呈现:

服务镜像职责
webghcr.io/karakeep-app/karakeep:releaseNext.js Web 应用 + Worker(同一镜像按进程职责运行)
chromeghcr.io/karakeep-app/karakeep-chrome:release无头 Chrome,供 Crawling Worker 使用
meilisearchgetmeili/meilisearch:v1.41.0全文检索与向量存储

其中web服务通过MEILI_ADDRBROWSER_WEB_URL环境变量与另外两个服务对接;Worker 的启停由环境变量WORKERS控制(见 packages/shared/config.ts 中的workers.enabledWorkers/workers.disabledWorkers配置),在 apps/workers/index.ts 的isWorkerEnabled中生效,允许按需裁剪后台任务。

架构的演进:v0.29 之后的多 Worker 生态

值得一提的另一个维度是:v0.29.0 文档聚焦的三类 Worker 只是系统的核心骨架。从当前仓库的 apps/workers/index.ts 可以看到,Worker 生态已经扩展为十余种——crawlerlowPriorityCrawler(导入等低优先级抓取)、embeddings(向量化)、inferencesearchadminMaintenancevideofeed(RSS 订阅刷新)、assetPreprocessingwebhookruleEnginebackup以及import。它们全部遵循同一套"SQLite 队列 + 独立消费进程"的范式,印证了架构文档所描述的队列模型具备极强的扩展性——新增一类后台任务,只需注册一个队列并实现一个 Worker 即可。

整体来看,Karakeep 的架构哲学是"少依赖、可裁剪、易自托管":用 SQLite 同时承载业务数据与任务队列,用无头 Chrome + 大模型 API + Meilisearch 三个成熟组件完成"抓取-理解-检索"的闭环,最终以 Docker Compose 一键拉起,这正是它作为自托管收藏工具在部署层面广受欢迎的根本原因。

【免费下载链接】hoarderA self-hostable bookmark-everything app (links, notes and images) with AI-based automatic tagging and full text search项目地址: https://gitcode.com/GitHub_Trending/ho/hoarder

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询