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 readonlyAppConfiguration Current; /// <summary> /// 配置完Redis之後,所有需要的Redis基礎服務物件,都在這裡面 /// </summary> public RedisConfigurations RedisConfigurations { get; set; } }
FluentConfiguration.cs 鏈式配置核心類
/// <summary> /// 鏈式程式設計模式,擴充套件方法實現 /// </summary> publicstatic 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會報溢位錯誤,上面的程式碼已經處理.