基于 openYuanrong 的生成式推荐缓存高可用方向验证实践

一、写在前面

这篇文章记录的是一次完整的本地验证过程,目标很明确:围绕生成式推荐场景,基于 openYuanrong 做一个可以真实运行、可以看到故障切换效果。

本文验证的核心方向有三个:

  1. Worker 不依赖固定的 DaemonSet 思路,能够以多副本方式灵活部署。
  2. 单节点故障后,业务能够快速感知并完成切换,中断时间控制在 3 秒以内。
  3. 通过热点数据快照和恢复加载机制,增强缓存场景的可靠性。

为了把这个方向做成一个真正可运行、可观察的案例,本文使用了三部分内容:

  1. openYuanrong 上游三个仓库,用来说明项目结构和能力边界。
  2. openyuanrong-real-demo,这是基于 openYuanrong datasystem 做的真实演示工程。
  3. openyuanrong-visual-console,这是一个 Vue + Nest.js 的可视化控制台,用来把 Worker 状态和故障切换过程直接展示在页面上。

需要说明的是,本文演示的核心数据访问能力来自 openYuanrong datasystem,页面只是把真实执行过程可视化,并没有脱离 openYuanrong 另起一套假的逻辑。

二、三个上游仓库分别是什么

先把 openYuanrong 的三个上游仓库说明白,这样后面的目录关系才不会混淆。

1. yuanrong

仓库地址:

bash 复制代码
https://gitcode.com/openeuler/yuanrong

这是 openYuanrong 的总仓,包含总体工程结构、接口、部署目录和文档。理解整个项目时,这个仓库是入口。

2. yuanrong-datasystem

仓库地址:

bash 复制代码
https://gitcode.com/openeuler/yuanrong-datasystem

这是本文真正直接使用到的部分。本文中的 Worker、KV 读写、会话缓存预热、故障切换验证,全部落在 datasystem 这一层。

3. yuanrong-functionsystem

仓库地址:

bash 复制代码
https://gitcode.com/openeuler/yuanrong-functionsystem

这是 openYuanrong 的函数执行与调度相关部分。本文的演示重点不在 functionsystem,但为了把 openYuanrong 的整体组成说明完整,仍然把它纳入工作目录。

从本文的演示目标来看,可以这样理解三者关系:

  • yuanrong 负责总览和整体组织。
  • yuanrong-datasystem 负责数据访问与缓存能力,本文的验证重点在这里。
  • yuanrong-functionsystem 负责函数执行能力,本文不直接使用,但属于 openYuanrong 的整体能力版图。

三、本文配套工程说明

除了上游三个仓库,本文还配套了两个演示目录:

  • openyuanrong-real-demo
  • openyuanrong-visual-console

它们的作用分别如下:

  • openyuanrong-real-demo 负责真正启动 ETCD、两个 datasystem Worker,以及演示应用。
  • openyuanrong-visual-console 负责把演示流程做成页面,方便观察状态、日志和切换路径。

最终的目录结构如下:

text 复制代码
openyuanrong/
├── yuanrong
├── yuanrong-datasystem
├── yuanrong-functionsystem
├── openyuanrong-real-demo
├── openyuanrong-visual-console

如果是按本文配套代码包一起查看,openyuanrong-real-demoopenyuanrong-visual-console 已经包含在工程目录中,直接使用即可。

四、实验环境

本文在如下环境中完成验证:

text 复制代码
Node.js  v24.12.0
npm      11.6.2
Docker   29.2.0
Compose  v5.0.2

本地需要提前满足两个条件:

  1. Docker Desktop 已经启动。
  2. 终端可以正常执行 gitnodenpmdocker compose

五、从克隆仓库开始准备环境

先创建一个工作目录,然后把三个上游仓库克隆下来。

bash 复制代码
mkdir -p ~/workspace/openyuanrong
cd ~/workspace/openyuanrong

git clone https://gitcode.com/openeuler/yuanrong
git clone https://gitcode.com/openeuler/yuanrong-datasystem
git clone https://gitcode.com/openeuler/yuanrong-functionsystem

执行完成后,可以看到如下三个目录:

bash 复制代码
ls

输出应当包含:

text 复制代码
yuanrong
yuanrong-datasystem
yuanrong-functionsystem

