本系列將和大家分享Redis分布式快取,本章主要簡單介紹下Redis中的String型別,以及如何使用Redis解決訂單秒殺超賣問題,
Redis中5種資料結構之String型別:key-value的快取,支持過期,value不超過512M,

Redis是單執行緒的,比如SetAll & AppendToValue & GetValues & GetAndSetValue & IncrementValue & IncrementValueBy等等,這些看上去像是組合命令,但實際上是一個具體的命令,是一個原子性的命令,不可能出現中間狀態,可以應對一些并發情況,下面我們直接通過代碼來看下具體使用,
首先來看下Demo的專案結構:

此處推薦使用的是ServiceStack包,雖然它是收費的,有1小時3600次請求限制,但是它是開源的,可以將它的原始碼下載下來破解后使用,網上應該有挺多相關資料,有興趣的可以去了解一波,
一、Redis中與String型別相關的API
首先先來看下Redis客戶端的初始化作業:
using System; namespace TianYa.Redis.Init { /// <summary> /// redis組態檔資訊 /// 也可以放到組態檔去 /// </summary> public sealed class RedisConfigInfo { /// <summary> /// 可寫的Redis鏈接地址 /// format:ip1,ip2 /// /// 默認6379埠 /// </summary> public string WriteServerList = "127.0.0.1:6379"; /// <summary> /// 可讀的Redis鏈接地址 /// format:ip1,ip2 /// /// 默認6379埠 /// </summary> public string ReadServerList = "127.0.0.1:6379"; /// <summary> /// 最大寫鏈接數 /// </summary> public int MaxWritePoolSize = 60; /// <summary> /// 最大讀鏈接數 /// </summary> public int MaxReadPoolSize = 60; /// <summary> /// 本地快取到期時間,單位:秒 /// </summary> public int LocalCacheTime = 180; /// <summary> /// 自動重啟 /// </summary> public bool AutoStart = true; /// <summary> /// 是否記錄日志,該設定僅用于排查redis運行時出現的問題, /// 如redis作業正常,請關閉該項 /// </summary> public bool RecordeLog = false; } }
using ServiceStack.Redis; namespace TianYa.Redis.Init { /// <summary> /// Redis管理中心 /// </summary> public class RedisManager { /// <summary> /// Redis組態檔資訊 /// </summary> private static RedisConfigInfo _redisConfigInfo = new RedisConfigInfo(); /// <summary> /// Redis客戶端池化管理 /// </summary> private static PooledRedisClientManager _prcManager; /// <summary> /// 靜態構造方法,初始化鏈接池管理物件 /// </summary> static RedisManager() { CreateManager(); } /// <summary> /// 創建鏈接池管理物件 /// </summary> private static void CreateManager() { string[] writeServerConStr = _redisConfigInfo.WriteServerList.Split(','); string[] readServerConStr = _redisConfigInfo.ReadServerList.Split(','); _prcManager = new PooledRedisClientManager(readServerConStr, writeServerConStr, new RedisClientManagerConfig { MaxWritePoolSize = _redisConfigInfo.MaxWritePoolSize, MaxReadPoolSize = _redisConfigInfo.MaxReadPoolSize, AutoStart = _redisConfigInfo.AutoStart, }); } /// <summary> /// 客戶端快取操作物件 /// </summary> public static IRedisClient GetClient() { return _prcManager.GetClient(); } } }
using System; using TianYa.Redis.Init; using ServiceStack.Redis; namespace TianYa.Redis.Service { /// <summary> /// redis操作的基類 /// </summary> public abstract class RedisBase : IDisposable { /// <summary> /// Redis客戶端 /// </summary> protected IRedisClient _redisClient { get; private set; } /// <summary> /// 建構式 /// </summary> public RedisBase() { this._redisClient = RedisManager.GetClient(); } private bool _disposed = false; protected virtual void Dispose(bool disposing) { if (!this._disposed) { if (disposing) { _redisClient.Dispose(); _redisClient = null; } } this._disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// Redis事務處理示例 /// </summary> public void Transcation() { using (IRedisTransaction irt = this._redisClient.CreateTransaction()) { try { irt.QueueCommand(r => r.Set("key", 20)); irt.QueueCommand(r => r.Increment("key", 1)); irt.Commit(); //事務提交 } catch (Exception ex) { irt.Rollback(); //事務回滾 throw ex; } } } /// <summary> /// 清除全部資料 請小心 /// </summary> public virtual void FlushAll() { _redisClient.FlushAll(); } /// <summary> /// 保存資料DB檔案到硬碟 /// </summary> public void Save() { _redisClient.Save(); //阻塞式Save } /// <summary> /// 異步保存資料DB檔案到硬碟 /// </summary> public void SaveAsync() { _redisClient.SaveAsync(); //異步Save } } }
下面直接給大家Show一波Redis中與String型別相關的API:
using System; using System.Collections.Generic; namespace TianYa.Redis.Service { /// <summary> /// key-value 鍵值對 value可以是序列化的資料 (字串) /// </summary> public class RedisStringService : RedisBase { #region 賦值 /// <summary> /// 設定永久快取 /// </summary> /// <param name="key">存盤的鍵</param> /// <param name="value">存盤的值</param> /// <returns></returns> public bool Set(string key, string value) { return base._redisClient.Set(key, value); } /// <summary> /// 設定永久快取 /// </summary> /// <param name="key">存盤的鍵</param> /// <param name="value">存盤的值</param> /// <returns></returns> public bool Set<T>(string key, T value) { return base._redisClient.Set<T>(key, value); } /// <summary> /// 帶有過期時間的快取 /// </summary> /// <param name="key">存盤的鍵</param> /// <param name="value">存盤的值</param> /// <param name="expireTime">過期時間</param> /// <returns></returns> public bool Set(string key, string value, DateTime expireTime) { return base._redisClient.Set(key, value, expireTime); } /// <summary> /// 帶有過期時間的快取 /// </summary> /// <param name="key">存盤的鍵</param> /// <param name="value">存盤的值</param> /// <param name="expireTime">過期時間</param> /// <returns></returns> public bool Set<T>(string key, T value, DateTime expireTime) { return base._redisClient.Set<T>(key, value, expireTime); } /// <summary> /// 帶有過期時間的快取 /// </summary> /// <param name="key">存盤的鍵</param> /// <param name="value">存盤的值</param> /// <param name="expireTime">過期時間</param> /// <returns></returns> public bool Set<T>(string key, T value, TimeSpan expireTime) { return base._redisClient.Set<T>(key, value, expireTime); } /// <summary> /// 設定多個key/value /// </summary> public void SetAll(Dictionary<string, string> dic) { base._redisClient.SetAll(dic); } #endregion 賦值 #region 追加 /// <summary> /// 在原有key的value值之后追加value,沒有就新增一項 /// </summary> public long AppendToValue(string key, string value) { return base._redisClient.AppendToValue(key, value); } #endregion 追加 #region 獲取值 /// <summary> /// 讀取快取 /// </summary> /// <param name="key">存盤的鍵</param> /// <returns></returns> public string Get(string key) { return base._redisClient.GetValue(key); } /// <summary> /// 讀取快取 /// </summary> /// <param name="key">存盤的鍵</param> /// <returns></returns> public T Get<T>(string key) { return _redisClient.ContainsKey(key) ? _redisClient.Get<T>(key) : default; } /// <summary> /// 獲取多個key的value值 /// </summary> /// <param name="keys">存盤的鍵集合</param> /// <returns></returns> public List<string> Get(List<string> keys) { return base._redisClient.GetValues(keys); } /// <summary> /// 獲取多個key的value值 /// </summary> /// <param name="keys">存盤的鍵集合</param> /// <returns></returns> public List<T> Get<T>(List<string> keys) { return base._redisClient.GetValues<T>(keys); } #endregion 獲取值 #region 獲取舊值賦上新值 /// <summary> /// 獲取舊值賦上新值 /// </summary> /// <param name="key">存盤的鍵</param> /// <param name="value">存盤的值</param> /// <returns></returns> public string GetAndSetValue(string key, string value) { return base._redisClient.GetAndSetValue(key, value); } #endregion 獲取舊值賦上新值 #region 移除快取 /// <summary> /// 移除快取 /// </summary> /// <param name="key">存盤的鍵</param> /// <returns></returns> public bool Remove(string key) { return _redisClient.Remove(key); } /// <summary> /// 移除多個快取 /// </summary> /// <param name="keys">存盤的鍵集合</param> public void RemoveAll(List<string> keys) { _redisClient.RemoveAll(keys); } #endregion 移除快取 #region 輔助方法 /// <summary> /// 是否存在快取 /// </summary> /// <param name="key">存盤的鍵</param> /// <returns></returns> public bool ContainsKey(string key) { return _redisClient.ContainsKey(key); } /// <summary> /// 獲取值的長度 /// </summary> /// <param name="key">存盤的鍵</param> /// <returns></returns> public long GetStringCount(string key) { return base._redisClient.GetStringCount(key); } /// <summary> /// 自增1,回傳自增后的值 /// </summary> /// <param name="key">存盤的鍵</param> /// <returns></returns> public long IncrementValue(string key) { return base._redisClient.IncrementValue(key); } /// <summary> /// 自增count,回傳自增后的值 /// </summary> /// <param name="key">存盤的鍵</param> /// <param name="count">自增量</param> /// <returns></returns> public long IncrementValueBy(string key, int count) { return base._redisClient.IncrementValueBy(key, count); } /// <summary> /// 自減1,回傳自減后的值 /// </summary> /// <param name="key">存盤的鍵</param> /// <returns></returns> public long DecrementValue(string key) { return base._redisClient.DecrementValue(key); } /// <summary> /// 自減count,回傳自減后的值 /// </summary> /// <param name="key">存盤的鍵</param> /// <param name="count">自減量</param> /// <returns></returns> public long DecrementValueBy(string key, int count) { return base._redisClient.DecrementValueBy(key, count); } #endregion 輔助方法 } }
測驗如下:
using System; namespace MyRedis { /// <summary> /// 學生類 /// </summary> public class Student { public int Id { get; set; } public string Name { get; set; } public string Remark { get; set; } public string Description { get; set; } } }
using System; using System.Collections.Generic; using TianYa.Redis.Service; using Newtonsoft.Json; namespace MyRedis { /// <summary> /// ServiceStack API封裝測驗 五大結構理解 (1小時3600次請求限制--可破解) /// </summary> public class ServiceStackTest { /// <summary> /// String /// key-value的快取,支持過期,value不超過512M /// Redis是單執行緒的,比如SetAll & AppendToValue & GetValues & GetAndSetValue & IncrementValue & IncrementValueBy, /// 這些看上去是組合命令,但實際上是一個具體的命令,是一個原子性的命令,不可能出現中間狀態,可以應對一些并發情況 /// </summary> public static void ShowString() { var student1 = new Student() { Id = 10000, Name = "TianYa" }; using (RedisStringService service = new RedisStringService()) { service.Set("student1", student1); var stu = service.Get<Student>("student1"); Console.WriteLine(JsonConvert.SerializeObject(stu)); service.Set<int>("Age", 28); Console.WriteLine(service.IncrementValue("Age")); Console.WriteLine(service.IncrementValueBy("Age", 3)); Console.WriteLine(service.DecrementValue("Age")); Console.WriteLine(service.DecrementValueBy("Age", 3)); } } } }
using System; namespace MyRedis { /// <summary> /// Redis:Remote Dictionary Server 遠程字典服務器 /// 基于記憶體管理(資料存在記憶體),實作了5種資料結構(分別應對各種具體需求),單執行緒模型的應用程式(單行程單執行緒),對外提供插入--查詢--固化--集群功能, /// 正是因為基于記憶體管理所以速度快,可以用來提升性能,但是不能當資料庫,不能作為資料的最終依據, /// 單執行緒多行程的模式來提供集群服務, /// 單執行緒最大的好處就是原子性操作,就是要么都成功,要么都失敗,不會出現中間狀態,Redis每個命令都是原子性(因為單執行緒),不用考慮并發,不會出現中間狀態,(執行緒安全) /// Redis就是為開發而生,會為各種開發需求提供對應的解決方案, /// Redis只是為了提升性能,不做資料標準,任何的資料固化都是由資料庫完成的,Redis不能代替資料庫, /// Redis實作的5種資料結構:String、Hashtable、Set、ZSet和List, /// </summary> class Program { static void Main(string[] args) { ServiceStackTest.ShowString(); Console.ReadKey(); } } }
運行結果如下:


Redis中的String型別在專案中使用是最多的,想必大家都有所了解,此處就不再做過多的描述了,
二、使用Redis解決訂單秒殺超賣問題
首先先來看下什么是訂單秒殺超賣問題:
/// <summary> /// 模擬訂單秒殺超賣問題 /// 超賣:訂單數超過商品 /// 如果使用傳統的鎖來解決超賣問題合適嗎? /// 不合適,因為這個等于是單執行緒了,其他都要阻塞,會出現各種超時, /// -1的時候除了操作庫存,還得增加訂單,等支付等等, /// 10個商品秒殺,一次只能進一個? 違背了業務, /// </summary> public class OverSellFailedTest { private static bool _isGoOn = true; //秒殺活動是否結束 private static int _stock = 0; //商品庫存 public static void Show() { _stock = 10; for (int i = 0; i < 5000; i++) { int k = i; Task.Run(() => //每個執行緒就是一個用戶請求 { if (_isGoOn) { long index = _stock; Thread.Sleep(100); //模擬去資料庫查詢庫存 if (index >= 1) { _stock = _stock - 1; //更新庫存 Console.WriteLine($"{k.ToString("0000")}秒殺成功,秒殺商品索引為{index}"); //可以分佇列,去操作資料庫 } else { if (_isGoOn) { _isGoOn = false; } Console.WriteLine($"{k.ToString("0000")}秒殺失敗,秒殺商品索引為{index}"); } } else { Console.WriteLine($"{k.ToString("0000")}秒殺停止......"); } }); } } }
運行OverSellFailedTest.Show(),結果如下所示:

從運行結果可以看出不僅一個商品賣給了多個人,而且還出現了訂單數超過商品數,這就是典型的秒殺超賣問題,
下面我們來看下如何使用Redis解決訂單秒殺超賣問題:
/// <summary> /// 使用Redis解決訂單秒殺超賣問題 /// 超賣:訂單數超過商品 /// 1、Redis原子性操作--保證一個數值只出現一次--防止一個商品賣給多個人 /// 2、用上了Redis,一方面保證絕對不會超賣,另一方面沒有效率影響,還有撤單的時候增加庫存,可以繼續秒殺, /// 限制秒殺的庫存是放在redis,不是資料庫,不會造成資料的不一致性 /// 3、Redis能夠攔截無效的請求,如果沒有這一層,所有的請求壓力都到資料庫 /// 4、快取擊穿/穿透---快取down掉,請求全部到資料庫 /// 5、快取預熱功能---快取重啟,資料丟失,多了一個初始化快取資料動作(寫代碼去把資料讀出來放入快取) /// </summary> public class OverSellTest { private static bool _isGoOn = true; //秒殺活動是否結束 public static void Show() { using (RedisStringService service = new RedisStringService()) { service.Set<int>("Stock", 10); //庫存 } for (int i = 0; i < 5000; i++) { int k = i; Task.Run(() => //每個執行緒就是一個用戶請求 { using (RedisStringService service = new RedisStringService()) { if (_isGoOn) { long index = service.DecrementValue("Stock"); //減1并且回傳 if (index >= 0) { Console.WriteLine($"{k.ToString("0000")}秒殺成功,秒殺商品索引為{index}"); //service.IncrementValue("Stock"); //加1,如果取消了訂單則添加庫存繼續秒殺 //可以分佇列,去操作資料庫 } else { if (_isGoOn) { _isGoOn = false; } Console.WriteLine($"{k.ToString("0000")}秒殺失敗,秒殺商品索引為{index}"); } } else { Console.WriteLine($"{k.ToString("0000")}秒殺停止......"); } } }); } } }
運行OverSellTest.Show(),結果如下所示:

從運行結果可以看出使用Redis能夠很好的解決訂單秒殺超賣問題,
至此本文就全部介紹完了,如果覺得對您有所啟發請記得點個贊哦!!!
Demo原始碼:
鏈接:https://pan.baidu.com/s/1qbHQywQfhQSaSY-nwsFRrA 提取碼:78so
此文由博主精心撰寫轉載請保留此原文鏈接:https://www.cnblogs.com/xyh9039/p/13979522.html
著作權宣告:如有雷同純屬巧合,如有侵權請及時聯系本人修改,謝謝!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/218786.html
標籤:ASP.NET
上一篇:深度探秘.NET 5.0
