缓存

缓存(Cache)

AdonisJS Cache(@adonisjs/cache)是一个简单、轻量的封装,构建于 bentocache.dev 之上,用于缓存数据并提升你应用的性能。它提供了一个简洁而统一的 API,用于与各种缓存驱动交互,例如 Redis、DynamoDB、PostgreSQL、内存缓存等等。

我们强烈建议你阅读 Bentocache 文档。Bentocache 提供了一些高级的、可选的概念,它们在某些场景下会非常有用,例如多层缓存(multi-tiering)宽限期(grace periods)标签(tagging)超时(timeouts)缓存踩踏保护(Stampede Protection)等等。

安装

运行以下命令安装并配置 @adonisjs/cache 包:

node ace add @adonisjs/cache
  1. 使用检测到的包管理器安装 @adonisjs/cache 包。

  2. adonisrc.ts 文件中注册以下服务提供者:

    {
    providers: [
    // ...other providers
    () => import('@adonisjs/cache/cache_provider'),
    ]
    }
  3. 创建 config/cache.ts 文件。

  4. .env 文件中为所选的缓存驱动定义环境变量。

配置

缓存包的配置文件位于 config/cache.ts。你可以配置默认的缓存驱动、驱动列表及其各自的配置。

另见:配置 stub

import { defineConfig, store, drivers } from '@adonisjs/cache'
const cacheConfig = defineConfig({
default: 'redis',
stores: {
/**
* Cache data only on DynamoDB
*/
dynamodb: store().useL2Layer(drivers.dynamodb({})),
/**
* Cache data using your Lucid-configured database
*/
database: store().useL2Layer(drivers.database({ connectionName: 'default' })),
/**
* Cache data in-memory as the primary store and Redis as the secondary store.
* If your application is running on multiple servers, then in-memory caches
* need to be synchronized using a bus.
*/
redis: store()
.useL1Layer(drivers.memory({ maxSize: '100mb' }))
.useL2Layer(drivers.redis({ connectionName: 'main' }))
.useBus(drivers.redisBus({ connectionName: 'main' })),
},
})
export default cacheConfig

在上面的代码示例中,我们为每个缓存 store 设置了多个层。这被称为多层缓存系统。它让我们可以先检查快速的内存缓存(第一层)。如果我们在那里找不到数据,就再使用分布式缓存(第二层)。

Redis

要将 Redis 用作你的缓存系统,你必须安装 @adonisjs/redis 包并进行配置。请参阅这里的文档:Redis

config/cache.ts 中,你必须指定一个 connectionName。该属性应与 config/redis.ts 文件中的 Redis 配置键(key)相匹配。

Database

database 驱动对 @adonisjs/lucid 有一个对等依赖(peer dependency)。因此,你必须安装并配置该包才能使用 database 驱动。

config/cache.ts 中,你必须指定一个 connectionName。该属性应与 config/database.ts 文件中的数据库配置键相对应。

此外,在配置 database 驱动时,会向你的 database/migrations 目录发布一个迁移,你必须运行它来创建用于存储缓存条目的必要表。

其他驱动

你可以使用其他驱动,例如 memorydynamodbkyselyorchid

更多信息请参阅 Cache Drivers

用法

配置好缓存后,你可以导入 cache 服务来与之交互。在以下示例中,我们将用户详情缓存 5 分钟:

import cache from '@adonisjs/cache/services/main'
import router from '@adonisjs/core/services/router'
import User from '#models/user'
router.get('/user/:id', async ({ params }) => {
return cache.getOrSet({
key: `user:${params.id}`,
factory: async () => {
const user = await User.find(params.id)
return user.toJSON()
},
ttl: '5m',
})
})

如你所见,我们使用 user.toJSON() 序列化用户的数据。这是必要的,因为你的数据必须经过序列化才能存储在缓存中。像 Lucid 模型或 Date 实例这样的类无法直接存储在 Redis 或数据库等缓存中。

ttl 定义了缓存键的生存时间(time-to-live)。TTL 过期后,缓存键被视为陈旧(stale),下一次请求将从 factory 方法重新获取数据。

标签(Tagging)

你可以将一个缓存条目与一个或多个标签关联,以简化失效操作。无需管理各个键,条目可以归组到多个标签下,并在单次操作中使其失效。

await cache.getOrSet({
key: 'foo',
factory: getFromDb(),
tags: ['tag-1', 'tag-2']
});
await cache.deleteByTag({ tags: ['tag-1'] });

命名空间(Namespaces)

另一种对键进行分组的方式是使用命名空间。这让你之后可以一次性使所有内容失效:

const users = cache.namespace('users')
users.set({ key: '32', value: { name: 'foo' } })
users.set({ key: '33', value: { name: 'bar' } })
users.clear()