接下来,把本文配套的两个演示目录放到同级目录中:

  • openyuanrong-real-demo
  • openyuanrong-visual-console

放好之后,再执行一次:

bash 复制代码
ls

应当可以看到完整目录:

text 复制代码
openyuanrong-real-demo
openyuanrong-visual-console
yuanrong
yuanrong-datasystem
yuanrong-functionsystem

六、先安装可视化控制台依赖

页面控制台使用的是 Vue + Nest.js,因此先安装前后端依赖。

bash 复制代码
cd ~/workspace/openyuanrong/openyuanrong-visual-console
npm install --workspaces

安装成功后,终端会输出类似结果:

text 复制代码
added 213 packages in 2m

到这里,页面工程已经准备完成。

七、启动可视化控制台

1. 启动后端

打开第一个终端窗口,执行:

bash 复制代码
cd ~/workspace/openyuanrong/openyuanrong-visual-console
npm --workspace backend run start:dev

启动成功后,可以看到关键日志:

text 复制代码
Mapped {/api/demo/status, GET} route
Mapped {/api/demo/logs, GET} route
Mapped {/api/demo/actions/start, POST} route
Mapped {/api/demo/actions/warmup, POST} route
Mapped {/api/demo/actions/failover, POST} route
Mapped {/api/demo/actions/restart-primary, POST} route
Mapped {/api/demo/actions/cleanup, POST} route
Mapped {/api/demo/actions/clear-logs, POST} route
Mapped {/api/demo/events, GET} route
Visual console backend listening on http://127.0.0.1:3001

2. 启动前端

打开第二个终端窗口,执行:

bash 复制代码
cd ~/workspace/openyuanrong/openyuanrong-visual-console
npm --workspace frontend run dev -- --host 127.0.0.1 --port 5173

启动成功后,可以看到:

text 复制代码
VITE v6.4.1  ready in 98 ms

Local:   http://127.0.0.1:5173/

3. 打开页面

浏览器访问:

text 复制代码
http://127.0.0.1:5173

这时页面处于初始状态,界面上还没有开始真正的 Worker 演示流程。

4. 为什么页面能够拉起真实的 openyuanrong-real-demo

很多人在第一次看到这里时,会有一个很自然的问题:页面本身只是一个 Vue + Nest.js 项目,它为什么能够把另一个目录下的 Docker 环境真正拉起来。

这里的关键不在前端,而在后端。

openyuanrong-visual-console 的后端服务中,预先保存了 openyuanrong-real-demo 的本地路径。当前演示代码中,这个路径就是:

text 复制代码
/Users/luqingjiedemac/gitcode_obj/openyuanrong/openyuanrong-real-demo

当页面上点击"启动环境"时,请求会先进入可视化控制台后端,然后由后端在这个目录下执行:

bash 复制代码
docker compose up --build -d

这里最重要的一点是:Docker 不是自己在磁盘里搜索到了 openyuanrong-real-demo,而是后端明确把命令的执行目录切到了这个路径下。因为当前目录就是 openyuanrong-real-demo,所以 Docker Compose 会自动读取该目录中的 docker-compose.yml,并据此启动:

  • yr-etcd
  • yr-worker-1
  • yr-worker-2
  • yr-demo-app

也就是说,整个调用链路实际上是下面这样:

text 复制代码
浏览器页面
    ↓
openyuanrong-visual-console 前端
    ↓
openyuanrong-visual-console 后端
    ↓
在 openyuanrong-real-demo 目录下执行 docker compose 命令
    ↓
Docker Compose 读取 openyuanrong-real-demo/docker-compose.yml
    ↓
启动 etcd、worker 和 demo-app

因此,页面本身不是在"模拟"容器状态,而是在调用真实的后端命令;后端也不是"猜测" demo 在哪里,而是拿着一个明确的本地路径,在这个路径中直接执行 Docker Compose。

读者如果想进一步确认这一点,可以在点击"启动环境"之后,在 openyuanrong-real-demo 目录中手动执行下面的命令:

bash 复制代码
cd ~/workspace/openyuanrong/openyuanrong-real-demo
docker compose ps

如果页面确实成功拉起了真实环境,那么这里会看到:

  • yr-etcd
  • yr-worker-1
  • yr-worker-2
  • yr-demo-app

