.NetCore+Jexus代理+Redis模擬秒殺商品活動
開篇敘
本篇將和大家分享一下秒殺商品活動架構,採用的架構方案正如標題名稱.NetCore+Jexus代理+Redis,由於精力有限所以這裡只設計到商品新增,搶購,訂單查詢,處理佇列搶購訂單的功能;有不足或者不夠詳細的還請見諒,順手點個推薦也不錯;
a. 秒殺流程
b. 封裝
StackExchange.Redis
的使用類c. Ubuntu16.04上使用
Jexus
搭建代理完成分散式部署d.
NetCore
寫實時監控佇列服務
秒殺架構設計圖︿( ̄︶ ̄)︿三幅
一般業務性架構
後端分散式架構
整站分散式
專案工程結構描述
a. 該專案git開源地址: https://github.com/shenniubuxing3/SeckillPro ,線上效果地址: http://www.lovexins.com:3333/
b.
SeckillPro.Web
:面向使用者的web站點,主要提供商品展示,秒殺搶購,搶購結果,訂單列表等功能;c.
SeckillPro.Api
:主要處理秒殺活動的請求,然後加入到秒殺佇列中,以及訂單狀態的查詢介面;d.
SeckillPro.Server
:處理秒殺佇列的服務;根據Redis
模糊匹配key的方式,開啟多個商品秒殺的任務,並處理秒殺請求和改變訂單搶購狀態;e.
SeckillPro.Com
:整合公共的方法;這裡面前有操作Redis
的list,hash,string的封裝類;
SeckillPro.Web
商品後臺管理
對於商品活動來說,商品維護是必不可少的,由於這裡商品維護的資訊比較少,並且這裡只加入到了RedisDb
中,所以就不直接上程式碼了;一個列表,一個新增僅此而已;這裡就不再貼程式碼了,如果你感興趣可以去我的git上面看原始碼: https://github.com/shenniubuxing3/SeckillPro/blob/master/SeckillPro/SeckillPro.Web/Controllers/HomeController.cs
SeckillPro.Web
使用者端商品列表+秒殺請求+使用者訂單列表
商品列表和訂單列表沒有可以太多說的,一般訂單系統都有這兩個列表;關鍵點在於訂單秒殺流程中,咋們來簡單分析下面向客戶秒殺的流程需要注意的事項:
a. 限制秒殺開始時間和結束時間(測試未限制)
b. 未開始活動限制提交按鈕不可點(測試未限制)
c. 獲取真實剩餘庫存限制秒殺提交(獲取redis中商品hash儲存的真實剩餘量)
d. 把客戶的秒殺請求轉移到另外的api叢集,以此提高面向客戶端的web站點併發承載率(測試專案中我直接指定4545埠的api測試)
這裡就不再貼程式碼了,如果你感興趣可以去我的git上面看看這部分原始碼: https://github.com/shenniubuxing3/SeckillPro/blob/master/SeckillPro/SeckillPro.Web/Controllers/HomeController.cs
.NetCore寫處理秒殺活動佇列的服務
這個處理佇列服務處理流程:模糊匹配Redis
中每種商品的佇列key-》開啟不同商品的處理佇列任務-》處理秒殺訂單-》更新庫存和秒殺訂單狀態;
a. 模糊匹配Redis
中每種商品的佇列key
:這裡採用的是StackExchange.Redis
中指定redis
原生命令的方法來獲取匹配佇列key
,設計的程式碼如下:
/// <summary>
/// 模糊匹配redis中的key
/// </summary>
/// <param name="paramArr"></param>
/// <returns></returns>
public async Task<List<string>> MatchKeys(params string[] paramArr)
{
var list = new List<string>();
try
{
var result = await this.ExecuteAsync("keys", paramArr);
var valArr = ((RedisValue[])result);
foreach (var item in valArr)
{
list.Add(item);
}
}
catch (Exception ex) { }
return list;
}
/// <summary>
/// 執行redis原生命令
/// </summary>
/// <param name="cmd"></param>
/// <param name="paramArr"></param>
/// <returns></returns>
public async Task<RedisResult> ExecuteAsync(string cmd, params string[] paramArr)
{
try
{
var db = this.GetDb();
return await db.ExecuteAsync(cmd, paramArr);
}
catch (Exception ex) { }
return default(RedisResult);
}
b. 開啟不同商品的處理佇列任務:通過Task.Factory.StartNew(action,object)方法開啟不同商品的處理秒殺訂單的任務;
c. 更新庫存和秒殺訂單狀態:由於搶購商品要求庫存剩餘實時性,所以每處理一個搶購訂單,需要對該商品減去相應的庫存和修改秒殺訂單的狀態方便使用者檢視秒殺結果;
d. 處理佇列具體的實現程式碼可以去git看下,個人覺得還是有用的:https://github.com/shenniubuxing3/SeckillPro/blob/master/SeckillPro/SeckillPro.Server/Program.cs
使用Jexus代理部署分散式站點和介面
這裡部署的代理採用的是Jexus
代理;作為在linux
和unix
上部署.net程式實用的工具,真的很感謝jexus
作者;首先本篇講解的部署環境是ubunt16.04x64(至於這麼安裝jexus可以參考上一篇分享文章),為了更直觀的看出來效果我在伺服器上拷貝了兩份SeckillPro.Web
釋出的站點,他們程式碼都是一樣的只是分別把_Layout.cshtml
試圖模板中加入了埠7777
和8888
,我就用這兩個埠來測試jexus的代理效果;
測試方便直接分別在兩個複製站點中執行如下終端命令:dotnet SeckillPro.Web.dll http://ip:埠
;一個監聽7777埠一個監聽8888;執行命令效果圖:
監聽7777和8888埠成功後,我們就可以直接在瀏覽器輸入:http://172.16.9.66:7777 訪問,正常情況下能夠看到如下圖示例:
單個站點訪問沒問題了,下面開始配置jexus代理;只需要在jexus/siteconf的配置檔案中(我這裡是default配置檔案),增加如下設定:
注意reproxy引數:
a. 第一個/表示根目錄,一般不變
b. 多個被代理地址使用‘,’隔開;
c. 被代理地址後面也同樣需要加/
此時我們配置完後,只需要啟動jexus
就行了:./jws start
(怎麼啟動可以參考上一篇文章);當啟動jws成功後,我們就能通過配置的80埠,來訪問SeckillPro.Web
站點了,效果圖:
至於代理分發的策略暫不在本章的討論範圍內,如果可以建議去jexus官網瞭解下;同樣對於Seckill.Api
我們也可以這樣部署,這裡部署了個秒殺線上地址,有興趣的朋友可以點選試試:http://www.lovexins.com:3333/ (注:這裡沒有使用代理)
封裝StackExchange.Redis
的使用類StackRedis.cs
其實這個在之前已經分享過了,只不過只有操作string
和list
的分裝;本篇測試涉及到訂單查詢和商品查詢等功能,所以這裡我又擴充套件了對hash
的操作方法,可以說更豐富了吧,如果您正打算使用redis
或許直接用我這個封裝類是個不錯的打算;
public class StackRedis : IDisposable
{
#region 配置屬性 基於 StackExchange.Redis 封裝
//連線串 (注:IP:埠,屬性=,屬性=)
public string _ConnectionString = "127.0.0.1:6377,password=shenniubuxing3";
//操作的庫(注:預設0庫)
public int _Db = 0;
#endregion
#region 管理器物件
/// <summary>
/// 獲取redis操作類物件
/// </summary>
private static StackRedis _StackRedis;
private static object _locker_StackRedis = new object();
public static StackRedis Current
{
get
{
if (_StackRedis == null)
{
lock (_locker_StackRedis)
{
_StackRedis = _StackRedis ?? new StackRedis();
return _StackRedis;
}
}
return _StackRedis;
}
}
/// <summary>
/// 獲取併發連結管理器物件
/// </summary>
private static ConnectionMultiplexer _redis;
private static object _locker = new object();
public ConnectionMultiplexer Manager
{
get
{
if (_redis == null)
{
lock (_locker)
{
_redis = _redis ?? GetManager(this._ConnectionString);
return _redis;
}
}
return _redis;
}
}
/// <summary>
/// 獲取連結管理器
/// </summary>
/// <param name="connectionString"></param>
/// <returns></returns>
public ConnectionMultiplexer GetManager(string connectionString)
{
return ConnectionMultiplexer.Connect(connectionString);
}
/// <summary>
/// 獲取運算元據庫物件
/// </summary>
/// <returns></returns>
public IDatabase GetDb()
{
return Manager.GetDatabase(_Db);
}
#endregion
#region 操作方法
#region string 操作
/// <summary>
/// 根據Key移除
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public async Task<bool> Remove(string key)
{
var db = this.GetDb();
return await db.KeyDeleteAsync(key);
}
/// <summary>
/// 根據key獲取string結果
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public async Task<string> Get(string key)
{
var db = this.GetDb();
return await db.StringGetAsync(key);
}
/// <summary>
/// 根據key獲取string中的物件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async Task<T> Get<T>(string key)
{
var t = default(T);
try
{
var _str = await this.Get(key);
if (string.IsNullOrWhiteSpace(_str)) { return t; }
t = JsonConvert.DeserializeObject<T>(_str);
}
catch (Exception ex) { }
return t;
}
/// <summary>
/// 儲存string資料
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="expireMinutes"></param>
/// <returns></returns>
public async Task<bool> Set(string key, string value, int expireMinutes = 0)
{
var db = this.GetDb();
if (expireMinutes > 0)
{
return db.StringSet(key, value, TimeSpan.FromMinutes(expireMinutes));
}
return await db.StringSetAsync(key, value);
}
/// <summary>
/// 儲存物件資料到string
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="expireMinutes"></param>
/// <returns></returns>
public async Task<bool> Set<T>(string key, T value, int expireMinutes = 0)
{
try
{
var jsonOption = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var _str = JsonConvert.SerializeObject(value, jsonOption);
if (string.IsNullOrWhiteSpace(_str)) { return false; }
return await this.Set(key, _str, expireMinutes);
}
catch (Exception ex) { }
return false;
}
/// <summary>
/// 是否存在key
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async Task<bool> KeyExists(string key)
{
try
{
var db = this.GetDb();
return await db.KeyExistsAsync(key);
}
catch (Exception ex) { }
return false;
}
#endregion
#region hash操作
/// <summary>
/// 是否存在hash的列
/// </summary>
/// <param name="key"></param>
/// <param name="filedKey"></param>
/// <returns></returns>
public async Task<bool> HashFieldExists(string key, string filedKey)
{
try
{
if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(filedKey)) { return false; }
var result = await this.HashFieldsExists(key, new Dictionary<string, bool> { { filedKey, false } });
return result[filedKey];
}
catch (Exception ex) { }
return false;
}
/// <summary>
/// 是否存在hash的列集合
/// </summary>
/// <param name="key"></param>
/// <param name="dics"></param>
/// <returns></returns>
public async Task<Dictionary<string, bool>> HashFieldsExists(string key, Dictionary<string, bool> dics)
{
try
{
if (dics.Count <= 0) { return dics; }
var db = this.GetDb();
foreach (var fieldKey in dics.Keys)
{
dics[fieldKey] = await db.HashExistsAsync(key, fieldKey);
}
}
catch (Exception ex) { }
return dics;
}
/// <summary>
/// 設定hash
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="filedKey"></param>
/// <param name="t"></param>
/// <returns></returns>
public async Task<long> SetOrUpdateHashsField<T>(string key, string filedKey, T t, bool isAdd = true)
{
var result = 0L;
try
{
return await this.SetOrUpdateHashsFields<T>(key, new Dictionary<string, T> { { filedKey, t } }, isAdd);
}
catch (Exception ex) { }
return result;
}
/// <summary>
/// 設定hash集合,新增和更新操作
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="dics"></param>
/// <returns></returns>
public async Task<long> SetOrUpdateHashsFields<T>(string key, Dictionary<string, T> dics, bool isAdd = true)
{
var result = 0L;
try
{
var jsonOption = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var db = this.GetDb();
foreach (var fieldKey in dics.Keys)
{
var item = dics[fieldKey];
var _str = JsonConvert.SerializeObject(item, jsonOption);
result += await db.HashSetAsync(key, fieldKey, _str) ? 1 : 0;
if (!isAdd) { result++; }
}
return result;
}
catch (Exception ex) { }
return result;
}
/// <summary>
/// 移除hash的列
/// </summary>
/// <param name="key"></param>
/// <param name="filedKey"></param>
/// <returns></returns>
public async Task<bool> RemoveHashField(string key, string filedKey)
{
try
{
if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(filedKey)) { return false; }
var result = await this.RemoveHashFields(key, new Dictionary<string, bool> { { filedKey, false } });
return result[filedKey];
}
catch (Exception ex) { }
return false;
}
/// <summary>
/// 異常hash的列集合
/// </summary>
/// <param name="key"></param>
/// <param name="dics"></param>
/// <returns></returns>
public async Task<Dictionary<string, bool>> RemoveHashFields(string key, Dictionary<string, bool> dics)
{
try
{
var jsonOption = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var db = this.GetDb();
foreach (var fieldKey in dics.Keys)
{
dics[fieldKey] = await db.HashDeleteAsync(key, fieldKey);
}
return dics;
}
catch (Exception ex) { }
return dics;
}
/// <summary>
/// 設定hash
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="filedKey"></param>
/// <param name="t"></param>
/// <returns></returns>
public async Task<T> GetHashField<T>(string key, string filedKey)
{
var t = default(T);
try
{
var dics = await this.GetHashFields<T>(key, new Dictionary<string, T> { { filedKey, t } });
return dics[filedKey];
}
catch (Exception ex) { }
return t;
}
/// <summary>
/// 獲取hash的列值集合
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="dics"></param>
/// <returns></returns>
public async Task<Dictionary<string, T>> GetHashFields<T>(string key, Dictionary<string, T> dics)
{
try
{
var db = this.GetDb();
foreach (var fieldKey in dics.Keys)
{
var str = await db.HashGetAsync(key, fieldKey);
if (string.IsNullOrWhiteSpace(str)) { continue; }
dics[fieldKey] = JsonConvert.DeserializeObject<T>(str);
}
return dics;
}
catch (Exception ex) { }
return dics;
}
/// <summary>
/// 獲取hash的key的所有列的值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async Task<Dictionary<string, T>> GetHashs<T>(string key)
{
var dic = new Dictionary<string, T>();
try
{
var db = this.GetDb();
var hashFiles = await db.HashGetAllAsync(key);
foreach (var field in hashFiles)
{
dic[field.Name] = JsonConvert.DeserializeObject<T>(field.Value);
}
return dic;
}
catch (Exception ex) { }
return dic;
}
/// <summary>
/// 獲取hash的Key的所有列的值的list集合
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async Task<List<T>> GetHashsToList<T>(string key)
{
var list = new List<T>();
try
{
var db = this.GetDb();
var hashFiles = await db.HashGetAllAsync(key);
foreach (var field in hashFiles)
{
var item = JsonConvert.DeserializeObject<T>(field.Value);
if (item == null) { continue; }
list.Add(item);
}
}
catch (Exception ex) { }
return list;
}
#endregion
#region List操作(注:可以當做佇列使用)
/// <summary>
/// list長度
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async Task<long> GetListLen<T>(string key)
{
try
{
var db = this.GetDb();
return await db.ListLengthAsync(key);
}
catch (Exception ex) { }
return 0;
}
/// <summary>
/// 獲取List資料
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async Task<List<T>> GetList<T>(string key)
{
var t = new List<T>();
try
{
var db = this.GetDb();
var _values = await db.ListRangeAsync(key);
foreach (var item in _values)
{
if (string.IsNullOrWhiteSpace(item)) { continue; }
t.Add(JsonConvert.DeserializeObject<T>(item));
}
}
catch (Exception ex) { }
return t;
}
/// <summary>
/// 獲取佇列出口資料並移除
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async Task<T> GetListAndPop<T>(string key)
{
var t = default(T);
try
{
var db = this.GetDb();
var _str = await db.ListRightPopAsync(key);
if (string.IsNullOrWhiteSpace(_str)) { return t; }
t = JsonConvert.DeserializeObject<T>(_str);
}
catch (Exception ex) { }
return t;
}
/// <summary>
/// 集合物件新增到list左邊
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="values"></param>
/// <returns></returns>
public async Task<long> SetLists<T>(string key, List<T> values)
{
var result = 0L;
try
{
var jsonOption = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var db = this.GetDb();
foreach (var item in values)
{
var _str = JsonConvert.SerializeObject(item, jsonOption);
result += await db.ListLeftPushAsync(key, _str);
}
return result;
}
catch (Exception ex) { }
return result;
}
/// <summary>
/// 單個物件新增到list左邊
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public async Task<long> SetList<T>(string key, T value)
{
var result = 0L;
try
{
result = await this.SetLists(key, new List<T> { value });
}
catch (Exception ex) { }
return result;
}
#endregion
#region 額外擴充套件
public async Task<List<string>> MatchKeys(params string[] paramArr)
{
var list = new List<string>();
try
{
var result = await this.ExecuteAsync("keys", paramArr);
var valArr = ((RedisValue[])result);
foreach (var item in valArr)
{
list.Add(item);
}
}
catch (Exception ex) { }
return list;
}
/// <summary>
/// 執行redis原生命令
/// </summary>
/// <param name="cmd"></param>
/// <param name="paramArr"></param>
/// <returns></returns>
public async Task<RedisResult> ExecuteAsync(string cmd, params string[] paramArr)
{
try
{
var db = this.GetDb();
return await db.ExecuteAsync(cmd, paramArr);
}
catch (Exception ex) { }
return default(RedisResult);
}
/// <summary>
/// 手動回收管理器物件
/// </summary>
public void Dispose()
{
this.Dispose(_redis);
}
public void Dispose(ConnectionMultiplexer con)
{
if (con != null)
{
con.Close();
con.Dispose();
}
}
#endregion
#endregion
}
相關文章
- .NetCore+Redis模擬秒殺商品活動(分析)NetCoreRedis
- 同步秒殺實現:Redis在秒殺功能的實踐Redis
- PHP高併發 商品秒殺 問題的 2大種(MySQL or Redis) 解決方案PHPMySqlRedis
- Redis在秒殺功能的實踐Redis
- 模擬簡單的動態代理
- Redis輕鬆實現秒殺系統Redis
- 用Redis輕鬆實現秒殺系統Redis
- 基於redis分散式鎖實現“秒殺”Redis分散式
- PHP高併發商品秒殺問題的解決方案PHP
- Redis秒殺實戰-微信搶紅包-秒殺庫存,附案例原始碼(Jmeter壓測)Redis原始碼JMeter
- python版:單機redis實現秒殺,防止超限PythonRedis
- 大型PHP電商網站商品秒殺功能實現思路分析PHP網站
- 秒殺網
- 高併發下秒殺商品,必須知道的9個細節
- 直播帶貨app開發,制定商品秒殺倒數計時提示APP
- 【Redis核心知識】實現秒殺的三種方案Redis
- 高併發秒殺系統架構詳解,不是所有的秒殺都是秒殺!架構
- 重學 Java 設計模式:實戰享元模式「基於Redis秒殺,提供活動與庫存資訊查詢場景」Java設計模式Redis
- mumu模擬器設定代理
- 分享一個整合SSM框架的高併發和商品秒殺專案SSM框架
- 解密 Redis 助力雙十一背後電商秒殺系統解密Redis
- Redis 實現高併發下的搶購 / 秒殺功能Redis
- 【Redis場景4】單機環境下秒殺問題Redis
- 【高併發】秒殺系統架構解密,不是所有的秒殺都是秒殺(升級版)!!架構解密
- 秒殺系統
- 秒殺流程圖流程圖
- python使用requests秒殺茅臺(適用某寶,也可搶購其他商品)Python
- 騰訊雲2020雙11爆品秒殺活動都有哪些有吸引力的產品?
- Redis秒殺系統架構設計-微信搶紅包Redis架構
- 【Redis場景5】叢集秒殺最佳化-分散式鎖Redis分散式
- 秒殺系統分析
- 秒殺系統如何保證資料庫不崩潰以及防止商品超賣資料庫
- 從京東618秒殺聊聊秒殺限流的多種實現
- 【Python秒殺指令碼】淘寶或京東等秒殺搶購Python指令碼
- Java程式猿筆記——基於redis分散式鎖實現“秒殺”Java筆記Redis分散式
- 秒殺架構實踐架構
- 秒殺系統設計
- Redis+Lua解決高併發場景搶購秒殺問題Redis