Skip to content

后端自定义管理命令 ​

本文档记录当前后端项目中的自定义 Django management commands。命令均在 back_end/saolei 目录下执行:

bash
python manage.py <command>

本地公开数据快照 ​

dangerzone.init_local_test(独立脚本) ​

下载和初始化入口分别位于 dangerzone/download_public_data.py、dangerzone/init_local_test.py。在 back_end/saolei 目录使用 python -m 启动,后端根目录不再保留同名入口脚本。

仅用于替换本地测试数据,会清空本地数据库和 saolei_website 对应的 Redis 数据库。此功能不注册为 Django 管理命令,manage.py 不提供 import_public_data。执行前停止本地后台 worker、定时任务和其他写入;不需要启动 HTTP 服务。要求不存在项目配置的 .production 标记、DEBUG=True、E2E_TEST=True,数据库及 Redis 均为回环地址。即使误开调试选项,存在生产标记仍会拒绝导入。正式导入前检查快照校验和、引用关系和数据库迁移状态。

bash
# 仅 GET 生产公开 API,每次响应完成后至少等待 1.25 秒;失败后重复执行可续传。
python -m dangerzone.download_public_data
# detailbulk 不可用时,使用已有详情及公开录像列表完成快照:
python -m dangerzone.download_public_data --skip-details
python manage.py migrate
# 读取快照,清库、导入,并创建当天经典周赛。
python -m dangerzone.init_local_test
# 只导入,不创建周赛:
python -m dangerzone.init_local_test --no-weekly

默认快照目录是 back_end/saolei/tmp/public-data,已由 Git 忽略。下载脚本使用 --output-dir,导入脚本使用 --snapshot-dir 指定其他目录。已完成的快照不会自动重新下载,获取新快照请换一个目录。

下载通过 infoupdated 获取用户 ID,调用 userprofile/infobulk 下载用户资料,通过每个用户的 videolist 建立公开录像索引,再调用 video/detailbulk 下载详情。保留 ID 空洞,不会因空页提前停止。索引后被删除或变成不可见的录像列入 manifest.json 的 unavailable_video_ids。这不是跨请求一致的数据库备份。TLS 使用 certifi 的 CA 包验证,不跳过证书检查;遇到 429 或服务端临时错误会等待重试。

请求串行执行,上一响应完成后至少等待 1.25 秒再发送下一请求,主动控制 videolist 在每秒一次以内。--interval 可增加间隔,不能低于 1.25 秒;慢请求结束后也不会立即发送下一个请求。

--skip-details 不请求 detailbulk,已有的详情仍然优先使用,其他录像直接使用 get_user_videos 的列表数据;只有列表数据的录像列入 missing_detail_video_ids。可空的缺失指标保持 NULL,录像内标识为空字符串。列表里的 cl、ce 不能直接写入生成列,缺少分项点击数时,相关生成值也为 NULL。生产列表已返回 right_ce,列表回退也可以按 right_ce == 0 重建 NF 榜;详情缺少该字段或为 NULL 时保留列表值。旧快照缺失该值时保持 NULL,不凭旧模式编码推测。完成的快照不会重新下载,补充新字段或详情请使用新的 --output-dir 下载,再通过 --snapshot-dir 导入。

导入保留原用户/录像 ID、上传时间和公开属性,旧 NF 模式 12 转成 STD 00,不修改 right_ce。批量写入不会触发录像信号,随后重建录像计数、上传额度、扫雷网普通/NF 榜、PB 普通/NF 榜、top 普通/NF 榜及锚点、效率 Thrp 榜、pluck 纪录缓存和录像状态队列,不创建 pluck 计算任务。

  • 管理员:ID 2,密码 admin123456;普通账号:ID 48,密码 user123456。用户名保留生产数据;可通过 --admin-password / --user-password 指定其他密码。
  • 其他用户禁用密码登录,且所有用户中仅 ID 2 获得本地 is_staff 权限;邮箱统一为 user-{id}@example.invalid。生产账号不受影响。
  • 这两个公开详情 API 不提供原密码、邮箱、录像文件、头像文件、标识绑定和比赛关联;不会推测这些数据。录像可以用于列表和排行测试,但不能在本地播放或重新解析。录像计数只能基于本次导入的公开录像计算,无法排除缺失比赛关联的已公开比赛录像。
  • is_lucky 当前未由这两个详情 API 提供,导入保留模型默认值。已有 pluck 从公开录像列表补齐,缺失时保持 NULL,不重新解析录像。
  • 再次初始化会丢弃之前的本地数据;应使用独立的本地数据库及 Redis 数据库。不要对生产配置运行这些命令。