四个服务的运行状态。

八、页面驱动真实 openYuanrong 演示

这一步开始,页面上的每一个按钮,驱动的都是真实的 openyuanrong-real-demo,并不是前端本地模拟。

建议操作顺序固定为:

  1. 清空日志
  2. 启动环境
  3. 预热缓存
  4. 故障切换

这样最适合做演示,也最适合写博客和做截图。

Step 1. 点击"清空日志"

先点击页面上的"清空日志"按钮,把上一次演示留下来的日志清空。这样后面看到的内容就是一条完整、干净的验证链路。

清空之后,页面的日志区域会被清空,最近动作恢复为空闲状态。

Step 2. 点击"启动环境"

点击"启动环境"按钮后,后端会调用真实的 Docker 编排,启动:

  • yr-etcd
  • yr-worker-1
  • yr-worker-2
  • yr-demo-app

页面中服务状态会依次变为运行中。

此时后端日志中可以看到真实启动输出,关键成功日志如下:

text 复制代码
$ docker compose up --build -d
Image openyuanrong-real-demo-yr-worker-1 Built
Image openyuanrong-real-demo-yr-worker-2 Built
Image openyuanrong-real-demo-demo-app Built
Container yr-etcd Healthy
Container yr-worker-1 Started
Container yr-worker-2 Started
Container yr-demo-app Started

到这里,openYuanrong datasystem 的基础演示环境已经真正跑起来了。

从实现逻辑上看,这一步对应的是下面这条链路:

  1. 页面按钮触发前端请求。
  2. 前端请求进入 openyuanrong-visual-console 的 Nest.js 后端。
  3. 后端在 openyuanrong-real-demo 目录下执行 docker compose up --build -d
  4. Docker Compose 根据 docker-compose.yml 启动 etcd、两个 Worker 和一个 demo-app
  5. 页面再通过后端读取容器状态,并把结果更新到页面上。

这一步之所以重要,是因为它对应了本文第一个目标方向,也就是 Worker 的灵活部署能力。在这个演示中,Worker 不是以"每台节点固定一个实例"的方式去组织,而是直接以两个副本的方式独立启动。换句话说,当前案例已经把 Worker 当成一个可以按业务需求部署和伸缩的服务来使用,而不是依赖固定节点形态。

从 openYuanrong 的角度来看,它在这一层的优势主要体现在两个方面:

  1. Worker 是一个可以独立拉起的数据服务实例,便于按业务规模组织部署。
  2. 数据访问接口和 Worker 生命周期是解耦的,前端页面并不关心底层数据节点如何启动,只需要通过后端统一调度即可。

因此,页面上看到的"主 Worker、备 Worker、演示应用均已运行",本质上是在说明:这套系统已经具备了多 Worker 副本的运行形态,能够支撑后续的故障切换和数据恢复演示。

Step 3. 点击"预热缓存"

点击"预热缓存"按钮后,页面背后会执行:

text 复制代码
docker compose exec -T demo-app python /app/demo_client.py

这一步做的事情有三件:

  1. 把生成式推荐会话缓存写入主 Worker。
  2. 从主 Worker 读取热点 key。
  3. 把热点 key 写入快照文件,并预加载到备 Worker。

从页面的日志区域可以看到完整成功输出。实际验证时,关键日志如下:

text 复制代码
[demo] waiting for workers
[demo] step 1: write recommendation session cache through openYuanrong
[demo] wrote session:user-001 -> candidate-list:v1 to 172.28.0.11
[demo] wrote session:user-042 -> candidate-list:v2 to 172.28.0.11
[demo] wrote session:user-108 -> candidate-list:v3 to 172.28.0.11
[demo] step 2: read hot keys from primary worker
[demo] read session:user-042 -> candidate-list:v2 from 172.28.0.11
[demo] read session:user-001 -> candidate-list:v1 from 172.28.0.11
[demo] step 3: write demo-layer hot-key snapshot
[demo] snapshot saved to /app/runtime/hot_keys.json
[demo] step 4: preload snapshot into backup worker
[demo] backup preload check session:user-042 -> candidate-list:v2
[demo] backup preload check session:user-001 -> candidate-list:v1
[demo] done

