Ubuntu 22.04 上电商推荐系统从零部署:数据管道、召回排序与API实战
很多人做电商的个性化推荐第一反应是去找推荐算法框架觉得装个库、训练一个模型、写个接口就算上线了。真上手过就会明白一个能稳定承接线上流量的AI推荐系统难点根本不在模型花不花哨而在数据管道、召回、排序、服务化、效果评估这些模块怎么在 Ubuntu 22.04 上扎实地串起来。这套部署方案适合商品量在万级以上、日请求量几万到几十万、想自己掌控推荐逻辑而不是单纯调用外部接口的电商团队。如果你也处于“模型训练好了但不知道怎么放到线上”的阶段这篇内容应该能帮你少走不少弯路。下面直接按我实际部署的顺序写所有操作都在 Ubuntu 22.04 环境里验证过包含具体命令、配置和排障过程。1. 先别急着装模型从业务链路反推部署边界1.1 电商推荐系统到底需要哪几个模块很多人对推荐系统有个误解认为它就是一个模型文件放在那用户请求来了模型给个分数就完事。实际上一个“猜你喜欢”接口背后是一条完整链路用户打开 APP → 请求打到推荐服务 → 服务先拉取用户画像和上下文特征 → 召回出候选商品集合 → 经过排序模型打分 → 过滤已购、下架、违禁商品 → 返回列表 → 客户端曝光 → 后端记录行为日志 → 日志回流用于统计特征和训练新模型。所以部署推荐系统本质上是部署六个部分数据管道、特征仓库、召回引擎、排序模型、在线接口、评估监控。任何一个环节没跟上模型再强也发挥不出来。我在某电商项目的实践里最初就是把精力全放在排序模型上结果召回用的还是最朴素的 SQL 查询特征全靠在线实时算。上线之后 P95 延迟直接飙到 800ms而且用户行为日志没有回流模型根本没有新数据可学。后来才把这些模块拆清楚逐个补齐。这六部分里最容易被低估的是数据管道和特征仓库。数据管道负责把曝光、点击、下单这些日志变成干净的训练样本特征仓库负责把用户和商品的各种属性变成统一的特征服务供召回和排序共用。这两块如果做不好排序模型只能算半个模型。1.2 一套够用且不折腾的技术栈选型技术选型的原则很简单尽量少组件尽量好运维前期能跑通链路比什么都重要。参考我当时用的方案模块选型理由操作系统Ubuntu 22.04 LTS稳定、Python 生态友好、驱动兼容性好业务元数据MySQL 8.0商品、用户、订单等结构化数据缓存与在线特征Redis 7特征缓存、结果缓存、分布式锁行为日志队列Redis Stream用现成 Redis 实现轻量队列少维护一个组件离线特征存储ClickHouse / Parquet 文件样本明细、统计分析灵活且查询快向量召回FAISS支持 CPU索引构建简单排序模型DeepFM LightGBM深度模型负责 CTR树模型兜底可解释性API 服务FastAPI Gunicorn/Uvicorn异步支持好部署简单容器化Docker Compose本地环境和生产环境保持一致性监控Prometheus Grafana接口延迟、GPU 使用率、推荐效果监控这套组合不是性能最优解但对绝大多数电商团队来说足够稳定、足够简单。如果一开始就上大规模分布式计算引擎、微服务治理那一套光是排查问题就能让你崩溃。用 Redis Stream 做行为日志队列是我比较推荐的做法。很多人一想到消息队列就上重组件但对于每秒几百条日志的场景Redis Stream 完全够用而且不用额外部署 Broker故障排查面小很多。1.3 为什么选 Ubuntu 22.04 当服务端选 Ubuntu 22.04 不是因为它是“最新长期支持版”而是因为它在显卡驱动、CUDA、Python 3.10 这些推荐系统最依赖的组件上兼容性最省心。具体来说有几点很实际Python 3.10 是系统默认版本很多深度学习库对 3.10 的支持已经非常成熟避免自己编译 Python 的麻烦。显卡驱动和 CUDA 工具链有现成 apt 包安装和升级都方便而且内核模块和驱动版本冲突的概率比别的发行版小。systemd 管理服务非常顺手写个 unit 文件就能把 Gunicorn、定时训练脚本托管起来不需要额外守护进程工具。当然用别的 Linux 发行版也能做只是我需要把大量时间花在解决“为什么这个库编译不过”而不是“推荐效果怎么提升”上。Ubuntu 22.04 让我把这些无关问题降到最低。另外生产环境我强烈建议单独挂一块数据盘给 /data把模型文件、日志、数据库备份都放这里。系统盘坏了不致命数据盘有备份能恢复这是最基础的容灾意识。2. 环境与数据底座Ubuntu 22.04 上的搭建顺序2.1 系统初始化、Python 虚拟环境和 GPU 准备部署的第一步先把系统基础环境收拾干净。以新装好的 Ubuntu 22.04 为例sudo apt update sudo apt full-upgrade -y sudo apt install -y python3-pip python3-venv build-essential git curl htop tmux这里有几个细节值得说。tmux一定要装训练模型、看日志的时候经常需要挂在后台断开 SSH 也不会中断任务。build-essential是很多 Python 源码包编译的前置依赖缺了它装某些库的时候会爆“fatal error: Python.h: No such file or directory”。接着创建专用目录和虚拟环境sudo mkdir -p /opt/recsys/{src,data,models,logs} sudo chown -R $USER:$USER /opt/recsys cd /opt/recsys python3 -m venv venv source venv/bin/activate pip install --upgrade pip很多人图省事直接在系统 Python 里装深度学习库这是我很不推荐的做法。系统 Python 环境很容易被 apt 包管理搞乱一旦依赖冲突排查成本非常高。单独虚拟环境可以随便折腾坏了直接删掉重建。GPU 驱动的准备分两步走。先看显卡驱动状态# 检查驱动和显存是否正常 nvidia-smi如果系统里没有驱动可以用 Ubuntu 自带的驱动管理工具安装。Ubuntu 22.04 的默认源里就有专有驱动直接装稳定版本即可。装完重启后确认nvidia-smi能正常显示显卡信息。然后安装 CUDA 工具链。这条最坑的地方在于显卡驱动本身会声明一个最高支持的 CUDA 版本这意味着系统已经具备底层支持但 PyTorch 安装时还要带对应的 CUDA 运行库。我的做法是先确定驱动支持的最高版本再让 pip 安装完全匹配的 PyTorch 版本。pip install torch2.1.1 torchvision0.16.1安装完成后在 Python 里验证import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果torch.cuda.is_available()返回 False不要急着换显卡驱动先回 5.1 节看排查思路多半是 PyTorch 装成了 CPU 版本或者 CUDA 版本不匹配。2.2 用 Docker Compose 拉起 MySQL、Redis 和队列我不想在裸机上直接装 MySQL 和 Redis因为环境容易越装越乱。用 Docker Compose 管理基础服务是最省心的方式一条命令启动数据和配置都通过卷映射出来重装系统也不怕丢。在/opt/recsys/docker目录下创建docker-compose.ymlversion: 3.8 services: mysql: image: mysql:8.0 container_name: recsys-mysql restart: always environment: MYSQL_ROOT_PASSWORD: your_root_password MYSQL_DATABASE: ecommerce MYSQL_USER: recsys MYSQL_PASSWORD: recsys_password ports: - 3306:3306 volumes: - /data/mysql:/var/lib/mysql - /data/mysql-backup:/backup command: --character-set-serverutf8mb4 --collation-serverutf8mb4_unicode_ci redis: image: redis:7 container_name: recsys-redis restart: always ports: - 6379:6379 volumes: - /data/redis:/data command: redis-server --appendonly yes --maxmemory 4gb --maxmemory-policy allkeys-lru要注意的几点MySQL 一定要显式指定 utf8mb4电商商品名称、用户评论都可能有生僻字默认 latin1 会直接乱码。Redis 开启 appendonly 持久化防止机器重启之后缓存和队列数据全丢。maxmemory-policy allkeys-lru是为了避免 Redis 无限增长把内存吃满推荐结果缓存可以接受淘汰但队列数据要小心。启动时记得检查日志docker compose up -d docker compose ps docker compose logs mysql redis这套基础服务跑起来后业务系统就能往里写元数据了。我的习惯是先把商品表、用户表、订单表迁进来再考虑日志表和特征表因为后面的推荐链路全部依赖这些数据。2.3 埋点日志与特征表的标准化处理推荐系统最怕脏数据。我踩过最大的坑是模型训练时用的样本和线上真实数据不一致。为了解决这个问题必须把埋点日志和特征表做标准化。一条标准的行为日志至少要包含这些字段字段示例说明user_id100023用户唯一标识item_idSKU88321商品唯一标识scene_idhome_guess_like场景标识比如首页猜你喜欢action_typeview / click / add_cart / order行为类型ts1739683200000事件时间戳毫秒级extra{from:recsys_v2}扩展信息推荐位版本等埋点日志可以统一推送到 Redis Stream再通过一个消费脚本定时写入 ClickHouse 或 Parquet 文件。建表时按天分区这样训练脚本和报表都能按分区扫描。商品特征表、用户特征表也需要提前设计好。商品特征表至少包含类目、品牌、价格、销量、上下架状态、上架时间、标题关键词分词结果。用户特征表至少包含注册时间、历史点击类目分布、历史客单价、活跃度、最近浏览序列。Redis 里我一般保存两份特征缓存feature:item:{item_id}Hash存商品实时特征比如近一小时销量、价格。feature:user:{user_id}Hash存用户画像和最近行为序列设置 30 分钟过期避免特征太旧。在线推荐的时候直接读这些缓存不需要反复查 MySQL。缓存没有命中的用户再走降级逻辑从 MySQL 拉取基础特征并回填缓存。这一步能让推荐接口的延迟从几百毫秒降到几十毫秒后面 4.1 节会细说。3. 召回、排序到 API核心推荐引擎的完整实现思路3.1 双路召回向量召回和协同过滤召回召回的目标是从几万到几十万个商品中快速缩小到几百个候选。这个阶段宁多勿缺召回越全排序模型才有机会选更好的结果。我采用了“双路召回”策略第一路向量召回。先用 Item2Vec 或类似思路把用户点击序列转换成语义向量。做法是基于用户历史点击的商品序列训练商品的 Embedding 向量再通过 FAISS 建索引。线上请求时把用户最近点击过的 N 个商品向量求平均作为用户向量在 FAISS 里检索最相近的 topK 商品。构建 FAISS 索引的代码大致如下import faiss import numpy as np # item_vectors: shape (num_items, embedding_dim) # item_ids: 商品原始 ID 列表 index faiss.IndexFlatIP(embedding_dim) index.add(item_vectors.astype(np.float32)) # 保存 ID 映射关系 faiss.write_index(index, /opt/recsys/models/item2vec.index) np.save(/opt/recsys/models/item_ids.npy, np.array(item_ids))FAISS 的IndexFlatIP是内积索引适合归一化后的向量余弦相似度检索。批量检索时可以直接用index.search(user_vector, top_k)速度非常快。前期数据量不大用 CPU 版本就够了。第二路协同过滤召回。根据用户最近点击的商品找到“看了还看”的相似商品。这一路实现简单线上很稳还能弥补向量召回在某些冷门商品上的不足。两路召回结果合并后按来源和热度做一个简单加权再作为排序模型的输入。这里切记召回阶段不要只依赖一路结果不同用户群体在不同场景下的行为差异很大单路召回很容易造成推荐单调。3.2 排序模型从特征工程到 DeepFM 训练排序模型负责给候选商品打分。召回可以简单排序不能太糙因为它直接决定用户最终看到什么。特征工程方面我把特征分成三类用户侧特征性别、年龄段、历史点击率、历史下单率、平均浏览时长、最近 7 天点击类目分布。商品侧特征类目、品牌、价格、近 24 小时销量、历史 CTR、库存状态。交叉特征用户最近浏览的类目和当前商品类目是否一致、当前商品价格与用户历史客单价的比值。样本构造上曝光日志里被点击的样本标为正样本被曝光但未点击的标为负样本。下单行为可以在训练时赋予更高权重让模型更关注成交人群。DeepFM 的结构不算复杂核心是 FM 部分的二次特征交叉和 Deep 部分的隐式交叉。我用 PyTorch 写了一个简化版本线上推理时加载训练好的权重import torch from torch import nn class DeepFM(nn.Module): def __init__(self, num_features, embedding_dim, deep_layers): super().__init__() self.linear nn.Linear(num_features, 1) self.embedding nn.Linear(num_features, embedding_dim) self.deep nn.Sequential( nn.Linear(embedding_dim, deep_layers[0]), nn.ReLU(), nn.Linear(deep_layers[0], deep_layers[1]), nn.ReLU(), nn.Linear(deep_layers[1], 1) ) def forward(self, x): # FM 一阶部分 linear_part self.linear(x) # FM 二阶部分简化内积形式 emb self.embedding(x) sum_square torch.pow(emb.sum(dim1, keepdimTrue), 2) square_sum (emb.pow(2)).sum(dim1, keepdimTrue) fm_part 0.5 * (sum_square - square_sum).sum(dim1, keepdimTrue) deep_part self.deep(emb) return torch.sigmoid(linear_part fm_part deep_part)训练时用 BCEWithLogitsLoss配合 Adam 优化器。离线评估我不只看 AUC还看 GAUC也就是按用户分组后的 AUC。GAUC 更能反映每个用户内部的排序能力比全局 AUC 有意义得多。模型训练完成后保存成 TorchScript 或直接保存state_dict。在线服务启动时加载权重推理时把一个用户的所有候选商品拼成一个 batch 输入一次前向传播拿到所有分数。3.3 用 FastAPI 把推荐服务发布成标准 HTTP 接口模型训练好之后下一步就是让推荐结果通过接口暴露给业务方。FastAPI 是我用过最省心的 Web 框架天然支持异步、自动生成接口文档部署也简单。接口示例from fastapi import FastAPI, Query from pydantic import BaseModel app FastAPI(titleRecommendation API, version2.0.0) class RecommendResponse(BaseModel): code: int items: list app.get(/api/v1/recommend, response_modelRecommendResponse) async def recommend(user_id: int, scene: str home, limit: int 20): # 1. 获取用户特征若 Redis 有缓存直接取 user_features get_user_features(user_id) # 2. 执行双路召回 candidates recall(user_id, scene, limit * 5) # 3. 排序模型打分 scores rank_net(user_features, candidates) # 4. 后置过滤和重排 final_items post_filter_and_rerank(user_id, candidates, scores, limit) return {code: 0, items: final_items}后置过滤这步很多人会忘但它极其重要。比如过滤已购商品、下架商品、黑名单商品同品类最多连续出现两个价格带和用户历史偏好匹配新品和爆品保持一个合理的比例。服务启动时我用 Gunicorn 托管 Uvicorn Workergunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 --timeout 60在 Docker 里跑也一样镜像里装好依赖把模型文件打进镜像或者挂在宿主机目录。我的做法是模型文件通过卷映射挂载这样每次更新模型不用重新打镜像只要重启服务进程即可。4. 优化不是只调参数延迟、资源与效果的三方平衡4.1 特征缓存与批量推理把 P95 延迟降下来的过程第一版服务上线后压测结果其实不太行。P95 延迟在 400ms 左右用户体感很差。通过链路追踪发现耗时主要出现在两个地方一是每个请求都分别取用户特征和商品特征大量时间耗在 Redis 往返上二是模型推理一次只算一个商品GPU 的算力根本没有被利用。针对第一个问题我改造了特征读取逻辑。用户特征、商品特征全部用 Redis Pipeline 批量读取一次网络请求把十几个 key 全拿回来。Redis 连接也改成全局连接池避免每次请求都新建连接。针对第二个问题我做了批量推理。具体做法是把并发请求的候选商品收集到一个缓冲队列攒够一定数量之后统一推理。比如攒够 32 个候选商品或者最多等 30ms再用一个 batch 喂给模型。实测下来GPU kernel 的启动开销被大幅摊薄单次推理成本几乎可以忽略。优化后的效果非常明显P95 延迟从 400ms 降到 80ms 左右。更让人意外的是模型推理的 GPU 使用率从 5% 涨到了 50% 以上说明之前一直是在空转。这里要注意批量推理会导致单次请求的响应时间略微波动所以必须设置一个最大等待时间不能为了凑 batch 无限等下去。我用的 30ms 就是一个平衡点。4.2 GPU 显存复用、模型半精度与 C 推理的取舍GPU 资源不是免费的很多团队只租了一台带显卡的服务器。显存不够用的时候不要拍脑袋加机器先把已有资源榨干。第一个技巧是模型半精度。PyTorch 里把模型转成半精度显存占用直接减半推理速度也有提升model.half() # 转半精度 model.eval() with torch.inference_mode(): outputs model(half_inputs)数据转半精度时要注意特征数值范围避免溢出。第二个技巧是利用进程隔离来避免 GPU 显存碎片。每个模型服务进程启动时就固定显存分配不和其他进程抢。比如排序模型占 2GB 显存向量召回服务占 1GB两者各司其职互不干扰。第三个技巧是想清楚是否真的需要 GPU 推理。如果线上量不大LightGBM 这类树模型在 CPU 上跑排序延迟同样很低而且部署运维简单很多。我以前有个阶段把 DeepFM 放到夜间批量打分白天线上用 LR 和 LightGBM 顶住效果差距并不大。C 推理确实可以把 GPU 推理延迟再压低 50% 左右但代价是编译复杂、调试困难、迭代模型还要同步 C 代码。对于大部分电商场景Python 的 FastAPI PyTorch 服务足够用了没必要为几十毫秒增加大量维护成本。4.3 冷启动、流行度偏置与 A/B 实验配置推荐系统上线后第一个逃不掉的问题是冷启动。新商品没有任何用户行为数据协同过滤和向量召回都很难把它推出去。我的处理办法是用商品的类目、品牌、价格区间等属性构造一个内容 Embedding作为冷启动商品的初始向量。这样即使没有行为也能通过内容相似度进入召回候选。在召回结果里强制保留 5% 的新品流量位保证新品有曝光机会。新品曝光之后系统会快速积累行为数据第二天就能进入正常召回链路。第二个问题是流行度偏置。如果模型训练只用了真实曝光样本热门商品会越来越“占便宜”导致推荐结果单调。我做了两件事负样本采样时热门商品被采样为负样本的概率按 popularity 的 0.75 次方加权压制热门商品在训练中的过度优势。排序完成后用 MMR 做多样性重排让同类目商品不要连续霸占榜单。第三个问题是效果验证。每次改动推荐算法都必须在真实流量上做 A/B 实验。我的做法是用 nginx 层按 user_id 哈希分类把用户稳定分到实验桶里不管请求几次同一个用户都在同一个桶里避免实验污染。实验配置放在 Redis 里key 为experiment:scene_idvalue 是 JSON 配置包含实验桶比例、模型版本、是否启用新策略。线上切换版本时只需要改 Redis 配置不用重新发布服务。评估指标上我同时看这几项点击率CTR、点击到下单转化率、人均点击次数、成交额GMV。单一指标上涨不代表整体变好这是我们在 5.3 节踩过的坑。5. 部署过程中真实遇到过的坑和解决过程5.1 CUDA 版本与 PyTorch 编译差异第一次部署时我在服务器上执行python -c import torch; print(torch.cuda.is_available())一直返回 False。当时我以为是显卡驱动没装好重装了三次驱动折腾了快两个小时后才发现问题不在驱动。真正的原因有两个可能一是 PyTorch 装成了 CPU 版本。pip 默认安装的 PyTorch 可能是不带 CUDA 的版本必须明确指定 CUDA 版本的下载源。这个细节放在安装步骤里很容易被漏掉。二是驱动版本和 CUDA runtime 版本不兼容。显卡驱动支持的最高 CUDA 版本可以通过驱动管理工具查看如果驱动只支持到 12.1而你安装的 PyTorch 是编译自 CUDA 12.4 的版本就会出现“找不到可用的 CUDA 设备”这种诡异问题。我的排查链路是这样的# 1. 先确认驱动是否正常显存是否可见 nvidia-smi # 2. 查看 PyTorch 编译时用的 CUDA 版本 python -c import torch; print(torch.version.cuda) # 3. 查看 Linux 系统的 CUDA 驱动 API 版本 # 通过驱动管理工具查看最高支持的 CUDA 版本确认之后把 PyTorch 卸载重装成匹配的版本问题迎刃而解。还有一个坑是 apt 更新驱动后系统重启显卡驱动内核模块加载失败。解决办法是在内核模块加载配置里把驱动版本锁定不让系统自动升级驱动。这属于“装完就好”的经典陷阱。5.2 Redis 连接池被打满导致超时雪崩上线后第一次压测我直接用 500 并发打推荐接口结果 Redis 连接异常大量请求在获取连接时等待超时后重试重试又加重 Redis 负担最后整条链路雪崩。看监控才发现问题不在 Redis 本身而是代码里每次请求都新建一个 Redis 连接。并发一高几百上千个连接同时建立Redis 默认配置直接扛不住。更离谱的是某些请求失败后还会重试重试再次新建连接形成恶性循环。解决措施有三条使用 Redis 连接池全局只维护固定数量的连接请求时从池里拿用完归还。等待连接的时间设置了超时上限拿不到连接就快速失败让上游走降级逻辑。对推荐结果做缓存。同一个用户的推荐结果在 60 秒内直接返回缓存不用重新走完整链路。这个缓存极大降低了对 Redis 的访问频率。关闭请求失败后的无限重试只允许一次重试并且加上随机退避避免重试风暴。这几步做完压测再也没出现 Redis 超时。运维层面的教训是任何对外部组件的访问都必须有连接池和超时控制哪怕它再快再稳。5.3 纯 CTR 指标上涨但线上 GMV 下滑的尴尬优化完延迟之后我开始迭代算法。改了样本权重精排模型在离线测试集上的 CTR 预估 AUC 涨了两个点线上 A/B 实验 CTR 也涨了。可一看核心指标GMV 反而跌了 5%。我当时很困惑。后来分析点击商品的特征分布才发现CTR 涨是因为模型偏爱低价、标题吸引眼球的“马扁”商品这类商品点击率高但用户未必买成交转化很差。模型整体在向“点击诱饵”偏移。解决办法是在排序目标里引入成交偏好训练样本里把“加购”和“下单”行为样本的权重调高让模型不只是学习点击而是学习购买意图。排序打分后再加一个商品成交倾向加权final_score ctr_score * pow(price_rate, alpha)其中价格系数按商品价格带和用户历史客单价动态调整在点击率和成交额之间取平衡。增加一个评估指标预估 GMV 点击概率 × 下单概率 × 客单价。线上实验时用预估 GMV 作为主指标之一而不是只看 CTR。这个坑的教训是电商推荐系统的核心目标是成交不是点击。单一指标优化很容易把系统带到沟里去必须把业务目标写进模型目标里。5.4 算力受限时的模型降级方案不是所有团队都有充足的 GPU 资源。我接触过一套项目只有 CPU 机器一样需要上线推荐系统。这时候核心思想是降级模型但不能降级链路。具体做法是排序模型用 LightGBM 或 XGBoost直接 CPU 推理单个请求延迟可以控制在 20ms 以内。DeepFM 的 Embedding 部分可以先蒸馏成一个浅层神经网络效果保住八成。向量召回用 FAISS 的 CPU 版本几万商品的索引在普通服务器上照样跑得飞快。重排阶段尽量用规则类目打散、价格带过滤、新品加权、同店互斥。线上所有策略都做成可配置模型不行就切规则规则不行就切热门兜底。我之前在一个只能跑 CPU 的环境里靠“LightGBM 规则重排”的方式一样把推荐效果做到了用户可感知的水平。等后续有 GPU 预算了再无缝切换到 DeepFM而链路完全不用动。6. 上线后的迭代节奏与最终建议6.1 日志回流、小时级增量训练和全量训练的双轨推荐系统不是上线就结束的东西。用户行为时刻在变商品在上下架价格在调整模型必须持续更新。我的迭代节奏是“双轨制”全量训练每天凌晨跑一次。用前一天全量日志重新训练排序模型训练完成后自动评估指标没有回退才进入模型版本库。这一步是基础保证了模型不会带有太长的延迟反馈。增量训练每小时跑一次轻量更新。主要更新用户和商品的统计特征、向量召回索引里的高频物品 Embedding、以及排序模型最后一层全连接网络的参数。增量训练不用从头跑收敛快能及时反映用户最新的兴趣变化。定时任务用 cron 就行# 每天凌晨 3 点全量训练 0 3 * * * /opt/recsys/scripts/full_train.sh # 每小时的第 15 分钟增量训练 15 * * * * /opt/recsys/scripts/incremental_train.sh增量脚本里我特别注意训练完先写临时模型文件再通过原子替换让线上服务加载新模型避免训练到一半的模型被线上读到。模型版本管理也很重要。我不会覆盖式保存模型而是保留最近 7 天的模型文件出问题随时能回滚到上一个版本。每次发布新模型之前在压测环境跑一遍接口确认延迟和打分分布正常再切线上流量。6.2 我对这套部署方案的一些个人体会踩过这么多坑之后我最大的体会是推荐系统部署最难的并不是算法而是系统工程。模型再复杂离线 AUC 再高只要数据管线不稳定、特征对不齐、服务扛不住压线上效果都会大打折扣。如果你正要开始做这件事我建议按这个顺序走先把数据管道打通保证日志能回、特征能算、训练样本能出。用最简单的召回和排序比如热门召回 LR 排序先把整条链路跑通。链路稳定之后再逐步换成向量召回、DeepFM 这类更复杂的模型。先跑通链路、再做优化这个顺序能让你少走很多弯路。我见过不少团队在模型上花了一个月最后才发现日志采集居然是错的所有训练数据都是脏的等于白做。另外上线前一定要准备回滚方案。推荐系统模型版本切换后一旦发现指标下跌要在几分钟内切回旧版本。不要等到用户投诉了再慢慢排查那时候损失已经造成了。我在实际维护这套系统时最关注的反而不是模型指标而是两个监控面板一个是服务健康度包含 P95 延迟、错误率、GPU 使用率另一个是业务效果包含 CTR、下单转化率、GMV 小时曲线。任何异常都能第一时间发现避免问题从“小问题”演变成“事故”。这套部署方案不一定是最先进的但它是验证过、能落地、可维护的一套。如果你正在 Ubuntu 22.04 上做类似的推荐系统照着这个思路把环境和链路先跑起来后面再慢慢调优会比反复换模型框架走得快得多。