缓存重建 ​

rebuild_eff_ranks ​

位置:effranking/management/commands/rebuild_eff_ranks.py

从录像数据库按玩家分段重建效率 Thrp 榜的个人纪录 hash 和 b/i/e/sum 四个 zset。--ranking-name 当前仅支持 thrp,默认即为该榜;--batch-size 默认 1000,必须为正数。

bash
python manage.py rebuild_eff_ranks --ranking-name thrp --batch-size 1000

执行前暂停录像上传、审核、修改、删除、标识绑定/解绑、比赛公开等写入。命令在独立临时命名空间构建全部五个 key,成功后用一个 Redis 事务原子替换正式榜;空的小榜删除对应旧 key,构建失败保留正式榜。结束时清理已知临时 key,不扫描其他 Redis 数据。不会修改录像数据库或其他排行榜。

首次部署先完成数据库迁移,再执行建榜;API 不回源数据库。后台刷新入口和管理员修复不能替代首次完整建榜。本地 python -m dangerzone.init_local_test 已自动执行重建,--no-weekly 同样会执行。单用户修复使用 POST /api/effranking/thrp/admin/rebuild_record,其中 sum 仅使用缓存中的三个原始单项计算。具体规则见 back_end/saolei/effranking/README.md。

rebuild_speed_ranks ​

位置:speedranking/management/commands/rebuild_speed_ranks.py

从 VideoModel 按玩家分段重建竞速排行榜。--ranking-name 可选 saolei 或 saolei_nf,省略则重建两者;--batch-size 默认 1000,必须为正数。

bash
python manage.py rebuild_speed_ranks
python manage.py rebuild_speed_ranks --ranking-name saolei_nf --batch-size 1000

执行前暂停录像上传、修改、删除、标识绑定/解绑及比赛公开等写入。命令先构建临时榜,成功后原子发布单个大榜,最后清理临时缓存。构建失败保留该大榜的原缓存;两个大榜依次替换。排行 API 不自动回源,因此首次部署及缓存丢失后都需要执行此命令。重建不恢复旧新闻。

从旧的“微秒时间戳 + 玩家 id” member 升级为纯玩家 id、分钟级合成 score 时,应暂停相关读写,以新代码执行不带 --ranking-name 的重建命令,两个大榜都完成后再恢复服务。超出编码范围的单项或总值仍保存在个人纪录 hash,仅不写入对应 zset;总榜独立判断,不修改录像数据库和个人最佳选取规则。

rebuild_pb_ranks ​

位置:speedranking/management/commands/rebuild_pb_ranks.py

从录像数据库重建 PB 普通/NF 榜、用户 rank hash 和小榜人数 hash。--batch-size 默认 100,必须为正数;按玩家 id 分段,每批输出累计用户数和最后玩家 id,最后逐小榜刷新排名并同步人数。清空 PB 命名空间时也会删除旧人数 hash;重建后未出现的小榜按 0 人处理。

bash
python manage.py rebuild_pb_ranks --batch-size 100

首次部署及 PB 缓存丢失后需要手动执行,API 不回源。执行前暂停 PB 相关读写,包括上传、审核、绑定/解绑、删除及比赛公开。命令直接清空 PB 命名空间后重建,不使用临时榜或原子发布;失败会记录日志并中断,可能留下部分缓存,排除错误后重新运行。录像数据库和扫雷网缓存不受影响。rebuild_speed_ranks 仍仅重建扫雷网规则。

本地快照初始化 python -m dangerzone.init_local_test 已在扫雷网重建后自动调用 PB 重建,使用 --no-weekly 时也会执行,无需另外运行上述命令。

rebuild_top_ranks ​

位置:speedranking/management/commands/rebuild_top_ranks.py

从录像数据库按玩家分段构建 top 的十八个小榜、前二十名均值锚点及九个软实力/LSF 派生榜。--batch-size 默认 100,必须为正数;每批输出累计用户数和最后玩家 id。

bash
python manage.py rebuild_top_ranks --batch-size 100

