Redis 踩雷筆記

這起事件發生在我們把單節點的 Redis 改使用 Redis Cluster,因為不正確連線設定,導致服務發生問題。

Alvin
新加坡商鈦坦科技
8 min readApr 3, 2024

--

目錄
1. 背景
2. 起點
3. Redis Cluster 不支援 IDistributionCache?
4. 版本 Bug?
5. 特殊字元 with Redis 連線設定
6. Hangfire 依然正常連線之謎
7. 總結
8. 參考資料

背景

使用 dotnet 7,以下是 Redis 的相關設定。

//連線相關設定
//GetSection("Redis") 會取出 applications.json 的設定
var redisConfiguration = configuration.GetSection("Redis").Get<RedisConfiguration>()!;

services.AddStackExchangeRedisCache(options =>
{
options.Configuration = redisConfiguration.ConfigurationOptions.ToString();
});
// applications.json
"Redis": {
"Hosts": [
{
"Host": "redis-01.com",
"Port": "6666"
},
{
"Host": "redis-02.com",
"Port": "6666"
},
{
"Host": "redis-03.com",
"Port": "6666"
},
{
"Host": "redis-04.com",
"Port": "6666"
},
{
"Host": "redis-05.com",
"Port": "6666"
},
{
"Host": "redis-06.com",
"Port": "6666"
}
],
"Password": "from secret..."
// password = a,aqn4wddQ,從 K8S secret 取回
},

然後注入 IDistributionCache 在 service 中對 Redis 做操作

起點

在 release 後,當下的錯誤訊息可以抓出以下片段

StackExchange.Redis.RedisConnectionException: The message timed out in the backlog attempting to send because no connection became available (10000ms) — Last Connection Exception: AuthenticationFailure

看到訊息能聯想到連線 Redis Cluster 時驗證失敗,於是先從 K8S secret 設定的 Redis password 開始找起。但在經過一些簡單驗證的過程,排除了配置錯誤的問題,只好開始尋找其他的可能性

Redis Cluster 不支援 IDistributionCache ?

猜測

專案內用到 Redis 的地方都使用 IDistributionCache ,就想有沒有可能是 IDistributionCache 不支援 Redis Cluster 呢?

驗證

我們把 IDistributionCache 的實作介面改用 IDatabase,結果就成功對 Redis Cluster 讀寫了。

懷疑

成功使用 Redis Cluster,但同樣都是 StackExchangeRedis 的套件, IDistributionCache 不支援 Redis Cluster 好像說不過去。
我們也發現,專案內方便設定排程用的套件 Hangfire,不論我們怎麼調整,他都能正常連線,且官方的文件也沒特別提到這點,就繼續往其他方向找答案。(文章最後會提到)

StackExchange 註冊 IDirtributionCache
DistributionCache 內部是用 IDatabase 實作和連線

版本Bug ?

猜測

我們改猜是不是 IDistributionCache在某些版本的 bug,才導致無法連上 Redis Cluster。

驗證

IDatabase 先改回 IDistributionCache的版本後,嘗試使用各版本的 StackExchangeRedis,不論怎試都沒辦法再連線,一樣又出現 AuthenticationFailure 等錯誤訊息。

由於驗證失敗,在網路上也沒找到有人反應類似問題,只好再繼續尋找其他答案。

特殊字元 with Redis 連線設定

猜測

這時有人注意到,我們的 Redis Cluster 的密碼含有 , (comma)。
而且連線至 Redis 用的 Configuration 是一個 string。要拆成各個 config 就需要做 Split 的動作,且 password 會有被截斷的問題,於是又回到 Dotnet 連線至 Redis 相關的 Configuration。

驗證

能看到使用 Configuration 時,逐一取出參數設定會使用 comma 做字串切割,如下圖

Configuration 使用 comma 分割連線字串

原始碼中,也可以看到 StackExchangeRedis 的 RedisCacheOptions 註解上,官方建議使用 ConfigurationOptions 而不是 Configuraion 當作設定Redis 的方式。

configuration 是 string,且建議使用Options方式

先不管密碼內的 comma,把 Configuration 改成使用 ConfigurationOptions。像這樣

var redisConfiguration = configuration.GetSection("Redis").Get<RedisConfiguration>()!;

ConfigurationOptions options = new ConfigurationOptions
{
EndPoints = { { "my-redis.cloud.redislabs.com", 6379 } },
User = "default", // use your Redis user. More info https://redis.io/docs/management/security/acl/
Password = "secret", // use your Redis password
};

services.AddStackExchangeRedisCache(options =>
{
options.ConfigurationOptions = options
/*
原本做法
options.Configuration = redisConfiguration.ConfigurationOptions.ToString();
*/
});

服務果然正常運行了,結果是被機器產生的密碼給搞了~

Hangfire 依然正常連線之謎

迷霧散去之後,可以沒壓力的去找問題,也去掉重重疑點。
爬了一下連線設定發現,專案內有另外一處負責設定 Hangfire 連線(下面code 片段),使用 ConnectionMultiplexer 連線。
使用的是不會發生問題的 ConfigurationOptions,所以 Hangfire 才健健康康的囉。下圖也可以看到 ConnectionMultiplexer 直接從 Options 內拿Password,所以不會發生分割連線字串造成連線資訊錯誤的問題。

var redisConfig = configuration.GetSection("Redis").Get<RedisConfiguration>();
var redisConnection = ConnectionMultiplexer.Connect(redisConfig.ConfigurationOptions);
ConnectionMultiplexer 直接拿 password 不需分割

總結

  • Root cause 是在正式環境使用的 Redis cluster 密碼包含 comma。在執行 connect 時,會 split comma 讓密碼被截斷,導致連線字串有誤。
  • 更新 Redis 連線的 coding convention,從 connection string 改使用 ConfigurationOptions

參考資料

在 ASP.NET Core 中使用多個環境 | Microsoft Learn

ASP.NET Core 中的選項模式 | Microsoft Learn

使用 String.Split 分割字串 (C# 指南) — C# | Microsoft Learn

C#/.NET guide | Redis

--

--