这组日志非常关键,因为它证明了三件事情已经真实发生:

  1. openYuanrong KVClient 已经完成真实写入。
  2. 热点 key 已经被读取出来。
  3. 快照已经生成,并且备 Worker 已经完成预热。

从项目内部逻辑看,这一步是整个方向验证里最能体现"生成式推荐缓存场景"的部分。

页面上点击"预热缓存"之后,真正执行逻辑的是 openyuanrong-real-demo/app/demo_client.py。这个脚本在 demo-app 容器里运行,流程可以拆成四步:

  1. 等待主 Worker 和备 Worker 都处于可访问状态。
  2. 通过 openYuanrong 的 yr.datasystem.KVClient 把会话缓存写入主 Worker。
  3. 从主 Worker 再次读取热点 key,确认热点数据已经可以被访问。
  4. 把这些热点 key 保存成快照文件,并重新预加载到备 Worker。

这一步为什么适合作为"生成式推荐方向"的演示,是因为它和真实业务很接近。生成式推荐服务通常会把这些内容放在会话缓存里:

  • 当前用户会话的候选集
  • 上一次召回或粗排结果
  • 大模型生成前的上下文缓存
  • 高频访问的热点会话数据

本案例里的这些 key:

  • session:user-001
  • session:user-042
  • session:user-108

就可以把它理解成一组被推荐服务频繁访问的会话缓存。

从目标方向上看,这一步同时支撑了两个点。

第一,它证明了 openYuanrong datasystem 确实在承担"在线会话缓存"的角色,而不是只做静态演示。数据是通过真实 KVClient 写进去的,也是通过真实 Worker 读出来的。

第二,它把"热点数据落盘增强可靠性"的方向具体化了。虽然这里使用的是案例层的热点快照,而不是完整的生产级外部持久化链路,但对读者来说,已经能非常直观地理解这个设计思路:

  • 热点数据先进入主 Worker。
  • 应用把真正重要的热点 key 提取出来。
  • 热点 key 被保存为快照。
  • 备 Worker 提前装载这些热点数据。

这套机制的价值在于,当主 Worker 发生故障时,备 Worker 并不是完全冷启动状态,而是已经具备了一部分热点数据承接能力。这对于生成式推荐这种追求稳定时延的场景很重要,因为它可以减少缓存完全丢失后带来的重新计算开销。

从 openYuanrong 的优势来看,这一步体现得很明显:

  1. openYuanrong 提供了统一的 KV 访问接口,应用不需要自己维护底层数据访问协议。
  2. Worker 作为独立的数据节点,可以把主写入和备份预热这两条路径组织起来。
  3. 在此基础上,业务层可以很自然地叠加热点快照和恢复策略,形成更完整的可靠性方案。

Step 4. 点击"故障切换"

点击"故障切换"按钮后,页面背后会执行两步:

第一步,强制移除主 Worker:

text 复制代码
docker rm -f yr-worker-1

第二步,执行故障切换检查:

text 复制代码
docker compose exec -T demo-app python /app/failover_check.py

这一步的意义就是验证"主 Worker 故障后,业务能否快速识别并切到备 Worker"。

页面状态会发生两个非常直观的变化:

  1. "主 Worker" 的状态从运行中变为不存在。
  2. "故障切换路径" 区域的高亮从主 Worker 切换到备 Worker。

在实际验证中,页面底部日志会出现如下关键内容:

text 复制代码
$ docker rm -f yr-worker-1
yr-worker-1
$ docker compose exec -T demo-app python /app/failover_check.py
[failover] primary is unreachable by quick probe, skip direct read
[failover] backup served session:user-042 -> candidate-list:v2
[failover] application failover time: 1.29s

这段日志可以直接说明本文要验证的第二个方向已经跑通:

  • 主 Worker 故障后,应用没有长时间阻塞。
  • 业务首先快速感知主 Worker 不可达。
  • 然后直接切换到备 Worker。
  • 整个应用侧故障切换耗时约为 1.29 秒,满足"小于 3 秒"的目标。

从项目内部实现来看,这一步对应的是 openyuanrong-real-demo/app/failover_check.py 的逻辑。它不是简单地"主节点失败了就报错退出",而是按一条明确的恢复路径顺序执行:

  1. 先对主 Worker 做快速连通性探测。
  2. 如果主 Worker 不可达,则跳过直接读取主 Worker。
  3. 立刻尝试从备 Worker 读取热点数据。
  4. 如果备 Worker 也未命中,再回退到热点快照恢复。

