├── .github └── stale.yml ├── Basics.md ├── Configuration.md ├── Events.md ├── ExecSync.md ├── KeysScan.md ├── KeysValues.md ├── LICENSE ├── PipelinesMultiplexers.md ├── Profiling.md ├── Profiling_v1.md ├── Profiling_v2.md ├── PubSubOrder.md ├── README.md ├── SUMMARY.md ├── Scripting.md ├── TOC.md ├── Timeouts.md ├── Transactions.md └── _config.yml /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 30 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false -------------------------------------------------------------------------------- /Basics.md: -------------------------------------------------------------------------------- 1 | 基本使用 2 | === 3 | 4 | StackExchange.Redis 中核心对象是在 `StackExchange.Redis` 命名空间中的 `ConnectionMultiplexer` 类,这个对象隐藏了多个服务器的详细信息。 5 | 因为`ConnectionMultiplexer`要做很多事,它被设计为在调用者之间可以**共享**和**重用**。 6 | 你不应该在执行每一个操作的时候就创建一个 `ConnectionMultiplexer`. 它完全是线程安全的,并准备好这种用法(多线程)。 7 | 在后续所有的例子中,我们假设你有一个`ConnectionMultiplexer`类的实例保存以重用。 8 | 但现在,让我们来先创建一个。 这是使用 `ConnectionMultiplexer.Connect` 或 `ConnectionMultiplexer.ConnectAsync` 完成的,传递配置字符串或`ConfigurationOptions` 对象。 9 | 配置字符串可以采用逗号分隔的一系列节点的形式,所以让我们在默认端口(6379)上连接到本地机器上的一个实例: 10 | 11 | ``` C# 12 | using StackExchange.Redis; 13 | ... 14 | ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost"); 15 | // ^^^ store and re-use this!!! 16 | ``` 17 | 18 | 请注意,`ConnectionMultiplexer` 实现了 `IDisposable` 接口而且可以在不再需要的时候处理释放掉。 19 | 这是故意不展示使用 `using` 语句用法,因为你想要只是简单地使用一个`ConnectionMultiplexer`的情况是极少见的 ,因为想法是重用这个对象。 20 | 21 | 更复杂的情况可能涉及主/从设置; 对于此用法,只需简单的指定组成逻辑redis层的所有所需节点(它将自动标识主节点) 22 | 23 | ``` C# 24 | ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("server1:6379,server2:6379"); 25 | ``` 26 | 27 | 如果它发现两个节点都是主节点,则可以可选地指定可以用于解决问题的仲裁密钥,然而幸运地是这样的条件是非常罕见的。 28 | 29 | 一旦你有一个 `ConnectionMultiplexer` ,你可能有3个主要想做的事: 30 | 31 | - 访问一个 redis 数据库(注意,在集群的情况下,单个逻辑数据库可以分布在多个节点上) 32 | - 使用 redis 的的 [发布/订阅](http://redis.io/topics/pubsub) 功能 33 | - 访问单独的服务器以进行维护/监视 34 | 35 | 使用 redis 数据库 36 | --- 37 | 38 | 访问redis数据库非常简单: 39 | 40 | ```C# 41 | IDatabase db = redis.GetDatabase(); 42 | ``` 43 | 44 | 从 `GetDatabase` 返回的对象是一个成本很低的通道对象,不需要存储。 45 | 注意,redis支持多个数据库(虽然“集群”不支持),这可以可选地在调用 `GetDatabase` 中指定。 46 | 此外,如果您计划使用异步API,您需要 [`Task.AsyncState`][2] 有一个值,也可以指定: 47 | 48 | ```C# 49 | int databaseNumber = ... 50 | object asyncState = ... 51 | IDatabase db = redis.GetDatabase(databaseNumber, asyncState); 52 | ``` 53 | 54 | 一旦你有了`IDatabase`,它只是一个使用 [redis API](http://redis.io/commands) 的情况。 55 | 注意,所有方法都具有同步和异步实现。 56 | 根据微软的命名指导,异步方法都以 `...Async(...)` 结尾,并且完全是可以等待的 `await` 等。 57 | 58 | 最简单的操作也许是存储和检索值: 59 | 60 | ```C# 61 | string value = "abcdefg"; 62 | db.StringSet("mykey", value); 63 | ... 64 | string value = db.StringGet("mykey"); 65 | Console.WriteLine(value); // writes: "abcdefg" 66 | ``` 67 | 68 | 需要注意的是,这里的 `String...` 前缀表示 [redis 的 String 类型](http://redis.io/topics/data-types),尽管它和 [.NET 的字符串类型][3] 都可以保存文本, 它们还是有较大差别的。 69 | 然而,redis 还允许键和值使用原始的二进制数据,用法和字符串是一样的: 70 | 71 | ```C# 72 | byte[] key = ..., value = ...; 73 | db.StringSet(key, value); 74 | ... 75 | byte[] value = db.StringGet(key); 76 | ``` 77 | 78 | 覆盖所有redis数据类型的所有 [redis数据库命令](http://redis.io/commands) 的都是可以使用的。 79 | 80 | 使用 redis 发布/订阅 81 | ---- 82 | 83 | redis的另一个常见用法是作为 [发布/订阅消息](http://redis.io/topics/pubsub)分发工具; 84 | 这也很简单,并且在连接失败的情况下, `ConnectionMultiplexer` 将处理重新订阅所请求的信道的所有细节。 85 | 86 | ```C# 87 | ISubscriber sub = redis.GetSubscriber(); 88 | ``` 89 | 90 | 同样,从 `GetSubscriber` 返回的对象是一个不需要存储的低成本的通道对象。 91 | 发布/订阅 API没有数据库的概念,但和之前一样,我们可以选择的提供一个异步状态。 92 | 注意,所有订阅都是全局的:它们不限于 `ISubscriber` 实例的生命周期。 93 | redis中的 发布/订阅 功能使用命名的“channels”; channels 不需要事先在服务器上定义(这里有一个有趣的用法是利用每个用户的通知渠道类驱动部分的实时更新) 94 | 如在.NET中常见的,订阅采用回调委托的形式,它接受通道名称和消息: 95 | 96 | ```C# 97 | sub.Subscribe("messages", (channel, message) => { 98 | Console.WriteLine((string)message); 99 | }); 100 | ``` 101 | 102 | 另外(通常在一个单独的机器上的一个单独的进程),你可以发布到该通道: 103 | 104 | ```C# 105 | sub.Publish("messages", "hello"); 106 | ``` 107 | 108 | 这将(实际上瞬间)将“hello”写到订阅进程的控制台。 和之前一样,通道名和消息都可以是二进制的。 109 | 110 | 有关顺序和并发消息处理的使用文档说明,请参见 [发布/订阅消息顺序](./PubSubOrder.md) 。 111 | 112 | 访问单独的服务器 113 | --- 114 | 115 | 出于维护目的,有时需要发出服务器特定的命令: 116 | 117 | ```C# 118 | IServer server = redis.GetServer("localhost", 6379); 119 | ``` 120 | 121 | `GetServer` 方法会接受一个 [`EndPoint`](http://msdn.microsoft.com/en-us/library/system.net.endpoint(v=vs.110).aspx) 终结点或者是一个可以唯一标识一个服务器的键/值对。 122 | 123 | 像之前介绍的那样, `GetServer` 方法返回的是一个不需要被存储的轻量级的通道对象,并且可以可选的指定 async-state(异步状态)。 124 | 需要注意的是,多个可用的节点也是可以的: 125 | 126 | ```C# 127 | EndPoint[] endpoints = redis.GetEndPoints(); 128 | ``` 129 | 130 | 一个 `IServer` 实例是可以使用服务器命令的 [Server commands](http://redis.io/commands#server),例如: 131 | 132 | ```C# 133 | DateTime lastSave = server.LastSave(); 134 | ClientInfo[] clients = server.ClientList(); 135 | ``` 136 | 137 | 同步 vs 异步 vs 执行后不理 138 | --- 139 | 140 | StackExchange.Redis有3种主要使用机制: 141 | 142 | - 同步 - 适用于操作在方法返回到调用者之前完成(注意,尽管这可能阻止调用者,但它绝对**不会**阻止其他线程:StackExchange.Redis的关键思想是它积极地与并发调用者共享连接) 143 | 144 | - 异步 - 操作在将来完成一些时间,并且立即返回一个 `Task` 或 'Task' 对象,也可以稍后再返回: 145 | - 是可以等待的(阻塞当前线程,直到响应可用) `.Wait()` 146 | - 可以增加一个后续的回调 (TPL 中的 [`ContinueWith`](http://msdn.microsoft.com/en-us/library/system.threading.tasks.task.continuewith(v=vs.110).aspx)) 147 | - *awaited* 可等待的(这是简化后者的语言级特性,同时如果答复已经知道也立即继续 148 | 149 | - 执行后不理 - 适用于你真的对这个回复不感兴趣,并且乐意继续不管回应 150 | 151 | 同步的用法已经在上面的示例中展示了。这是最简单的用法,并且不涉及 [TPL][1]。 152 | 153 | 对于异步使用,关键的区别是方法名称上的 `Async` 后缀,以及(通常)使用“await”语言特性。例如: 154 | 155 | ```C# 156 | string value = "abcdefg"; 157 | await db.StringSetAsync("mykey", value); 158 | ... 159 | string value = await db.StringGetAsync("mykey"); 160 | Console.WriteLine(value); // writes: "abcdefg" 161 | ``` 162 | 163 | 执行后不理 的用法可以通过所有方法上的可选参数 `CommandFlags flags` (默认值为 `null`)来访问使用。 164 | 这种用法中,方法会立即方法一个默认值(所以通常返回一个 `String` 的方法总是返回 `null`,而一个通常返回一个 `Int64` 的方法总是返回 `0`)。 165 | 操作会在后台继续执行。这种情况的典型用例可能是增加页面浏览数量: 166 | 167 | ```C# 168 | db.StringIncrement(pageKey, flags: CommandFlags.FireAndForget); 169 | ``` 170 | 171 | [查看原文](https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/Basics.md) 172 | --- 173 | 174 | [返回主页](./README.md) 175 | --- 176 | 177 | [1]: http://msdn.microsoft.com/en-us/library/dd460717%28v=vs.110%29.aspx 178 | [2]: http://msdn.microsoft.com/en-us/library/system.threading.tasks.task.asyncstate(v=vs.110).aspx 179 | [3]: http://msdn.microsoft.com/en-us/library/system.string(v=vs.110).aspx 180 | -------------------------------------------------------------------------------- /Configuration.md: -------------------------------------------------------------------------------- 1 | 配置 2 | === 3 | 4 | 因为有很多不同配置 redis 的方式,StackExchange.Redis 提供了一个丰富的配置模型,当调用 `Connect` (或 `ConnectAsync` )时调用它。 5 | 6 | ```C# 7 | var conn = ConnectionMultiplexer.Connect(configuration); 8 | ``` 9 | 10 | 这里的 `configuration` 可以是下面的任意一个: 11 | 12 | - 一个 `ConfigurationOptions` 实例 13 | - 一个代表配置的 `string` 14 | 15 | 后者是 *基本上* 是前者的标记化形式。 16 | 17 | 基本配置字符串 18 | - 19 | 20 | *最简单的* 配置示例只需要一个主机名: 21 | 22 | ```C# 23 | var conn = ConnectionMultiplexer.Connect("localhost"); 24 | ``` 25 | 26 | 这将使用默认的redis端口(6379)连接到本地计算机上的单个服务器。 27 | 附加选项只是简单地附加(逗号分隔)。 端口通常用冒号(`:`)表示。 配置*选项*在名称后面包含一个`=`。 例如: 28 | 29 | ```C# 30 | var conn = ConnectionMultiplexer.Connect("redis0:6380,redis1:6380,allowAdmin=true"); 31 | ``` 32 | 33 | 下面显示了 `string` 和 `ConfigurationOptions` 表示之间的映射概述,但您可以轻松地在它们之间切换: 34 | 35 | ```C# 36 | ConfigurationOptions options = ConfigurationOptions.Parse(configString); 37 | ``` 38 | 39 | 或者: 40 | 41 | ```C# 42 | string configString = options.ToString(); 43 | ``` 44 | 45 | 常见的用法是将 *基础配置* 细节存储在一个字符串中,然后在运行时应用特定的详细信息: 46 | 47 | ```C# 48 | string configString = GetRedisConfiguration(); 49 | var options = ConfigurationOptions.Parse(configString); 50 | options.ClientName = GetAppName(); // only known at runtime 51 | options.AllowAdmin = true; 52 | conn = ConnectionMultiplexer.Connect(options); 53 | ``` 54 | 55 | 带密码的 Microsoft Azure Redis 示例 56 | 57 | ```C# 58 | var conn = ConnectionMultiplexer.Connect("contoso5.redis.cache.windows.net,ssl=true,password=..."); 59 | ``` 60 | 61 | 配置选项 62 | --- 63 | 64 | `ConfigurationOptions`对象具有许多的属性,所有这些都在智能提示中都有。 65 | 66 | 一些更常用的选项包括: 67 | 68 | | 配置字符串 | `ConfigurationOptions` | 默认值 | 含义 | 69 | | ---------------------- | ---------------------- | ----------------------------|--------------------------------------------------- | 70 | | abortConnect={bool} | `AbortOnConnectFail` | `true` (Azure 上默认值为 `false`) | 如果为true,`Connect` 没有服务器可用时将不会创建连接 | 71 | | allowAdmin={bool} | `AllowAdmin` | `false` | 启用被认为具有风险的一系列命令 | 72 | | channelPrefix={string} | `ChannelPrefix` | `null` | 所有发布/订阅操作的可选频道前缀 | 73 | | connectRetry={int} | `ConnectRetry` | `3` | 在初始 `Connect` 期间重复连接尝试的次数 | 74 | | connectTimeout={int} | `ConnectTimeout` | `5000` | 连接操作的超时时间(ms) | 75 | | configChannel={string} | `ConfigurationChannel` | `__Booksleeve_MasterChanged` | 用于传达配置更改的广播通道名称 | 76 | | configCheckSeconds={int} | `ConfigCheckSeconds` | `60` | 检查配置的时间(秒)。如果支持的话,这会以交互式套接字的方式保持活动。 | 77 | | defaultDatabase={int} | `DefaultDatabase` | `null` | 默认数据库索引, 从 `0` 到 `databases - 1`(0 到 Databases.Count -1) 78 | | keepAlive={int} | `KeepAlive` | `-1` | 发送消息以帮助保持套接字活动的时间(秒)(默认时间60s) | 79 | | name={string} | `ClientName` | `null` | 标识 redis 中的连接 | 80 | | password={string} | `Password` | `null` | redis 服务器的密码 | 81 | | proxy={proxy type} | `Proxy` | `Proxy.None` | 正在使用的代理类型(如果有); 例如“twemproxy” | 82 | | resolveDns={bool} | `ResolveDns` | `false` | 指定DNS解析应该是显式和热切,而不是隐式 | 83 | | responseTimeout={int} | `ResponseTimeout` | `SyncTimeout` | socket 健康检查的响应时间(ms,毫秒) | 84 | | serviceName={string} | `ServiceName` | `null` | 目前尚未实施(预期与sentinel一起使用) | 85 | | ssl={bool} | `Ssl` | `false` | 指定应使用SSL加密 | 86 | | sslHost={string} | `SslHost` | `null` | 在服务器证书上强制执行特定的SSL主机标识 | 87 | | syncTimeout={int} | `SyncTimeout` | `1000` | 允许同步操作的时间(ms) | 88 | | tiebreaker={string} | `TieBreaker` | `__Booksleeve_TieBreak` | 用于在不明确的主场景中选择服务器的键 | 89 | | version={string} | `DefaultVersion` | `(3.0 in Azure, else 2.0)` | Redis版本级别(当服务器要使用的版本默认不可用时使用) | 90 | | writeBuffer={int} | `WriteBuffer` | `4096` | 输出缓冲区的大小 | 91 | | ReconnectRetryPolicy={IReconnectRetryPolicy} | `ReconnectRetryPolicy` | 重新连接重试策略 | 92 | 93 | 补充的只有代码才支持的选项: 94 | 95 | > ReconnectRetryPolicy (IReconnectRetryPolicy) - Default: ReconnectRetryPolicy = LinearRetry(ConnectTimeout); 96 | 97 | - 重连重试策略(`IReconnectRetryPolicy`)。默认设置:`ReconnectRetryPolicy = LinearRetry(ConnectTimeout);` 98 | 99 | 配置字符串中的令牌是逗号分隔的;任何没有`=`符号的都假定为redis服务器端点。 100 | 没有显式端口的端点将在未启用ssl的情况下使用6379,如果启用了ssl则使用6380。 101 | 102 | 以`$`开头的令牌被用来表示命令映射,例如:`$ config = cfg`。 103 | 104 | 自动和手动配置 105 | --- 106 | 107 | 在许多常见的情况下,StackExchange.Redis将自动配置很多设置,包括服务器类型和版本,连接超时和主/从关系。 108 | 有时,在redis服务器上禁用了这些命令。 在这种情况下,可以提供更多的信息: 109 | 110 | ```C# 111 | ConfigurationOptions config = new ConfigurationOptions 112 | { 113 | EndPoints = 114 | { 115 | { "redis0", 6379 }, 116 | { "redis1", 6380 } 117 | }, 118 | CommandMap = CommandMap.Create(new HashSet 119 | { // EXCLUDE a few commands 120 | "INFO", "CONFIG", "CLUSTER", 121 | "PING", "ECHO", "CLIENT" 122 | }, available: false), 123 | KeepAlive = 180, 124 | DefaultVersion = new Version(2, 8, 8), 125 | Password = "changeme" 126 | }; 127 | ``` 128 | 129 | 它相当于命令字符串: 130 | 131 | ```config 132 | redis0:6379,redis1:6380,keepAlive=180,version=2.8.8,$CLIENT=,$CLUSTER=,$CONFIG=,$ECHO=,$INFO=,$PING= 133 | ``` 134 | 135 | 重命名命令 136 | --- 137 | 138 | redis的一个很不寻常的功能是可以禁用或重命名或禁用并重命名单个命令。 139 | 140 | 根据前面的例子,这是通过`CommandMap`来实现的,但不是传递一个`HashSet `到`Create()`(表示可用或不可用的命令),而是传递一个`Dictionary < string>`。 141 | 字典中未提及的所有命令都默认已启用且未重命名。 142 | 143 | “null”或空白值记录命令被禁用。 例如: 144 | 145 | ```C# 146 | var commands = new Dictionary { 147 | { "info", null }, // disabled 148 | { "select", "use" }, // renamed to SQL equivalent for some reason 149 | }; 150 | var options = new ConfigurationOptions { 151 | // ... 152 | CommandMap = CommandMap.Create(commands), 153 | // ... 154 | } 155 | ``` 156 | 157 | 以上代码等同于(在连接字符串中): 158 | 159 | ```config 160 | $INFO=,$SELECT=use 161 | ``` 162 | 163 | Twemproxy 164 | --- 165 | 166 | [Twemproxy](https://github.com/twitter/twemproxy) 是允许使用多个 redis 实例就像它是一个单个服务器,具有内置分片和容错(很像 redis 集群,但单独实现)的工具。 167 | Twemproxy可用的功能集减少。 168 | 为了避免手动配置,可以使用 `Proxy` 选项: 169 | 170 | ```C# 171 | var options = new ConfigurationOptions 172 | { 173 | EndPoints = { "my-server" }, 174 | Proxy = Proxy.Twemproxy 175 | }; 176 | ``` 177 | 178 | Tiebreakers 和配置更改公告 179 | --- 180 | 181 | 通常 StackExchange.Redis 都会自动解析 主/从节点。然而,如果你不使用诸如 redis-sentinel 或 redis 集群之类的管理工具,有时你会有多个主节点(例如,在重置节点以进行维护时,它可能会作为主节点重新显示在网络上)。 182 | 为了帮助解决这个问题,StackExchange.Redis 可以使用 *tie-breaker* 的概念 - 这仅在检测到多个主节点时使用(不包括需要多个主节点的redis集群)。 183 | 为了与 BookSleeve 兼容,tiebreaker 使用默认的key为 `"__Booksleeve_TieBreak"` (总是在数据库0中)。 184 | 这用作粗略的投票机制,以帮助确定*首选* 主机,以便能够按正确的路由工作。 185 | 186 | 同样,当配置改变时(特别是主/从配置),连接的实例使得他们意识到新的情况(在可用的地方通过 `INFO`,`CONFIG` 等进行通知 )是很重要的。 187 | StackExchange.Redis 通过自动订阅可以在其上发送这样的通知的发布/订阅通道来实现。 188 | 由于类似的原因,这默认为`"__Booksleeve_MasterChanged"`。 189 | 190 | 这两个选项都可以通过 `.ConfigurationChannel` 和 `.TieBreaker` 配置属性来定制或禁用(设置为`""`)。 191 | 192 | These settings are also used by the `IServer.MakeMaster()` method, which can set the tie-breaker in the database and broadcast the configuration change message. 193 | The configuration message can also be used separately to master/slave changes simply to request all nodes to refresh their configurations, via the `ConnectionMultiplexer.PublishReconfigure` method. 194 | 195 | 这些设置也由 `IServer.MakeMaster()` 方法使用,它可以设置数据库中的 tie-breaker 并广播配置更改消息。 196 | 配置消息也可以单独用于主/从变化,只需通过调用 `ConnectionMultiplexer.PublishReconfigure` 方法请求所有节点刷新其配置。 197 | 198 | 重新连接重试策略 199 | --- 200 | 201 | 当连接由于任何原因丢失时,StackExchange.Redis会自动尝试在后台重新连接。 202 | 它将继续重试,直到连接恢复。 它将使用 ReconnectRetryPolicy 来决定在重试之间应该等待多长时间。 203 | ReconnectRetryPolicy可以是线性的(默认),指数的或者是一个自定义的重试策略。 204 | 205 | 举个例子: 206 | 207 | ```C# 208 | config.ReconnectRetryPolicy = new ExponentialRetry(5000); // defaults maxDeltaBackoff to 10000 ms 209 | //retry# retry to re-connect after time in milliseconds 210 | //1 a random value between 5000 and 5500 211 | //2 a random value between 5000 and 6050 212 | //3 a random value between 5000 and 6655 213 | //4 a random value between 5000 and 8053 214 | //5 a random value between 5000 and 10000, since maxDeltaBackoff was 10000 ms 215 | //6 a random value between 5000 and 10000 216 | 217 | config.ReconnectRetryPolicy = new LinearRetry(5000); 218 | //retry# retry to re-connect after time in milliseconds 219 | //1 5000 220 | //2 5000 221 | //3 5000 222 | //4 5000 223 | //5 5000 224 | //6 5000 225 | ``` 226 | 227 | [查看原文](https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/Configuration.md) 228 | --- 229 | 230 | [返回主页](./README.md) 231 | --- -------------------------------------------------------------------------------- /Events.md: -------------------------------------------------------------------------------- 1 | 事件 2 | === 3 | 4 | `ConnectionMultiplexer` 类型提供了许多事件可以用来理解被封装的底层是怎么工作的。这在记录日志时会特别有用。 5 | 6 | - `ConfigurationChanged` - 当连接的配置从 `ConnectionMultiplexer` 内部发生修改时触发 7 | - `ConfigurationChangedBroadcast` - 当经由发布/订阅接收到重新配置消息时引发; 这通常是由于 `IServer.MakeMaster` 用于更改节点的复制配置,可以选择将这样的请求广播到所有客户端 8 | - `ConnectionFailed` - 当连接由于无论任何原因失败时触发; 请注意,在连接重新建立之前是不会再收到该连接的 `ConnectionFailed` 通知 9 | - `ConnectionRestored` - 当重新建立到先前失败的节点的连接时触发 10 | - `ErrorMessage` - 当redis服务器响应任何用户发起的具有错误消息的请求时触发; 这种情况不包含将被报告给直接调用者的常规异常/故障的情况 11 | - `HashSlotMoved` - 当“redis集群”指出 散列槽(hash-slot) 已在节点之间迁移时触发; 请注意,请求通常会自动重新路由,因此用户不需要在这里做任何特殊操作 12 | - `InternalError` - 当库在一些意想不到的方式失败时触发; 这主要是为了调试目的,并且大多数用户应该不需要这个事件 13 | 14 | 需要注意,StackExchange.Redis 中的 发布/订阅 工作方式与事件*非常相似*,接收到消息时会调用接受一个 `Action` 类型回调方法的 `Subscribe` / `SubscribeAsync` 方法。 15 | 16 | [查看原文](https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/Events.md) 17 | --- 18 | 19 | [返回主页](./README.md) 20 | --- -------------------------------------------------------------------------------- /ExecSync.md: -------------------------------------------------------------------------------- 1 | 连续同步执行的危险 2 | === 3 | 4 | 一旦遇到这样的问题,这里还有更多内容,然后发现了 [一个适当恶劣的解决方法](http://stackoverflow.com/a/22588431/23354)。 5 | 这篇文章没有列在索引中,但是为满足你的好奇心而保留了下来。 6 | 7 | [查看原文](https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/ExecSync.md) 8 | --- 9 | 10 | [返回主页](./README.md) 11 | --- -------------------------------------------------------------------------------- /KeysScan.md: -------------------------------------------------------------------------------- 1 | `KEYS`, `SCAN`, `FLUSHDB` 这些在哪里? 2 | === 3 | 4 | 这里是一些非常常见的常见问题: 5 | 6 | > 似乎没有一个 `Keys(...)` 或者 `Scan(...)` 方法? 如何查询数据库中存在哪些键? 7 | 8 | 或者 9 | 10 | > 似乎没有一个 `Flush(...)` 方法?如何删除数据库中的所有的键? 11 | 12 | 很奇怪的是,这里的最后一个关键词是数据库。 13 | 因为StackExchange.Redis的目标是针对集群等场景,知道哪些命令针对 *数据库* (可以是分布在多个节点上的逻辑数据库)以及哪些命令针对 *服务器* 是很重要的。 14 | 以下命令都针对单个服务器: 15 | 16 | - `KEYS` / `SCAN` 17 | - `FLUSHDB` / `FLUSHALL` 18 | - `RANDOMKEY` 19 | - `CLIENT` 20 | - `CLUSTER` 21 | - `CONFIG` / `INFO` / `TIME` 22 | - `SLAVEOF` 23 | - `SAVE` / `BGSAVE` / `LASTSAVE` 24 | - `SCRIPT` (不要混淆 `EVAL` / `EVALSHA`) 25 | - `SHUTDOWN` 26 | - `SLOWLOG` 27 | - `PUBSUB` (不要混淆 `PUBLISH` / `SUBSCRIBE` / 等) 28 | - 一些 `DEBUG` 操作 29 | 30 | (我可能错过了至少一个)大多数这些将显得很明显,但前3行不那么明显: 31 | 32 | - `KEYS` / `SCAN` 只列出当前服务器上的键; 而不是更广泛的逻辑数据库 33 | - `FLUSHDB` / `FLUSHALL` 只删除当前服务器上的密钥;而不是更广泛的逻辑数据库 34 | - `RANDOMKEY` 仅选择当前服务器上的密钥; 而不是更广泛的逻辑数据库 35 | 36 | 实际上,StackExchange.Redis 通过简单地随机选择目标服务器来欺骗 `IDatabase` API上的 `RANDOMKEY`,但这对其他服务器是不可能的。 37 | 38 | 那么如何使用它们呢? 39 | --- 40 | 41 | 最简单的:从服务器开始,而不是数据库。 42 | 43 | ```C# 44 | // get the target server 45 | var server = conn.GetServer(someServer); 46 | 47 | // show all keys in database 0 that include "foo" in their name 48 | foreach(var key in server.Keys(pattern: "*foo*")) { 49 | Console.WriteLine(key); 50 | } 51 | 52 | // completely wipe ALL keys from database 0 53 | server.FlushDatabase(); 54 | ``` 55 | 56 | 注意,与 `IDatabase` API(在 `GetDatabase()` 调用中已经选择了的目标数据库)不同,这些方法对数据库使用可选参数,或者默认为`0`。 57 | 58 | `Keys(...)` 方法值得特别一提:它并不常见,因为它没有一个 `*Async` 对应。 这样做的原因是,在后台,系统将确定使用最合适的方法(基于服务器版本的 `KEYS` VS `SCAN`),如果可能的话,将使用 `SCAN` 方法 一个 `IEnumerable` 在内部执行所有的分页 - 所以你永远不需要看到游标操作的实现细节。 59 | 如果 `SCAN` 不可用,它将使用 `KEYS`,这可能导致服务器上的阻塞。 无论哪种方式,`SCAN` 和 `KEYS` 都需要扫描整个键空间,所以在生产服务器上应该避免 - 或者至少是针对从节点服务器。 60 | 61 | 所以我需要记住我连接到哪个服务器? 这真糟糕! 62 | --- 63 | 64 | 不,不完全是。 你可以使用 `conn.GetEndPoints()` 来列出节点(所有已知的节点,或者在原始配置中指定的节点,这些不一定是相同的东西),并且使用 `GetServer()` 迭代找到想要的服务器(例如,选择一个从节点)。 65 | 66 | [查看原文](https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/KeysScan.md) 67 | --- 68 | 69 | [返回主页](./README.md) 70 | --- -------------------------------------------------------------------------------- /KeysValues.md: -------------------------------------------------------------------------------- 1 | 键,值和通道 2 | === 3 | 4 | 在处理redis时,是键不是键之间有很重要的区别。 5 | 键是数据库中数据片段(可以是String,List,Hash或任何其他[redis数据类型](http://redis.io/topics/data-types))的独一无二的名称。 6 | 键永远不会被解释为...好吧,任何东西:它们只是惰性名称。 7 | 此外,当处理集群或分片系统时,它是定义包含此数据的节点(或者如果有从节点的节点)的关键 - 因此键对于路由命令是至关重要的。 8 | 9 | 这与 *值* 形成对比; 值是单独(对于字符串数据)或分组对键的*内容存储* 。 10 | 值不影响命令路由 (注意:使用 SORT 命令时除非指定 BY 或者 GET,否则是很难解释的) 11 | 12 | 同样,为了操作的目的,值通常被redis翻译为: 13 | 14 | - `incr` (和各种类似的命令)将String值转换为数值数据 15 | - 排序可以使用数字或unicode规则解释值 16 | - 和许多其他操作 17 | 18 | 关键是使用API需要理解什么是键,什么是值。 19 | 这反映在StackExchange.Redis API中,但是好消息是,**大部分时间**你根本不需要知道这一点。 20 | 21 | 当使用 发布/订阅 时,我们处理 *channels* ; channel 不会影响路由(因此它们不是密钥),但与常规值非常不同,因此要单考虑。 22 | 23 | 键 24 | --- 25 | 26 | StackExchange.Redis 通过 `RedisKey` 类型表示键。 27 | 好消息是,可以从 `string` 和 `byte[]` 的隐式转换,允许使用文本和二进制密钥,没有任何复杂性。 28 | 29 | 例如,`StringIncrement` 方法使用一个 `RedisKey` 作为第一个参数,但是*你不需要知道* ; 30 | 31 | 举个例子: 32 | 33 | ```C# 34 | string key = ... 35 | db.StringIncrement(key); 36 | ``` 37 | 38 | or 39 | 40 | ```C# 41 | byte[] key = ... 42 | db.StringIncrement(key); 43 | ``` 44 | 45 | 同样,有一些操作*返回* 键为 `RedisKey` - 再次,它依然可以自动隐式转换: 46 | 47 | ```C# 48 | string someKey = db.KeyRandom(); 49 | ``` 50 | 51 | 值 52 | --- 53 | 54 | StackExchange.Redis 用 `RedisValue` 类型表示值。 与 `RedisKey` 一样,存在隐式转换,这意味着大多数时候你从来没有看到这种类型,例如: 55 | 56 | ```C# 57 | db.StringSet("mykey", "myvalue"); 58 | ``` 59 | 60 | 然而,除了文本和二进制内容,值还可能需要表示类型化的原始数据 - 最常见的(在.NET术语中)`Int32`,`Int64`,`Double`或`Boolean`。 因此,`RedisValue`提供了比 `RedisKey` 更多的转换支持: 61 | 62 | ```C# 63 | db.StringSet("mykey", 123); // this is still a RedisKey and RedisValue 64 | ... 65 | int i = (int)db.StringGet("mykey"); 66 | ``` 67 | 68 | 请注意,虽然从基元类型到 `RedisValue` 的转换是隐式的,但是从 `RedisValue` 到基元类型的许多转换是显式的:这是因为如果数据没有合适的值,这些转换很可能会失败。 69 | 70 | 另外注意,*当做数字* 处理时,redis将不存在的键视为零; 为了与此一致,将空响应视为零: 71 | 72 | ```C# 73 | db.KeyDelete("abc"); 74 | int i = (int)db.StringGet("abc"); // this is ZERO 75 | ``` 76 | 77 | 如果您需要检测空状态,那么你就可以这样检查: 78 | 79 | ```C# 80 | db.KeyDelete("abc"); 81 | var value = db.StringGet("abc"); 82 | bool isNil = value.IsNull; // this is true 83 | ``` 84 | 85 | 或者更简单地,只是使用提供的 `Nullable ` 支持: 86 | 87 | ```C# 88 | db.KeyDelete("abc"); 89 | var value = (int?)db.StringGet("abc"); // behaves as you would expect 90 | ``` 91 | 92 | 哈希 93 | --- 94 | 95 | 由于哈希中的字段名称不影响命令路由,它们不是键,但可以接受文本和二进制名称, 因此它们被视为用于API目的的值。 96 | 97 | 通道 98 | --- 99 | 100 | 发布/订阅 的通道名称由 `RedisChannel` 类型表示; 这与 `RedisKey` 大体相同,但是是独立处理的,因为虽然通道名是正当的第一类元素,但它们不影响命令路由。 101 | 102 | 脚本 103 | --- 104 | 105 | [redis中的脚本](http://redis.io/commands/EVAL) 有两项显著的特性: 106 | 107 | - 输入必须保持键和值分离(在脚本内部分别成为 `KEYS` 和 `ARGV`) 108 | - 返回格式未预先定义:这将特定于您的脚本 109 | 110 | 正因为如此,`ScriptEvaluate` 方法接受两个独立的输入数组:一个用于键的 `RedisKey []`,一个用于值的 `RedisValue []` (两者都是可选的,如果省略则假定为空)。 这可能是你实际需要在代码中键入 `RedisKey` 或 `RedisValue` 的少数几次之一,这只是因为数组变动规则: 111 | 112 | ```C# 113 | var result = db.ScriptEvaluate(TransferScript, 114 | new RedisKey[] { from, to }, new RedisValue[] { quantity }); 115 | ``` 116 | 117 | (其中 `TransferScript` 是一些包含Lua的 `string`,在这个例子中没有显示) 118 | 119 | 响应使用 `RedisResult` 类型(这是脚本专用的;通常API尝试尽可能直接清晰地表示响应)。 和前面一样, `RedisResult` 提供了一系列转换操作 - 实际上比 `RedisValue` 更多,因为除了可以转换为文本,二进制,一些基元类型和可空元素,响应*也*可以转换为 *数组* ,例如: 120 | 121 | ```C# 122 | string[] items = db.ScriptEvaluate(...); 123 | ``` 124 | 125 | 结论 126 | --- 127 | 128 | API中使用的类型是非常故意选择的,以区分redis *keys* 和 *values*。 然而,在几乎所有情况下,您不需要直接去参考所涉及的底层类型,因为提供了转换操作。 129 | 130 | [查看原文](https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/KeysValues.md) 131 | --- 132 | 133 | [返回主页](./README.md) 134 | --- -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More_considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution 4.0 International Public License 58 | 59 | By exercising the Licensed Rights (defined below), You accept and agree 60 | to be bound by the terms and conditions of this Creative Commons 61 | Attribution 4.0 International Public License ("Public License"). To the 62 | extent this Public License may be interpreted as a contract, You are 63 | granted the Licensed Rights in consideration of Your acceptance of 64 | these terms and conditions, and the Licensor grants You such rights in 65 | consideration of benefits the Licensor receives from making the 66 | Licensed Material available under these terms and conditions. 67 | 68 | 69 | Section 1 -- Definitions. 70 | 71 | a. Adapted Material means material subject to Copyright and Similar 72 | Rights that is derived from or based upon the Licensed Material 73 | and in which the Licensed Material is translated, altered, 74 | arranged, transformed, or otherwise modified in a manner requiring 75 | permission under the Copyright and Similar Rights held by the 76 | Licensor. For purposes of this Public License, where the Licensed 77 | Material is a musical work, performance, or sound recording, 78 | Adapted Material is always produced where the Licensed Material is 79 | synched in timed relation with a moving image. 80 | 81 | b. Adapter's License means the license You apply to Your Copyright 82 | and Similar Rights in Your contributions to Adapted Material in 83 | accordance with the terms and conditions of this Public License. 84 | 85 | c. Copyright and Similar Rights means copyright and/or similar rights 86 | closely related to copyright including, without limitation, 87 | performance, broadcast, sound recording, and Sui Generis Database 88 | Rights, without regard to how the rights are labeled or 89 | categorized. For purposes of this Public License, the rights 90 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 91 | Rights. 92 | 93 | d. Effective Technological Measures means those measures that, in the 94 | absence of proper authority, may not be circumvented under laws 95 | fulfilling obligations under Article 11 of the WIPO Copyright 96 | Treaty adopted on December 20, 1996, and/or similar international 97 | agreements. 98 | 99 | e. Exceptions and Limitations means fair use, fair dealing, and/or 100 | any other exception or limitation to Copyright and Similar Rights 101 | that applies to Your use of the Licensed Material. 102 | 103 | f. Licensed Material means the artistic or literary work, database, 104 | or other material to which the Licensor applied this Public 105 | License. 106 | 107 | g. Licensed Rights means the rights granted to You subject to the 108 | terms and conditions of this Public License, which are limited to 109 | all Copyright and Similar Rights that apply to Your use of the 110 | Licensed Material and that the Licensor has authority to license. 111 | 112 | h. Licensor means the individual(s) or entity(ies) granting rights 113 | under this Public License. 114 | 115 | i. Share means to provide material to the public by any means or 116 | process that requires permission under the Licensed Rights, such 117 | as reproduction, public display, public performance, distribution, 118 | dissemination, communication, or importation, and to make material 119 | available to the public including in ways that members of the 120 | public may access the material from a place and at a time 121 | individually chosen by them. 122 | 123 | j. Sui Generis Database Rights means rights other than copyright 124 | resulting from Directive 96/9/EC of the European Parliament and of 125 | the Council of 11 March 1996 on the legal protection of databases, 126 | as amended and/or succeeded, as well as other essentially 127 | equivalent rights anywhere in the world. 128 | 129 | k. You means the individual or entity exercising the Licensed Rights 130 | under this Public License. Your has a corresponding meaning. 131 | 132 | 133 | Section 2 -- Scope. 134 | 135 | a. License grant. 136 | 137 | 1. Subject to the terms and conditions of this Public License, 138 | the Licensor hereby grants You a worldwide, royalty-free, 139 | non-sublicensable, non-exclusive, irrevocable license to 140 | exercise the Licensed Rights in the Licensed Material to: 141 | 142 | a. reproduce and Share the Licensed Material, in whole or 143 | in part; and 144 | 145 | b. produce, reproduce, and Share Adapted Material. 146 | 147 | 2. Exceptions and Limitations. For the avoidance of doubt, where 148 | Exceptions and Limitations apply to Your use, this Public 149 | License does not apply, and You do not need to comply with 150 | its terms and conditions. 151 | 152 | 3. Term. The term of this Public License is specified in Section 153 | 6(a). 154 | 155 | 4. Media and formats; technical modifications allowed. The 156 | Licensor authorizes You to exercise the Licensed Rights in 157 | all media and formats whether now known or hereafter created, 158 | and to make technical modifications necessary to do so. The 159 | Licensor waives and/or agrees not to assert any right or 160 | authority to forbid You from making technical modifications 161 | necessary to exercise the Licensed Rights, including 162 | technical modifications necessary to circumvent Effective 163 | Technological Measures. For purposes of this Public License, 164 | simply making modifications authorized by this Section 2(a) 165 | (4) never produces Adapted Material. 166 | 167 | 5. Downstream recipients. 168 | 169 | a. Offer from the Licensor -- Licensed Material. Every 170 | recipient of the Licensed Material automatically 171 | receives an offer from the Licensor to exercise the 172 | Licensed Rights under the terms and conditions of this 173 | Public License. 174 | 175 | b. No downstream restrictions. You may not offer or impose 176 | any additional or different terms or conditions on, or 177 | apply any Effective Technological Measures to, the 178 | Licensed Material if doing so restricts exercise of the 179 | Licensed Rights by any recipient of the Licensed 180 | Material. 181 | 182 | 6. No endorsement. Nothing in this Public License constitutes or 183 | may be construed as permission to assert or imply that You 184 | are, or that Your use of the Licensed Material is, connected 185 | with, or sponsored, endorsed, or granted official status by, 186 | the Licensor or others designated to receive attribution as 187 | provided in Section 3(a)(1)(A)(i). 188 | 189 | b. Other rights. 190 | 191 | 1. Moral rights, such as the right of integrity, are not 192 | licensed under this Public License, nor are publicity, 193 | privacy, and/or other similar personality rights; however, to 194 | the extent possible, the Licensor waives and/or agrees not to 195 | assert any such rights held by the Licensor to the limited 196 | extent necessary to allow You to exercise the Licensed 197 | Rights, but not otherwise. 198 | 199 | 2. Patent and trademark rights are not licensed under this 200 | Public License. 201 | 202 | 3. To the extent possible, the Licensor waives any right to 203 | collect royalties from You for the exercise of the Licensed 204 | Rights, whether directly or through a collecting society 205 | under any voluntary or waivable statutory or compulsory 206 | licensing scheme. In all other cases the Licensor expressly 207 | reserves any right to collect such royalties. 208 | 209 | 210 | Section 3 -- License Conditions. 211 | 212 | Your exercise of the Licensed Rights is expressly made subject to the 213 | following conditions. 214 | 215 | a. Attribution. 216 | 217 | 1. If You Share the Licensed Material (including in modified 218 | form), You must: 219 | 220 | a. retain the following if it is supplied by the Licensor 221 | with the Licensed Material: 222 | 223 | i. identification of the creator(s) of the Licensed 224 | Material and any others designated to receive 225 | attribution, in any reasonable manner requested by 226 | the Licensor (including by pseudonym if 227 | designated); 228 | 229 | ii. a copyright notice; 230 | 231 | iii. a notice that refers to this Public License; 232 | 233 | iv. a notice that refers to the disclaimer of 234 | warranties; 235 | 236 | v. a URI or hyperlink to the Licensed Material to the 237 | extent reasonably practicable; 238 | 239 | b. indicate if You modified the Licensed Material and 240 | retain an indication of any previous modifications; and 241 | 242 | c. indicate the Licensed Material is licensed under this 243 | Public License, and include the text of, or the URI or 244 | hyperlink to, this Public License. 245 | 246 | 2. You may satisfy the conditions in Section 3(a)(1) in any 247 | reasonable manner based on the medium, means, and context in 248 | which You Share the Licensed Material. For example, it may be 249 | reasonable to satisfy the conditions by providing a URI or 250 | hyperlink to a resource that includes the required 251 | information. 252 | 253 | 3. If requested by the Licensor, You must remove any of the 254 | information required by Section 3(a)(1)(A) to the extent 255 | reasonably practicable. 256 | 257 | 4. If You Share Adapted Material You produce, the Adapter's 258 | License You apply must not prevent recipients of the Adapted 259 | Material from complying with this Public License. 260 | 261 | 262 | Section 4 -- Sui Generis Database Rights. 263 | 264 | Where the Licensed Rights include Sui Generis Database Rights that 265 | apply to Your use of the Licensed Material: 266 | 267 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 268 | to extract, reuse, reproduce, and Share all or a substantial 269 | portion of the contents of the database; 270 | 271 | b. if You include all or a substantial portion of the database 272 | contents in a database in which You have Sui Generis Database 273 | Rights, then the database in which You have Sui Generis Database 274 | Rights (but not its individual contents) is Adapted Material; and 275 | 276 | c. You must comply with the conditions in Section 3(a) if You Share 277 | all or a substantial portion of the contents of the database. 278 | 279 | For the avoidance of doubt, this Section 4 supplements and does not 280 | replace Your obligations under this Public License where the Licensed 281 | Rights include other Copyright and Similar Rights. 282 | 283 | 284 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 285 | 286 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 287 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 288 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 289 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 290 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 291 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 292 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 293 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 294 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 295 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 296 | 297 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 298 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 299 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 300 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 301 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 302 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 303 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 304 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 305 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 306 | 307 | c. The disclaimer of warranties and limitation of liability provided 308 | above shall be interpreted in a manner that, to the extent 309 | possible, most closely approximates an absolute disclaimer and 310 | waiver of all liability. 311 | 312 | 313 | Section 6 -- Term and Termination. 314 | 315 | a. This Public License applies for the term of the Copyright and 316 | Similar Rights licensed here. However, if You fail to comply with 317 | this Public License, then Your rights under this Public License 318 | terminate automatically. 319 | 320 | b. Where Your right to use the Licensed Material has terminated under 321 | Section 6(a), it reinstates: 322 | 323 | 1. automatically as of the date the violation is cured, provided 324 | it is cured within 30 days of Your discovery of the 325 | violation; or 326 | 327 | 2. upon express reinstatement by the Licensor. 328 | 329 | For the avoidance of doubt, this Section 6(b) does not affect any 330 | right the Licensor may have to seek remedies for Your violations 331 | of this Public License. 332 | 333 | c. For the avoidance of doubt, the Licensor may also offer the 334 | Licensed Material under separate terms or conditions or stop 335 | distributing the Licensed Material at any time; however, doing so 336 | will not terminate this Public License. 337 | 338 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 339 | License. 340 | 341 | 342 | Section 7 -- Other Terms and Conditions. 343 | 344 | a. The Licensor shall not be bound by any additional or different 345 | terms or conditions communicated by You unless expressly agreed. 346 | 347 | b. Any arrangements, understandings, or agreements regarding the 348 | Licensed Material not stated herein are separate from and 349 | independent of the terms and conditions of this Public License. 350 | 351 | 352 | Section 8 -- Interpretation. 353 | 354 | a. For the avoidance of doubt, this Public License does not, and 355 | shall not be interpreted to, reduce, limit, restrict, or impose 356 | conditions on any use of the Licensed Material that could lawfully 357 | be made without permission under this Public License. 358 | 359 | b. To the extent possible, if any provision of this Public License is 360 | deemed unenforceable, it shall be automatically reformed to the 361 | minimum extent necessary to make it enforceable. If the provision 362 | cannot be reformed, it shall be severed from this Public License 363 | without affecting the enforceability of the remaining terms and 364 | conditions. 365 | 366 | c. No term or condition of this Public License will be waived and no 367 | failure to comply consented to unless expressly agreed to by the 368 | Licensor. 369 | 370 | d. Nothing in this Public License constitutes or may be interpreted 371 | as a limitation upon, or waiver of, any privileges and immunities 372 | that apply to the Licensor or You, including from the legal 373 | processes of any jurisdiction or authority. 374 | 375 | 376 | ======================================================================= 377 | 378 | Creative Commons is not a party to its public 379 | licenses. Notwithstanding, Creative Commons may elect to apply one of 380 | its public licenses to material it publishes and in those instances 381 | will be considered the “Licensor.” The text of the Creative Commons 382 | public licenses is dedicated to the public domain under the CC0 Public 383 | Domain Dedication. Except for the limited purpose of indicating that 384 | material is shared under a Creative Commons public license or as 385 | otherwise permitted by the Creative Commons policies published at 386 | creativecommons.org/policies, Creative Commons does not authorize the 387 | use of the trademark "Creative Commons" or any other trademark or logo 388 | of Creative Commons without its prior written consent including, 389 | without limitation, in connection with any unauthorized modifications 390 | to any of its public licenses or any other arrangements, 391 | understandings, or agreements concerning use of licensed material. For 392 | the avoidance of doubt, this paragraph does not form part of the 393 | public licenses. 394 | 395 | Creative Commons may be contacted at creativecommons.org. -------------------------------------------------------------------------------- /PipelinesMultiplexers.md: -------------------------------------------------------------------------------- 1 | 管道和链接复用 2 | === 3 | 4 | 延迟严重。现代计算机可以以惊人的速度搅动数据,并且高速网络(通常具有在重要服务器之间的多个并行链路)提供巨大的带宽,但是... 该延迟意味着计算机花费大量的时间*等待数据* 和 这是基于连续的编程越来越受欢迎的几个原因之一。 5 | 6 | 让我们考虑一些常规的程序代码: 7 | 8 | ```C# 9 | string a = db.StringGet("a"); 10 | string b = db.StringGet("b"); 11 | ``` 12 | 13 | 在涉及的步骤方面,这看起来大致是这样: 14 | 15 | [req1] # client: the client library constructs request 1 16 | [c=>s] # network: request one is sent to the server 17 | [server] # server: the server processes request 1 18 | [s=>c] # network: response one is sent back to the client 19 | [resp1] # client: the client library parses response 1 20 | [req2] 21 | [c=>s] 22 | [server] 23 | [s=>c] 24 | [resp2] 25 | 26 | 现在让我们突出*客户端正在做的一些事的时间点*: 27 | 28 | [req1] 29 | [====waiting=====] 30 | [resp1] 31 | [req2] 32 | [====waiting=====] 33 | [resp2] 34 | 35 | 请记住,这是**不成比例的** - 如果这是按时间缩放,它将是完全由 `waiting` 控制的。 36 | 37 | 管道线 38 | --- 39 | 40 | 因此,许多redis客户端允许你使用 *pipelining*, 这是在管道上发送多个消息而不等待来自每个的答复的过程 - 并且(通常)稍后当它们进入时处理答复。 41 | 在 .NET 中,可以启动但尚未完成,并且可能以后完成或故障的由TPL封装的操作可以由 [TPL][1] 通过 [`Task`][2] / [`Task `][3] API 来实现。 42 | 本质上, `Task` 表示 " `T` 类型未来可能的值"(非泛型的 `Task` 本质尚是 `Task` )。你可以使用任意一种用法: 43 | 44 | - 在稍后的代码块等待直到操作完成(`.Wait()`) 45 | - 当操作完成时,调度一个后续操作(`.ContinueWith(...)` 或 `await`) 46 | 47 | 例如,要使用过程化(阻塞)代码来借助管道传递这两个 get 操作,我们可以使用: 48 | 49 | ```C# 50 | var aPending = db.StringGetAsync("a"); 51 | var bPending = db.StringGetAsync("b"); 52 | var a = db.Wait(aPending); 53 | var b = db.Wait(bPending); 54 | ``` 55 | 56 | 注意,我在这里使用`db.Wait`,因为它会自动应用配置的同步超时,但如果你喜欢你也可以使用 `aPending.Wait()` 或 `Task.WaitAll(aPending,bPending);`。 57 | 使用管道技术,我们可以立即将这两个请求发送到网络,从而消除大部分延迟。 58 | 此外,它还有助于减少数据包碎片:单独发送(等待每个响应)的20个请求将需要至少20个数据包,但是在管道中发送的20个请求可以通过少得多的数据包(也许只有一个)。 59 | 60 | 执行后不理 61 | --- 62 | 63 | 管道的一个特例是当我们明确地不关心来自特定操作的响应时,这允许我们的代码在排队操作在后台继续时立即继续。 通常,这意味着我们可以在单个调用者的连接上并发工作。 这是使用 `flags` 参数来实现的: 64 | 65 | ```C# 66 | // sliding expiration 67 | db.KeyExpire(key, TimeSpan.FromMinutes(5), flags: CommandFlags.FireAndForget); 68 | var value = (string)db.StringGet(key); 69 | ``` 70 | 71 | `FireAndForget` 标志使客户端库正常地排队工作,但立即返回一个默认值(因为 `KeyExpire` 返回一个 `bool` ,这将返回 `false` ,因为 `default(bool)` 是 `false` - 但是返回值是无意义的,应该忽略)。 72 | 这也适用于 `*Async` 方法:一个已经完成的 `Task ` 返回默认值(或者为 `void` 方法返回一个已经完成的 `Task` )。 73 | 74 | 复用链接 75 | --- 76 | 77 | 管道是很好的,但是通常任何单个代码块只需要一个值(或者可能想要执行几个操作,但是依赖于彼此)。 78 | 这意味着我们仍然有一个问题,我们花大部分时间等待数据在客户端和服务器之间传输。 79 | 现在考虑一个繁忙的应用程序,也许是一个Web服务器。 80 | 这样的应用程序通常是并发的,所以如果你有20个并行应用程序请求都需要数据,你可能会想到旋转20个连接,或者你可以同步访问单个连接(这意味着最后一个调用者需要等待 延迟的所有其他19之前,甚至开始)。 或者折中一下,也许一个5个连接的租赁池 - 无论你怎么做,都会有很多的等待。 81 | **StackExchange.Redis不做这个**; 相反,它做 *很多* 的工作,使你有效地利用所有这个空闲时间*复用* 一个连接。 82 | 当不同的调用者同时使用它时,它**自动把这些单独的请求加入管道**,所以不管请求使用阻塞还是异步访问,工作都是按进入管道的顺序处理的。 83 | 因此,我们可能有10或20个的“get a 和 b”包括此前的(从不同的应用程序请求)情景中,并且他们都将尽快到达连接。 84 | 基本上,它用完成其他调用者的工作的时间来填充 `waiting` 时间。 85 | 86 | 因此,StackExchange.Redis不提供的唯一redis特性(*不会提供*)是“阻塞弹出”([BLPOP](http://redis.io/commands/blpop),[BRPOP](http://redis.io/commands/brpop) 和 [BRPOPLPUSH](http://redis.io/commands/brpoplpush)),因为这将允许单个调用者停止整个多路复用器,阻止所有其他调用者 。 87 | StackExchange.Redis 需要保持工作的唯一其他时间是在验证事务的前提条件时,这就是为什么 StackExchange.Redis 将这些条件封装到内部管理的 `condition` 实例中。 88 | 89 | [在这里阅读更多关于事务](https://github.com/StackExchange/StackExchange.Redis/blob/master/Docs/Transactions.md)。 90 | 如果你觉得你想“阻止出栈”,那么我强烈建议你考虑 发布 / 订阅 代替: 91 | 92 | ```C# 93 | sub.Subscribe(channel, delegate { 94 | string work = db.ListRightPop(key); 95 | if (work != null) Process(work); 96 | }); 97 | //... 98 | db.ListLeftPush(key, newWork, flags: CommandFlags.FireAndForget); 99 | sub.Publish(channel, ""); 100 | ``` 101 | 102 | 这实现了相同的目的,而不需要阻塞操作。 注意: 103 | 104 | - *数据* 不通过 发布 / 订阅 发送; 发布 / 订阅 API只用于通知处理器检查更多的工作 105 | - 如果没有处理器,则新项目保持缓存在列表中; 工作不会丢失 106 | - 只有一个处理器可以弹出单个值; 当消费者比生产者多时,一些消费者会被通知,然后发现没有什么可做的 107 | - 当你重新启动一个处理器,你应该*假设* 有工作,以便你处理任何积压的任务 108 | - 但除此之外,语义与阻止出栈相同 109 | 110 | StackExchange.Redis 的多路复用特性使得在使用常规的不复杂代码的同时在单个连接上达到极高的吞吐量成为可能。 111 | 112 | 并发 113 | --- 114 | 115 | 应当注意,管道/链接复用器/未来值 方法对于基于连续的异步代码也很好地起作用;例如你可以写: 116 | 117 | ```C# 118 | string value = await db.StringGetAsync(key); 119 | if (value == null) { 120 | value = await ComputeValueFromDatabase(...); 121 | db.StringSet(key, value, flags: CommandFlags.FireAndForget); 122 | } 123 | return value; 124 | ``` 125 | 126 | [查看原文](https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/PipelinesMultiplexers.md) 127 | --- 128 | 129 | [返回主页](./README.md) 130 | --- 131 | 132 | [1]: http://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx 133 | [2]: http://msdn.microsoft.com/en-us/library/system.threading.tasks.task(v=vs.110).aspx 134 | [3]: http://msdn.microsoft.com/en-us/library/dd321424(v=vs.110).aspx -------------------------------------------------------------------------------- /Profiling.md: -------------------------------------------------------------------------------- 1 | 性能分析 2 | === 3 | 4 | 性能分析的 API 发生了很大变化从 `1.*` 到 `2.*` 版本。 5 | 6 | 在 `1.*` 版本,特别是 `object GetContext()` API 对于消费者来说是不直观的,对于类库而言是昂贵的(由于 book-keeping)。 7 | 在 `2.*` 版本,这个 API 更加简单,更加 “明显”。 8 | 9 | 这是一个巨大变化。 10 | 11 | [1.\* 性能分析](./Profiling_v1.md) 12 | 13 | [2.\* 性能分析](./Profiling_v2.md) 14 | 15 | [查看原文](https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/Profiling.md) 16 | --- 17 | 18 | [返回主页](./README.md) 19 | --- -------------------------------------------------------------------------------- /Profiling_v1.md: -------------------------------------------------------------------------------- 1 | 性能分析 2 | === 3 | 4 | StackExchange.Redis公开了一些方法和类型来启用性能分析。 由于其异步和多路复用 5 | 表现分析是一个有点复杂的主题。 6 | 7 | 接口 8 | --- 9 | 10 | 分析接口由 `IProfiler`, `ConnectionMultiplexer.RegisterProfiler(IProfiler)` ,`ConnectionMultiplexer.BeginProfiling(object)` , 11 | `ConnectionMultiplexer.FinishProfiling(object)` 和 `IProfiledCommand` 组合而成。 12 | 13 | 你可以用一个 `ConnectionMultiplexer` 实例注册一个 `IProfiler` ,它不能被改变。你可以通过调用 `BeginProfiling(object)` 来开始分析一个给定的上下文对象(例如,线程,Http请求等等),调用 `FinishProfiling(object)` 来完成。 14 | `FinishProfiling(object)` 返回一个 `IProfiledCommand` 集合的对象,它包含了通过 `(Begin|Finish)Profiling` 调用和给定上下文配置好的 `ConnectionMultiplexer` 对象发送到 redis 的所有命令的时间信息。 15 | 16 | 应该使用什么“上下文”对象是应用程序来确定的。 17 | 18 | 可用时间 19 | --- 20 | 21 | StackExchange.Redis显示有关以下内容的信息: 22 | 23 | - 涉及到的redis服务器 24 | - 正在查询的redis数据库 25 | - redis命令运行 26 | - 用于路由命令的标志 27 | - 命令的初始创建时间 28 | - 使命令进入队列所需的时间 29 | - 命令入队后,发送命令需要多长时间 30 | - 发送命令后,从redis接收响应需要多长时间 31 | - 收到响应后处理响应所需的时间 32 | - 如果命令是响应集群 ASK 或 MOVED 响应而发送的 33 | - 如果是,原始命令是什么 34 | 35 | `TimeSpan` 有较高的精度,如果运行时支持。 `DateTime` 准确度如 `DateTime.UtcNow`。 36 | 37 | 选择上下文 38 | --- 39 | 40 | 由于StackExchange.Redis的异步接口,分析需要外部协助将相关的命令组合在一起。 这是 41 | 通过提供上下文对象,当你开始和结束profiling(通过 `BeginProfiling(object)` & `FinishProfiling(object)` 方法),当一个命令被发送(通过 `IProfiler` 接口的 `GetContext()` 方法)实现的。 42 | 43 | 一个将许多不同线程发出的命令关联在一起的 toy 示例: 44 | 45 | ``` csharp 46 | class ToyProfiler : IProfiler 47 | { 48 | public ConcurrentDictionary Contexts = new ConcurrentDictionary(); 49 | 50 | public object GetContext() 51 | { 52 | object ctx; 53 | if(!Contexts.TryGetValue(Thread.CurrentThread, out ctx)) ctx = null; 54 | 55 | return ctx; 56 | } 57 | } 58 | 59 | // ... 60 | 61 | ConnectionMultiplexer conn = /* initialization */; 62 | var profiler = new ToyProfiler(); 63 | var thisGroupContext = new object(); 64 | 65 | conn.RegisterProfiler(profiler); 66 | 67 | var threads = new List(); 68 | 69 | for (var i = 0; i < 16; i++) 70 | { 71 | var db = conn.GetDatabase(i); 72 | 73 | var thread = 74 | new Thread( 75 | delegate() 76 | { 77 | var threadTasks = new List(); 78 | 79 | for (var j = 0; j < 1000; j++) 80 | { 81 | var task = db.StringSetAsync("" + j, "" + j); 82 | threadTasks.Add(task); 83 | } 84 | 85 | Task.WaitAll(threadTasks.ToArray()); 86 | } 87 | ); 88 | 89 | profiler.Contexts[thread] = thisGroupContext; 90 | 91 | threads.Add(thread); 92 | } 93 | 94 | conn.BeginProfiling(thisGroupContext); 95 | 96 | threads.ForEach(thread => thread.Start()); 97 | threads.ForEach(thread => thread.Join()); 98 | 99 | IEnumerable timings = conn.FinishProfiling(thisGroupContext); 100 | ``` 101 | 102 | 最后,`timings` 将包含16,000个 `IProfiledCommand` 对象 - 每个发送给redis的命令对应一个对象。 103 | 104 | 如果相反,你像下面这样做: 105 | 106 | ``` csharp 107 | ConnectionMultiplexer conn = /* initialization */; 108 | var profiler = new ToyProfiler(); 109 | 110 | conn.RegisterProfiler(profiler); 111 | 112 | var threads = new List(); 113 | 114 | var perThreadTimings = new ConcurrentDictionary>(); 115 | 116 | for (var i = 0; i < 16; i++) 117 | { 118 | var db = conn.GetDatabase(i); 119 | 120 | var thread = 121 | new Thread( 122 | delegate() 123 | { 124 | var threadTasks = new List(); 125 | 126 | conn.BeginProfiling(Thread.CurrentThread); 127 | 128 | for (var j = 0; j < 1000; j++) 129 | { 130 | var task = db.StringSetAsync("" + j, "" + j); 131 | threadTasks.Add(task); 132 | } 133 | 134 | Task.WaitAll(threadTasks.ToArray()); 135 | 136 | perThreadTimings[Thread.CurrentThread] = conn.FinishProfiling(Thread.CurrentThread).ToList(); 137 | } 138 | ); 139 | 140 | profiler.Contexts[thread] = thread; 141 | 142 | threads.Add(thread); 143 | } 144 | 145 | threads.ForEach(thread => thread.Start()); 146 | threads.ForEach(thread => thread.Join()); 147 | ``` 148 | 149 | `perThreadTimings` 最终会有1000个 `IProfilingCommand` 的16个,键由 `Thread` 发出。 150 | 151 | 不再看玩具示例,这里是如何在一个MVC5应用程序中配置 StackExchange.Redis。 152 | 153 | 首先针对你的 `ConnectionMultiplexer` 对象注册以下 `IProfiler`: 154 | 155 | ``` csharp 156 | public class RedisProfiler : IProfiler 157 | { 158 | const string RequestContextKey = "RequestProfilingContext"; 159 | 160 | public object GetContext() 161 | { 162 | var ctx = HttpContext.Current; 163 | if (ctx == null) return null; 164 | 165 | return ctx.Items[RequestContextKey]; 166 | } 167 | 168 | public object CreateContextForCurrentRequest() 169 | { 170 | var ctx = HttpContext.Current; 171 | if (ctx == null) return null; 172 | 173 | object ret; 174 | ctx.Items[RequestContextKey] = ret = new object(); 175 | 176 | return ret; 177 | } 178 | } 179 | ``` 180 | 181 | 然后,将以下内容添加到 Global.asax.cs 文件: 182 | 183 | ``` csharp 184 | protected void Application_BeginRequest() 185 | { 186 | var ctxObj = RedisProfiler.CreateContextForCurrentRequest(); 187 | if (ctxObj != null) 188 | { 189 | RedisConnection.BeginProfiling(ctxObj); 190 | } 191 | } 192 | 193 | protected void Application_EndRequest() 194 | { 195 | var ctxObj = RedisProfiler.GetContext(); 196 | if (ctxObj != null) 197 | { 198 | var timings = RedisConnection.FinishProfiling(ctxObj); 199 | 200 | // do what you will with `timings` here 201 | } 202 | } 203 | ``` 204 | 205 | 这个实现将所有redis命令(包括 `async / await` -ed 命令)与触发它们的http请求分组。 206 | 207 | [查看原文](https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/Profiling_v1.md) 208 | --- 209 | 210 | [返回主页](./README.md) 211 | --- -------------------------------------------------------------------------------- /Profiling_v2.md: -------------------------------------------------------------------------------- 1 | 性能分析 2 | === 3 | 4 | StackExchange.Redis公开了一些方法和类型来启用性能分析。 由于其异步和多路复用 5 | 表现分析是一个有点复杂的主题。 6 | 7 | 接口 8 | --- 9 | 10 | 分析接口由 `ProfilingSession`, `ConnectionMultiplexer.RegisterProfiler(Func)`, 11 | `ProfilingSession.FinishProfiling()`, 和 `IProfiledCommand` 组合而成。 12 | 13 | 你可以用一个 `ConnectionMultiplexer` 实例注册一个会提供一个相关的 `ProfilingSession` 的回调 (`Func`)。当需要时,类库会执行这个回调, 14 | 并且 `如果` 返回的不是一个空的 session: 操作会附加到这个 session 上, 调用某个特定的 session 的 `FinishProfiling` 会返回一个包含了所有发送给 `ConnectionMultiplexer` 配置的 redis 服务器要执行的命令相关时间信息的 `IProfiledCommand` 集合。 15 | 主要是由 callback 来维护追踪的独立的 session 需要的状态。 16 | 17 | 可用时间 18 | --- 19 | 20 | StackExchange.Redis显示有关以下内容的信息: 21 | 22 | - 涉及到的redis服务器 23 | - 正在查询的redis数据库 24 | - redis 运行命令 25 | - 用于路由命令的标志 26 | - 命令的初始创建时间 27 | - 使命令进入队列所需的时间 28 | - 命令入队后,发送命令需要多长时间 29 | - 发送命令后,从redis接收响应需要多长时间 30 | - 收到响应后处理响应所需的时间 31 | - 如果命令是响应集群 ASK 或 MOVED 响应而发送的 32 | - 如果是,原始命令是什么 33 | 34 | `TimeSpan` 有较高的精度,如果运行时支持。 `DateTime` 准确度如 `DateTime.UtcNow`。 35 | 36 | 性能分析示例 37 | --- 38 | 39 | 由于 StackExchange.Redis 的异步接口,分析需要外部协助将相关的命令组合在一起。 40 | 这是通过借助一个回调返回需要的 `ProfilingSession` 对象,之后调用这个 session 的 `FinishProfiling()` 来实现的。 41 | 42 | 可能最有用的通用会话提供程序是一个自动提供会话并在 `async` 调用之间工作的会话提供程序。 这很简单: 43 | 44 | ```c# 45 | class AsyncLocalProfiler 46 | { 47 | private readonly AsyncLocal perThreadSession = new AsyncLocal(); 48 | 49 | public ProfilingSession GetSession() 50 | { 51 | var val = perThreadSession.Value; 52 | if (val == null) 53 | { 54 | perThreadSession.Value = val = new ProfilingSession(); 55 | } 56 | return val; 57 | } 58 | } 59 | // ... 60 | var profiler = new AsyncLocalProfiler(); 61 | multiplexer.RegisterProfiler(profiler.GetSession); 62 | ``` 63 | 64 | 这将自动为每个异步上下文创建一个分析会话(如果有,则重新使用现有会话)。 在一些工作单元结束时, 65 | 调用代码可以使用 66 | `var commands = profiler.GetSession()。FinishProfiling();` 67 | 来获取执行的操作和时间信息数据。 68 | 69 | --- 70 | 71 | 一个将许多不同线程发出的命令关联在一起的 toy 示例(同时仍然允许不相关的工作不被记录) 72 | 73 | ``` csharp 74 | class ToyProfiler 75 | { 76 | // note this won't work over "await" boundaries; "AsyncLocal" would be necessary there 77 | private readonly ThreadLocal perThreadSession = new ThreadLocal(); 78 | public ProfilingSession PerThreadSession 79 | { 80 | get => perThreadSession.Value; 81 | set => perThreadSession.Value = value; 82 | } 83 | } 84 | 85 | // ... 86 | 87 | ConnectionMultiplexer conn = /* initialization */; 88 | var profiler = new ToyProfiler(); 89 | var sharedSession = new ProfilingSession(); 90 | 91 | conn.RegisterProfiler(() => profiler.PerThreadSession); 92 | 93 | var threads = new List(); 94 | 95 | for (var i = 0; i < 16; i++) 96 | { 97 | var db = conn.GetDatabase(i); 98 | 99 | var thread = 100 | new Thread( 101 | delegate() 102 | { 103 | // set each thread to share a session 104 | profiler.PerThreadSession = sharedSession; 105 | 106 | var threadTasks = new List(); 107 | 108 | for (var j = 0; j < 1000; j++) 109 | { 110 | var task = db.StringSetAsync("" + j, "" + j); 111 | threadTasks.Add(task); 112 | } 113 | 114 | Task.WaitAll(threadTasks.ToArray()); 115 | } 116 | ); 117 | 118 | threads.Add(thread); 119 | } 120 | 121 | threads.ForEach(thread => thread.Start()); 122 | threads.ForEach(thread => thread.Join()); 123 | 124 | var timings = sharedSession.FinishProfiling(); 125 | ``` 126 | 127 | 最后,`timings` 将包含16,000个 `IProfiledCommand` 对象 - 每个发送给redis的命令对应一个对象。 128 | 129 | 如果你像下面这样做: 130 | 131 | ``` csharp 132 | ConnectionMultiplexer conn = /* initialization */; 133 | var profiler = new ToyProfiler(); 134 | 135 | conn.RegisterProfiler(() => profiler.PerThreadSession); 136 | 137 | var threads = new List(); 138 | 139 | var perThreadTimings = new ConcurrentDictionary>(); 140 | 141 | for (var i = 0; i < 16; i++) 142 | { 143 | var db = conn.GetDatabase(i); 144 | 145 | var thread = 146 | new Thread( 147 | delegate() 148 | { 149 | var threadTasks = new List(); 150 | profiler.PerThreadSession = new ProfilingSession(); 151 | 152 | for (var j = 0; j < 1000; j++) 153 | { 154 | var task = db.StringSetAsync("" + j, "" + j); 155 | threadTasks.Add(task); 156 | } 157 | 158 | Task.WaitAll(threadTasks.ToArray()); 159 | 160 | perThreadTimings[Thread.CurrentThread] = profiler.PerThreadSession.FinishProfiling().ToList(); 161 | } 162 | ); 163 | threads.Add(thread); 164 | } 165 | 166 | threads.ForEach(thread => thread.Start()); 167 | threads.ForEach(thread => thread.Join()); 168 | ``` 169 | 170 | `perThreadTimings` 最终会有1000个 `IProfilingCommand` 的16个,主要是由 `Thread` 发出。 171 | 172 | 不再看这个示例,这里是如何在一个MVC5应用程序中配置 StackExchange.Redis。 173 | 174 | 首先针对你的 `ConnectionMultiplexer` 对象注册以下 `IProfiler`: 175 | 176 | ``` csharp 177 | public class RedisProfiler 178 | { 179 | const string RequestContextKey = "RequestProfilingContext"; 180 | 181 | public ProfilingSession GetSession() 182 | { 183 | var ctx = HttpContext.Current; 184 | if (ctx == null) return null; 185 | 186 | return (ProfilingSession)ctx.Items[RequestContextKey]; 187 | } 188 | 189 | public void CreateSessionForCurrentRequest() 190 | { 191 | var ctx = HttpContext.Current; 192 | if (ctx != null) 193 | { 194 | ctx.Items[RequestContextKey] = new ProfilingSession(); 195 | } 196 | } 197 | } 198 | ``` 199 | 200 | 然后,将以下内容添加到Global.asax.cs文件中(其中`_redisProfiler`是profiler 的 *实例* ): 201 | 202 | ``` csharp 203 | protected void Application_BeginRequest() 204 | { 205 | _redisProfiler.CreateSessionForCurrentRequest(); 206 | } 207 | 208 | protected void Application_EndRequest() 209 | { 210 | var session = _redisProfiler.GetSession(); 211 | if (session != null) 212 | { 213 | var timings = session.FinishProfiling(); 214 | 215 | // do what you will with `timings` here 216 | } 217 | } 218 | ``` 219 | 220 | 并且要确保连接创建的时候注册 profiler 221 | 222 | ```C# 223 | connection.RegisterProfiler(() => _redisProfiler.GetSession()); 224 | ``` 225 | 226 | 这个实现将所有redis命令(包括 `async / await` -ed 命令)与触发它们的http请求分组。 227 | 228 | [查看原文](https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/Profiling_v2.md) 229 | --- 230 | 231 | [返回主页](./README.md) 232 | --- -------------------------------------------------------------------------------- /PubSubOrder.md: -------------------------------------------------------------------------------- 1 | 发布/订阅 消息顺序 2 | === 3 | 4 | 当使用 发布/订阅 API 时,需要决定使用同一连接的消息应该是*顺序处理* 还是*并行处理* 。 5 | 6 | 顺序处理意味着你(很大程度上)不需要担心线程安全问题,并且这意味着你保持了事件的顺序。它们会完全按照(通过队列)接受的顺序来处理,但是结果这也意味着消息会延迟彼此。 7 | 8 | 另一种选择是 *concurrent(并行)* 处理。这使得工作的处理顺序 **没有特定的保证** 并且你的代码完全负责确保并发的消息不应该破坏内部的状态——但这样可以显著的更快,更加可以扩展。 9 | 如果消息间一般都不相关,这种处理方式*特别*好。 10 | 11 | 出于安全考虑, **默认处理方式是顺序处理**。但是,强烈建议你尽可能的使用并行处理。这是一个简单的修改: 12 | 13 | ```C# 14 | multiplexer.PreserveAsyncOrder = false; 15 | ``` 16 | 17 | 这不是一个*配置* 选项,因为这样做是否合适 *完全* 取决于订阅消息的代码。 18 | 19 | [查看原文](https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/PubSubOrder.md) 20 | --- 21 | 22 | [返回主页](./README.md) 23 | --- -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StackExchange.Redis 中文使用文档 2 | 3 | ## Intro 4 | 5 | 翻译 StackExchange.Redis 的文档 6 | 7 | 原文文档在线地址: 8 | 9 | ## Redis 简介 10 | 11 | Redis是一个使用ANSI C编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。 12 | 从2015年6月开始,Redis的开发由Redis Labs赞助,而2013年5月至2015年6月期间,其开发由Pivotal赞助。在2013年5月之前,其开发由VMware赞助。 13 | 根据月度排行网站DB-Engines.com的数据显示,Redis是最流行的键值对存储数据库。 14 | 更多介绍可参考 15 | 16 | Redis官网 17 | 18 | ## StackExchange.Redis 简介 19 | 20 | StackExchange.Redis 是 Stackoverflow 开发的 Redis C# 客户端,是目前.net应用使用的最多的 redis 客户端,性能优越。 21 | 22 | ## StackExchange.Redis中文使用文档 23 | 24 | - Github: 25 | - Gitbook : 26 | - [点击阅读](https://weihanli.gitbooks.io/stackexchange-redis-docs-zh-cn/) 27 | - [下载 PDF](https://www.gitbook.com/download/pdf/book/weihanli/stackexchange-redis-docs-zh-cn) 28 | 29 | ## 目录 30 | 31 | - [基础](Basics.md) 32 | - [配置](Configuration.md) 33 | - [事件](Events.md) 34 | - [同步执行](ExecSync.md) 35 | - [键查找](KeysScan.md) 36 | - [键与值](KeysValues.md) 37 | - [管道与重用链接](PipelinesMultiplexers.md) 38 | - [性能分析](Profiling.md) 39 | - [发布订阅顺序](PubSubOrder.md) 40 | - [脚本](Scripting.md) 41 | - [超时](Timeouts.md) 42 | - [事务](Transactions.md) 43 | 44 | ## More 45 | 46 | 作者水平有限,若有疏漏或错误还望提醒,十分感谢。 47 | 48 | 您可以 [提出问题](https://github.com/WeihanLi/StackExchange.Redis-docs-zh-cn/issues/new) 或者给我 [发邮件](mailto:weihanli@outlook.com)。 49 | 50 | ### Contact me: 51 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [基础](Basics.md) 4 | * [配置](Configuration.md) 5 | * [事件](Events.md) 6 | * [同步执行](ExecSync.md) 7 | * [键查找](KeysScan.md) 8 | * [键与值](KeysValues.md) 9 | * [管道与重用链接](PipelinesMultiplexers.md) 10 | * [性能分析](Profiling.md) 11 | * [发布订阅顺序](PubSubOrder.md) 12 | * [脚本](Scripting.md) 13 | * [超时](Timeouts.md) 14 | * [事务](Transactions.md) -------------------------------------------------------------------------------- /Scripting.md: -------------------------------------------------------------------------------- 1 | 脚本 2 | === 3 | 4 | `IServer.ScriptLoad(Async)`、 `IServer.ScriptExists(Async)`、`IServer.ScriptFlush(Async)`、 `IDatabase.ScriptEvaluate` 和 `IDatabaseAsync.ScriptEvaluateAsync` 这些方法为基本的 [Lua脚本](http://redis.io/commands/EVAL) 提供了支持。 5 | 这些方法暴露了向Redis提交和执行Lua脚本所需的基本命令。 6 | 7 | 通过 `LuaScript` 类可以获得更复杂的脚本。 `LuaScript` 类使得更容易准备和提交参数以及脚本,以及允许您使用清理代码后变量名称。 8 | 9 | `LuaScript` 的使用示例: 10 | 11 | ``` csharp 12 | const string Script = "redis.call('set', @key, @value)"; 13 | 14 | using (ConnectionMultiplexer conn = /* init code */) 15 | { 16 | var db = conn.GetDatabase(0); 17 | 18 | var prepared = LuaScript.Prepare(Script); 19 | db.ScriptEvaluate(prepared, new { key = (RedisKey)"mykey", value = 123 }); 20 | } 21 | ``` 22 | 23 | `LuaScript` 类将 `@myVar` 形式的脚本中的变量重写为redis所需的合适的 `ARGV [someIndex]`。 24 | 如果传递的参数是 `RedisKey` 类型,它将作为 `KEYS` 集合的一部分自动发送。 25 | 26 | Any object that exposes field or property members with the same name as @-prefixed variables in the Lua script can be used as a parameter hash to 27 | `Evaluate` calls. 28 | 29 | 任何在Lua脚本中暴露的以`@`为前缀变量同名的字段或属性成员的对象都可以用作参数哈希 `Evaluate` 调用。 30 | 31 | 支持的成员类型如下: 32 | 33 | - int(?) 34 | - long(?) 35 | - double(?) 36 | - string 37 | - byte[] 38 | - bool(?) 39 | - RedisKey 40 | - RedisValue 41 | 42 | 为了避免在每次评估时重新传输Lua脚本到redis,`LuaScript` 对象可以通过 `LuaScript.Load(IServer)` 转换为 `LoadedLuaScript`。 43 | `LoadedLuaScripts` 使用 [`EVALSHA`](http://redis.io/commands/evalsha) 求值,并由 hash 引用。 44 | 45 | `LoadedLuaScript` 的使用示例: 46 | 47 | ``` csharp 48 | const string Script = "redis.call('set', @key, @value)"; 49 | 50 | using (ConnectionMultiplexer conn = /* init code */) 51 | { 52 | var db = conn.GetDatabase(0); 53 | var server = conn.GetServer(/* appropriate parameters*/); 54 | 55 | var prepared = LuaScript.Prepare(Script); 56 | var loaded = prepared.Load(server); 57 | loaded.Evaluate(db, new { key = (RedisKey)"mykey", value = 123 }); 58 | } 59 | ``` 60 | 61 | `LuaScript` 和 `LoadedLuaScript` 上的所有方法都有Async替代方法,并将提交到redis的实际脚本公开为 `ExecutableScript` 属性。 62 | 63 | [查看原文](https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/Scripting.md) 64 | --- 65 | 66 | [返回主页](./README.md) 67 | --- -------------------------------------------------------------------------------- /TOC.md: -------------------------------------------------------------------------------- 1 | * [基础](Basics.md) 2 | * [配置](Configuration.md) 3 | * [事件](Events.md) 4 | * [同步执行](ExecSync.md) 5 | * [键查找](KeysScan.md) 6 | * [键与值](KeysValues.md) 7 | * [管道与重用链接](PipelinesMultiplexers.md) 8 | * [分析](Profiling.md) 9 | * [发布订阅顺序](PubSubOrder.md) 10 | * [脚本](Scripting.md) 11 | * [超时](Timeouts.md) 12 | * [事务](Transactions.md) -------------------------------------------------------------------------------- /Timeouts.md: -------------------------------------------------------------------------------- 1 | 你是否正遇到网络或 CPU 的瓶颈? 2 | ============== 3 | 4 | 验证客户端和托管redis-server的服务器上支持的最大带宽。如果有请求被带宽限制,则它们需要更长时间才能完成,从而可能导致超时。 5 | 同样,验证您没有在客户端或服务器框上获得CPU限制,这将导致请求等待CPU时间,从而超时。 6 | 7 | 有没有命令需要在 redis 服务器上处理很长时间? 8 | --------------- 9 | 10 | 可能有一些命令需要很长时间才能在redis服务器上处理,导致请求超时。 11 | 长时间运行的命令的很少例子有 mget有大量的键,键*或写得不好的lua脚本。 12 | 可以运行通过 SlowLog 命令查看是否有请求花费比预期更长的时间。 13 | 14 | 在 [这里](http://redis.io/commands/slowlog) 可以找到关于命令的更多细节。 15 | 16 | 在向Redis发出的几个小请求之前是否有大的请求超时? 17 | --------------- 18 | 19 | 错误消息中的参数“qs”告诉您有多少从客户端发送到服务器,但尚未处理响应的请求。 20 | 对于某些类型的加载,您可能会看到此值不断增长,因为 StackExchange.Redis 使用单个TCP连接,并且一次只能读取一个响应。 21 | 即使第一个操作超时,它也不会停止 向服务器发送/从服务器发送 数据,其他请求也会被阻塞,直到该操作完成。 从而,导致超时。 22 | 一个解决方案是通过确保redis-server缓存对于您的工作负载足够大并将大值分割为更小的块来最小化超时的可能性。 23 | 24 | 另一个可能的解决方案是在客户端中使用 ConnectionMultiplexer 对象池,并在发送新请求时选择“最小化加载”ConnectionMultiplexer。 这样可能会防止单个超时导致其他请求也超时。 25 | 26 | 在超时异常中,是否有很多 busyio 或 busyworker 线程? 27 | --------------- 28 | 29 | 让我们先了解一下 ThreadPool 增长的一些细节: 30 | 31 | CLR ThreadPool有两种类型的线程 - “工作线程”和“I/O 完成端口”(也称为 IOCP)线程。 32 | 33 | - 工作线程用于处理 `Task.Run(...)` 或 `ThreadPool.QueueUserWorkItem(...)` 方法时。当工作需要在后台线程上发生时,这些线程也被CLR中的各种组件使用。 34 | - 当异步IO发生时(例如从网络读取),使用IOCP线程。 35 | 36 | 线程池根据需要提供新的工作线程或I / O完成线程(无任何调节),直到达到每种类型线程的“最小”设置。 默认情况下,最小线程数设置为系统上的处理器数。 37 | 38 | 一旦现有(繁忙)线程的数量达到“最小”线程数,ThreadPool将调节每500毫秒向一个线程注入新线程的速率。 这意味着如果你的系统需要一个IOCP线程的工作,它会很快处理这个工作。 但是,如果工作突发超过配置的“最小”设置,那么在处理一些工作时会有一些延迟,因为ThreadPool会等待两个事情之一发生: 39 | 40 | 1. 现有线程可以自由处理工作 41 | 2. 连续 500ms 没有现有线程空闲,因此创建一个新线程。 42 | 43 | 基本上,这意味着当忙线程数大于最小线程时,在应用程序处理网络流量之前,可能需要付出500毫秒的延迟。 44 | 此外,重要的是要注意,当现有线程保持空闲超过15秒(基于我记得),它将被清理,这个增长和收缩的循环可以重复。 45 | 46 | 如果我们看一个来自 StackExchange.Redis(build 1.0.450或更高版本)的示例错误消息,您将看到它现在会打印 ThreadPool 统计信息(请参阅下面的IOCP和WORKER详细信息)。 47 | 48 | ``` 49 | System.TimeoutException: Timeout performing GET MyKey, inst: 2, mgr: Inactive, queue: 6, qu: 0, qs: 6, qc: 0, wr: 0, wq: 0, in: 0, ar: 0, 50 | IOCP: (Busy=6,Free=994,Min=4,Max=1000), 51 | WORKER: (Busy=3,Free=997,Min=4,Max=1000) 52 | ``` 53 | 54 | 在上面的示例中,您可以看到,对于 IOCP 线程,有6个忙线程,并且系统配置为允许4个最小线程。 在这种情况下,客户端可能会看到两个500毫秒的延迟,因为6> 4。 55 | 56 | 请注意,如果 IOCP 或 WORKER 线程的增长受到限制,StackExchange.Redis 可能会超时。 57 | 58 | 同样需要注意的是 如果你使用的 .NET Core 版本使用的 `netstandard` 版本小于 2.0,IOCP 和 WORKER 线程将不会显示。 59 | 60 | 建议: 61 | 62 | 鉴于上述信息,建议将 IOCP 和 WORKER 线程的最小配置值设置为大于默认值的值。 我们不能给出一个大小适合所有指导这个值应该是什么,因为对于一个应用程序的正确的值对于另一个应用程序而言总会太高/低。 63 | 此设置也会影响复杂应用程序的其他部分的性能,因此您需要根据您的特定需求调整此设置。 64 | 一个好的起点是200或300,然后根据需要进行测试和调整。 65 | 66 | 如何配置这个设置: 67 | 68 | - 在 ASP.NET 中,使用 machine.config 中 `` 配置元素下的 69 | [“minIoThreads”配置设置](https://msdn.microsoft.com/en-us/library/7w2sway1(v=vs.71).aspx)。 70 | 根据微软的做法,你不能修改每个站点 web.config 中的这个值(即使你过去这样做是可以的),如果你这样改的话你所有的.NET 站点都会使用这个设置的值。 71 | 请注意如果你设置 `autoconfig` 为 `false` 是不需要添加每一个属性的,仅需要添加 `autoconfig="false"` 并且覆盖原来的值就可以了: 72 | `` 73 | 74 | > **重要说明:** 此配置元素中指定的值是为*每个核* 设置。例如,如果你有一个4核的机器,并希望你的 minIthreads 设置为200在运行时,你应该使用 ``。 75 | 76 | - 在 ASP.NET 之外,使用 [ThreadPool.SetMinThreads(...)](https://msdn.microsoft.com//en-us/library/system.threading.threadpool.setminthreads(v=vs.100).aspx)API。 77 | 78 | - 在 .NET Core 中 添加环境变量 `COMPlus_ThreadPool_ForceMinWorkerThreads` 来覆盖默认的 `MinThreads` 设置,参考 [Environment/Registry Configuration Knobs](https://github.com/dotnet/coreclr/blob/master/Documentation/project-docs/clr-configuration-knobs.md) 79 | 80 | [查看原文](https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/Timeouts.md) 81 | --- 82 | 83 | [返回主页](./README.md) 84 | --- -------------------------------------------------------------------------------- /Transactions.md: -------------------------------------------------------------------------------- 1 | Redis中的事务 2 | ===================== 3 | 4 | Redis中的事务不像SQL数据库中的事务。 5 | [完整的文档在这里](http://redis.io/topics/transactions),这里稍微借用一下: 6 | 7 | redis中的事务包括放置在 `MULTI` 和 `EXEC` 之间的一组命令(或者用于回滚的 `DISCARD`)。 8 | 一旦遇到 `MULTI`,该连接相关的命令*不会被执行* - 它们会进入一个队列(并且 每一个命令调用者得到一个答复 `QUEUED`)。 9 | 10 | 当遇到一个 `EXEC` 时,它们都被应用在一个单元中(即在操作期间没有其他连接获得时间去做任何事)。 11 | 如果看到 `DISCARD` 而不是 `EXEC`,则一切都被抛弃。因为事务中的命令会排队,你不能在事务*里面* 操作。 12 | 13 | 例如,在SQL数据库中,您可以执行以下操作(伪代码 - 仅供说明): 14 | 15 | ```C# 16 | // assign a new unique id only if they don't already 17 | // have one, in a transaction to ensure no thread-races 18 | var newId = CreateNewUniqueID(); // optimistic 19 | using(var tran = conn.BeginTran()) 20 | { 21 | var cust = GetCustomer(conn, custId, tran); 22 | var uniqueId = cust.UniqueID; 23 | if(uniqueId == null) 24 | { 25 | cust.UniqueId = newId; 26 | SaveCustomer(conn, cust, tran); 27 | } 28 | tran.Complete(); 29 | } 30 | ``` 31 | 32 | 如何在Redis中实现事务呢? 33 | --- 34 | 35 | 这在redis事务中是不可能的:一旦事务被打开你*不能获取数据* - 你的操作被排队。 幸运的是,还有另外两个命令帮助我们: `WATCH` 和 `UNWATCH` 。 36 | 37 | `WATCH {key}` 告诉Redis,我们对用于事务目的的特定的键感兴趣。 38 | Redis会自动跟踪这个键,任何变化基本上都会使我们的事务回滚 - `EXEC` 和 `DISCARD` 一样(调用者可以检测到这一点,并从头开始重试)。 39 | 所以你可以做的是: `WATCH` 一个键,以正常的方式检查该键的数据,然后 `MULTI` / `EXEC` 你的更改。 40 | 41 | 如果,当你检查数据,你发现你实际上不需要事务,你可以使用 `UNWATCH` 来取消关注所有关注的键。 42 | 注意,关注的键在 `EXEC` 和 `DISCARD` 期间也被复位。 所以*在Redis层*,事务是从概念上讲的。 43 | 44 | ``` 45 | WATCH {custKey} 46 | HEXISTS {custKey} "UniqueId" 47 | (check the reply, then either:) 48 | MULTI 49 | HSET {custKey} "UniqueId" {newId} 50 | EXEC 51 | (or, if we find there was already an unique-id:) 52 | UNWATCH 53 | ``` 54 | 55 | 这可能看起来很奇怪 - 有一个 `MULTI` / `EXEC` 只跨越一个操作 - 但重要的是,我们现在也从其他所有连接跟踪对 `{custKey}` 的更改 - 如果任何人更改键 ,事务将被中止。 56 | 57 | 在 StackExchange.Redis 中如何实现事务? 58 | --- 59 | 60 | 说实话,StackExchange.Redis 使用多路复用器方法实现事务更复杂。 61 | 我们不能简单地让并发的调用者发出 `WATCH` / `UNWATCH` / `MULTI` / `EXEC` / `DISCARD`:它会全部混在一起。 62 | 63 | 因此,StackExchange.Redis 提供了额外的抽象来使事情更简单的变得正常:*constraints*。 64 | 65 | *Constraints* 基本上是预先测试涉及 `WATCH` ,一些测试,以及结果的检查。 66 | 如果所有约束都通过,则触发 `MULTI` / `EXEC` , 否则触发 `UNWATCH` 。 67 | 68 | 这是以防止与其他调用者混合在一起的命令的方式完成的。 所以我们的例子变成了: 69 | 70 | ```C# 71 | var newId = CreateNewId(); 72 | var tran = db.CreateTransaction(); 73 | tran.AddCondition(Condition.HashNotExists(custKey, "UniqueID")); 74 | tran.HashSetAsync(custKey, "UniqueID", newId); 75 | bool committed = tran.Execute(); 76 | // ^^^ if true: it was applied; if false: it was rolled back 77 | ``` 78 | 79 | 注意,从 `CreateTransaction` 返回的对象只能访问 *async* 方法 - 因为每个操作的结果在 `Execute`(或 `ExecuteAsync` )完成之前都不会知道。 80 | 如果操作不应用,所有的任务将被标记为已取消 - 否则,命令执行*后*,您可以正常获取每个的结果。 81 | 82 | 可用*条件* 的集合不是广泛的,而是涵盖最常见的情况;如果你还想看到其他条件,请与我联系(或更好的方式:提交 pull-request)。 83 | 84 | 借助 `When` 的内置操作 85 | --- 86 | 87 | 还应该注意的是,Redis 预期已经了许多常见的情况(特别是:密钥/散列 存在,如上所述),所以存在单次操作原子命令。 88 | 89 | 这些是通过`When`参数访问的 - 所以我们前面的例子可以也可以写成: 90 | 91 | ```C# 92 | var newId = CreateNewId(); 93 | bool wasSet = db.HashSet(custKey, "UniqueID", newId, When.NotExists); 94 | ``` 95 | 96 | (这里,`When.NotExists` 导致使用 `HSETNX` 命令,而不是 `HSET`) 97 | 98 | Lua 脚本 99 | --- 100 | 101 | 你还应该知道,Redis 2.6及以上版本[支持Lua脚本](http://redis.io/commands/EVAL),用于在服务器端执行多个作为单个原子单元的操作的通用工具。由于在Lua脚本中没有服务于其他连接,它的行为很像一个事务,但没有 `MULTI` / `EXEC` 等这样复杂。 102 | 这也避免了在调用者和服务器之间的带宽和延迟等问题 103 | ,但是需要与脚本垄断服务器的持续时间之间权衡。 104 | 105 | 在Redis层(假设 `HSETNX` 不存在),这可以实现为: 106 | 107 | ``` lua 108 | EVAL "if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end" 1 {custKey} {newId} 109 | ``` 110 | 111 | 这可以在 StackExchange.Redis 中使用: 112 | 113 | ```C# 114 | var wasSet = (bool) db.ScriptEvaluate(@"if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end", 115 | new RedisKey[] { custKey }, new RedisValue[] { newId }); 116 | ``` 117 | 118 | (注意 `ScriptEvaluate` 和 `ScriptEvaluateAsync` 的响应是可变的,这取决于你确切的脚本,响应可以被强制(类型)转换- 在这种情况下为 `bool`) 119 | 120 | [查看原文](https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/Transactions.md) 121 | --- 122 | 123 | [返回主页](./README.md) 124 | --- -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman --------------------------------------------------------------------------------