1. 程式人生 > >Redis學習系列二之.Net開發環境搭建及基礎資料結構String字串

Redis學習系列二之.Net開發環境搭建及基礎資料結構String字串

一、簡介

Redis有5種基本資料結構,分別是string、list(列表)、hash(字典)、set(集合)、zset(有序集合),這是必須掌握的5種基本資料結構.注意Redis作為一個鍵值對快取系統,其所有的資料結構,都以唯一的key(字串)作為名稱,然後通過key來獲取對應的資料.

 

二、.Net開發環境搭建

這個版本,暫時不考慮併發問題,後續的文章會說!
第一步:安裝StackExchange.Redis包,我用的是2.0.519版本的.

第二步:編寫程式碼,採用擴充套件方法的鏈式程式設計模式+async/await的程式設計模型

AppConfiguration.cs  全域性配置類

    /// <summary>
    /// 全域性配置類
    /// </summary>
    public class AppConfiguration
    {
        /// <summary>
        /// 單例實現,static關鍵字預設加鎖
        /// </summary>
        static AppConfiguration()
        {
            Current = new AppConfiguration();
        }

        public static readonly
AppConfiguration Current; /// <summary> /// 配置完Redis之後,所有需要的Redis基礎服務物件,都在這裡面 /// </summary> public RedisConfigurations RedisConfigurations { get; set; } }

FluentConfiguration.cs 鏈式配置核心類

    /// <summary>
    /// 鏈式程式設計模式,擴充套件方法實現
    /// </summary>
    public
static class FluentConfiguration { /// <summary> /// 配置Redis /// </summary> /// <typeparam name="T"></typeparam> /// <param name="configuration"></param> /// <returns></returns> public static AppConfiguration ConfigureRedis<T>(this AppConfiguration configuration) where T: IRedisConfig, new() { if (configuration == null) throw new ArgumentNullException("configuration"); var config = new T(); var redisConfigurations=config.ConfigRedis(); configuration.RedisConfigurations = redisConfigurations; return configuration; } }

RedisConfigurations.cs Redis全域性配置共享類

    /// <summary>
    /// Redis配置完畢後,返回需要使用的相關物件
    /// </summary>
    public class RedisConfigurations
    {
        public IConnectionMultiplexer ConnectionMultiplexer { get; set; }
    }

RedisConfig.cs Redis配置類

    /// <summary>
    /// Redis配置類
    /// </summary>
    public class RedisConfig : IRedisConfig
    {
        /// <summary>
        /// 比較耗費資源,所以寫入快取,全域性共享
        /// 封裝了Redis基礎服務物件的詳細資訊
        /// </summary>
        public static ConnectionMultiplexer ConnectionMultiplexer { get; }

        /// <summary>
        /// 可能存線上程安全的配置,或者只需要初始化一次的配置,放這裡
        /// </summary>
        static RedisConfig()
        {
            //暫時讀配置檔案,後期可以用Core的配置檔案系統讀json檔案
            var redisServerAdress = ConfigurationManager.AppSettings["RedisServerAdress"];
            if (string.IsNullOrEmpty(redisServerAdress))
                throw new ApplicationException("配置檔案中未找到RedisServer的有效配置");
            ConnectionMultiplexer = ConnectionMultiplexer.Connect(redisServerAdress);
        }

        /// <summary>
        /// 配置Redis
        /// </summary
        public RedisConfigurations ConfigRedis()
        {
            var config = new RedisConfigurations();
            config.ConnectionMultiplexer = ConnectionMultiplexer;
            return config;
        }
    }

相關約束介面如下:

    /// <summary>
    /// Redis配置約束
    /// </summary>
    public interface IRedisConfig
    {
        /// <summary>
        /// 配置Redis
        /// </summary>
        RedisConfigurations ConfigRedis();
    }

    /// <summary>
    /// Redis客戶端例項約束介面
    /// </summary>
    public interface IRedisInstance
    {

    }

RedisClient.cs Redis客戶端呼叫類

    /// <summary>
    /// 基於async和await的非同步操作的Redis客戶端,有效利用CPU資源
    /// </summary>
    public class RedisClient: IRedisInstance
    {
        private static RedisConfigurations RedisConfigurations { get; }

        static RedisClient()
        {
            RedisConfigurations=AppConfiguration.Current.RedisConfigurations;
        }

        /// <summary>
        /// 非同步,寫入鍵值對
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static async Task<bool> StringSetAsync(string key,string value)
        {
            var db=GetDatabase();
            return await db.StringSetAsync(key,value);
        }

        /// <summary>
        /// 根據傳入鍵,非同步獲取對應的值
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static async Task<string> StringGetAsync(string key)
        {
            var db = GetDatabase();
            return await db.StringGetAsync(key);
        }

        /// <summary>
        /// 非同步判斷是否存在某個鍵
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static async Task<bool> KeyExistsAsync(string key)
        {
            var db = GetDatabase();
            return await db.KeyExistsAsync(key);
        }

        /// <summary>
        /// 非同步刪除某個鍵
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static async Task<bool> KeyDeleteAsync(string key)
        {
            var db = GetDatabase();
            return await db.KeyDeleteAsync(key);
        }

        /// <summary>
        /// Redis DataBase工廠方法
        /// </summary>
        /// <returns></returns>
        private static IDatabase GetDatabase()
        {
            return RedisConfigurations.ConnectionMultiplexer.GetDatabase();
        }
    }

暫時只擴充套件了一些方法,或許會持續擴充套件.

Program.cs 控制檯入口類

    class Program
    {
        static Program()
        {
            //鏈式配置Redis
            AppConfiguration.Current.ConfigureRedis<RedisConfig>();
        }

        static void Main(string[] args)
        {
            StringSetGetAsync();
            Console.ReadKey();
        }

        static async void StringSetGetAsync()
        {
            if (await RedisClient.StringSetAsync("name", "xiaochao"))
            {
                Console.WriteLine("Redis中鍵為name的值為:{0}", await RedisClient.StringGetAsync("name"));
            }
            else {
                Console.WriteLine("寫入異常");
            }
        }
    }

ok,到這裡.Net下使用StackExchange.Redis包操作Redis的環境構建完畢.

執行程式碼:

控制檯環境:

Redis桌面管理工具

Linux下Redis-cli

後續的文章都會圍繞上面三個操作方式展開.

 

三、string(字串)

1、簡單鍵值對操作

字串string是Redis中最簡單的資料型別,內部原理和C#的string型別一樣,是一個字元陣列.常見的用法是快取一些使用者資料,將使用者資料序列化程Json,然後以使用者Id作為鍵值,然後將使用者資料存入Redis中.獲取的時候,只需要通過使用者Id去獲取,然後將Json反序列化成對應的實體.

注:Redis的string型別是動態字串,而且支援修改,這和C#中的string不一樣,內部結構類似於C#中的List,有一個初始大小,如果存入string的長度大小大於string的初始大小,那麼每次都會擴充套件1倍的大小.但是字串最大長度只能為512MB.

程式碼實戰:

(1)、Linux終端

(2)、C#控制檯

修改控制檯方法如下:

        static void Main(string[] args)
        {
            StringSetGetAsync();
            Console.ReadKey();
        }

        static async void StringSetGetAsync()
        {
            var key = "name";
            if (await RedisClient.StringSetAsync(key, "xiaochao"))
            {
                Console.WriteLine("Redis中鍵為name的值為:{0}", await RedisClient.StringGetAsync(key));
                if (await RedisClient.KeyExistsAsync(key))
                {
                    Console.WriteLine("Redis中,存在key為name的鍵值對");
                }
                if (await RedisClient.KeyDeleteAsync(key))
                {
                    Console.WriteLine($"刪除鍵:{key}成功");
                    if (await RedisClient.KeyExistsAsync(key))
                        Console.WriteLine($"{key}存在,刪除失敗");
                    else
                        Console.WriteLine($"{key}不存在了,被刪除了");
                }
            }
            else {
                Console.WriteLine("寫入異常");
            }
        }

桌面管理工具:

 

2、批量鍵值對操作

C#控制檯:首先引入Newtonsoft.Json包

修改RedisClient.cs如下,給它擴充套件兩個方法

        /// <summary>
        /// 非同步批量插入鍵值對
        /// </summary>
        /// <param name="keyValuePair"></param>
        /// <returns></returns>
        public static async Task<bool> StringSetAsync(KeyValuePair<RedisKey, RedisValue>[] keyValuePair)
        {
            var db = GetDatabase();
            return await db.StringSetAsync(keyValuePair);
        }

        /// <summary>
        /// 非同步批量獲取值
        /// </summary>
        /// <param name="keyValuePair"></param>
        /// <returns></returns>
        public static async Task<RedisValue[]> StringGetAsync(RedisKey[] keys)
        {
            var db = GetDatabase();
            return await db.StringGetAsync(keys);
        }

Program.cs如下:

    class Program
    {
        static Program()
        {
            //鏈式配置Redis
            AppConfiguration.Current.ConfigureRedis<RedisConfig>();
        }

        static void Main(string[] args)
        {
            StringSetGetAsync();
            Console.ReadKey();
        }

        static async void StringSetGetAsync()
        {
            var userInfos = UserInfo.UserInfos;
            var keyValues = new KeyValuePair<RedisKey, RedisValue>[userInfos.Count];
            var keys =new RedisKey[userInfos.Count];
            for (var i = 0; i < userInfos.Count; i++)
            {
                var currUserInfo = userInfos[i];
                var key = currUserInfo.Id.ToString();
                var value = JsonConvert.SerializeObject(currUserInfo);
                keyValues[i] = new KeyValuePair<RedisKey, RedisValue>(key, value);
                keys[i] = key;
            }
            if (await RedisClient.StringSetAsync(keyValues))
            {
                try
                {
                    var values = await RedisClient.StringGetAsync(keys);
                    for (var i = 0; i < values.Length; i++)
                    {
                        Console.WriteLine(values[i]);
                    }
                }
                //捕獲輔助執行緒產生的異常
                catch (AggregateException ex)
                {
                    ex.Handle(x =>
                    {
                        //記錄日誌
                        Console.WriteLine("異常處理完畢,批量獲取值失敗!");
                        return true;
                    });
                }
            }
            else
            {
                //記錄日誌
                Console.WriteLine("寫入異常");
            }
        }

        class UserInfo
        {

            public Guid Id { get; set; }

            public string Name { get; set; }

            public int Age { get; set; }

            internal static List<UserInfo> UserInfos = new List<UserInfo>()
            {
                new UserInfo()
                {
                    Id=Guid.NewGuid(),
                    Name="小超",
                    Age=23
                },
                 new UserInfo()
                {
                    Id=Guid.NewGuid(),
                    Name="大超",
                    Age=23
                },
            };

        }
    }

(2)、管理工具

(3)、Linux終端

 

3、過期時間

Redis可以給Key設定過期時間,到達設定的時間,對應的鍵值對會被刪除,記憶體會被回收,這個功能常用來控制快取的失效時間.這裡這個自動刪除的機制很複雜,這裡不想說太多,只介紹基本用法,後續的文章會介紹.

C#控制檯,修改RedisClient.cs中的StringSetAsync方法如下:

        /// <summary>
        /// 非同步,寫入鍵值對,可指定過期時間
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expireTime">過期時間</param>
        /// <returns></returns>
        public static async Task<bool> StringSetAsync(string key,string value, TimeSpan? expireTime=null)
        {
            var db=GetDatabase();
            return await db.StringSetAsync(key,value, expireTime);
        }

Program.cs程式碼如下:

    class Program
    {
        static Program()
        {
            //鏈式配置Redis
            AppConfiguration.Current.ConfigureRedis<RedisConfig>();
        }

        static void Main(string[] args)
        {
            StringSetGetAsync();
            Console.ReadKey();
        }

        static async void StringSetGetAsync()
        {
         
            if (await RedisClient.StringSetAsync("name","xiaochao",TimeSpan.FromSeconds(2)))
            {
                Console.WriteLine("Redis中存在鍵為name的鍵值對,值為:{0}",await RedisClient.StringGetAsync("name"));
                await Task.Delay(2000);//模擬休息兩秒
                Console.WriteLine("休息兩秒後,Redis的鍵為name的鍵值對:{0}", string.IsNullOrEmpty(await RedisClient.StringGetAsync("name")) ? "不存在" : "存在");
            }
            else
            {
                //記錄日誌
                Console.WriteLine("寫入異常");
            }
        }
    }

這邊其它兩個終端就不演示了,自行觀察.

 

4、計數器

Redis提供了自增命令,前提操作的資料必須是整數,而且自增是有範圍的.預設對應long的最大值,一般達不到這個值.

C#控制檯:

修改RedisClient.cs下的StringSetAsync方法如下:

        /// <summary>
        /// 非同步,寫入鍵值對,可指定過期時間
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expireTime">過期時間</param>
        /// <returns></returns>
        public static async Task<bool> StringSetAsync(string key,RedisValue value, TimeSpan? expireTime=null)
        {
            var db=GetDatabase();
            return await db.StringSetAsync(key, value, expireTime);
        }

Program.cs程式碼如下:

    class Program
    {
        static Program()
        {
            //鏈式配置Redis
            AppConfiguration.Current.ConfigureRedis<RedisConfig>();
        }

        static void Main(string[] args)
        {
            StringSetGetAsync();
            Console.ReadKey();
        }

        static async void StringSetGetAsync()
        {
         
            if (await RedisClient.StringSetAsync("站點首頁",0))
            {
               
                    //模擬使用者訪問
                    Parallel.For(0, 250000, async (i, ParallelLoopState) =>
                     {
                         try
                         {
                             await RedisClient.StringIncrementAsync("站點首頁");
                         }
                         catch (RedisServerException ex)
                         {
                             //記錄日誌
                             Console.WriteLine(ex.Message);
                             ParallelLoopState.Stop();
                             return;
                         }
                     });
                    //輸出站點的UV
                    Console.WriteLine(await RedisClient.StringGetAsync("站點首頁"));
            }
            else
            {
                //記錄日誌
                Console.WriteLine("寫入異常");
            }
        }
    }

注:這裡存在兩個問題,如果你把Parallel的上限值設定的過大,那麼短時間內,可能Redis無法處理這麼多的併發量,而報超時錯誤,這個時候,解決方案是使用叢集的方式,解決這個問題,但是這裡就不演示了.第二個問題是,如果把Set的初始值設為Long.MaxValue,那麼Redis會報溢位錯誤,上面的程式碼已經處理.