13 KiB
91迭代后端技术方案
适用分支:feat/91-iteration
本文覆盖本次迭代的后端整体改造。短视频环形队列的底层细节另见
SHORT_VIDEO_RECOMMENDATION_BACKEND_TECH_DESIGN.md,前端对接以 短视频推荐接口.md 为准。
1. 总体架构
本次不新增独立服务、Kafka Topic、MQ、Redis实例、端口或操作系统crontab,继续使用现有三个发布单元:
app:App/H5接口、推荐读取、观看次数、付费引导、AI女友等。web:亚模块、付费引导、评论Banner、VIP A/B等管理接口。skd:短视频推荐分刷新与全局队列构建。
数据存储沿用MongoDB与Redis。观看次数消费、AI女友上下分和VIP实验发布使用MongoDB事务,生产MongoDB必须支持事务。
2. 短视频全局推荐
2.1 推荐分
视频增加单调递增的真实互动计数和每日计算分:
recommendScore = recommendLikeCount
+ recommendCollectCount * 2
+ recommendCommentCount * 3
+ recommendShareCount * 5
真实互动成功新增时进入累计:点赞、收藏每次从未激活变为激活时增加,
评论每次成功发布时增加;取消或删除不回退,不计入 fake* 数据。
分享按用户+视频+北京自然日去重,可选 eventId 用于跨日重试幂等。
SKD使用Mongo游标固定高水位分批扫描,有界worker并发计算和BulkWrite。历史数据不依赖一次性推荐分脚本:刷新时对每项真实计数取
max(推荐累计, 历史真实计数, 0),并写入 recommendInitialized=true。生产首次写入量受 maxInitializationWrites 保护。
2.2 队列构建
候选仅包含当前审核通过、newsType=SHORT、未删除、recoWeight!=-1、未被亚模块配置排除的视频。
- 新视频:按审核通过时间计算24小时,从高分池排除。
- 每块:严格17条高分+3条新视频,新视频不足时用高分补足。
- 去重:同一队列中视频不重复,尾块不足20条保留实际数量。
- 定时:SKD启动时及默认每10分钟做健康检查;当天队列健康时不重复生成,缺失或不健康才重建。
新队列通过临时building key分批Pipeline写入,验证完整后原子切换 current。版本为 YYYYMMDD-<revision>,切版后新的offset Hash天然使全体用户从起点开始。
2.3 请求侧环形消费
推荐接口不使用pageNumber计算服务端位置。每个用户的下一读取位置存在当前版本的Redis Hash中,队尾按取模回绕。
读取使用 Reserve -> Mongo分批校验/补位 -> Commit 协议:
- Reserve阶段不立即推进offset,同一用户并发请求由15秒租约串行化。
- 只按实际扫描前缀Commit,查询失败或取消时Abort。
- 只有客户端显式传入、Trim后非空且不超过128字节的
X-Request-ID才对应commit receipt,解决“Redis已提交但HTTP响应丢失”的重试幂等; 中间件自动生成并回传的request ID不参与此业务幂等。 - Mongo按有界批次取数,扫描量受队列长度、
scanMultiplier和maxBatches共同限制,避免失效视频导致无界扫描。
主接口 /api/app/recommend/vid/list 和兼容接口 /api/app/vid/module/short/all
复用同一队列服务。只有尚未成功Reserve、返回的 QueueVersion 为空且非请求取消类故障时,
才使用原随机Set降级。Reserve成功后的Mongo校验或Commit失败会Abort并返回错误,
不再切到随机集;请求取消或超时在两个入口中保留返回空成功结果的兼容分支。
3. 首页随机刷新与亚模块治理
module_conf 增加稳定能力字段,前端不再根据可编辑标题判断:
haiJiaoStyle.sortRules[].refreshMode=RANDOM_TOP_NrandomCandidateN=30onlineAt/offlineAtexcludeLatest/excludeRecommendsearchOnlyWhenInactive
历史 val=2 在没有新字段时由后端兼容归一为随机刷新。新刷新接口从热门候选前30条中随机返回,refreshToken 保证同一次重试顺序稳定。
亚模块元数据使用15秒进程内不可变快照和singleflight。管理后台变更后会清理共享缓存和当前进程快照;
其他App进程最多延迟约15秒生效。excludeLatest 和 excludeRecommend
分别只控制“最新”和“推荐”场景。只有当亚模块当前失效且
searchOnlyWhenInactive=true 时,才拦截搜索以外的普通入口、分享和直接详情;
搜索结果签发10分钟、绑定UID和视频ID的 searchAccessToken,详情接口校验后放行。
4. 更新红点
GET /api/app/content/update-markers 聚合首页最新、当日最新及各亚模块最后内容时间。视频取最近审核时间,动漫/漫画同时考虑子集更新时间;“当日”按UTC+8自然日计算。
返回结果在Redis缓存30秒,前端保存本地已读时间并自行判断红点,后端不保存每用户红点状态。
5. AI女友V2与钱包
POST /api/app/aimatev2/url 在用户级Redis锁内:
- 先回收上一次第三方剩余金额。
- 读取主钱包,按
10金币=1元转换并请求第三方授权URL。 - 授权URL成功后,在Mongo事务内扣减主钱包并写入
fund_transfer_log。
GET /api/app/mine/wallet 返回钱包前尝试将第三方余额下分回主钱包,保留不足1金币的换算余数供下次结算。
fund_transfer_log 记录上下分类型、金额、操作后余额和余数,用于审计和辅助状态排查;
它没有业务幂等唯一键,第三方上下分与本地Mongo事务也不是跨系统原子操作。
6. 付费引导与会员内容上新
payment_guide 按场景、用户分层、排序、启用状态和有效期选择配置。过期会员、已退款且权益回收的用户按未付费分层处理。注册不足24小时的未付费用户始终属于新用户分层;免费次数是否用完只控制 HOME_NEW_USER_FREE_TRIAL 的 show,不得影响其他场景的新老用户分层。
VIP_CONTENT_UPDATE 动态查询审核通过的最新VIP视频,排除免费区、金币视频、不可推荐和业务配置排除的亚模块,按 reviewAt desc, _id desc 返回。数量由 videoLimit 控制,默认4,最大20。
视频ID顺序生成 contentVersion。展示回执按“UID+配置+场景+内容版本”幂等记录到 payment_guide_impression;新内容产生新版本后可再次提示。
7. 评论区Banner
scene_banner 支持 COMMENT_TOP 场景的多张图片/GIF、内链/外链/无跳转、排序、启用和上下架时间。App端只返回当前有效数据,按 sort desc, updatedAt desc 轮播。历史每场景单条唯一索引需在模型初始化时删除,再创建查询索引。
8. VIP卡片A/B与下单归因
vip_card_experiment 保存A/B流量、套餐集合、默认套餐、角标文案、皮肤标识和 uiConfig。UID通过稳定哈希命中A或B,发布新实验时在事务内停用旧实验,唯一active-slot索引保证全局只有一个生效实验。
App套餐接口仅返回当前UID命中分组的:
experimentId/variant/skinKey/defaultProductIduiConfig.backgroundImage(会员中心整页背景)uiConfig.badgeStyles(角标背景色和文字色)- 每个套餐的
badgeType/badgeText
vip_card_analytics_event 使用唯一 eventId 幂等保存卡皮展示、套餐展示和无购买关闭事件,统计同时返回去重人数和次数。
下单增加 sourcePage/sourceRef/videoId/activityId/experimentId/experimentVariant/sessionId。sourcePage 允许任意非空值,Trim后最长128个Unicode字符,空值归一为 UNKNOWN。参与实验时,后端校验UID分组、套餐归属和会话ID,并在创建订单时固化归因,支付回调不重新分组。
9. 免费观看与试看角标
GET /api/app/vid/user/count 增加 totalWatchCount,值改为后端系统配置 sys_conf 的 totalWatchCount(gpCode=common)。新客户端通过 POST /api/app/vid/play/consume 消费次数:
- 在事务内写当日观看记录并扣减用户剩余次数。
consumeKey=UID+视频+自然日的唯一索引保证并发只扣1次。- 遇到Mongo短暂事务冲突时最多有界重试5次,提交后再清理用户缓存。
当前实现边界:消费接口会校验审核、VIP/作者、免费区、金币和当日已看,
但没有限制 newsType=SP/SHORT。因此其他审核通过且同样非金币、非免费区的视频也可能消费次数;
若产品要求与角标资格完全一致,上线前需补充类型校验。
视频列表/详情增加 showFreeTrialBadge/freeTrialRemaining/canUseFreeTrial。后端在一次列表组装中复用用户上下文,不逐视频查用户或配置。只有同时满足以下条件时显示:
freeTrialBadgeEnabled=true- 已登录、非VIP、剩余次数大于0
- 普通/短视频VIP内容,非金币、非免费区、非用户自己发布
10. 动漫/漫画最新时间
media 增加 latestPublishedAt。媒体首次/重新上架、批量激活或成功新增子集时同步更新;
普通媒体元数据更新不改该时间。亚模块“最新”排序为:
latestPublishedAt desc,
contentUpdateTime desc,
createdAt desc,
_id desc
查询会追加旧时间字段作为后续排序键,但不等价于针对每条记录做
$ifNull 回退,新旧文档混排仍可能失序。因此上线时必须单独交付并执行
script/mongo/backfill_media_latest_published_at.js;该脚本不依赖服务启动,本次与文档包一起迁移。
11. 视频置顶
现有视频编辑接口的 liaoBaTopSort 取消0~99限制,允许重复值;保存时仅更新当前视频,不交换其他视频。置顶列表按 liaoBaTopSort desc, reviewAt desc, _id desc 稳定排序。
12. 数据模型、索引与缓存
新增Mongo集合:
fund_transfer_logpayment_guidepayment_guide_impressionscene_bannervip_card_experimentvip_card_analytics_event
关键索引:
- 推荐扫描:
newsType, status, deleteAt, _id。Mongo读取和写入按有界批次执行, worker数有上限;但所有合格候选的精简快照会留在内存中再排序并组装17+3队列, 内存随候选量增长,上线前需用生产规模数据压测。 - 免费消费:
user_act.consumeKey部分唯一索引。 - 付费引导曝光:
uid, configId, scene, contentVersion唯一。 - VIP实验:
experimentId唯一、active-slot部分唯一、eventId唯一。 - 订单归因:
sourcePage, createdAt复合索引,以及experimentId, experimentVariant, productID, status复合索引。 - AI资金日志:
uid, category, createdAt desc。 - 媒体最新:模块、状态、删除标记与
latestPublishedAt/contentUpdateTime/createdAt/_id复合索引。
关键Redis键:
recommend:short:current
recommend:short:queue:{version}
recommend:short:offset:{version}
recommend:short:meta:{version}
recommend:short:build-lock
recommend:short:reservation:{version}:{uid}
recommend:short:commit-receipt:{uid}:{receiptId}
content:update-markers:v1
redsync:ai-fund:{uid}
App与SKD的 keyTTLHours 必须一致。queue/meta 默认按发布时间+72小时过期,
offset 使用meta记录的同一绝对过期时间;current 不设TTL,
build-lock 固定10分钟,reservation 固定15秒,commit-receipt 固定30秒。
13. 发布与数据迁移
- 核对
91迭代配置改动点.md,完成App、SKD运行配置和各环境业务配置。 - 在低峰发布
web,确认新集合/索引初始化成功,再发布app。 - 统计未初始化推荐字段数量,根据生产量调整
maxInitializationWrites。 - 发布
skd。可先使skd.shortRecommend.enabled=true生成队列,保持app.shortRecommend.enabled=false验证健康度,再开启App全局读取。 - 单独执行媒体历史回填脚本;执行前后统计缺失数并抽样验证。
- 逐项配置亚模块、付费引导、评论Banner、VIP A/B和免费试看开关;测试库数据不会自动迁移到生产。
推荐开关是服务实例级全局开关,不是按UID白名单灰度。影子验证应通过“SKD开、App关”完成。
14. 自测与验收
- 推荐:17+3比例、新视频不足、环形回绕、每日切版、同UID并发、超时重试、失效视频补位、降级。
- 亚模块:上下架边界、各入口排除、搜索token的UID/视频/过期校验、随机刷新重试稳定。
- 付费引导:全部用户分层、仅展示一次、内容版本变化、
videoLimit0/4/20/越界。 - VIP A/B:UID稳定分流、UI配置下发、事件去重、人/次统计、创建/支付/退款口径、下单归因。
- 免费观看:同UID+视频并发只扣1次,VIP/发布者/免费区/金币/当日已看分支,角标开关和剩余次数实时失效。
- AI女友:重复进入、重复返回钱包、余数处理、第三方失败、Mongo事务回滚和流水一致。
- 媒体:存量回填、新上架、子集更新、相同时间稳定排序与索引explain。
上线前应在接近生产数量级的数据上验证推荐全量扫描时间、BulkWrite批次、Redis发布耗时、接口P95/P99、Mongo连接池和Redis内存占用。