本系列將和大家分享Redis分布式緩存,本章主要簡(jiǎn)單介紹下Redis中的String類(lèi)型,以及如何使用Redis解決訂單秒殺超賣(mài)問(wèn)題。
Redis中5種數(shù)據(jù)結(jié)構(gòu)之String類(lèi)型:key-value的緩存,支持過(guò)期,value不超過(guò)512M。
Redis是單線程的,比如SetAll AppendToValue GetValues GetAndSetValue IncrementValue IncrementValueBy等等,這些看上去像是組合命令,但實(shí)際上是一個(gè)具體的命令,是一個(gè)原子性的命令,不可能出現(xiàn)中間狀態(tài),可以應(yīng)對(duì)一些并發(fā)情況。下面我們直接通過(guò)代碼來(lái)看下具體使用。
首先來(lái)看下Demo的項(xiàng)目結(jié)構(gòu):
此處推薦使用的是ServiceStack包,雖然它是收費(fèi)的,有1小時(shí)3600次請(qǐng)求限制,但是它是開(kāi)源的,可以將它的源碼下載下來(lái)破解后使用,網(wǎng)上應(yīng)該有挺多相關(guān)資料,有興趣的可以去了解一波。
一、Redis中與String類(lèi)型相關(guān)的API
首先先來(lái)看下Redis客戶(hù)端的初始化工作:
using System;
namespace TianYa.Redis.Init
{
/// summary>
/// redis配置文件信息
/// 也可以放到配置文件去
/// /summary>
public sealed class RedisConfigInfo
{
/// summary>
/// 可寫(xiě)的Redis鏈接地址
/// format:ip1,ip2
///
/// 默認(rèn)6379端口
/// /summary>
public string WriteServerList = "127.0.0.1:6379";
/// summary>
/// 可讀的Redis鏈接地址
/// format:ip1,ip2
///
/// 默認(rèn)6379端口
/// /summary>
public string ReadServerList = "127.0.0.1:6379";
/// summary>
/// 最大寫(xiě)鏈接數(shù)
/// /summary>
public int MaxWritePoolSize = 60;
/// summary>
/// 最大讀鏈接數(shù)
/// /summary>
public int MaxReadPoolSize = 60;
/// summary>
/// 本地緩存到期時(shí)間,單位:秒
/// /summary>
public int LocalCacheTime = 180;
/// summary>
/// 自動(dòng)重啟
/// /summary>
public bool AutoStart = true;
/// summary>
/// 是否記錄日志,該設(shè)置僅用于排查redis運(yùn)行時(shí)出現(xiàn)的問(wèn)題,
/// 如redis工作正常,請(qǐng)關(guān)閉該項(xiàng)
/// /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客戶(hù)端池化管理
/// /summary>
private static PooledRedisClientManager _prcManager;
/// summary>
/// 靜態(tài)構(gòu)造方法,初始化鏈接池管理對(duì)象
/// /summary>
static RedisManager()
{
CreateManager();
}
/// summary>
/// 創(chuàng)建鏈接池管理對(duì)象
/// /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>
/// 客戶(hù)端緩存操作對(duì)象
/// /summary>
public static IRedisClient GetClient()
{
return _prcManager.GetClient();
}
}
}
using System;
using TianYa.Redis.Init;
using ServiceStack.Redis;
namespace TianYa.Redis.Service
{
/// summary>
/// redis操作的基類(lèi)
/// /summary>
public abstract class RedisBase : IDisposable
{
/// summary>
/// Redis客戶(hù)端
/// /summary>
protected IRedisClient _redisClient { get; private set; }
/// summary>
/// 構(gòu)造函數(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事務(wù)處理示例
/// /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(); //事務(wù)提交
}
catch (Exception ex)
{
irt.Rollback(); //事務(wù)回滾
throw ex;
}
}
}
/// summary>
/// 清除全部數(shù)據(jù) 請(qǐng)小心
/// /summary>
public virtual void FlushAll()
{
_redisClient.FlushAll();
}
/// summary>
/// 保存數(shù)據(jù)DB文件到硬盤(pán)
/// /summary>
public void Save()
{
_redisClient.Save(); //阻塞式Save
}
/// summary>
/// 異步保存數(shù)據(jù)DB文件到硬盤(pán)
/// /summary>
public void SaveAsync()
{
_redisClient.SaveAsync(); //異步Save
}
}
}
下面直接給大家Show一波Redis中與String類(lèi)型相關(guān)的API:
using System;
using System.Collections.Generic;
namespace TianYa.Redis.Service
{
/// summary>
/// key-value 鍵值對(duì) value可以是序列化的數(shù)據(jù) (字符串)
/// /summary>
public class RedisStringService : RedisBase
{
#region 賦值
/// summary>
/// 設(shè)置永久緩存
/// /summary>
/// param name="key">存儲(chǔ)的鍵/param>
/// param name="value">存儲(chǔ)的值/param>
/// returns>/returns>
public bool Set(string key, string value)
{
return base._redisClient.Set(key, value);
}
/// summary>
/// 設(shè)置永久緩存
/// /summary>
/// param name="key">存儲(chǔ)的鍵/param>
/// param name="value">存儲(chǔ)的值/param>
/// returns>/returns>
public bool SetT>(string key, T value)
{
return base._redisClient.SetT>(key, value);
}
/// summary>
/// 帶有過(guò)期時(shí)間的緩存
/// /summary>
/// param name="key">存儲(chǔ)的鍵/param>
/// param name="value">存儲(chǔ)的值/param>
/// param name="expireTime">過(guò)期時(shí)間/param>
/// returns>/returns>
public bool Set(string key, string value, DateTime expireTime)
{
return base._redisClient.Set(key, value, expireTime);
}
/// summary>
/// 帶有過(guò)期時(shí)間的緩存
/// /summary>
/// param name="key">存儲(chǔ)的鍵/param>
/// param name="value">存儲(chǔ)的值/param>
/// param name="expireTime">過(guò)期時(shí)間/param>
/// returns>/returns>
public bool SetT>(string key, T value, DateTime expireTime)
{
return base._redisClient.SetT>(key, value, expireTime);
}
/// summary>
/// 帶有過(guò)期時(shí)間的緩存
/// /summary>
/// param name="key">存儲(chǔ)的鍵/param>
/// param name="value">存儲(chǔ)的值/param>
/// param name="expireTime">過(guò)期時(shí)間/param>
/// returns>/returns>
public bool SetT>(string key, T value, TimeSpan expireTime)
{
return base._redisClient.SetT>(key, value, expireTime);
}
/// summary>
/// 設(shè)置多個(gè)key/value
/// /summary>
public void SetAll(Dictionarystring, string> dic)
{
base._redisClient.SetAll(dic);
}
#endregion 賦值
#region 追加
/// summary>
/// 在原有key的value值之后追加value,沒(méi)有就新增一項(xiàng)
/// /summary>
public long AppendToValue(string key, string value)
{
return base._redisClient.AppendToValue(key, value);
}
#endregion 追加
#region 獲取值
/// summary>
/// 讀取緩存
/// /summary>
/// param name="key">存儲(chǔ)的鍵/param>
/// returns>/returns>
public string Get(string key)
{
return base._redisClient.GetValue(key);
}
/// summary>
/// 讀取緩存
/// /summary>
/// param name="key">存儲(chǔ)的鍵/param>
/// returns>/returns>
public T GetT>(string key)
{
return
_redisClient.ContainsKey(key)
? _redisClient.GetT>(key)
: default;
}
/// summary>
/// 獲取多個(gè)key的value值
/// /summary>
/// param name="keys">存儲(chǔ)的鍵集合/param>
/// returns>/returns>
public Liststring> Get(Liststring> keys)
{
return base._redisClient.GetValues(keys);
}
/// summary>
/// 獲取多個(gè)key的value值
/// /summary>
/// param name="keys">存儲(chǔ)的鍵集合/param>
/// returns>/returns>
public ListT> GetT>(Liststring> keys)
{
return base._redisClient.GetValuesT>(keys);
}
#endregion 獲取值
#region 獲取舊值賦上新值
/// summary>
/// 獲取舊值賦上新值
/// /summary>
/// param name="key">存儲(chǔ)的鍵/param>
/// param name="value">存儲(chǔ)的值/param>
/// returns>/returns>
public string GetAndSetValue(string key, string value)
{
return base._redisClient.GetAndSetValue(key, value);
}
#endregion 獲取舊值賦上新值
#region 移除緩存
/// summary>
/// 移除緩存
/// /summary>
/// param name="key">存儲(chǔ)的鍵/param>
/// returns>/returns>
public bool Remove(string key)
{
return _redisClient.Remove(key);
}
/// summary>
/// 移除多個(gè)緩存
/// /summary>
/// param name="keys">存儲(chǔ)的鍵集合/param>
public void RemoveAll(Liststring> keys)
{
_redisClient.RemoveAll(keys);
}
#endregion 移除緩存
#region 輔助方法
/// summary>
/// 是否存在緩存
/// /summary>
/// param name="key">存儲(chǔ)的鍵/param>
/// returns>/returns>
public bool ContainsKey(string key)
{
return _redisClient.ContainsKey(key);
}
/// summary>
/// 獲取值的長(zhǎng)度
/// /summary>
/// param name="key">存儲(chǔ)的鍵/param>
/// returns>/returns>
public long GetStringCount(string key)
{
return base._redisClient.GetStringCount(key);
}
/// summary>
/// 自增1,返回自增后的值
/// /summary>
/// param name="key">存儲(chǔ)的鍵/param>
/// returns>/returns>
public long IncrementValue(string key)
{
return base._redisClient.IncrementValue(key);
}
/// summary>
/// 自增count,返回自增后的值
/// /summary>
/// param name="key">存儲(chǔ)的鍵/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">存儲(chǔ)的鍵/param>
/// returns>/returns>
public long DecrementValue(string key)
{
return base._redisClient.DecrementValue(key);
}
/// summary>
/// 自減count,返回自減后的值
/// /summary>
/// param name="key">存儲(chǔ)的鍵/param>
/// param name="count">自減量/param>
/// returns>/returns>
public long DecrementValueBy(string key, int count)
{
return base._redisClient.DecrementValueBy(key, count);
}
#endregion 輔助方法
}
}
測(cè)試如下:
using System;
namespace MyRedis
{
/// summary>
/// 學(xué)生類(lèi)
/// /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封裝測(cè)試 五大結(jié)構(gòu)理解 (1小時(shí)3600次請(qǐng)求限制--可破解)
/// /summary>
public class ServiceStackTest
{
/// summary>
/// String
/// key-value的緩存,支持過(guò)期,value不超過(guò)512M
/// Redis是單線程的,比如SetAll AppendToValue GetValues GetAndSetValue IncrementValue IncrementValueBy,
/// 這些看上去是組合命令,但實(shí)際上是一個(gè)具體的命令,是一個(gè)原子性的命令,不可能出現(xiàn)中間狀態(tài),可以應(yīng)對(duì)一些并發(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.GetStudent>("student1");
Console.WriteLine(JsonConvert.SerializeObject(stu));
service.Setint>("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 遠(yuǎn)程字典服務(wù)器
/// 基于內(nèi)存管理(數(shù)據(jù)存在內(nèi)存),實(shí)現(xiàn)了5種數(shù)據(jù)結(jié)構(gòu)(分別應(yīng)對(duì)各種具體需求),單線程模型的應(yīng)用程序(單進(jìn)程單線程),對(duì)外提供插入--查詢(xún)--固化--集群功能。
/// 正是因?yàn)榛趦?nèi)存管理所以速度快,可以用來(lái)提升性能。但是不能當(dāng)數(shù)據(jù)庫(kù),不能作為數(shù)據(jù)的最終依據(jù)。
/// 單線程多進(jìn)程的模式來(lái)提供集群服務(wù)。
/// 單線程最大的好處就是原子性操作,就是要么都成功,要么都失敗,不會(huì)出現(xiàn)中間狀態(tài)。Redis每個(gè)命令都是原子性(因?yàn)閱尉€程),不用考慮并發(fā),不會(huì)出現(xiàn)中間狀態(tài)。(線程安全)
/// Redis就是為開(kāi)發(fā)而生,會(huì)為各種開(kāi)發(fā)需求提供對(duì)應(yīng)的解決方案。
/// Redis只是為了提升性能,不做數(shù)據(jù)標(biāo)準(zhǔn)。任何的數(shù)據(jù)固化都是由數(shù)據(jù)庫(kù)完成的,Redis不能代替數(shù)據(jù)庫(kù)。
/// Redis實(shí)現(xiàn)的5種數(shù)據(jù)結(jié)構(gòu):String、Hashtable、Set、ZSet和List。
/// /summary>
class Program
{
static void Main(string[] args)
{
ServiceStackTest.ShowString();
Console.ReadKey();
}
}
}
運(yùn)行結(jié)果如下:
Redis中的String類(lèi)型在項(xiàng)目中使用是最多的,想必大家都有所了解,此處就不再做過(guò)多的描述了。
二、使用Redis解決訂單秒殺超賣(mài)問(wèn)題
首先先來(lái)看下什么是訂單秒殺超賣(mài)問(wèn)題:
/// summary>
/// 模擬訂單秒殺超賣(mài)問(wèn)題
/// 超賣(mài):訂單數(shù)超過(guò)商品
/// 如果使用傳統(tǒng)的鎖來(lái)解決超賣(mài)問(wèn)題合適嗎?
/// 不合適,因?yàn)檫@個(gè)等于是單線程了,其他都要阻塞,會(huì)出現(xiàn)各種超時(shí)。
/// -1的時(shí)候除了操作庫(kù)存,還得增加訂單,等支付等等。
/// 10個(gè)商品秒殺,一次只能進(jìn)一個(gè)? 違背了業(yè)務(wù)。
/// /summary>
public class OverSellFailedTest
{
private static bool _isGoOn = true; //秒殺活動(dòng)是否結(jié)束
private static int _stock = 0; //商品庫(kù)存
public static void Show()
{
_stock = 10;
for (int i = 0; i 5000; i++)
{
int k = i;
Task.Run(() => //每個(gè)線程就是一個(gè)用戶(hù)請(qǐng)求
{
if (_isGoOn)
{
long index = _stock;
Thread.Sleep(100); //模擬去數(shù)據(jù)庫(kù)查詢(xún)庫(kù)存
if (index >= 1)
{
_stock = _stock - 1; //更新庫(kù)存
Console.WriteLine($"{k.ToString("0000")}秒殺成功,秒殺商品索引為{index}");
//可以分隊(duì)列,去操作數(shù)據(jù)庫(kù)
}
else
{
if (_isGoOn)
{
_isGoOn = false;
}
Console.WriteLine($"{k.ToString("0000")}秒殺失敗,秒殺商品索引為{index}");
}
}
else
{
Console.WriteLine($"{k.ToString("0000")}秒殺停止......");
}
});
}
}
}
運(yùn)行OverSellFailedTest.Show(),結(jié)果如下所示:
從運(yùn)行結(jié)果可以看出不僅一個(gè)商品賣(mài)給了多個(gè)人,而且還出現(xiàn)了訂單數(shù)超過(guò)商品數(shù),這就是典型的秒殺超賣(mài)問(wèn)題。
下面我們來(lái)看下如何使用Redis解決訂單秒殺超賣(mài)問(wèn)題:
/// summary>
/// 使用Redis解決訂單秒殺超賣(mài)問(wèn)題
/// 超賣(mài):訂單數(shù)超過(guò)商品
/// 1、Redis原子性操作--保證一個(gè)數(shù)值只出現(xiàn)一次--防止一個(gè)商品賣(mài)給多個(gè)人
/// 2、用上了Redis,一方面保證絕對(duì)不會(huì)超賣(mài),另一方面沒(méi)有效率影響,還有撤單的時(shí)候增加庫(kù)存,可以繼續(xù)秒殺,
/// 限制秒殺的庫(kù)存是放在redis,不是數(shù)據(jù)庫(kù),不會(huì)造成數(shù)據(jù)的不一致性
/// 3、Redis能夠攔截?zé)o效的請(qǐng)求,如果沒(méi)有這一層,所有的請(qǐng)求壓力都到數(shù)據(jù)庫(kù)
/// 4、緩存擊穿/穿透---緩存down掉,請(qǐng)求全部到數(shù)據(jù)庫(kù)
/// 5、緩存預(yù)熱功能---緩存重啟,數(shù)據(jù)丟失,多了一個(gè)初始化緩存數(shù)據(jù)動(dòng)作(寫(xiě)代碼去把數(shù)據(jù)讀出來(lái)放入緩存)
/// /summary>
public class OverSellTest
{
private static bool _isGoOn = true; //秒殺活動(dòng)是否結(jié)束
public static void Show()
{
using (RedisStringService service = new RedisStringService())
{
service.Setint>("Stock", 10); //庫(kù)存
}
for (int i = 0; i 5000; i++)
{
int k = i;
Task.Run(() => //每個(gè)線程就是一個(gè)用戶(hù)請(qǐng)求
{
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,如果取消了訂單則添加庫(kù)存繼續(xù)秒殺
//可以分隊(duì)列,去操作數(shù)據(jù)庫(kù)
}
else
{
if (_isGoOn)
{
_isGoOn = false;
}
Console.WriteLine($"{k.ToString("0000")}秒殺失敗,秒殺商品索引為{index}");
}
}
else
{
Console.WriteLine($"{k.ToString("0000")}秒殺停止......");
}
}
});
}
}
}
運(yùn)行OverSellTest.Show(),結(jié)果如下所示:
從運(yùn)行結(jié)果可以看出使用Redis能夠很好的解決訂單秒殺超賣(mài)問(wèn)題。
至此本文就全部介紹完了,如果覺(jué)得對(duì)您有所啟發(fā)請(qǐng)記得點(diǎn)個(gè)贊哦!!!
Demo源碼:
鏈接: https://pan.baidu.com/s/1vukiDxOLQYZX4Qd94izMpQ 提取碼: bdfm
此文由博主精心撰寫(xiě)轉(zhuǎn)載請(qǐng)保留此原文鏈接:https://www.cnblogs.com/xyh9039/p/13979522.html
版權(quán)聲明:如有雷同純屬巧合,如有侵權(quán)請(qǐng)及時(shí)聯(lián)系本人修改,謝謝?。?!
到此這篇關(guān)于Redis中的String類(lèi)型及使用Redis解決訂單秒殺超賣(mài)問(wèn)題的文章就介紹到這了,更多相關(guān)Redis解決訂單秒殺超賣(mài)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
您可能感興趣的文章:- 使用redis的increment()方法實(shí)現(xiàn)計(jì)數(shù)器功能案例
- Python獲取Redis所有Key以及內(nèi)容的方法
- 基于redis key占用內(nèi)存量分析