# Redis

[Redis](https://redis.io/) 是一种最流行的内存数据结构存储，通常用作缓存或消息代理。

这个库是 Vapor 和 [**RediStack**](https://github.com/swift-server/RediStack) 的集成，它是与 Redis 通信的底层驱动程序。

!!! note "注意" 
    Redis 的大部分功能都是由 **RediStack** 提供的。我们强烈建议你熟悉其文档。

    _链接稍后提供。_


## Package

使用 Redis 的第一步是将它作为依赖项添加到你的 Package.swift 文件中。

> 本示例针对已有项目，要了解如何构建新项目，请参阅[入门指南](../getting-started/hello-world.zh.md)。

```swift
dependencies: [
    // ...
    .package(url: "https://github.com/vapor/redis.git", from: "4.0.0")
]
// ...
targets: [
    .target(name: "App", dependencies: [
        // ...
        .product(name: "Redis", package: "redis")
    ])
]
```

## 配置

Vapor 对 [`RedisConnection`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redisconnection) 实例采用池化策略，并且有几个选项可以配置单个连接以及池本身。

配置 Redis 的最低要求是提供一个 URL 来连接：

```swift
let app = Application()

app.redis.configuration = try RedisConfiguration(hostname: "localhost")
```

### Redis 配置

> API 文档：[`RedisConfiguration`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration)

#### 服务器地址

如果你有多个 Redis 端点，比如一个 Redis 实例集群，你需要创建一个 [`[SocketAddress]`](https://swiftpackageindex.com/apple/swift-nio/main/documentation/niocore/socketaddress) 集合来传递给初始化器。

创建 `SocketAddress` 最常见的方法是使用  [`makeAddressResolvingHost(_:port:)`](https://swiftpackageindex.com/apple/swift-nio/main/documentation/niocore/socketaddress/makeaddressresolvinghost(_:port:)) 静态方法。

```swift
let serverAddresses: [SocketAddress] = [
  try .makeAddressResolvingHost("localhost", port: RedisConnection.Configuration.defaultPort)
]
```

对于单个 Redis 端点，使用便利构造器初始化更容易，因为它将为你创建 `SocketAddress`：

- [`.init(url:pool)`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration/init(url:tlsconfiguration:pool:)-o9lf) (带 `String` 或 [`Foundation.URL`](https://developer.apple.com/documentation/foundation/url))
- [`.init(hostname:port:password:database:pool:)`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration/init(hostname:port:password:tlsconfiguration:database:pool:))

#### 密码

如果你的 Redis 实例受密码保护，则需要将其作为 `password` 参数传递。

每个连接在创建时都将使用密码进行身份验证。

#### 数据库

这是你希望在创建每个连接时选择的数据库索引。

这使你不必自己将 `SELECT` 命令发送到 Redis。

!!! warning "警告"
    未维护数据库选择。在自己发送 `SELECT` 命令时要小心。

### 连接池选项

> API 文档：[`RedisConfiguration.PoolOptions`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration/pooloptions)

!!! note "注意" 
    这里只突出显示最常更改的选项。对于所有选项，请参考 API 文档。

#### 最小连接数

这是设置你希望每个池始终保持多少连接的值。

值为`0`时，如果连接因任何原因丢失，则池在需要之前不会重新创建它们。

这被称为`冷启动`连接，并且在维持最小连接数方面确实有一些开销。

#### 最大连接数

此选项确定如何维护最大连接数的行为。

!!! seealso "也可以看看"
    请参阅 `RedisConnectionPoolSize` API 文档以熟悉更多可用选项。

## 发送命令

你可以使用  [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) 或 [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) 实例上的 `.redis` 属性发送命令，这使得你可以访问 [`RedisClient`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redisclient)。

对于各别的 [Redis 命令](https://redis.io/commands)，`RedisClient` 都有其对应的扩展。

```swift
let value = try app.redis.get("my_key", as: String.self).wait()
print(value)
// Optional("my_value")

// or

let value = try await app.redis.get("my_key", as: String.self)
print(value)
// Optional("my_value")
```

### 不支持的命令

如果 **RediStack** 不支持带有扩展方法的命令，你仍然可以手动发送它。

```swift
// command 后的每个值都是 Redis 期望的位置参数
try app.redis.send(command: "PING", with: ["hello"])
    .map {
        print($0)
    }
    .wait()
// "hello"

// or

let res = try await app.redis.send(command: "PING", with: ["hello"])
print(res)
// "hello"
```

## 发布/订阅 模式

Redis 支持进入[发布/订阅模式](https://redis.io/topics/pubsub)，其中连接可以监听特定的`通道`，并在订阅的通道发布`消息`（一些数据值）时运行特定的闭包。

订阅的生命周期定义：

1. **subscribe**：订阅第一次开始时调用一次
1. **message**：在消息发布到订阅频道时调用 0+ 次
1. **unsubscribe**：订阅结束时调用一次，无论是通过请求还是连接丢失

创建订阅时，你必须至少提供一个 [`messageReceiver`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redissubscriptionmessagereceiver) 来处理订阅频道发布的所有消息。

你可以选择为 `onSubscribe` 和 `onUnsubscribe`  提供一个 `RedisSubscriptionChangeHandler` 来处理它们各自的生命周期事件。

```swift
// 创建2个订阅，每个给定频道一个订阅
app.redis.subscribe
  to: "channel_1", "channel_2",
  messageReceiver: { channel, message in
    switch channel {
    case "channel_1": // 处理消息
    default: break
    }
  },
  onUnsubscribe: { channel, subscriptionCount in
    print("unsubscribed from \(channel)")
    print("subscriptions remaining: \(subscriptionCount)")
  }
```
