使用 C# 與 Lua 方式操作 Redis

本文介紹改用 Lua 方式存取 Redis,以省下連線、存取次數。

Demo內容

  1. 先在 redis 建立一個 key
  2. 以 lua 方式執行,把值取出來,加上整數 123,寫回去 redis
  3. 以一般指令方式,把值取出來,加上整數 123,寫回去 redis

建立連線機制

1
2
3
4
5
private const string RedisHost = "127.0.0.1:6379";
private const string TestKey = "TestRedisKey";

using var conn = await ConnectionMultiplexer.ConnectAsync(RedisHost);
var redisDb = conn.GetDatabase();

一般存取方式

當有一些邏輯比較複雜就必須把值取回程式中運算。

1
2
3
4
5
6
7
8
9
10
11
12
private static async Task RunWithoutLua(IDatabase db)
{
var currentValue = await db.StringGetAsync(TestKey);

// Some complicated logic in C#
var newValue = Convert.ToInt32(currentValue) + 123;
await db.StringSetAsync(TestKey, newValue);

// Validate
var afterUpdatedValue = await db.StringGetAsync(TestKey);
Console.WriteLine($"RunWithoutLua: After updated value: {afterUpdatedValue}");
}

用 Lua 方式存取

這邊是示意才用這種方式寫,但大型專案還是建議把 .lua 整理起來分類放在資料夾內,這樣 repository 層比較乾淨,也不會有過多、複雜的邏輯。

1
2
3
4
5
6
7
8
9
10
11
12
private static async Task RunWithoutLua(IDatabase db)
{
var currentValue = await db.StringGetAsync(TestKey);

// Some complicated logic in C#
var newValue = Convert.ToInt32(currentValue) + 123;
await db.StringSetAsync(TestKey, newValue);

// Validate
var afterUpdatedValue = await db.StringGetAsync(TestKey);
Console.WriteLine($"RunWithoutLua: After updated value: {afterUpdatedValue}");
}

執行結果

1
2
RunWithLua: After updated value: 123
RunWithoutLua: After updated value: 246

兩者都能達到預期的結果,但把處理邏輯放在 lua 內會比較乾淨,且你只需要呼叫一次而已。

完整 Sample Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using System;
using System.Threading.Tasks;
using StackExchange.Redis;

namespace ConsoleApp1
{
public static class ConsoleApp1
{
private const string RedisHost = "127.0.0.1:6379";
private const string TestKey = "TestRedisKey";

public static async Task Main()
{
using var conn =
await ConnectionMultiplexer.ConnectAsync(RedisHost);
var redisDb = conn.GetDatabase();

await redisDb.StringSetAsync(TestKey, 0);

await RunWithLua(redisDb);
await RunWithoutLua(redisDb);

Console.WriteLine("Main end");
}
private static async Task RunWithLua(IDatabase db)
{
var luaScript = @"
local currentValue = redis.call('GET','[ReplaceKey]')
local newValue = currentValue + 123
redis.call('SET', '[ReplaceKey]', newValue)";

luaScript = luaScript.Replace("[ReplaceKey]", TestKey);

await db.ScriptEvaluateAsync(luaScript);

var afterUpdatedValue = await db.StringGetAsync(TestKey);
Console.WriteLine($"RunWithLua: After updated value: {afterUpdatedValue}");
}

private static async Task RunWithoutLua(IDatabase db)
{
var currentValue = await db.StringGetAsync(TestKey);

// Some complicated logic in C#
var newValue = Convert.ToInt32(currentValue) + 123;
await db.StringSetAsync(TestKey, newValue);

// Validate
var afterUpdatedValue = await db.StringGetAsync(TestKey);
Console.WriteLine($"RunWithoutLua: After updated value: {afterUpdatedValue}");
}
}
}

docker-compose.yml

我很懶惰,都會寫在 docker-compose.yml 內,以後起、停 container 就能少打點指令。

1
2
3
4
5
6
7
8
version: "3"

services:
database:
image: redis:latest
container_name: local_redis
ports:
- 6379:6379
  • 作者: MingYi Chou
  • 版權聲明: 轉載不用問,但請註明出處!本網誌均採用 BY-NC-SA 許可協議。