宽限期(Grace period)

你可以使用 grace 选项,允许 Bentocache 在缓存键已过期但仍处于宽限期内时返回陈旧数据。这使得 Bentocache 的工作方式与 SWR 或 TanStack Query 等库相同。

import cache from '@adonisjs/cache/services/main'
cache.getOrSet({
key: 'slow-api',
factory: async () => {
await sleep(5000)
return 'slow-api-response'
},
ttl: '1h',
grace: '6h',
})

在上面的示例中,数据在 1 小时后会被视为陈旧。不过,在 6 小时的宽限期内的下一次请求将返回陈旧数据,同时从 factory 方法重新获取数据并更新缓存。

超时(Timeouts)

你可以使用 timeout 选项配置在返回陈旧数据之前允许 factory 方法运行多长时间。默认情况下,Bentocache 设置了 0ms 的软超时(soft timeout),这意味着我们总是返回陈旧数据,同时在后台重新获取数据。

import cache from '@adonisjs/cache/services/main'
cache.getOrSet({
key: 'slow-api',
factory: async () => {
await sleep(5000)
return 'slow-api-response'
},
ttl: '1h',
grace: '6h',
timeout: '200ms',
})

在上面的示例中,factory 方法最多允许运行 200ms。如果 factory 方法耗时超过 200ms,陈旧数据将返回给用户,但 factory 方法将继续在后台运行。

如果你没有定义 grace 期,你仍然可以使用硬超时(hard timeout)来阻止 factory 方法在一定时间后继续运行。

import cache from '@adonisjs/cache/services/main'
cache.getOrSet({
key: 'slow-api',
factory: async () => {
await sleep(5000)
return 'slow-api-response'
},
ttl: '1h',
hardTimeout: '200ms',
})

在此示例中,factory 方法将在 200ms 后被停止,并抛出一个错误。

你可以同时定义 timeouthardTimeouttimeout 是在返回陈旧数据之前允许 factory 方法运行的最长时间,而 hardTimeout 是在被停止之前允许 factory 方法运行的最长时间。

Cache 服务

@adonisjs/cache/services/main 导出的 cache 服务是使用 config/cache.ts 中定义的配置创建的 BentoCache 类的单例实例。

你可以将 cache 服务导入到你的应用中,并用它来与缓存交互:

import cache from '@adonisjs/cache/services/main'
/**
* Without calling the `use` method, the methods you call on the cache service
* will use the default store defined in `config/cache.ts`.
*/
cache.put({ key: 'username', value: 'jul', ttl: '1h' })
/**
* Using the `use` method, you can switch to a different store defined in
* `config/cache.ts`.
*/
cache.use('dynamodb').put({ key: 'username', value: 'jul', ttl: '1h' })

你可以在这里找到所有可用的方法:BentoCache API

await cache.namespace('users').set({ key: 'username', value: 'jul' })
await cache.namespace('users').get({ key: 'username' })
await cache.get({ key: 'username' })
await cache.set({ key: 'username', value: 'jul' })
await cache.setForever({ key: 'username', value:'jul' })
await cache.getOrSet({
key: 'username',
factory: async () => fetchUserName(),
ttl: '1h',
})
await cache.has({ key: 'username' })
await cache.missing({ key: 'username' })
await cache.pull({ key: 'username' })
await cache.delete({ key: 'username' })
await cache.deleteMany({ keys: ['products', 'users'] })
await cache.deleteByTag({ tags: ['products', 'users'] })
await cache.clear()

Edge 辅助工具

cache 服务可作为 Edge 辅助工具在你的视图中使用。你可以用它直接在模板中检索缓存的值。

<p>
Hello {{ await cache.get('username') }}
</p>

Ace 命令

@adonisjs/cache 包还提供了一组 Ace 命令,用于从终端与缓存交互。

cache:clear

清除指定 store 的缓存。如果未指定,将清除默认的那个。

# Clear the default cache store
node ace cache:clear
# Clear a specific cache store
node ace cache:clear redis
# Clear a specific namespace
node ace cache:clear store --namespace users
# Clear multiple specific tags
node ace cache:clear store --tags products --tags users

cache:delete

从指定 store 中删除特定的缓存键。如果未指定,将从默认的那个中删除。

# Delete a specific cache key
node ace cache:delete cache-key
# Delete a specific cache key from a specific store
node ace cache:delete cache-key store

cache:prune

某些缓存驱动(如 database 驱动)不会自动移除过期的键,因为它们缺乏原生的 TTL 支持。你可以使用 cache:prune 命令手动移除过期的键。在支持 TTL 的 store 上,此命令将不执行任何操作(no-op)。

# Prune expired keys from the default cache store
node ace cache:prune
# Prune expired keys from a specific cache store
node ace cache:prune store