本系列將和你們分享Redis分佈式緩存,本章主要簡單介紹下Redis中的String類型,以及如何使用Redis解決訂單秒殺超賣問題。html
Redis中5種數據結構之String類型:key-value的緩存,支持過時,value不超過512M。redis
Redis是單線程的,好比SetAll & AppendToValue & GetValues & GetAndSetValue & IncrementValue & IncrementValueBy等等,這些看上去像是組合命令,但其實是一個具體的命令,是一個原子性的命令,不可能出現中間狀態,能夠應對一些併發狀況。下面咱們直接經過代碼來看下具體使用。數據庫
首先來看下Demo的項目結構:緩存
對於.Net而言,Redis操做通常使用ServiceStack.Redis 或者 StackExchange.Redis ,但整體來講ServiceStack.Redis性能更優。安全
此處推薦使用的是ServiceStack包,雖然它是收費的,有每小時6000次請求限制,可是它是開源的,能夠將它的源碼下載下來破解後使用,網上應該有挺多相關資料,有興趣的能夠去了解一波。服務器
首先先來看下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小時6000次請求限制--可破解) /// </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類型在項目中使用是最多的,想必你們都有所瞭解,此處就再也不作過多的描述了。
首先先來看下什麼是訂單秒殺超賣問題:
/// <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解決訂單秒殺超賣問題 /// 超賣:訂單數超過商品 /// 一、Redis原子性操做--保證一個數值只出現一次--防止一個商品賣給多我的 /// 二、用上了Redis,一方面保證絕對不會超賣,另外一方面沒有效率影響,還有撤單的時候增長庫存,能夠繼續秒殺, /// 限制秒殺的庫存是放在redis,不是數據庫,不會形成數據的不一致性 /// 三、Redis可以攔截無效的請求,若是沒有這一層,全部的請求壓力都到數據庫 /// 四、緩存擊穿/穿透---緩存down掉,請求所有到數據庫 /// 五、緩存預熱功能---緩存重啓,數據丟失,多了一個初始化緩存數據動做(寫代碼去把數據讀出來放入緩存) /// </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/1v6vGIBeIPUlTR9ASjXhcfA 提取碼:mjam
此文由博主精心撰寫轉載請保留此原文連接:https://www.cnblogs.com/xyh9039/p/13979522.html