Kener 技术架构解析:Express + SvelteKit + BullMQ 单进程状态页服务如何组织调度、队列与数据库层 可观测性后端前端运维【免费下载链接】kenerStunning status pages, batteries included!项目地址https://gitcode.com/gh_mirrors/ke/kener点击查看免费下载Kener 是一个SvelteKit 应用 Express 服务器 后台调度器 BullMQ 队列的完整状态页status page系统生产环境所有组件收敛在同一个 Node.js 进程中运行。本文基于仓库中的 架构文档/docs/content/v4/architecture.md) 并结合scripts/、src/lib/server/下的实际源码逐层拆解 Kener 的启动流程、定时调度器、队列处理管线、数据库访问模式与两段式构建系统帮助你在部署、排障或二次开发 Kener 时准确理解各组件的边界与数据流向。总体架构概览从源码结构看Kener 的运行时可以概括为一张单进程、双平面的图HTTP 请求平面由 Express 挂载 SvelteKit handler 处理后台作业平面由一组 BullMQ 调度器驱动经队列管线写入数据库并向订阅者扇出通知。两个平面共享同一套 Knex 数据库层与 Redis 连接但通过独立的连接池隔离后文详述。┌─────────────────────────────────────────────────────────┐ │ Node.js Process │ │ │ │ ┌───────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Express │───▶│ SvelteKit │ │ Schedulers │ │ │ │ Server │ │ Handler │ │ (BullMQ) │ │ │ └───────────┘ └──────────────┘ └──────┬───────┘ │ │ │ │ │ ┌──────▼───────┐ │ │ │ Queues │ │ │ │ (BullMQ) │ │ │ └──────┬───────┘ │ │ │ │ │ ┌──────────────────────────────────────────▼────────┐ │ │ │ Database (Knex.js) │ │ │ │ SQLite │ PostgreSQL │ MySQL │ │ │ └───────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ Redis (BullMQ backend) │ │ │ └───────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘Express 服务器负责 HTTP 请求所有页面渲染与 API 路由都委托给 SvelteKit handler后台调度器按 cron 表达式轮询各个 monitor监控目标执行结果经过一条多级队列管线完成处理、入库与告警。生产入口scripts/main.ts 的启动序列生产服务器定义在 scripts/main.ts构建时被打包为build/main.js。阅读 main.ts 可以看到完整的启动顺序动态import(../build/handler.js)加载 SvelteKit handler注释解释了为什么用动态导入确保BODY_SIZE_LIMIT等.env变量在 handler 模块顶层读取前已生效创建 Express 应用并额外创建一份独立的 Knex 实例db用于启动期操作挂载/healthcheck端点受KENER_BASE_PATH前缀影响app.use(handler)将其余全部路由交给 SvelteKitapp.listen(PORT, ...)监听端口PORT默认 3000监听回调内依次执行runMigrations()先修正knex_migrations表中历史.js记录为.ts再db.migrate.latest()→runSeed()db.seed.run()→db.destroy()销毁这份启动期实例 → 调用Startup()启动后台调度器注册SIGTERM/SIGINT优雅退出处理器。/healthcheck 的设计细节main.ts 中的健康检查有几个值得注意的工程决策每次探测有 2 秒超时Promise.race防止某个挂死的依赖拖垮健康端点默认始终返回 200响应体形如{ status: ok | degraded, db: true, redis: true }。源码注释解释了原因依赖宕机时重启应用无法救活数据库若让探活返回 5xx基于健康检查的自动重启机制反而会把应用弹射bounce如需严格模式可传?strict1此时任一组件异常返回 503Redis 探测前先检查redis.status ready因为共享的 ioredis 客户端设置了maxRetriesPerRequest: null断线期间直接发 PING 会让命令无限排队并在多次探测中不断累积。Startup()本身定义在 src/lib/server/startup.ts)它先把进程时区固定为 UTCprocess.env.TZ UTC安装代理环境然后依次启动mainScheduler应用级调度、maintenanceScheduler与dailyCleanupScheduler最后打印版本号横幅。开发模式与生产模式的差异package.json 中的 scripts 字段印证了两种运行形态方面开发生产SvelteKitvite devdevelopment带 HMRExpress 托管预构建的build/handler.js后台作业独立进程devschedulevite-node src/lib/server/startup.ts同一进程打包进build/main.jsTypeScriptJIT 编译esbuild 预编译为 JavaScript启动命令npm run devnpm-run-all --parallel devschedule development并行起两个进程npm start→node build/main.js这种开发拆进程、生产合进程的划分解释了 startup.ts 末尾的判断逻辑当模块作为主模块被tsx或vite-node直接运行时才调用Startup()——生产环境下Startup()是由main.ts显式调用的避免调度器被启动两次。路由分组Route GroupsSvelteKit 路由位于src/routes/下使用带括号的 route group 目录组织路由分组用途示例路由(kener)/公开状态页/、/[page_path]、/badge/、/incidents/(manage)/管理后台需登录/manage/app/monitors/、/manage/app/incidents/(api)/供外部消费方的 REST API/api/monitors/、/api/incidents/、/api/pages/(embed)/可嵌入的状态小组件/embed/monitor-[tag]/、/embed/latency-[tag]/(docs)/基于 Markdown 的文档站/docs/、/docs/v4/architecture(account)/认证页面/account/signin/、/account/logout/(assets)/上传资源的静态服务/assets/images/(ext)/扩展端点如 heartbeat 推送/ext/heartbeat/此外还有(embed)/layout.ts、(kener)/layout.ts这类布局级逻辑各分组之间通过括号目录共享布局但互不影响 URL 结构。服务器架构监控执行服务Services监控执行逻辑位于src/lib/server/services/每种 monitor 类型有独立的服务类。service.ts 中的Service工厂类根据 monitor 的monitor_type字段分发到具体实现服务文件Monitor 类型说明apiCall.tsAPIHTTP/HTTPS 端点监控pingCall.tsPINGICMP ping 检测tcpCall.tsTCPTCP 端口连通性dnsCall.tsDNSDNS 记录解析sslCall.tsSSLSSL 证书有效期/到期检查sqlCall.tsSQL数据库查询监控heartbeatCall.tsHEARTBEAT被动式推送监控uptime-kuma 风格gamedigCall.tsGAMEDIG游戏服务器监控dockerCall.tsDOCKERDocker 容器/守护进程健康groupCall.tsGROUP聚合子 monitor 的状态grpcCall.tsGRPCgRPC 端点监控prometheusCall.tsPROMETHEUSPrometheus 指标监控noneCall.ts兜底未知类型回退注意从源码结构看当前仓库的工厂类实际支持GRPC、PROMETHEUS两种文档表格未列出的类型且对无法识别的monitor_type会回退到NoneCall保证调度链路不因新增类型而崩溃。调度器Schedulers调度器位于src/lib/server/schedulers/全部基于 BullMQ 的 repeatable jobupsertJobScheduler实现调度器触发节奏职责appScheduler.ts每 10 秒对账reconcile各 monitor 的调度器新增、移除或更新monitorSchedulers.ts按 monitor 的 cron 表达式按每个 monitor 配置的 cron 触发其执行maintenanceScheduler.ts周期轮询源码中every: 3600000即每小时启动时immediately: true依据 RRULE 为未来 7 天生成计划内的 maintenance 事件dailyCleanup.tscron0 0 * * *UTC 午夜每日清理任务文档表格未提及见源码appScheduler 的对账机制值得展开。appScheduler.ts 的 worker 每 10 秒执行一次拉取所有ACTIVE状态的 monitor为每个 monitor 计算身份哈希monitor.tag :: HashString(JSON.stringify(monitor))——把 tag 和完整配置序列化后一起哈希意味着 monitor 配置一旦变更旧调度器会被判定为不匹配而被移除、新调度器被重建从而让新的 cron 或类型参数即时生效列出monitorScheduleQueue中现有的所有 job scheduler删除不在活跃 monitor 集合中的即已删除或改名的 monitor为活跃但没有对应 scheduler的 monitor 调用addJobToSchedulerQueue新建顺带调用UpdateMaintenanceEventStatuses()刷新维护事件状态。monitorSchedulers 的 cron 触发monitorSchedulers.ts 中addJobToSchedulerQueue以 monitor 身份哈希为 scheduler id{ pattern: monitor.cron, immediately: true }注册——immediately: true表示注册后立即先跑一次。worker 的并发度concurrency被设置为Math.max(activeMonitors.length, 1)保证多 monitor 并发触发时互不排队。被触发的 job 进入 cron-minute.ts 的Minuter先校验monitor_type合法性取当前分钟的 UTC 整分时间戳作为统一的作业时间键ts对GROUP类型附加 1 秒可由type_data.executionDelay覆盖延迟——这是为了等待子 monitor 先完成执行、避免 GROUP 聚合读到过期状态——最后推入monitorExecuteQueue。队列管线Queues队列位于src/lib/server/queues/由 Redis 通过 BullMQ 支撑形成一条执行 → 存储 → 告警 → 通知的管线Scheduler (cron 触发) │ ▼ monitorExecuteQueue ─── 运行 monitor 服务API、Ping 等 │ ▼ monitorResponseQueue ── 结果入库、更新缓存 │ ▼ alertingQueue ────────── 评估告警规则创建/解决 incident │ ▼ subscriberQueue ──────── 向订阅者扇出通知 │ ▼ emailQueue ───────────── 发送单封邮件统一的基础设施在 q.ts 中所有队列共享 Redis 连接key 前缀统一为kener默认作业策略attempts: 3重试 3 次backoff为指数退避、基准延迟 5000msremoveOnComplete: true、removeOnFail: false——即失败的作业保留现场便于排障worker 默认并发 5concurrency: 5且每个函数型 processor 会被db.runInWorkerContext(...)包裹把所有BullMQ worker 的数据库访问路由到独立 worker 连接池与 Web 请求池隔离这是防止后台任务突发耗尽页面请求连接的关键设计。各队列的具体职责与去重策略monitorExecuteQueue.ts核心执行器。worker 内new Service(monitor).execute(ts)完成探测后还会做几件文档未展开的事在探测之后读取该分钟是否有手动 incident / maintenance 覆盖overlay 优先生效对配置了confirmation_threshold 1的 monitor 执行确认阈值阻尼连续 N 次才翻转状态缓解抖动误报最后按default → realtime → incident → maintenance的优先级合并出最终MonitoringResult并逐分钟推给monitorResponseQueue。去重 id 为${monitor.tag}-${ts}保证同一 monitor 同一分钟不会重复执行。monitorResponseQueue.ts调用InsertMonitoringData把结果写入monitoring_data表通过SetLastMonitoringValue更新最近一次探测值缓存再向alertingQueue推送{ monitorTag, ts, status }。alertingQueue.ts评估 monitor 的告警规则v2 告警配置、trigger 条件创建或解决 incident并通过sendEmail/sendWebhook/sendSlack/sendDiscord等通知函数产出告警。subscriberQueue.ts 与 emailQueue.ts前者向订阅者扇出后者发送单封邮件两者都在push时设置了 deduplication id防止重复通知。每个队列模块都导出自己的shutdown()统一由 queues/shutdown.ts 按 execute → response → alerting → subscriber → email 的顺序依次关闭 worker。中间件SvelteKit handle 钩子src/hooks.server.ts 的handle钩子对每个请求生效职责包括API 鉴权所有/api/*路由校验 Bearer tokenVerifyAPIKey仅PUBLIC_API_PATHS [/api/status]免鉴权资源预加载通过正则从路径中提取monitor_tag、incident_id、maintenance_id、page_path同时兼容/api/v{n}/...带版本前缀的写法从数据库取对应资源挂到event.locals供下游 handler 直接消费环境代理启动时调用InstallEnvProxy()安装代理环境变量。数据库层Kener 使用Knex.js作为查询构建器knexfile.ts 根据DATABASE_URL的前缀选择后端数据库连接前缀驱动备注SQLitesqlite://better-sqlite3默认值sqlite://./database/kener.sqlite.db无独立池复用单连接PostgreSQLpostgresql://pg开启keepAliveDATABASE_KEEPALIVE ! false防止云端网络静默丢弃空闲连接MySQLmysql://mysql2enableKeepAlive: true初始延迟 10s双连接池设计Postgres/MySQL 才适用knexfile.ts 构建两套池——WEB 池默认DATABASE_POOL_MAX10服务 SvelteKit 请求WORKER 池默认DATABASE_WORKER_POOL_MAX5服务 BullMQ 后台任务min默认为 0源码注释指出 knex 默认的 min 2 连接从不回收恰恰是最先变死的acquireConnectionTimeout默认 15 秒目的是数据库故障时快速失败而不是挂起 60 秒。预算约束副本数 × (web worker) 数据库 max_connections。Repository 模式数据库访问遵循仓库模式src/lib/server/db/repositories/ —— 按领域划分的仓库类仓库中实际包含monitors、incidents、monitoring、maintenances、alerts、users、pages、monitorAlertConfig、emailTemplateConfig、subscriptionSystem、images、site-data、base等src/lib/server/db/dbimpl.ts —— 门面facade组合全部仓库并暴露统一接口构造时接收knexOb与workerKnexOb两个配置实例src/lib/server/db/db.ts —— 导出单例DbImpl实例全应用统一通过import db from $lib/server/db/db访问数据库src/lib/server/db/poolContext.ts —— 请求上下文/worker 上下文的连接池路由实现与q.ts中的runInWorkerContext配合。迁移Migrations迁移文件存放在仓库根目录 migrations/启动时由db.migrate.latest()自动执行到最新状态覆盖 monitors、incidents、subscriptions、maintenances、告警体系、RBAC、OIDC 等历次 schema 变更种子数据在 seeds/ 目录roles、permissions、monitors、pages、site_data 等由db.seed.run()在迁移后执行。构建系统构建分两个阶段见 package.json 的 scripts阶段 1SvelteKit 构建npm run build:sveltekit即vite build把 SvelteKit 应用编译到build/build/handler.js—— SvelteKit 请求处理器生产运行时由 Express 动态加载build/server/—— SSR 产物build/client/—— 静态客户端资源。阶段 2服务端构建npm run build:server运行 scripts/build-server.js用esbuild把scripts/main.ts及其全部服务端依赖打包为单一 ESM 文件build/main.js。源码可确认的关键选项entryPoints: [scripts/main.ts]platform: nodetarget: node20format: esmexternal 化全部node_modules运行时再解析但CJS_PACKAGES_TO_BUNDLE [rrule]例外——该 CJS 包在 ESM 命名导入下会出问题必须打包进产物alias将 SvelteKit 的$lib映射到./src/lib使服务端代码脱离 Vite 上下文后导入路径依然正确define在构建时注入import.meta.env.PACKAGE_VERSION pkg.version让 src/lib/version.ts 无需运行期插件即可取到版本自定义插件rewrite-build-imports把产物中的../build/前缀重写为./因为main.js与handler.js同在build/下否则 handler 的相对导入会指向错误位置。构建命令速查命令作用npm run build完整生产构建build-sveltekit.jsbuild-server.jsnpm run build:sveltekit仅 SvelteKit 构建npm run build:server仅服务端构建npm start运行生产服务器node build/main.js优雅退出Graceful Shutdownmain.ts 在SIGTERM/SIGINT时执行gracefulShutdown顺序为shutdownSchedulers()schedulers/shutdown.ts依次关闭 appScheduler、monitorSchedulers、maintenanceScheduler、dailyCleanupScheduler 的 workershutdownQueues()queues/shutdown.ts依次关闭 execute、response、alerting、subscriber、email 五个队列的 workerdbInstance.close()关闭数据库连接process.exit(0)任一步失败则以退出码 1 结束。先停调度器、再排空队列、最后关库的顺序保证停机时进行中的探测作业能跑完并落库部署与重启不丢数据。延伸阅读以下文档位于仓库src/routes/(docs)/docs/content/v4/下可与本文配套阅读Environment Variables/docs/content/v4/setup/environment-variables.md) —— 必需与可选配置项Database Setup/docs/content/v4/setup/database-setup.md) —— 数据库选择与配置Redis Setup/docs/content/v4/setup/redis-setup.md) —— 为 BullMQ 队列配置 RedisMonitors Overview/docs/content/v4/monitors/overview.md) —— 各 monitor 类型与配置说明Getting Started/docs/content/v4/getting-started.md) 与 Installation/docs/content/v4/installation.md) —— 安装与首次部署。赞分享可观测性后端前端运维【免费下载链接】kenerStunning status pages, batteries included!项目地址https://gitcode.com/gh_mirrors/ke/kener点击查看免费下载相关推荐Kener 工作原理深度解析从 SvelteKit 前端到调度器、队列与数据存储Kener 工作原理深度解析从 SvelteKit 前端到调度器、队列与数据存储 本篇技术指南以 Kener 官方文档《How it works》为核心脉络可观测性后端前端运维终极指南Stack-on-a-budget如何构建免费开发者服务数据库终极指南Stack on a budget如何构建免费开发者服务数据库 Stack on a budget简称st是一个专为预算有限的开发者打造的免费服务教程开发工具Kener 开源状态页系统深度解读SvelteKit Node.js 架构、部署与核心能力全景Kener 开源状态页系统深度解读SvelteKit Node.js 架构、部署与核心能力全景 Kener 是一个基于 SvelteKit Node.可观测性后端前端运维创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考