基于 Java 21 + Spring Boot 3.2 的推荐系统,Maven 多模块单仓,面向生产标准但可本地一键跑通。
设计文档见
docs/(项目总览 / 技术栈 / 架构设计 / 关键技术点 / 系统能力 / 本地运行 / 权限接入,及docs/skills/19 篇技术拆解);PLAN.md为初始搭建计划(历史存档),现状以docs/00-项目总览.md为准。
| 依赖 | 版本 | 说明 |
|---|---|---|
| JDK | 21 | /usr/libexec/java_home -v 21 |
| Maven | 3.9+ | |
| Docker | + Compose | 提供 postgres/redis 等中间件 |
| Gemini API Key | 可选 | 向量化用;未申请前留空不影响编译与骨架 |
recsys/
├── recsys-common # 共享契约:接口/DTO/常量(被所有模块依赖,改动需广播)
├── recsys-ad-common # 广告共享契约(BidType/AdCatalogEvent/sharding.yaml) [lib]
├── recsys-proto # gRPC 契约(.proto + 防腐 mapper + 内部令牌拦截器) [lib]
├── recsys-platform # 平台安全/Web(内部 HMAC token/过滤器/全局异常) [lib]
├── recsys-gateway # 网关(:8080;边缘认证:自签 JWT 默认/Casdoor OIDC 可选) [app]
├── recsys-rec-engine # 推荐编排,对外主入口(:8081) [app]
├── recsys-query # Query 理解(归一/分词/意图/IDF) [lib]
├── recsys-recall # 多路召回(12 通道) [lib] Track B
├── recsys-rank # 排序(规则/ONNX,9 策略) [lib] Track C
├── recsys-ad # 搜索广告在线库(召回/竞价/计费/出价) [lib]
├── recsys-feature # 特征读写 [lib] Track C
├── recsys-embedding # 向量化(Gemini,可降级本地 BGE) [lib] Track A
├── recsys-content # 物品元数据 [lib] Track A
├── recsys-user # 用户画像 [lib]
├── recsys-behavior # 行为采集(:8082) [app] Track E
├── recsys-offline # 离线作业(导入/灌向量/CF/样本/双塔) [app] Track A/E
│ └── sql/ # 数据库 schema(容器首启自动执行)
├── recsys-console # 控制台后端 console-api(:8090) [app] Track F
├── recsys-advertiser # 广告主管理服务(:8083;可选 auth-platform 判权) [app]
├── recsys-ad-serving # 广告投放内部服务(HTTP :8085 / gRPC :9095) [app]
├── recsys-content-service # 内容内部服务(HTTP :8086 / gRPC :9096) [app]
├── recsys-user-service # 用户画像内部服务(HTTP :8087 / gRPC :9097) [app]
├── recsys-streaming # 实时特征 Flink 作业(本地 MiniCluster) [app]
├── libs/authz/ # 供奉依赖:auth-platform SDK jar(未发布制品,根 pom 自动 install-file)
└── console/ # 控制台前端(独立 Vite 工程,nginx 同源托管) [前端]
前后端分离:前端为仓库根
console/(React SPA,npm run dev/ nginx),后端recsys-console只提供控制台 BFF 接口(离线报表读取等)。见console/README.md。
[lib]为被依赖的计算/领域库(不打可执行 jar);[app]为可执行服务。 单体起步:rec-engine聚合各[lib]在一个进程内跑通;后续可平滑拆为独立微服务。
# 1. 准备环境变量
cp .env.example .env # 按需填写 GEMINI_API_KEY 等
# 2. 启动中间件(核心:postgres + redis;schema 自动建好)。容器编排统一在 docker/ 目录。
# 一键全栈容器化(基础设施 + 8 app + 前端,推荐):scripts/dev-local.sh up
# 或手动用 docker compose(从仓库根 -f 指向 docker/,或 cd docker 后直接跑):
docker compose -f docker/docker-compose.yml up -d
# 需要 kafka 时(可选):docker compose -f docker/docker-compose.yml --profile full up -d
# 容器化全部后端服务(网关/编排/behavior/advertiser/console + 内部服务 ad-serving/content/user):
# docker compose -f docker/docker-compose.yml --profile apps up -d # 参数化 docker/Dockerfile 构建 fat jar,经 Nacos 互联
# (Nacos 默认开:apps profile 自带 nacos 容器,各服务 NACOS_DISCOVERY=true 注册;纯本地 mvn 无 Nacos 时
# 给网关加 --spring.profiles.active=static 回退静态路由)
# 观测栈(Prometheus/Grafana/Alertmanager/Tempo):docker compose -f docker/docker-compose.yml --profile obs up -d
# 细粒度判权(可选):docker compose -f docker/docker-compose.yml --profile authz up -d
# 起 recsys 专属 SpiceDB(:8544),配合 advertiser 的 RECSYS_AUTHZ_MODE=shadow|enforce,见 docs/09
# 3. 设置 JDK 21 并构建
export JAVA_HOME=$(/usr/libexec/java_home -v 21)
mvn clean install
# 4. (开发阶段)按需启动各服务,例如编排服务:
mvn -pl recsys-rec-engine spring-boot:run| 服务 | HTTP 端口 | gRPC 端口 | 说明 |
|---|---|---|---|
| gateway | 8080 | - | 统一 API 网关(Docker 默认 Casdoor OIDC 租户登录;可成对切回 legacy 演示模式,docs/09) |
| rec-engine | 8081 | - | 推荐编排,对外主入口 |
| behavior | 8082 | - | 行为采集 |
| advertiser | 8083 | - | 广告主管理(写侧) |
| ad-serving | 8085 | 9095 | 广告投放内部服务 |
| content-service | 8086 | 9096 | 内容内部服务(gRPC,HTTP 仅 actuator) |
| user-service | 8087 | 9097 | 用户画像内部服务(gRPC,HTTP 仅 actuator) |
| console-api | 8090 | - | 控制台 BFF(离线报表 + 系统总览) |
| postgres | 5432 | - | pgvector |
| redis | 6379 | - | |
| kafka(可选) | 9092 | - | --profile full |
| nacos | 8848 | - | --profile full/apps(默认开:服务注册发现 + rec-engine 配置中心) |
| spicedb(可选) | 8544 | 50052 | --profile authz(细粒度判权,docs/09) |
| prometheus(obs) | 9090 | - | --profile obs |
| grafana(obs) | 3001 | - | --profile obs |
内部服务化模块(
ad-serving/content-service/user-service):以 gRPC 对内提供能力,rec-engine 代码默认走in-process(单体)不依赖它们;设AD_SERVING_MODE=grpc/CONTENT_SERVING_MODE=grpc/USER_SERVING_MODE=grpc才切到 gRPC 调用(容器全栈--profile apps下三者默认已置 grpc,经 Nacosdiscovery:///发现)。三者同时暴露 HTTP/actuator/{health,prometheus}供健康探测与 Prometheus 抓取(需scanBasePackages含com.recsys.platform启用平台安全链,并在recsys.security.permit-paths放行/actuator/prometheus)。# 按需单独起某个内部服务(示例:内容服务) mvn -pl recsys-content-service spring-boot:run # HTTP :8086 + gRPC :9096 mvn -pl recsys-user-service spring-boot:run # HTTP :8087 + gRPC :9097 mvn -pl recsys-ad-serving spring-boot:run # HTTP :8085 + gRPC :9095
推荐链路的在线指标经 Micrometer 暴露在各服务的 /actuator/prometheus,由 Prometheus 抓取、Grafana 看板呈现。
# 1. 起观测栈(默认不启动,profile=obs)。Java 服务跑在宿主机,容器内经 host.docker.internal 抓取
docker compose -f docker/docker-compose.yml --profile obs up -d
# 2. 正常起 rec-engine(:8081)+ behavior(:8082)
mvn -pl recsys-rec-engine spring-boot:run # 另开终端
mvn -pl recsys-behavior spring-boot:run
# 3. 打开 Grafana → 看板 "Recsys 在线观测"
open http://localhost:3001 # admin/admin,数据源+看板已预置核心指标(recsys.*,Prometheus 中下划线命名):
| 指标 | 含义 |
|---|---|
recsys_recommend_duration_seconds(Timer,带直方图) |
编排端到端延迟,tag rank/cold/outcome,可算 P50/P95/P99 |
recsys_recommend_cache_total{result} |
结果缓存命中/未命中 → 命中率 |
recsys_recommend_empty_total / recsys_recommend_seen_cleared_total |
空召回 / 已看过滤把召回池清空的异常计数 |
recsys_exposure_total{recall,rank,rerank,cold} |
分桶曝光物品数(CTR 分母) |
recsys_click_total{recall,rank,rerank,...} |
分桶点击数(CTR 分子) |
recsys_rank_total{requested,served,reason} |
排序策略命中/回退;模型回退率 = served=rule 占 requested=onnx|deepfm 的比例,reason 区分 not_ready(模型没加载)/empty(返回空) |
在线分桶 CTR = recsys_click_total / recsys_exposure_total(按 rank/recall 聚合),与离线 ab-report 作业互补——一个实时、一个 T+1 精算。点击的分桶归因:曝光时编排层把 expo:{user}:{item}=bucket 写入 Redis(短 TTL),行为服务收到点击时回查回填,因此客户端不传 bucket 也能正确归因(服务端为准)。
recsys-streaming 消费 Kafka behavior-events 行为流,近实时算两类特征写 Redis,与离线 T+1 作业互补:
实时热度 ZSet recall:rt_hot(在线 HotRecaller 优先读它,缺失回落离线 recall:hot)、用户实时类目偏好 rt:user:{id}。
# 1. 起 Kafka(profile=full)。注:用官方 apache/kafka 镜像(Bitnami 旧 tag 已下架)
docker compose -f docker/docker-compose.yml --profile full up -d kafka
# 2. behavior 以 Kafka 模式起(投递行为到 behavior-events,不可用时自动降级入库)
BEHAVIOR_USE_KAFKA=true mvn -pl recsys-behavior spring-boot:run
# 3. 跑 Flink 实时作业(脚本含 Java 21 所需 --add-opens;首次自动打 fat jar)
bash recsys-streaming/run-streaming.sh --window-min 10 --slide-sec 20
# 4. 打点行为 → 观察实时热度
curl -XPOST localhost:8082/api/behavior -H 'Content-Type: application/json' \
-d '{"userId":1,"itemId":2959,"action":"CLICK","scene":"feed"}'
docker exec recsys-redis redis-cli zrevrange recall:rt_hot 0 -1 withscores各 Track 的范围、依赖、验收标准见 PLAN.md(历史存档)。原则:
- 只改本 Track 负责的模块目录;
- 依赖
recsys-common已定义的契约,不擅自改动(需改先广播); - 跨 Track 的下游依赖先 mock,Phase 2 集成时替换。
.env含密钥,已被.gitignore忽略,切勿提交。- 向量维度统一为 768(
recsys.embedding.dimension),与item_embedding vector(768)一致;换模型需全量重灌向量。