Loco 缓存层完全指南:从 Null、In-Memory 到 Redis 的配置与实战 Loco 缓存层完全指南从 Null、In-Memory 到 Redis 的配置与实战【免费下载链接】loco The one-person framework for Rust for side-projects and startups项目地址: https://gitcode.com/GitHub_Trending/lo/locoLoco 在应用上下文中内置了一层统一的缓存抽象ctx.cache通过可插拔的 Cache Driver 屏蔽底层存储差异帮助开发者以少量代码缓存高频访问的数据、显著降低数据库压力。本文以官方文档 cache.md 为核心骨架结合 src/cache 目录下的真实源码与测试系统讲解 Loco 缓存层的架构、三种内置驱动的行为差异、配置方式以及完整的 API 用法读完后你可以直接在 Loco 项目中为任意结构化数据接入带过期时间的缓存。缓存层在 Loco 中的定位Loco提供缓存层的目的很明确通过存储高频访问的数据来提升应用性能。它并非独立的服务而是挂在应用上下文AppContext上的一个Arccache::Cache实例随应用启动时一并初始化。从源码结构看缓存层由三部分构成见 src/cache/mod.rs 与 src/cache/drivers/mod.rsCache门面面向业务代码的统一入口负责值的序列化/反序列化并把操作委托给底层驱动CacheDrivertrait定义驱动必须实现的 7 个异步方法——ping、contains_key、get、insert、insert_with_expiry、remove、clear具体驱动实现Null、Inmem基于 moka、Redis基于 bb8-redis分别位于 src/cache/drivers/null.rs、src/cache/drivers/inmem.rs、src/cache/drivers/redis.rs。应用启动时src/boot.rs 调用cache::create_cache_provider(config)根据配置项cache.kind选择并构建对应的驱动最终存入 src/app.rs 的AppContext.cache字段。因此在你的 Controller、Worker、Task 等任何能拿到ctx: AppContext的地方都可以直接使用ctx.cache。三种内置缓存驱动Loco 开箱即支持以下三种缓存驱动驱动类型底层实现典型场景对应 Cargo featureNull Cache无操作缓存默认空实现开发环境、未配置缓存的默认兜底无需 featureIn-Memory Cache本地进程内缓存mokacrate单实例应用、极低延迟的读多写少场景cache_inmem默认启用Redis Cache分布式缓存bb8bb8-redis连接池多实例部署、需要跨进程共享缓存cache_redis需手动启用三种驱动统一实现CacheDrivertrait业务代码无需感知底层差异切换驱动只需要改配置。默认行为Null 缓存驱动默认情况下Loco 初始化的是Null缓存驱动。它的设计初衷见 src/cache/drivers/null.rs 顶部注释是简化用户工作流即使没有启用任何 feature 或配置缓存驱动框架也能正常启动不会因缺少缓存而报错。但 Null 驱动是一个空操作实现具体行为如下get()操作永远返回None不会报错其余操作如insert()、insert_with_expiry()、remove()、contains_key()、clear()、ping()都会返回错误错误信息为Operation not supported by null cache。也就是说如果你不配置真实的缓存驱动就直接使用缓存功能很多操作会以错误告终。官方文档明确建议生产环境务必配置真实的缓存驱动。Null 驱动更适合作为开发环境或功能开关未开启时的安全默认值。配置缓存驱动缓存的配置位于应用的配置文件如config/development.yaml中通过cache.kind字段选择驱动。配置解析实现在 src/config.rsCacheConfig是一个带#[serde(tag kind)]的枚举YAML 中的kind值直接决定反序列化到哪个变体。Null 缓存默认cache: kind: Null若完全不提供cache配置CacheConfig的默认值同样是Null枚举上标注了#[default]因此可以认为 Null 是双重兜底。In-Memory 缓存cache: kind: InMem max_capacity: 33554432 # 32MiB不指定时的默认值要点需要启用 featurecache_inmem该 feature 在default特性中默认开启见 Cargo.toml 与 Cargo.toml 的cache_inmem [dep:moka]max_capacity对应 moka 缓存的最大容量条目数上限源码中 InMemCacheConfig 通过#[serde(default cache_in_mem_max_capacity)]提供了默认值32 * 1024 * 1024即cache_in_mem_max_capacity()函数返回的 32MiB见 src/config.rs底层用moka::sync::Cache构建且设置了expire_after(InMemExpiry)自定义过期策略见 src/cache/drivers/inmem.rs注意max_capacity在 moka 中表示最大键数量32MiB 的默认值足够容纳大量小型键值对可按实际条目规模调整。Redis 缓存cache: kind: Redis uri: redis://localhost:6379 max_size: 10 # 连接池中最大连接数要点必须启用 featurecache_redis见 Cargo.toml 的cache_redis [dep:bb8-redis, dep:bb8]默认特性中不包含它需要手动在依赖中开启uri是 Redis 连接串支持标准的redis://协议测试中也会通过setup_redis_container动态生成对应 URLmax_size是bb8 连接池的最大连接数RedisCacheConfig中为必填的u32字段见 src/config.rs驱动通过Pool::builder().max_size(config.max_size)构建连接池见 src/cache/drivers/redis.rs分布式场景下所有应用实例共享同一份缓存数据适合多实例部署。在代码中使用缓存所有缓存值都以字符串键 序列化后的值形式存储。业务层使用ctx.cache即可完整用法如下摘自官方文档并保持可运行use std::time::Duration; use loco_rs::cache; use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] struct User { name: String, age: u32, } async fn test_cache(ctx: AppContext) - Result() { // 插入一个简单的字符串值 ctx.cache.insert(string_key, simple value).await?; // 插入一个结构化值 let user User { name: Alice.to_string(), age: 30 }; ctx.cache.insert(user:1, user).await?; // 插入带过期时间的值300 秒后自动失效 ctx.cache.insert_with_expiry(expiring_key, temporary value, Duration::from_secs(300)).await?; // 读取字符串值 let string_value ctx.cache.get::String(string_key).await?; // 读取结构化值 let user ctx.cache.get::User(user:1).await?; // 删除一个键 ctx.cache.remove(string_key).await?; // 检查键是否存在 let exists ctx.cache.contains_key(user:1).await?; // 取或插键存在则返回不存在则计算并写入 let lazy_value ctx.cache.get_or_insert::String, _(lazy_key, async { Ok(computed value.to_string()) }).await?; Ok(()) }核心 API 逐一解读对照 src/cache/mod.rs 的实现各方法的行为如下get::T(key)从驱动读取原始字符串值后用serde_json::from_str::T反序列化为目标类型T要求T: DeserializeOwned键不存在时返回Ok(None)src/cache/mod.rsinsert(key, value)先用serde_json::to_string将值序列化再交给驱动存储src/cache/mod.rs。因此任何实现Serialize的类型都能直接入缓存包括自定义结构体insert_with_expiry(key, value, duration)同insert但额外指定存活时长src/cache/mod.rs。Redis 驱动内部使用SETEXconn.set_ex(..., duration.as_secs())过期时间精确到秒见 src/cache/drivers/redis.rsget_or_insert::T, F(key, async_closure)经典的缓存旁路模式——先尝试读取命中则直接返回未命中则执行闭包计算新值、写入缓存并返回src/cache/mod.rs。要求T: Serialize DeserializeOwned Send Sync闭包返回FutureOutput LocoResultTget_or_insert_with_expiry(key, duration, async_closure)get_or_insert的带过期版本写入时使用insert_with_expirysrc/cache/mod.rscontains_key(key)/remove(key)/clear()分别对应存在性检查、删除单键、清空全部键值clear()在 Redis 驱动中通过FLUSHDB实现见 src/cache/drivers/redis.rs使用前需注意它会清空当前数据库所有键ping()探活方法被健康检查机制使用见下文。Cache门面的方法大多返回CacheResultT其错误类型CacheError覆盖了序列化/反序列化错误、Redis 底层错误与连接池错误等见 src/cache/mod.rs。序列化机制与类型安全从源码可以看出Loco 缓存的序列化采用serde_jsonJSON作为中间格式写入路径serde_json::to_string(value)失败映射为CacheError::Serialization读取路径serde_json::from_str::T(value)失败映射为CacheError::Deserialization。这意味着存储的值在底层驱动moka / Redis中都是 JSON 字符串键则是普通字符串get时必须显式指定目标类型get::User类型不匹配会得到反序列化错误而非静默失败只要自定义结构体派生Serialize/Deserialize即可直接存取无需额外样板代码。在 src/cache/mod.rs 的测试can_serialize_deserialize中TestUser结构体被插入后经get::TestUser取回并与原值断言相等验证了结构化数据存取的完整闭环。缓存与健康检查的联动缓存驱动还参与了应用的就绪检查readiness probe。在 src/controller/monitoring.rs 中当配置了InMem或Redis驱动时就绪检查会调用ctx.cache.driver.ping()若 ping 失败应用会返回503 SERVICE_UNAVAILABLE并记录readiness_cache_ping_error日志。而Null驱动则被跳过不会影响就绪状态。这意味着生产环境一旦配置 Redis 缓存缓存不可达会直接导致就绪探针失败便于负载均衡器摘除异常实例In-Memory 驱动的ping恒为Ok(())见 src/cache/drivers/inmem.rs因此不会误报。源码测试验证的行为契约缓存模块的测试散落在三处共同印证了各驱动的行为契约src/cache/mod.rs 门面层测试can_get_or_insert验证未命中时执行闭包、命中后不再重复计算can_get_or_insert_generic验证第二次调用get_or_insert时返回的是首次写入的 Alice 而非闭包中的 Bobsrc/cache/drivers/inmem.rs 驱动测试覆盖ping、contains_key、插入与读取、remove、clear全部操作src/cache/drivers/redis.rs 驱动测试借助 testcontainers 启动真实 Redis 容器覆盖探活、存在性、读写、删除、清空以及test_expiry验证写入 1 秒过期的键在 2 秒后确实消失。如果你需要验证自定义缓存使用逻辑可以参考这些测试模式在测试中通过tests_cfg::app::get_app_context()获取带有真实ctx.cache的应用上下文见 src/cache/mod.rs。生产实践建议综合官方文档与源码实现落地缓存时有几点值得注意默认是 Null生产必须显式配置忘记配置时缓存看起来能用get返回None但写入类操作会抛Operation not supported by null cache务必在部署前检查配置单实例选 InMem多实例选 RedisIn-Memory 缓存数据只存在于单个进程内水平扩容到多实例后各实例缓存互不相同需要全局一致性时应切换到 Redis 驱动并启用cache_redisfeature合理设置过期时间优先使用insert_with_expiry/get_or_insert_with_expiry为易变数据设置 TTL避免缓存与数据源长期不一致Redis 驱动的过期精度为秒警惕clear()的影响Redis 驱动的clear()执行FLUSHDB会清空当前 Redis 库中所有键包括非 Loco 写入的键生产环境慎用利用就绪检查配置 Redis 后缓存故障会自动反映到应用的 readiness 状态监控告警时应关注readiness_cache_ping_error日志。以上内容对应的核心实现均可在 src/cache/mod.rs、src/cache/drivers/inmem.rs、src/cache/drivers/redis.rs 与 src/config.rs 中继续深挖官方文档原文见 docs-site/content/docs/infrastructure/cache.md。【免费下载链接】loco The one-person framework for Rust for side-projects and startups项目地址: https://gitcode.com/GitHub_Trending/lo/loco创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考