Redis中的String類型及使用Redis解決訂單秒殺超賣問題
本系列將和大家分享Redis分布式緩存,本章主要簡單介紹下Redis中的String類型,以及如何使用Redis解決訂單秒殺超賣問題。
Redis中5種數(shù)據(jù)結構之String類型:key-value的緩存,支持過期,value不超過512M。
Redis是單線程的,比如SetAll & AppendToValue & GetValues & GetAndSetValue & IncrementValue & IncrementValueBy等等,這些看上去像是組合命令,但實際上是一個具體的命令,是一個原子性的命令,不可能出現(xiàn)中間狀態(tài),可以應對一些并發(fā)情況。下面我們直接通過代碼來看下具體使用。
首先來看下Demo的項目結構:
此處推薦使用的是ServiceStack包,雖然它是收費的,有1小時3600次請求限制,但是它是開源的,可以將它的源碼下載下來破解后使用,網(wǎng)上應該有挺多相關資料,有興趣的可以去了解一波。
一、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> /// 最大寫鏈接數(shù) /// </summary> public int MaxWritePoolSize = 60; /// <summary> /// 最大讀鏈接數(shù) /// </summary> public int MaxReadPoolSize = 60; /// <summary> /// 本地緩存到期時間,單位:秒 /// </summary> public int LocalCacheTime = 180; /// <summary> /// 自動重啟 /// </summary> public bool AutoStart = true; /// <summary> /// 是否記錄日志,該設置僅用于排查redis運行時出現(xiàn)的問題, /// 如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> /// 靜態(tài)構造方法,初始化鏈接池管理對象 /// </summary> static RedisManager() { CreateManager(); } /// <summary> /// 創(chuàng)建鏈接池管理對象 /// </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> /// 構造函數(shù) /// </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> /// 清除全部數(shù)據(jù) 請小心 /// </summary> public virtual void FlushAll() { _redisClient.FlushAll(); } /// <summary> /// 保存數(shù)據(jù)DB文件到硬盤 /// </summary> public void Save() { _redisClient.Save(); //阻塞式Save } /// <summary> /// 異步保存數(shù)據(jù)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可以是序列化的數(shù)據(jù) (字符串) /// </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, /// 這些看上去是組合命令,但實際上是一個具體的命令,是一個原子性的命令,不可能出現(xiàn)中間狀態(tài),可以應對一些并發(fā)情況 /// </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 遠程字典服務器 /// 基于內(nèi)存管理(數(shù)據(jù)存在內(nèi)存),實現(xiàn)了5種數(shù)據(jù)結構(分別應對各種具體需求),單線程模型的應用程序(單進程單線程),對外提供插入--查詢--固化--集群功能。 /// 正是因為基于內(nèi)存管理所以速度快,可以用來提升性能。但是不能當數(shù)據(jù)庫,不能作為數(shù)據(jù)的最終依據(jù)。 /// 單線程多進程的模式來提供集群服務。 /// 單線程最大的好處就是原子性操作,就是要么都成功,要么都失敗,不會出現(xiàn)中間狀態(tài)。Redis每個命令都是原子性(因為單線程),不用考慮并發(fā),不會出現(xiàn)中間狀態(tài)。(線程安全) /// Redis就是為開發(fā)而生,會為各種開發(fā)需求提供對應的解決方案。 /// Redis只是為了提升性能,不做數(shù)據(jù)標準。任何的數(shù)據(jù)固化都是由數(shù)據(jù)庫完成的,Redis不能代替數(shù)據(jù)庫。 /// Redis實現(xiàn)的5種數(shù)據(jù)結構:String、Hashtable、Set、ZSet和List。 /// </summary> class Program { static void Main(string[] args) { ServiceStackTest.ShowString(); Console.ReadKey(); } } }
運行結果如下:
Redis中的String類型在項目中使用是最多的,想必大家都有所了解,此處就不再做過多的描述了。
二、使用Redis解決訂單秒殺超賣問題
首先先來看下什么是訂單秒殺超賣問題:
/// <summary> /// 模擬訂單秒殺超賣問題 /// 超賣:訂單數(shù)超過商品 /// 如果使用傳統(tǒng)的鎖來解決超賣問題合適嗎? /// 不合適,因為這個等于是單線程了,其他都要阻塞,會出現(xiàn)各種超時。 /// -1的時候除了操作庫存,還得增加訂單,等支付等等。 /// 10個商品秒殺,一次只能進一個? 違背了業(yè)務。 /// </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); //模擬去數(shù)據(jù)庫查詢庫存 if (index >= 1) { _stock = _stock - 1; //更新庫存 Console.WriteLine($"{k.ToString("0000")}秒殺成功,秒殺商品索引為{index}"); //可以分隊列,去操作數(shù)據(jù)庫 } else { if (_isGoOn) { _isGoOn = false; } Console.WriteLine($"{k.ToString("0000")}秒殺失敗,秒殺商品索引為{index}"); } } else { Console.WriteLine($"{k.ToString("0000")}秒殺停止......"); } }); } } }
運行OverSellFailedTest.Show(),結果如下所示:
從運行結果可以看出不僅一個商品賣給了多個人,而且還出現(xiàn)了訂單數(shù)超過商品數(shù),這就是典型的秒殺超賣問題。
下面我們來看下如何使用Redis解決訂單秒殺超賣問題:
/// <summary> /// 使用Redis解決訂單秒殺超賣問題 /// 超賣:訂單數(shù)超過商品 /// 1、Redis原子性操作--保證一個數(shù)值只出現(xiàn)一次--防止一個商品賣給多個人 /// 2、用上了Redis,一方面保證絕對不會超賣,另一方面沒有效率影響,還有撤單的時候增加庫存,可以繼續(xù)秒殺, /// 限制秒殺的庫存是放在redis,不是數(shù)據(jù)庫,不會造成數(shù)據(jù)的不一致性 /// 3、Redis能夠攔截無效的請求,如果沒有這一層,所有的請求壓力都到數(shù)據(jù)庫 /// 4、緩存擊穿/穿透---緩存down掉,請求全部到數(shù)據(jù)庫 /// 5、緩存預熱功能---緩存重啟,數(shù)據(jù)丟失,多了一個初始化緩存數(shù)據(jù)動作(寫代碼去把數(shù)據(jù)讀出來放入緩存) /// </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,如果取消了訂單則添加庫存繼續(xù)秒殺 //可以分隊列,去操作數(shù)據(jù)庫 } else { if (_isGoOn) { _isGoOn = false; } Console.WriteLine($"{k.ToString("0000")}秒殺失敗,秒殺商品索引為{index}"); } } else { Console.WriteLine($"{k.ToString("0000")}秒殺停止......"); } } }); } } }
運行OverSellTest.Show(),結果如下所示:
從運行結果可以看出使用Redis能夠很好的解決訂單秒殺超賣問題。
至此本文就全部介紹完了,如果覺得對您有所啟發(fā)請記得點個贊哦?。?!
Demo源碼:
鏈接: https://pan.baidu.com/s/1vukiDxOLQYZX4Qd94izMpQ 提取碼: bdfm
此文由博主精心撰寫轉載請保留此原文鏈接:https://www.cnblogs.com/xyh9039/p/13979522.html
版權聲明:如有雷同純屬巧合,如有侵權請及時聯(lián)系本人修改,謝謝!??!
到此這篇關于Redis中的String類型及使用Redis解決訂單秒殺超賣問題的文章就介紹到這了,更多相關Redis解決訂單秒殺超賣內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
在Redis集群中使用pipeline批量插入的實現(xiàn)方法
這篇文章主要介紹了在Redis集群中使用pipeline批量插入的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-05-05RediSearch加RedisJSON大于Elasticsearch的搜索存儲引擎
這篇文章主要為大家介紹了RediSearch加RedisJSON大于Elasticsearch的王炸使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-07-07redis性能優(yōu)化之生產(chǎn)中實際遇到的問題及排查總結
這篇文章主要介紹了redis性能優(yōu)化之生產(chǎn)中實際遇到的問題及排查總結,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12