命令使用独立临时命名空间,构建明细、全部索引与锚点后,用一个 Redis 事务原子切换正式榜的三十个 key。构建失败保留旧榜,正常退出会清理临时 key;强制终止可能遗留临时数据。命令持单机独占锁:等待在途写操作完成,相关新 HTTP 写请求返回带原因的 503,后台业务等待后继续,旧榜只读服务继续。不需要停整个网站,但所有进程必须共享可写的 TOP_RANKING_LOCK_PATH(默认 BASE_DIR/runtime/top-lock.sqlite3);不要删除活动锁文件。自定义写入脚本也必须遵守准入。此命令不修改数据库,也不清理扫雷网或 PB 缓存。

首次部署和缓存丢失后手动执行,查询不自动回源。本地 python -m dangerzone.init_local_test 已自动调用此命令的重建服务,--no-weekly 不影响重建。单用户小榜修复使用 POST /api/speedranking/top/admin/rebuild_record。具体缓存与单位约定统一见 back_end/saolei/speedranking/README.md。

rebuild_top_soft_ranks ​

位置:speedranking/management/commands/rebuild_top_soft_ranks.py

仅从现有十八个原始 top zset 和实时锚点重建全部九个软实力/LSF 索引,发布相应锚点快照、版本和更新时间;不查录像数据库、不重新选取前五,也不修复原始榜。与后台刷新共用独占准入和临时榜发布逻辑,失败保留旧版并记录日志。首次部署或升级原始 score 编码应使用 rebuild_top_ranks,不能只运行此命令。

bash
python manage.py rebuild_top_soft_ranks

rebuild_tournament_cache ​

位置:tournament/management/commands/rebuild_tournament_cache.py

用途:重建比赛 Redis 缓存,包括当前 NORMAL 状态的比赛和这些比赛的参赛关系。

主要行为:

  • 清空 tournament:normal 和 tournament:normal:participants。
  • 读取 NORMAL 状态的 GSCTournament 和 WeeklyTournament。
  • 将比赛基础信息写入 tournament:normal。
  • 将当前 NORMAL 比赛的参赛关系按用户分组写入 tournament:normal:participants。

常用命令:

bash
python manage.py rebuild_tournament_cache

rebuild_tournament_user_cache ​

位置:tournament/management/commands/rebuild_tournament_user_cache.py

用途:重建 TournamentUser 的 Redis 排行缓存。

当前积分统一折算到当年 1 月 1 日 00:00 UTC,其余字段按数据库值写入;数据库记录不变。重建会同时清理旧的 7 个 zset 和时间基准 key。重建期间暂停积分结算及其他排行缓存写入;如果只需修复当前积分排序,使用下面的 rebuild_tournament_current_score_cache。

参数:

参数默认值说明
--batch-size1000每批处理的缓存行数

常用命令:

bash
python manage.py rebuild_tournament_user_cache
python manage.py rebuild_tournament_user_cache --batch-size 500

rebuild_tournament_current_score_cache ​

位置:tournament/management/commands/rebuild_tournament_current_score_cache.py

用途:从 TournamentUser.score_current / last_updated 重算当前积分 zset,统一使用当年 1 月 1 日 00:00 UTC 锚点。不修改数据库,不重放积分发放,不影响另外 6 个排行 zset。

积分折算沿用两年(730 天)减半的公式,允许负时间差:最后计分时间晚于年初时,反向折算会放大分数。所有用户使用同一锚点,排序等价于实时衰减后的排序。API 仍返回数据库原始积分与 last_updated,前端实时显示不变。

重算先将删除、锚点设置和分批写入加入 Redis 事务,完成后原子替换。期间读请求仍读取旧排行;并发缓存写入或锚点变化会触发重试。为避免读取尚未提交的数据库积分,重算期间应暂停积分结算和历史积分修正任务。空表同样清理旧排行并建立年初锚点。重复执行不会累计衰减。

参数默认值说明
--batch-size1000每批读取的用户数,必须大于 0;各批最终在同一 Redis 事务提交
bash
python manage.py rebuild_tournament_current_score_cache
python manage.py rebuild_tournament_current_score_cache --batch-size 500

部署年度锚点方案后先执行一次此命令,修复已有当前积分缓存,并重启 runapscheduler 注册年度任务。若其他 6 个排行也需要重建,则改用 rebuild_tournament_user_cache。

rebuild_custom_pluck_cache ​

位置:customranking/management/commands/rebuild_custom_pluck_cache.py

用途:重建自定义 pLuck 排行的 Redis 缓存。

参数:

参数默认值说明
--level无只重建指定自定义级别;省略则重建全部配置
--batch-size1000每批写入 Redis 的纪录数量

当前 --level 的合法值来自 CUSTOM_PLUCK_CONFIGS:

