0.介紹
.NET Core or .NET Framework 4.0+ client for Redis and Redis Sentinel (2.8) and Cluster. Includes both synchronous and asynchronous clients.
本文記錄CSRedis在開發過程中的簡單使用,可以直接除錯樣例原始碼。
1. 參考資料
Redis Runoob教程 https://www.runoob.com/redis/redis-install.html
2.核心內容
-
使用心得
1.科學使用快取
- 從Redis中讀取資料
從Redis中讀取資料需要考慮"資料存在,但是Redis中過期或者未寫入的情況"這時候就需要根據指定Key先獲取資料再寫入Redis中。
- 將資料寫入Redis
寫入Redis需要增加過期時。增加過期時間的時候可以將時間隨機,這樣可以避免快取在相同時間過期而引發快取雪崩。
在高併發的情況下,如果根據key獲取的資料不存在,也將null儲存至Redis中,而非抽象類(AbstractRedisService)中做刪除動作,這樣可避免快取穿透。
我對高併發場景下的快取使用理解不深,AbstractRedisService抽象類在更新失效值的時候使用了lock,使用lock寫法容易造成堵塞,但是如果不使用lock的話,就會出現重複讀取再寫入Redis的情況,並且在當前情況下如何處理快取擊穿也不是很清楚,希望大家能不吝賜教~
2.在使用CSRedis的時候遇到了多個業務模組都有相識的程式碼,於是抽取了抽象類AbstractRedisService,在業務模組實現的時候只需要實現RedisGroup 屬性與GetKeyByRedisInputKey、GetValueByKey兩個方法。
3.在實際應用中,有可能會出現程式與Redis服務連線不穩定的情況,如果Redis服務沒有發現問題的話,可以嘗試使用下面三種方式解決(參考 https://github.com/2881099/csredis/issues)
-
connectTimeout=30000 設定連線超時時間
-
tryit=3 設定重試次數
-
preheat=100 預熱連線數
-
初始化RedisHelper
// 初始化RedisHelper
RedisHelper.Initialization(CSRedisInstance.GetRedis());
/// <summary>
/// CSRedisClient 單例
/// </summary>
internal class CSRedisInstance
{
private static readonly object _lock = new object();
private static CSRedisClient _csRedis = null;
private const string ip = "127.0.0.1";
private const string port = "6379";
private const string preheat = "100"; // 設定預熱連線數
private const string connectTimeout = "100"; // 設定連線超時時間
private const string tryit = "1"; // 設定重試次數
private const string prefix = "CSRedisTest."; // 設定字首
private static readonly string _connectString = $"{ip}:{port}," +
$"preheat={preheat},connectTimeout={connectTimeout},tryit={tryit},prefix={prefix}";
internal static CSRedisClient GetRedis()
{
if (_csRedis == null)
{
lock (_lock)
{
if (_csRedis == null)
{
_csRedis = GetCSRedisClient();
}
}
}
return _csRedis;
}
private static CSRedisClient GetCSRedisClient()
{
return new CSRedisClient(_connectString);
}
}
-
業務應用 - 抽象類分享
AbstractRedisService抽象類
/// <summary>
/// Redis抽象類 - 快取內容根據Key指定重新整理
/// </summary>
/// <typeparam name="RedisInputKey">輸入Key值</typeparam>
/// <typeparam name="RedisValue">Redis儲存的Value值</typeparam>
public abstract class AbstractRedisService<RedisInputKey, RedisValue>
{
private readonly static object _lock = new object();
private const int _expireTime = 3600;
/// <summary>
/// 快取模組
/// </summary>
protected abstract RedisGroup CacheGroup { get; }
/// <summary>
/// 根據輸入Key值,返回真正RedisKey
/// </summary>
protected abstract string GetKeyByRedisInputKey(RedisInputKey redisInputKey);
/// <summary>
/// 根據輸入Key值,獲取對應Value
/// </summary>
protected abstract RedisValue GetValueByKey(RedisInputKey redisInputKey);
public RedisValue GetRedisByRedisInputKey(RedisInputKey redisInputKey)
{
if (!RedisControl.UseRedis())
return default(RedisValue);
var result = GetRedisValue(redisInputKey);
// 重新整理Redis之後還無法獲取正確的值,則記錄原因
if (result == null)
{
// 日誌輸出
};
return result;
}
public void NoticeRedisUpdateByKey(RedisInputKey redisInputKey)
{
try
{
UpdateByKey(redisInputKey);
}
catch (Exception e)
{
// 日誌輸出
}
}
/// <summary>
/// 有可能沒有Redis服務,則將異常捕捉,並停止使用Redis快取
/// </summary>
private RedisValue GetRedisValue(RedisInputKey redisInputKey)
{
RedisValue value = default(RedisValue);
string key = GetKeyByRedisInputKey(redisInputKey);
try
{
value = GetRedisValueByKey(key);
if (value != null)
return value;
lock (_lock)
{
value = GetRedisValueByKey(key);
if (value == null)
UpdateByKey(redisInputKey);
}
value = GetRedisValueByKey(key);
}
catch (Exception e)
{
RedisControl.StopUseRedis();
// 日誌輸出
}
return value;
}
private void UpdateByKey(RedisInputKey redisInputKey)
{
var key = GetKeyByRedisInputKey(redisInputKey);
RedisValue value = GetValueByKey(redisInputKey);
if (value == null) //刪除操作執行更新時,移除掉key
RedisHelper.Del(key);
else
RedisHelper.Set(key, value, _expireTime + 200 * new Random().Next(1, 10));
}
private RedisValue GetRedisValueByKey(string key)
{
return RedisHelper.Get<RedisValue>(key);
}
}
1.StudentRedisService實現類
/// <summary>
/// Redis Student實現類
/// </summary>
public class StudentRedisService : AbstractRedisService<string, Student>,IStudentRedisService
{
protected override RedisGroup CacheGroup => RedisGroup.Student;
protected override string GetKeyByRedisInputKey(string redisInputKey) => redisInputKey;
protected override Student GetValueByKey(string redisInputKey)
{
return Test.AllStudents.Where(v=>v.Name == redisInputKey).FirstOrDefault();
}
}
2.使用StudentRedisService
private static void AbstractRedisServiceTest()
{
var studentRedisService = new StudentRedisService();
// 一:通過key獲取
var mark = studentRedisService.GetRedisByRedisInputKey("Mark"); // 有此資料,獲取的時候會寫入Redis
var linda = studentRedisService.GetRedisByRedisInputKey("Linda"); // 無資料則返回null
// 二:資料變更通知
string markName = "Mark";
// 更新資料
int random = new Random().Next();
Test.AllStudents.Where(v => v.Name == markName).First().Age = random;
var mark2 = studentRedisService.GetRedisByRedisInputKey(markName); // 舊值
studentRedisService.NoticeRedisUpdateByKey(markName);// 更新Redis
mark2 = studentRedisService.GetRedisByRedisInputKey(markName);// 新值
}
3.樣例原始碼地址
除錯Demo可以先參考Redis Runoob安裝教程,部署Redis服務,再進行除錯
https://github.com/Impartsoft/Bins/tree/main/CSRedisDemo/CSRedisDemo