
后端Web框架微服务RPC框架异步编程【免费下载链接】hyperf A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.项目地址https://gitcode.com/hyperf/hyperf点击查看免费下载本文基于 hyperf/hyperf 仓库docs/en/cache.md编写围绕hyperf/cache组件的核心能力展开AOP 注解缓存、PSR-16 Simple Cache 接口、缓存失效机制以及 Redis / 进程内存 / 协程内存等多种驱动的选型与配置。读完本文你将掌握如何在 Hyperf 项目中用一行注解为方法加上缓存、如何精确控制缓存 KEY 与过期时间、如何通过事件或注解主动清理缓存并理解这些能力背后的切面Aspect与驱动实现原理。hyperf/cache是 Hyperf 生态中的缓存组件一方面它基于 AOP面向切面编程提供声明式注解缓存Cacheable、CacheAhead、CachePut、CacheEvict等让开发者无需侵入业务代码即可为方法增加缓存另一方面它也提供实现Psr\SimpleCache\CacheInterface的缓存类方便与 EasyWeChat 等依赖 PSR-16 规范的第三方库对接。组件默认使用 Redis 驱动同时内置进程内存、协程内存、文件系统、SQLite 等驱动可灵活适配不同场景。本文将以仓库中的 docs/en/cache.md 为骨架结合 src/cache 下的源码、配置与测试用例进行深度展开。安装使用 Composer 安装即可composer require hyperf/cache安装完成后组件通过ConfigProvider见 src/cache/src/ConfigProvider.php自动完成以下注册将Psr\SimpleCache\CacheInterface绑定到Hyperf\Cache\Cache实现使容器可直接解析出 PSR-16 缓存对象注册DeleteListener事件监听器用于处理注解缓存删除事件注册 5 个切面CacheableAspect、CacheAheadAspect、CacheEvictAspect、CachePutAspect、FailCacheAspect注册CacheListenerCollector元数据收集器收集注解中声明的 listener 与类方法映射提供config发布配置发布到config/autoload/cache.php。从 src/cache/composer.json 可以看到该组件要求 PHP 8.2依赖psr/simple-cache支持 ^1.0 / ^2.0 / ^3.0并建议配合hyperf/di注解缓存需要与hyperf/event事件删除缓存需要使用。默认配置组件默认配置的核心参数如下表所示配置项默认值说明driverHyperf\Cache\Driver\RedisDriver缓存驱动默认为 RedispackerHyperf\Codec\Packer\PhpSerializerPacker序列化/打包器prefixc:缓存 KEY 前缀skip_cache_results[]命中这些结果时不做缓存发布配置config/autoload/cache.php的完整形态如下对应发布源文件 src/cache/publish/cache.php?php use Hyperf\Cache\Driver\RedisDriver; use Hyperf\Codec\Packer\PhpSerializerPacker; use function Hyperf\Support\env; return [ default env(CACHE_DRIVER, default), stores [ default [ driver RedisDriver::class, packer PhpSerializerPacker::class, prefix c:, skip_cache_results [], options [ pool default, ], ], // sqlite [ // driver Hyperf\Cache\Driver\SqliteDriver::class, // packer Hyperf\Codec\Packer\PhpSerializerPacker::class, // prefix c:, // database :memory:, // table hyperf_cache, // options [ /* PDO 相关选项 */ ], // max_connections 10, // ], ], ];从源码角度看需要注意两个细节配置结构的兼容性在 CacheManager.php 的构造函数中如果配置里不存在cache.stores或cache.default本身是数组旧式写法框架会自动把配置规范化为[default default, stores ...]结构因此旧版直接以驱动数组作为cache配置的写法仍然可用。驱动层默认值基类 Driver.php 中prefix的默认值是cache:packer的默认值同样是PhpSerializerPacker。也就是说即便配置中省略了prefix或packer驱动也能正常工作——发布配置中显式给出的prefix c:覆盖了这一默认值最终落在 Redis 中的完整 KEY 形如c:user:1。Redis 驱动本身不直接持有连接而是通过RedisFactory::get($config[options][pool] ?? default)获取指定连接池见 RedisDriver.php因此options.pool需要与hyperf/redis的redis.php配置中的连接池名称对应。使用方式一Simple CachePSR-16hyperf/cache完整适配了 PSR-16 规范。若你希望使用Psr\SimpleCache\CacheInterface接口的缓存类——例如重写 EasyWeChat 的缓存模块——可以直接从依赖注入容器中取出该接口的实现$cache $container-get(\Psr\SimpleCache\CacheInterface::class);这一绑定由 ConfigProvider.php 中的dependencies配置完成CacheInterface::class Cache::class。容器解析出的 Cache.php 内部持有CacheManager::getDriver()获取的默认驱动并实现了 PSR-16 要求的全部方法get($key, $default)/set($key, $value, $ttl)/delete($key)/clear()/has($key)getMultiple($keys, $default)/setMultiple($values, $ttl)/deleteMultiple($keys)由于Cache::__call会把未定义方法转发给底层驱动你也可以直接调用驱动提供的扩展方法例如clearPrefix实现按前缀批量清理。使用方式二#[Cacheable]注解缓存组件提供Hyperf\Cache\Annotation\Cacheable注解作用于类方法可配置缓存前缀、过期时间、监听器与缓存分组。例如UserService提供一个按id查询用户信息的方法加上注解后会自动生成对应的 Redis 缓存KEY 为user:id超时时间为9000秒。首次查询走数据库后续查询直接命中缓存?php namespace App\Services; use App\Models\User; use Hyperf\Cache\Annotation\Cacheable; class UserService { #[Cacheable(prefix: user, ttl: 9000, listener: user-update)] public function user($id) { $user User::query()-where(id,$id)-first(); if($user){ return $user-toArray(); } return null; } }注解参数与默认行为Cacheable的完整参数定义见 Annotation/Cacheable.php参数类型说明prefix?string缓存 KEY 前缀业务前缀最终会再拼接驱动层的全局前缀value?string自定义 KEY 命名规则支持#{参数路径}占位符ttl?int过期时间秒缺省时从配置cache.{group}.ttl读取默认 3600listener?string删除监听器名称声明后会被收集进CacheListenerCollectoroffsetintTTL 随机偏移量实际 TTL ttl rand(0, offset)用于打散过期时间、防止缓存雪崩groupstring缓存分组对应cache.stores中的某个 store 名称默认defaultcollectbool是否把 KEY 收集进成员集合配合KeyCollectorInterface驱动实现批量清理skipCacheResults?array命中这些返回值时跳过缓存缺省时从配置cache.{group}.skip_cache_results读取从 AnnotationManager.php 的getCacheableValue()可以看到实际运行时的取值逻辑ttl与skipCacheResults都遵循“注解优先、配置兜底”的原则随后会调用getRandomOffset()在ttl上叠加随机偏移量。注解切面执行流程CacheableAspect见 Aspect/CacheableAspect.php的处理逻辑是通过AnnotationManager根据类名、方法名、方法实参计算出最终缓存 KEY、TTL、分组与注解对象调用$driver-fetch($key)命中则直接返回缓存值不再执行原方法未命中则执行原方法$proceedingJoinPoint-process()拿到结果若结果不在skipCacheResults中则将结果写入缓存若驱动实现了KeyCollectorInterface且注解开启了collect同时把 KEY 加入{prefix}MEMBERS集合。测试用例 tests/Cases/AspectTest.php 的testCacheableAspect验证了“第二次调用直接命中缓存、与首次返回值一致”的行为testCacheableAspectWithSkipCacheResults则验证了skipCacheResults生效时结果不被缓存的行为。KEY 的生成规则KEY 的格式化逻辑集中在 Helper/StringHelper.php 的format()方法未设置value时value implode(:, $arguments)即按方法实参顺序用:拼接。例如user(1)且prefix user时业务 KEY 为user:1加上驱动前缀c:后最终 Redis KEY 为c:user:1。设置了value时解析#{...}占位符通过data_get($arguments, $k)按点号路径取值并替换。data_get支持数组下标与对象属性访问因此可以写出_#{id}、#{user.id}这类规则。例如StringHelper::format(test, [id 1], _#{id})得到test:_1。对应的单元测试 tests/Cases/StringHelperTest.php 还覆盖了未传value时的拼接行为test:1:Hyperf以及从模型对象取值#{model.id}→test:_1等场景。清理#[Cacheable]生成的缓存hyperf/cache提供了CachePut与CacheEvict两个注解来实现缓存的更新与清除。此外也可以通过事件来删除缓存——例如新建一个 Service 提供方法来统一处理缓存清理官方建议优先使用注解处理而不是监听器。?php declare(strict_types1); namespace App\Service; use Hyperf\Di\Annotation\Inject; use Hyperf\Cache\Listener\DeleteListenerEvent; use Psr\EventDispatcher\EventDispatcherInterface; class SystemService { #[Inject] protected EventDispatcherInterface $dispatcher; public function flushCache($userId) { $this-dispatcher-dispatch(new DeleteListenerEvent(user-update, [$userId])); return true; } }其原理是Cacheable声明listener: user-update时会在CacheListenerCollector中记录listener → (className, method)的映射见 CacheListenerCollector.php。DeleteListenerEvent见 Listener/DeleteListenerEvent.php构造时根据 listener 名称反查出对应的类与方法生成DeleteEventDeleteListener见 Listener/DeleteListener.php监听DeleteEvent用事件携带的实参重新计算缓存 KEY然后调用驱动delete($key)完成删除。自定义value时的参数写法当你为Cacheable自定义了value时例如?php declare(strict_types1); namespace App\Service\Cache; use Hyperf\Cache\Annotation\Cacheable; class DemoService { #[Cacheable(prefix: cache, value: _#{id}, listener: user-update)] public function getCache(int $id) { return $id . _ . uniqid(); } }由于 KEY 是根据“方法实参名”计算的_#{id}需要从实参数组中取id键构造DeleteListenerEvent时$arguments必须与之一致改为关联数组写法?php declare(strict_types1); namespace App\Service; use Hyperf\Di\Annotation\Inject; use Hyperf\Cache\Listener\DeleteListenerEvent; use Psr\EventDispatcher\EventDispatcherInterface; class SystemService { #[Inject] protected EventDispatcherInterface $dispatcher; public function flushCache($userId) { $this-dispatcher-dispatch(new DeleteListenerEvent(user-update, [id $userId])); return true; } }注解详解Cacheable以下配置中缓存前缀为user、超时时间为7200秒、删除事件名为USER_CACHE生成的缓存 KEY 为c:user:1?php declare(strict_types1); namespace App\Service; use App\Models\User; use Hyperf\Cache\Annotation\Cacheable; class UserService { #[Cacheable(prefix: user, ttl: 7200, listener: USER_CACHE)] public function user(int $id): array { $user User::query()-find($id); return [ user $user-toArray(), uuid $this-unique(), ]; } }当设置value后框架会按规则对缓存 KEY 命名。下面的例子中当$user-id 1时缓存 KEY 为c:userBook:_1该value配置同样适用于下文所述的其他类型缓存注解。?php declare(strict_types1); namespace App\Service; use App\Models\User; use Hyperf\Cache\Annotation\Cacheable; class UserBookService { #[Cacheable(prefix: userBook, ttl: 6666, value: _#{user.id})] public function userBook(User $user): array { return [ book $user-book-toArray(), uuid $this-unique(), ]; } }CacheAhead缓存预刷新以下配置中缓存前缀为user、超时时间为7200秒生成的缓存 KEY 为c:user:1并且在缓存剩余 600 秒即 TTL 走到7200 - 600的时刻开始每 10 秒进行一次缓存初始化/刷新直到首次成功?php declare(strict_types1); namespace App\Service; use App\Models\User; use Hyperf\Cache\Annotation\CacheAhead; class UserService { #[CacheAhead(prefix: user, ttl: 7200, aheadSeconds: 600, lockSeconds: 10)] public function user(int $id): array { $user User::query()-find($id); return [ user $user-toArray(), uuid $this-unique(), ]; } }从源码 Annotation/CacheAhead.php 可以看到CacheAhead在Cacheable基础上额外增加三个参数参数默认值说明aheadSeconds0提前刷新阈值在expired_time now ttl - aheadSeconds时刻之后触发刷新lockSeconds10刷新互斥锁的持有时间秒防止并发请求同时刷新runAsyncfalse首次无缓存时是否异步执行为true时异步执行原方法并写入缓存本次调用直接返回null注意源码注释明确标注CacheAhead仅支持 Redis 驱动Only Support Redis Driver.。其切面实现 Aspect/CacheAheadAspect.php 的缓存结构是一个数组[expired_time now ttl - aheadSeconds, data 结果]。命中缓存后若当前时间已超过expired_time则尝试通过SET key:lock 1 NX EX lockSeconds获取互斥锁抢锁成功者创建协程异步执行原方法刷新缓存其余请求继续返回旧值从而避免缓存击穿与雪崩。测试用例testCacheAheadAspect与testCacheAheadAspectStoringCacheInCoroutine验证了“缓存过期前返回旧值并异步刷新、随后可读到新值”的完整链路。CachePutCachePut与Cacheable不同每次调用都会执行函数体然后再把结果重写进缓存。因此当我们需要更新缓存时可以调用相应方法?php declare(strict_types1); namespace App\Service; use App\Models\User; use Hyperf\Cache\Annotation\CachePut; class UserService { #[CachePut(prefix: user, ttl: 3601)] public function updateUser(int $id) { $user User::query()-find($id); $user-name HyperfDoc; $user-save(); return [ user $user-toArray(), uuid $this-unique(), ]; } }对应的 Aspect/CachePutAspect.php 流程是计算 KEY → 先执行原方法 → 结果不在skipCacheResults中则写入缓存。参数上CachePut没有listener与collect其余prefix、value、ttl、offset、group、skipCacheResults与Cacheable一致。CacheEvictCacheEvict更易理解当方法体执行后会主动清理缓存?php declare(strict_types1); namespace App\Service; use Hyperf\Cache\Annotation\CacheEvict; class UserBookService { #[CacheEvict(prefix: userBook, value: _#{id})] public function updateUserBook(int $id) { return true; } }CacheEvict见 Annotation/CacheEvict.php多出一个all参数默认false。结合 Aspect/CacheEvictAspect.php 的实现all false默认删除单个 KEY$driver-delete($key)all true按前缀批量清理——若驱动实现了KeyCollectorInterface且注解开启collect则读取{prefix}MEMBERS集合中的全部 KEY 一并删除否则调用$driver-clearPrefix($prefix . :)。FailCache故障缓存源码附加能力除文档中介绍的四个注解外仓库源码还提供了Hyperf\Cache\Annotation\FailCache见 Annotation/FailCache.php与对应的 Aspect/FailCacheAspect.php。其行为是正常执行时把结果写入缓存当原方法抛出异常时若缓存中已有历史数据则回退返回该数据并输出debug级别日志否则把异常继续抛出——适合在依赖的下游服务不可用时提供降级数据。其默认prefix为ClassName::method形式支持value、ttl、listener、group、skipCacheResults等参数。缓存驱动所有驱动都实现Hyperf\Cache\Driver\DriverInterface见 Driver/DriverInterface.php该接口继承Psr\SimpleCache\CacheInterface并额外定义fetch(string $key, $default null): array同时返回“是否存在”与数据供注解切面判断命中clearPrefix(string $prefix): bool按前缀批量清理getConnection(): mixed返回底层连接/对象CacheAhead的 Redis 锁依赖此方法。Redis 驱动默认Hyperf\Cache\Driver\RedisDriver将缓存数据存放在 Redis 中需要用户配置对应的 Redis 连接池这是默认驱动。实现细节见 Driver/RedisDriver.php写入时用packer序列化值TTL 大于 0 时走redis-set($key, $res, $seconds)否则永不过期fetch用redis-get并区分false与命中值批量读写使用mget/mset带 TTL 时逐条setclearPrefix通过SCAN游标迭代匹配prefix *的 KEY 并批量del避免阻塞 Redis额外实现了KeyCollectorInterfacesAdd/sMembers/sRem支撑collect成员集合的增删查。进程内存驱动如果希望把数据缓存到进程内存中可以使用Hyperf\Cache\Driver\MemoryDriver。配置如下?php return [ memory [ driver Hyperf\Cache\Driver\MemoryDriver::class, ], ];从 Driver/MemoryDriver.php 看该驱动基于Hyperf\Cache\Collector\Memory静态实例存储额外支持两个可选配置配置项说明size最大缓存条数超过后set返回false写入失败throw_when_size_exceeded超过size时是否抛出OverflowException见 Exception/OverflowException.php默认不抛出数据过期通过Carbon::now()-addSeconds($seconds)记录过期时间实现。注意进程内存驱动只对当前 Worker 进程有效进程间不共享。协程内存驱动如果希望把数据缓存到Context协程上下文中可以使用Hyperf\Cache\Driver\CoroutineMemoryDriver。例如下面的场景Demo::get在多个地方被多次调用但又不希望每次都查询 Redis?php use Hyperf\Cache\Annotation\Cacheable; class Demo { public function get($userId, $id) { return $this-getArray($userId)[$id] ?? 0; } #[Cacheable(prefix: test, group: co)] public function getArray(int $userId): array { return $this-redis-hGetAll($userId); } }对应配置如下?php return [ co [ driver Hyperf\Cache\Driver\CoroutineMemoryDriver::class, packer Hyperf\Codec\Packer\PhpSerializerPacker::class, ], ];从 Driver/CoroutineMemoryDriver.php 看它基于Hyperf\Cache\Collector\CoroutineMemory一个静态实例化的Collection见 Collector/CoroutineMemory.php存储。需要特别注意的是协程上下文的作用域是当前协程同一协程内多次调用可复用缓存协程结束后数据随之释放因此它适合“单次请求/单次协程内避免重复查询”的短生命周期缓存不能当作跨请求的持久缓存使用。文件系统驱动与 SQLite 驱动源码提供的扩展能力仓库中还提供了另外两种驱动可作为无 Redis 环境下的备选文件系统驱动Hyperf\Cache\Driver\FileSystemDriver见 Driver/FileSystemDriver.php缓存文件存放在BASE_PATH . /runtime/caches目录每个 KEY 对应一个prefixkey.cache文件内容由packer序列化FileStorage含数据与过期时间后写入clearPrefix通过glob匹配前缀删除文件。SQLite 驱动Hyperf\Cache\Driver\SqliteDriver发布配置中提供了完整的注释示例database、table、PDOoptions、max_connections等参数适合本地开发或轻量部署。对应的驱动测试覆盖在 tests/Cases 下的RedisDriverTest.php、MemoryDriverTest.php、FileSystemDriverTest.php、CoroutineMemoryDriverTest.php中。底层原理CacheManager 与 AnnotationManager理解整个组件最关键的是两个管理器CacheManager见 CacheManager.php负责按名称懒加载并缓存驱动实例getDriver(?string $name)从cache.stores.{name}读取配置缺省驱动类为RedisDriver通过容器make创建并缓存到$drivers数组call(callable $callback, string $key, int $ttl 3600, ?string $name null)提供了一个命令式缓存辅助方法——先fetch命中直接返回未命中执行回调并把结果写入缓存。这在不想用注解的场景下很有用。AnnotationManager见 AnnotationManager.php负责把注解转换为运行时数据通过AnnotationCollector读取类方法上的注解实例统一计算 KEY委托StringHelper::format、分组、TTL 与skipCacheResults对offset做随机化处理rand(0, offset)这是防止缓存雪崩的内置手段。整体调用链可以概括为注解在编译期被收集 → 运行时由AnnotationManager计算 KEY 与 TTL → 切面从CacheManager获取驱动 → 驱动通过 packer 序列化/反序列化并与底层存储交互。ConfigProvider中 5 个切面CacheableAspect、CacheAheadAspect、CacheEvictAspect、CachePutAspect、FailCacheAspect分别对应 5 个注解可组合使用。小结围绕docs/en/cache.md与src/cache源码可以梳理出hyperf/cache的完整能力图谱两套 API面向第三方库与命令式编码的 PSR-16Psr\SimpleCache\CacheInterface以及面向业务方法的 AOP 注解缓存四种核心注解Cacheable读缓存、CachePut写缓存、CacheEvict删缓存、CacheAhead缓存预刷新防击穿另有源码级的FailCache异常降级回退两种失效手段注解直删CacheEvict与事件驱动删除DeleteListenerEventDeleteListener后者可在业务代码任意位置触发多种驱动Redis默认支持连接池与SCAN前缀清理、进程内存、协程内存、文件系统、SQLite通过cache.stores分组配置即可按group切换工程化细节prefix全局前缀、ttl配置兜底与offset随机化、skip_cache_results跳过缓存、collect成员集合批量管理均有源码与测试用例src/cache/tests/Cases支撑。在实际项目中推荐组合使用热点读接口用Cacheable数据变更处用CachePut/CacheEvict同步缓存对高并发热点再加CacheAhead预刷新必要时用FailCache兜底降级。如需深入了解可继续阅读仓库内的 docs/en/cache.md、src/cache/src 源码以及 src/cache/tests/Cases 下的测试用例。赞分享后端Web框架微服务RPC框架异步编程【免费下载链接】hyperf A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.项目地址https://gitcode.com/hyperf/hyperf点击查看免费下载相关推荐KubeSphere WizTelemetry Logging 扩展实战指南从安装配置到日志查询 APIKubeSphere WizTelemetry Logging 扩展实战指南从安装配置到日志查询 API 导读 本文是面向 KubeSphere 4.x Ob人工智能计算机视觉预训练MaaAssistantArknights 远程控制协议实战基于 getTask / reportStatus 双端点的轮询式任务调度实现MaaAssistantArknights 远程控制协议实战基于 getTask / reportStatus 双端点的轮询式任务调度实现 本文基于 MaaA人工智能大模型Agent 记忆AI AgentRAG知识图谱dsh-pluginMobile-Detect 的 PSR-16 简单缓存接口从 psr/simple-cache 到 Detection\Cache 的完整实战解析Mobile Detect 的 PSR 16 简单缓存接口从 psr/simple cache 到 Detection\Cache 的完整实战解析 本指南围绕后端上一篇如何用AntdUI构建现代化专业级WinForm应用5大核心设计理念解析下一篇百度ERNIE 4.5开源210亿参数MoE模型如何重塑AI产业格局创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考