参数值配置
c8_8_408x8 40 雷
c16_16_10016x16 100 雷
c16_30_15016x30 150 雷
c24_30_20024x30 200 雷

常用命令:

bash
python manage.py rebuild_custom_pluck_cache
python manage.py rebuild_custom_pluck_cache --level c16_30_150
python manage.py rebuild_custom_pluck_cache --batch-size 500

数据刷新 ​

refresh_videos ​

位置:videomanager/management/commands/refresh_videos.py

用途:替代旧 refresh_stnb 的录像重解析入口,对指定 ID 范围内的 VideoModel 实例逐条调用 videomanager.view_utils.refresh_video,默认刷新全部录像。

  • 按主键顺序遍历,不限制状态、模式、级别或比赛标记。
  • 使用 iterator() 遍历录像 ID,避免 QuerySet 缓存所有实例;写入仍逐条执行,不使用 update() 或 bulk_update()。
  • 每条录像使用独立的 transaction.atomic(),通过 select_for_update() 加锁并重新读取该条主表记录后执行刷新,不连带锁住查询关联的用户等记录。解析与主表、扩展表保存均在该事务内;完成或失败后释放锁,不对全部录像使用一个长事务。其他事务修改或删除同一主表记录时会等待,因此耗时解析期间仍可能阻塞该条录像的写入。
  • 沿用 refresh_video 的差异保存逻辑,通过 save(update_fields=...) 触发信号;没有变化的字段不会强制保存。
  • 已知的录像解析异常会包装为 VideoParseError,报告录像 ID 并跳过,继续刷新后续录像;结束时汇总成功和跳过数量。
  • 数据库、文件读写、保存或信号接收器等其他错误会停止命令;当前条的数据库写入回滚,此前完成的刷新不会整体回滚。不会把刷新全过程中的 ValueError 等异常都视为解析错误。事务不回滚已经直接写入 Redis 的副作用。
  • 此命令不是无条件的排行榜或缓存全量重建。刷新模式等字段后如需重算录像计数,仍使用 refresh_video_counts。
  • 每处理 step 条录像输出并立即刷新进度,包括已处理数量、成功数量、跳过数量和当前录像 ID。解析失败也计入已处理数量;不足 step 的剩余部分在最终汇总中体现。
参数默认值说明
--start0起始录像 ID,包含,必须非负
--end不限制结束录像 ID,不包含,不能小于 start
--step100每处理多少条录像输出一次进度,必须为正整数

区间采用 [start, end),按录像 ID 筛选而非列表偏移;ID 存在空缺时,step 仍按实际处理条数计数。可以仅指定一个边界,或使用相邻区间连续分段刷新。

bash
python manage.py refresh_videos
python manage.py refresh_videos --start 10000 --end 20000 --step 50
python manage.py refresh_videos --start 20000 --end 30000 --step 50

refresh_video_counts ​

位置:msuser/management/commands/refresh_video_counts.py

用途:刷新全部 UserMS 的录像总数,以及各级别、模式的录像计数。

  • 排除 ongoing_tournament=True 的录像,以及通过 Tournament.videos 关联的所有比赛录像(包括已结束比赛)。
  • 普通录像不按审核状态筛选;没有普通录像的用户计数归零。
  • 按用户分批聚合并写入,不修改 video_num_limit 或个人纪录,也不更新 Redis。
  • 可重复执行。为避免与上传、删除并发造成计数覆盖,执行时应暂停录像写入。
参数默认值说明
--batch-size1000每批处理的用户数量,必须为正整数
bash
python manage.py refresh_video_counts
python manage.py refresh_video_counts --batch-size 500

refresh_tournament_scores ​

位置:tournament/management/commands/refresh_tournament_scores.py

用途:同步重算指定已颁奖比赛的成绩、排名、积分和参赛用户的历史最佳成绩,无需后台 worker。支持 GSC 和经典周赛。

  • 参数为比赛 ID,不是 GSC 届数或周数;只接受已颁奖、有结束时间的 GSC 或经典周赛。
  • 从关联的有效录像重新计算成绩,再刷新 rank 和 rank_score。缺少的成绩恢复默认值,不保留已失效录像的旧成绩。
  • 按新旧 rank_score 的差额修正历史积分总量,不重复累计。当前积分加入从该届结束时间衰减到 last_updated 的差额;若该届结束得更晚,则先将原当前积分衰减到该届结束时间。
  • last_updated 表示最后结束的比赛时间,不因重算历史比赛而回退。积分及个人历史最佳成绩的 Redis 缓存同步刷新;历史最佳成绩支持变好和变差。
  • 不删除参赛者、不修改比赛状态、不公开录像、不重新解析录像文件。
  • 执行期间应避免同时修改该届的录像或参赛者,并暂停涉及这些用户的积分结算。该届的数据库写入在一个事务内,中途失败会回滚,可在排除故障后重跑;Redis 已写入的内容不会随数据库回滚,失败后需重跑以恢复缓存一致性。
