.net使用CSRedis操作Redis快取的簡單筆記(新手教程)

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. 參考資料

github //github.com/2881099/csredis

作者部落格 //www.cnblogs.com/kellynic/p/9952386.html

Redis Runoob教程 //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服務沒有發現問題的話,可以嘗試使用下面三種方式解決(參考 //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服務,再進行調試

//github.com/Impartsoft/Bins/tree/main/CSRedisDemo/CSRedisDemo