页面上为什么会出现"主 Worker 不存在、备 Worker 高亮接管"的现象,原因正是这里的处理逻辑已经从"单点访问"切换成了"主节点优先、故障后自动切备、必要时再从快照恢复"的多级路径。

这一步最能体现本文第二个方向的核心价值,也就是"单节点故障场景下的快速感知和故障隔离"。如果没有这样的处理,主 Worker 一旦故障,应用通常会出现两个问题:

  1. 请求长时间等待超时,用户侧感知明显。
  2. 即使后面有备节点,也来不及快速接管。

而在当前案例中,应用首先做的是一次非常轻量的快速探测,而不是直接在主 Worker 上长时间阻塞。探测失败后,流量立即转向备 Worker,因此页面上最终看到的是:

  • 主 Worker 状态消失
  • 备 Worker 仍然运行
  • 当前路径切到备 Worker
  • 故障切换耗时约 1.29 秒

从 openYuanrong 的视角来看,这里的优势在于:

  1. 底层已经有可独立运行的多个 Worker 副本,业务才能谈得上切换路径。
  2. 业务访问依赖的是统一的 KV 能力,不需要因为切换 Worker 而改动应用访问方式。
  3. 在主 Worker 不可用的情况下,备 Worker 可以继续承接读请求,从而把单节点故障影响控制在较小范围内。

如果把这一步放回生成式推荐场景中去理解,就会更清楚它的价值。推荐服务通常对在线时延比较敏感,特别是会话缓存已经命中的情况下,服务期望直接读取缓存完成后续生成或排序。一旦主缓存节点故障,如果没有快速切换机制,用户请求就可能退化成重新计算,造成明显时延波动。而当前案例证明,基于 openYuanrong 的 Worker 形态和统一 KV 访问能力,可以把这个风险收敛到秒级切换范围内。

九、清理环境

演示完成后,可以清理 Docker 环境。

进入 openyuanrong-real-demo 目录,执行:

bash 复制代码
cd ~/workspace/openyuanrong/openyuanrong-real-demo
docker compose down -v

执行完成后,容器、网络和卷会被清理干净。

十、结语

这次实践的重点,不是把 openYuanrong 做成一个大而全的生产系统,而是围绕"生成式推荐缓存高可用"这个方向,做出一个真实可运行、真实可观察、真实可验证的最小闭环。

从结果看,这个闭环已经成立:

  1. openYuanrong datasystem 的 Worker 和 KV 访问是真实运行的。
  2. 预热缓存、热点快照和备 Worker 预加载是真实执行的。
  3. 主 Worker 故障后的快速切换是真实发生的。
  4. 页面日志和状态变化可以作为演示和材料输出的直接依据。

项目中用到的所有的案例代码都已开源:

https://gitcode.com/openeuler/yuanrong

https://gitcode.com/openeuler/yuanrong-functionsystem

https://gitcode.com/openeuler/yuanrong-datasystem

https://gitcode.com/lqjmac/openyuanrong-visual-console

https://gitcode.com/lqjmac/openyuanrong-real-demo

相关推荐
lhbian2 小时前
redis分页查询
数据库·redis·缓存
We་ct2 小时前
React 中的双缓存 Fiber 树机制
前端·react.js·缓存·前端框架·reactjs·fiber·缓存机制
We་ct2 小时前
React Render 与 Commit 阶段详解
前端·react.js·面试·前端框架·react·commit·render
英俊潇洒美少年2 小时前
React Hook 钩子 useInsertionEffect、useLayoutEffect、useEffect的区别
前端·javascript·react.js
X-⃢_⃢-X2 小时前
八、Redis之BigKey
数据库·redis·缓存
坚持学习前端日记2 小时前
Agent AI 后端接口对接与大模型适配指南
前端·人工智能·python·ios
~莫子2 小时前
Redis
数据库·redis·缓存
坚持学习前端日记2 小时前
Agent AI 多模态交互与全场景架构设计
前端·javascript·人工智能·visual studio
王家视频教程图书馆2 小时前
vue3移动端组件库清单
前端