参数默认值说明
tournament_id必填比赛 ID,不是 GSC 届数或周数
--batch-size1000批量写入大小,必须为正整数
bash
python manage.py refresh_tournament_scores 123
python manage.py refresh_tournament_scores 123 --batch-size 500

refresh_tournament_user_stats ​

位置:tournament/management/commands/refresh_tournament_user_stats.py

用途:刷新 TournamentUser 的历史 total 和 best 字段,不修改实时 score_current。

主要行为:

  • 根据已有 TournamentUser 和已颁奖比赛的参赛关系补齐缺失的 TournamentUser。
  • 刷新比赛积分历史总分。
  • 刷新金羊杯和打卡赛历史最好成绩。
  • 将更新后的历史字段同步到 Redis 缓存。

参数:

参数默认值说明
--batch-size1000批量创建、批量更新和缓存写入的批大小

常用命令:

bash
python manage.py refresh_tournament_user_stats
python manage.py refresh_tournament_user_stats --batch-size 500

缓存清理 ​

delete_legacy_speedranking_cache ​

位置:common/management/commands/delete_legacy_speedranking_cache.py

用途:竞速排行榜重构第一步中,清理旧排行榜和新闻的 Redis key。应在停用旧功能的写入逻辑后执行,否则旧代码会重新生成缓存。

  • 清理player_{stat}_{mode}_{用户ID}和player_{stat}_{mode}_ids:指标限于timems、bvs、stnb、ioe、path,模式限于std、nf、ng、dg。
  • 清理news_queue,不依赖该key是list还是zset。
  • 保留自定义pluck排行、比赛缓存、录像队列及其他Redis数据,不修改数据库。
  • 使用saolei_website Redis连接,通过SCAN遍历,每批最多1000个key,用UNLINK删除。可重复执行。
bash
python manage.py delete_legacy_speedranking_cache --dry-run
python manage.py delete_legacy_speedranking_cache

--dry-run仅列出匹配的key并统计数量,不删除数据。

delete_newest_queue ​

位置:videomanager/management/commands/delete_newest_queue.py

用途:调用 videomanager.services.delete_newest_queue,清理 Redis 最新录像队列。

队列不超过 100 条时不处理;超过 100 条时删除所有超过 7 天的记录,因此清理后可能少于 100 条。该逻辑也由 runapscheduler 每天 01:08 调用。

常用命令:

bash
python manage.py delete_newest_queue

后台任务与定时任务 ​

db_worker_robust ​

位置:common/management/commands/db_worker_robust.py

用途:安全启动 django-tasks-db worker。启动前会处理孤儿 RUNNING 任务,并通过 pidfile 防止重复 worker。

主要行为:

  • 使用 pidfile 防止重复启动。
  • 检测当前是否已有 db_worker 或 db_worker_robust 进程。
  • 若没有正在运行的 worker,则将遗留的 RUNNING 任务标记为 FAILED。
  • 调用 Django 内置的 db_worker 开始处理任务。

参数:

参数默认值说明
--queue-name默认任务队列要处理的队列;多个队列用逗号分隔,* 表示全部
--interval2worker 轮询间隔,单位秒
--backend默认任务后端使用的 task backend
--batchFalse传递给 db_worker 的批处理模式
--no-startup-delayFalse关闭 db_worker 的启动延迟
--max-tasks无最多处理的任务数
--worker-id自动生成覆盖自动生成的 worker id
--pidfilelogs/db_worker_robust.pid用于防止重复启动的 pidfile 路径

常用命令:

bash
python manage.py db_worker_robust
python manage.py db_worker_robust --queue-name default --interval 2

生产启动:

  • start.sh 通过 nohup bash run_background.sh db-worker 启动,日志写入 logs/db_worker.log。
  • START_DB_WORKER=0 可跳过启动;DB_WORKER_START_DELAY、DB_WORKER_NICE、DB_WORKER_INTERVAL 默认分别为 20、10、2。
  • 该命令的恢复逻辑仅在启动时处理遗留任务,运行期间退出后不会自动重启。

runapscheduler ​

位置:common/management/commands/runapscheduler.py

用途:在 common APP 的单一常驻进程中运行定时清理和比赛当前积分年度锚点更新。

任务注册位置:common/apscheduler.py

定时任务:

任务频率说明
delete_old_job_executions每周一 00:03清理旧的 APScheduler job execution 记录
delete_newest_queue每天 01:08队列超过 100 条时删除超过 7 天的记录
delete_freezed_video每天 01:28删除 7 天以前冻结状态的录像
delete_overdue_emailverifyrecord每周一 01:03清理 1 小时以前的邮箱验证码
delete_overdue_captcha每周一 01:05清理过期图形验证码
refresh_tournament_score_reference每年 1 月 1 日 00:00 UTC将当前积分 zset 整体折算到新年年初,并原子更新锚点;数据库不变

年度锚点任务还会在调度器启动时执行一次幂等检查,补上跨年停机或错过执行时间的更新;重复检查同一年不会再次衰减。普通结算只读取已有锚点,不会根据比赛结束时间或当前年份自行推进,因此调度器暂时停机也不会混用不同时间基准。此任务设置 coalesce=True、misfire_grace_time=None 和单实例运行。

旧的 World 监控页面和 CPU/网络 IO 采集任务已移除;monitor APP 改为承载服务器统计 Ninja API,不再注册定时采集。新增 /api/monitor/memoryusage 仅在请求缓存未命中时采集,完整结果缓存 300 秒,详见服务器内存统计。runapscheduler 启动注册任务时会删除数据库中残留的 refresh_state_always 任务,避免任务存储恢复已删除的采集函数;其余五项清理任务保持运行。更新后需重启原 APScheduler 进程。

参数:

参数默认值说明
--pidfilelogs/apscheduler.pid用于防止重复启动 APScheduler 进程的 pidfile 路径

常用命令:

bash
python manage.py runapscheduler
python manage.py runapscheduler --pidfile logs/apscheduler.pid

生产启动:

  • start.sh 会在数据库迁移完成后,通过 nohup bash run_background.sh apscheduler 启动该命令,日志写入 logs/apscheduler.log。
  • START_APSCHEDULER=0 可跳过启动 APScheduler。
  • APSCHEDULER_START_DELAY 控制启动延迟,默认 10 秒。
  • APSCHEDULER_NICE 控制进程 nice 值,默认 10。

WARNING

生产环境只应运行一个 APScheduler 进程。旧的 runapschedulermonitor、runapscheduleruserprofile 和 runapschedulervideomanager 命令已合并到 runapscheduler,不应再单独启动。

后台进程退出排查 ​

run_background.sh 保留启动延迟和进程优先级设置,使用 nohup 忽略挂断信号,并将标准输入重定向到 /dev/null。Python 使用 -u 即时输出日志,使用 -X faulthandler 输出部分原生崩溃的 traceback。

启动日志包含时间、launcher PID 和 Python 进程 PID。子进程结束后,launcher 会记录 exit_status 和对应的信号名称。exit_status=0 表示正常退出;非零状态需要结合此前日志分析。大于 128 的状态通常表示信号终止,例如 137/KILL、143/TERM。worker 会处理 SIGTERM 并可能以状态 0 退出,因此状态 0 不能排除外部停止,需要查看此前的停止日志。仅凭 137 不能认定 OOM,也可能是手动或脚本发送了 SIGKILL。stop.sh 当前使用 SIGKILL 停止这两个服务。

Python 无法记录 SIGKILL;若 launcher 也被杀死,退出记录同样可能缺失。挂断保护不防止 systemd 会话清理等机制终止进程。这时需要对照退出时段的系统日志:

bash
tail -n 100 logs/apscheduler.log
tail -n 100 logs/db_worker.log
sudo journalctl -k --since today --no-pager | grep -Ei 'oom|out of memory|killed process|segfault'
sudo journalctl -u systemd-logind --since today --no-pager

应用的部分日志还会写入 logs/root.log 及各 APP 的日志文件;服务日志为空不代表这些文件也没有记录。

维护建议 ​

  • 新增管理命令后,应同步更新本文档。
  • 改动缓存结构后,应检查对应的重建命令是否仍能从数据库恢复 Redis 状态。
  • 全量刷新类命令应在低流量时执行,并在执行前确认是否需要备份。
  • 定时任务命令是常驻进程,生产环境应由进程管理工具启动和守护。