本篇將和你們分享一下秒殺商品活動架構,採用的架構方案正如標題名稱.NetCore+Jexus代理+Redis,因爲精力有限因此這裏只設計到商品添加,搶購,訂單查詢,處理隊列搶購訂單的功能;有不足或者不夠詳細的還請見諒,順手點個推薦也不錯;html
a. 秒殺流程linux
b. 封裝StackExchange.Redis的使用類git
c. Ubuntu16.04上使用Jexus搭建代理完成分佈式部署github
d. NetCore寫實時監控隊列服務web
1. 通常業務性架構redis
2. 後端分佈式架構數據庫
3. 整站分佈式json
a. 該項目git開源地址: https://github.com/shenniubuxing3/SeckillPro ,線上效果地址: http://www.lovexins.com:3333/後端
b. SeckillPro.Web:面向用戶的web站點,主要提供商品展現,秒殺搶購,搶購結果,訂單列表等功能;api
c. SeckillPro.Api:主要處理秒殺活動的請求,而後加入到秒殺隊列中,以及訂單狀態的查詢接口;
d. SeckillPro.Server:處理秒殺隊列的服務;根據Redis模糊匹配key的方式,開啓多個商品秒殺的任務,並處理秒殺請求和改變訂單搶購狀態;
e. SeckillPro.Com:集成公共的方法;這裏面前有操做Redis的list,hash,string的封裝類;
對於商品活動來講,商品維護是必不可少的,因爲這裏商品維護的信息比較少,而且這裏只加入到了RedisDb中,因此就不直接上代碼了;一個列表,一個添加僅此而已;這裏就再也不貼代碼了,若是你感興趣能夠去個人git上面看源碼: https://github.com/shenniubuxing3/SeckillPro/blob/master/SeckillPro/SeckillPro.Web/Controllers/HomeController.cs
商品列表和訂單列表沒有能夠太多說的,通常訂單系統都有這兩個列表;關鍵點在於訂單秒殺流程中,咋們來簡單分析下面向客戶秒殺的流程須要注意的事項:
a. 限制秒殺開始時間和結束時間(測試未限制)
b. 未開始活動限制提交按鈕不可點(測試未限制)
c. 獲取真實剩餘庫存限制秒殺提交(獲取redis中商品hash存儲的真實剩餘量)
d. 把客戶的秒殺請求轉移到另外的api集羣,以此提升面向客戶端的web站點併發承載率(測試項目中我直接指定4545端口的api測試)
這裏就再也不貼代碼了,若是你感興趣能夠去個人git上面看看這部分源碼: https://github.com/shenniubuxing3/SeckillPro/blob/master/SeckillPro/SeckillPro.Web/Controllers/HomeController.cs
這個處理隊列服務處理流程:模糊匹配Redis中每種商品的隊列key-》開啓不一樣商品的處理隊列任務-》處理秒殺訂單-》更新庫存和秒殺訂單狀態;
a. 模糊匹配Redis中每種商品的隊列key:這裏採用的是StackExchange.Redis中指定redis原生命令的方法來獲取匹配隊列key,設計的代碼以下:
1 /// <summary> 2 /// 模糊匹配redis中的key 3 /// </summary> 4 /// <param name="paramArr"></param> 5 /// <returns></returns> 6 public async Task<List<string>> MatchKeys(params string[] paramArr) 7 { 8 var list = new List<string>(); 9 try 10 { 11 var result = await this.ExecuteAsync("keys", paramArr); 12 13 var valArr = ((RedisValue[])result); 14 foreach (var item in valArr) 15 { 16 list.Add(item); 17 } 18 } 19 catch (Exception ex) { } 20 return list; 21 } 22 23 /// <summary> 24 /// 執行redis原生命令 25 /// </summary> 26 /// <param name="cmd"></param> 27 /// <param name="paramArr"></param> 28 /// <returns></returns> 29 public async Task<RedisResult> ExecuteAsync(string cmd, params string[] paramArr) 30 { 31 try 32 { 33 var db = this.GetDb(); 34 return await db.ExecuteAsync(cmd, paramArr); 35 } 36 catch (Exception ex) { } 37 return default(RedisResult); 38 }
b. 開啓不一樣商品的處理隊列任務:經過Task.Factory.StartNew(action,object)方法開啓不一樣商品的處理秒殺訂單的任務;
c. 更新庫存和秒殺訂單狀態:因爲搶購商品要求庫存剩餘實時性,因此每處理一個搶購訂單,須要對該商品減去相應的庫存和修改秒殺訂單的狀態方便用戶查看秒殺結果;
d. 處理隊列具體的實現代碼能夠去git看下,我的以爲仍是有用的:https://github.com/shenniubuxing3/SeckillPro/blob/master/SeckillPro/SeckillPro.Server/Program.cs
這裏部署的代理採用的是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/ (注:這裏沒有使用代理)
其實這個在以前已經分享過了,只不過只有操做string和list的分裝;本篇測試涉及到訂單查詢和商品查詢等功能,因此這裏我又擴展了對hash的操做方法,能夠說更豐富了吧,若是您正打算使用redis或許直接用我這個封裝類是個不錯的打算;
1 public class StackRedis : IDisposable 2 { 3 #region 配置屬性 基於 StackExchange.Redis 封裝 4 //鏈接串 (注:IP:端口,屬性=,屬性=) 5 public string _ConnectionString = "127.0.0.1:6377,password=shenniubuxing3"; 6 //操做的庫(注:默認0庫) 7 public int _Db = 0; 8 #endregion 9 10 #region 管理器對象 11 12 /// <summary> 13 /// 獲取redis操做類對象 14 /// </summary> 15 private static StackRedis _StackRedis; 16 private static object _locker_StackRedis = new object(); 17 public static StackRedis Current 18 { 19 get 20 { 21 if (_StackRedis == null) 22 { 23 lock (_locker_StackRedis) 24 { 25 _StackRedis = _StackRedis ?? new StackRedis(); 26 return _StackRedis; 27 } 28 } 29 30 return _StackRedis; 31 } 32 } 33 34 /// <summary> 35 /// 獲取併發連接管理器對象 36 /// </summary> 37 private static ConnectionMultiplexer _redis; 38 private static object _locker = new object(); 39 public ConnectionMultiplexer Manager 40 { 41 get 42 { 43 if (_redis == null) 44 { 45 lock (_locker) 46 { 47 _redis = _redis ?? GetManager(this._ConnectionString); 48 return _redis; 49 } 50 } 51 52 return _redis; 53 } 54 } 55 56 /// <summary> 57 /// 獲取連接管理器 58 /// </summary> 59 /// <param name="connectionString"></param> 60 /// <returns></returns> 61 public ConnectionMultiplexer GetManager(string connectionString) 62 { 63 return ConnectionMultiplexer.Connect(connectionString); 64 } 65 66 /// <summary> 67 /// 獲取操做數據庫對象 68 /// </summary> 69 /// <returns></returns> 70 public IDatabase GetDb() 71 { 72 return Manager.GetDatabase(_Db); 73 } 74 #endregion 75 76 #region 操做方法 77 78 #region string 操做 79 80 /// <summary> 81 /// 根據Key移除 82 /// </summary> 83 /// <param name="key"></param> 84 /// <returns></returns> 85 public async Task<bool> Remove(string key) 86 { 87 var db = this.GetDb(); 88 89 return await db.KeyDeleteAsync(key); 90 } 91 92 /// <summary> 93 /// 根據key獲取string結果 94 /// </summary> 95 /// <param name="key"></param> 96 /// <returns></returns> 97 public async Task<string> Get(string key) 98 { 99 var db = this.GetDb(); 100 return await db.StringGetAsync(key); 101 } 102 103 /// <summary> 104 /// 根據key獲取string中的對象 105 /// </summary> 106 /// <typeparam name="T"></typeparam> 107 /// <param name="key"></param> 108 /// <returns></returns> 109 public async Task<T> Get<T>(string key) 110 { 111 var t = default(T); 112 try 113 { 114 var _str = await this.Get(key); 115 if (string.IsNullOrWhiteSpace(_str)) { return t; } 116 117 t = JsonConvert.DeserializeObject<T>(_str); 118 } 119 catch (Exception ex) { } 120 return t; 121 } 122 123 /// <summary> 124 /// 存儲string數據 125 /// </summary> 126 /// <param name="key"></param> 127 /// <param name="value"></param> 128 /// <param name="expireMinutes"></param> 129 /// <returns></returns> 130 public async Task<bool> Set(string key, string value, int expireMinutes = 0) 131 { 132 var db = this.GetDb(); 133 if (expireMinutes > 0) 134 { 135 return db.StringSet(key, value, TimeSpan.FromMinutes(expireMinutes)); 136 } 137 return await db.StringSetAsync(key, value); 138 } 139 140 /// <summary> 141 /// 存儲對象數據到string 142 /// </summary> 143 /// <typeparam name="T"></typeparam> 144 /// <param name="key"></param> 145 /// <param name="value"></param> 146 /// <param name="expireMinutes"></param> 147 /// <returns></returns> 148 public async Task<bool> Set<T>(string key, T value, int expireMinutes = 0) 149 { 150 try 151 { 152 var jsonOption = new JsonSerializerSettings() 153 { 154 ReferenceLoopHandling = ReferenceLoopHandling.Ignore 155 }; 156 var _str = JsonConvert.SerializeObject(value, jsonOption); 157 if (string.IsNullOrWhiteSpace(_str)) { return false; } 158 159 return await this.Set(key, _str, expireMinutes); 160 } 161 catch (Exception ex) { } 162 return false; 163 } 164 165 /// <summary> 166 /// 是否存在key 167 /// </summary> 168 /// <typeparam name="T"></typeparam> 169 /// <param name="key"></param> 170 /// <returns></returns> 171 public async Task<bool> KeyExists(string key) 172 { 173 try 174 { 175 var db = this.GetDb(); 176 return await db.KeyExistsAsync(key); 177 } 178 catch (Exception ex) { } 179 return false; 180 } 181 182 #endregion 183 184 #region hash操做 185 186 /// <summary> 187 /// 是否存在hash的列 188 /// </summary> 189 /// <param name="key"></param> 190 /// <param name="filedKey"></param> 191 /// <returns></returns> 192 public async Task<bool> HashFieldExists(string key, string filedKey) 193 { 194 try 195 { 196 if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(filedKey)) { return false; } 197 198 var result = await this.HashFieldsExists(key, new Dictionary<string, bool> { { filedKey, false } }); 199 return result[filedKey]; 200 } 201 catch (Exception ex) { } 202 return false; 203 } 204 205 /// <summary> 206 /// 是否存在hash的列集合 207 /// </summary> 208 /// <param name="key"></param> 209 /// <param name="dics"></param> 210 /// <returns></returns> 211 public async Task<Dictionary<string, bool>> HashFieldsExists(string key, Dictionary<string, bool> dics) 212 { 213 try 214 { 215 if (dics.Count <= 0) { return dics; } 216 217 var db = this.GetDb(); 218 foreach (var fieldKey in dics.Keys) 219 { 220 dics[fieldKey] = await db.HashExistsAsync(key, fieldKey); 221 } 222 } 223 catch (Exception ex) { } 224 return dics; 225 } 226 227 /// <summary> 228 /// 設置hash 229 /// </summary> 230 /// <typeparam name="T"></typeparam> 231 /// <param name="key"></param> 232 /// <param name="filedKey"></param> 233 /// <param name="t"></param> 234 /// <returns></returns> 235 public async Task<long> SetOrUpdateHashsField<T>(string key, string filedKey, T t, bool isAdd = true) 236 { 237 var result = 0L; 238 try 239 { 240 return await this.SetOrUpdateHashsFields<T>(key, new Dictionary<string, T> { { filedKey, t } }, isAdd); 241 } 242 catch (Exception ex) { } 243 return result; 244 } 245 246 /// <summary> 247 /// 設置hash集合,添加和更新操做 248 /// </summary> 249 /// <typeparam name="T"></typeparam> 250 /// <param name="key"></param> 251 /// <param name="dics"></param> 252 /// <returns></returns> 253 public async Task<long> SetOrUpdateHashsFields<T>(string key, Dictionary<string, T> dics, bool isAdd = true) 254 { 255 var result = 0L; 256 try 257 { 258 var jsonOption = new JsonSerializerSettings() 259 { 260 ReferenceLoopHandling = ReferenceLoopHandling.Ignore 261 }; 262 var db = this.GetDb(); 263 foreach (var fieldKey in dics.Keys) 264 { 265 var item = dics[fieldKey]; 266 var _str = JsonConvert.SerializeObject(item, jsonOption); 267 result += await db.HashSetAsync(key, fieldKey, _str) ? 1 : 0; 268 if (!isAdd) { result++; } 269 } 270 return result; 271 } 272 catch (Exception ex) { } 273 return result; 274 } 275 276 /// <summary> 277 /// 移除hash的列 278 /// </summary> 279 /// <param name="key"></param> 280 /// <param name="filedKey"></param> 281 /// <returns></returns> 282 public async Task<bool> RemoveHashField(string key, string filedKey) 283 { 284 try 285 { 286 if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(filedKey)) { return false; } 287 288 var result = await this.RemoveHashFields(key, new Dictionary<string, bool> { { filedKey, false } }); 289 return result[filedKey]; 290 } 291 catch (Exception ex) { } 292 return false; 293 } 294 295 /// <summary> 296 /// 異常hash的列集合 297 /// </summary> 298 /// <param name="key"></param> 299 /// <param name="dics"></param> 300 /// <returns></returns> 301 public async Task<Dictionary<string, bool>> RemoveHashFields(string key, Dictionary<string, bool> dics) 302 { 303 304 try 305 { 306 var jsonOption = new JsonSerializerSettings() 307 { 308 ReferenceLoopHandling = ReferenceLoopHandling.Ignore 309 }; 310 var db = this.GetDb(); 311 foreach (var fieldKey in dics.Keys) 312 { 313 dics[fieldKey] = await db.HashDeleteAsync(key, fieldKey); 314 } 315 return dics; 316 } 317 catch (Exception ex) { } 318 return dics; 319 } 320 321 /// <summary> 322 /// 設置hash 323 /// </summary> 324 /// <typeparam name="T"></typeparam> 325 /// <param name="key"></param> 326 /// <param name="filedKey"></param> 327 /// <param name="t"></param> 328 /// <returns></returns> 329 public async Task<T> GetHashField<T>(string key, string filedKey) 330 { 331 var t = default(T); 332 try 333 { 334 var dics = await this.GetHashFields<T>(key, new Dictionary<string, T> { { filedKey, t } }); 335 return dics[filedKey]; 336 } 337 catch (Exception ex) { } 338 return t; 339 } 340 341 /// <summary> 342 /// 獲取hash的列值集合 343 /// </summary> 344 /// <typeparam name="T"></typeparam> 345 /// <param name="key"></param> 346 /// <param name="dics"></param> 347 /// <returns></returns> 348 public async Task<Dictionary<string, T>> GetHashFields<T>(string key, Dictionary<string, T> dics) 349 { 350 try 351 { 352 var db = this.GetDb(); 353 foreach (var fieldKey in dics.Keys) 354 { 355 var str = await db.HashGetAsync(key, fieldKey); 356 if (string.IsNullOrWhiteSpace(str)) { continue; } 357 358 dics[fieldKey] = JsonConvert.DeserializeObject<T>(str); 359 } 360 return dics; 361 } 362 catch (Exception ex) { } 363 return dics; 364 } 365 366 /// <summary> 367 /// 獲取hash的key的全部列的值 368 /// </summary> 369 /// <typeparam name="T"></typeparam> 370 /// <param name="key"></param> 371 /// <returns></returns> 372 public async Task<Dictionary<string, T>> GetHashs<T>(string key) 373 { 374 var dic = new Dictionary<string, T>(); 375 try 376 { 377 var db = this.GetDb(); 378 379 var hashFiles = await db.HashGetAllAsync(key); 380 foreach (var field in hashFiles) 381 { 382 dic[field.Name] = JsonConvert.DeserializeObject<T>(field.Value); 383 } 384 return dic; 385 } 386 catch (Exception ex) { } 387 return dic; 388 } 389 390 /// <summary> 391 /// 獲取hash的Key的全部列的值的list集合 392 /// </summary> 393 /// <typeparam name="T"></typeparam> 394 /// <param name="key"></param> 395 /// <returns></returns> 396 public async Task<List<T>> GetHashsToList<T>(string key) 397 { 398 var list = new List<T>(); 399 try 400 { 401 var db = this.GetDb(); 402 403 var hashFiles = await db.HashGetAllAsync(key); 404 foreach (var field in hashFiles) 405 { 406 var item = JsonConvert.DeserializeObject<T>(field.Value); 407 if (item == null) { continue; } 408 list.Add(item); 409 } 410 } 411 catch (Exception ex) { } 412 return list; 413 } 414 415 #endregion 416 417 #region List操做(注:能夠當作隊列使用) 418 419 /// <summary> 420 /// list長度 421 /// </summary> 422 /// <typeparam name="T"></typeparam> 423 /// <param name="key"></param> 424 /// <returns></returns> 425 public async Task<long> GetListLen<T>(string key) 426 { 427 try 428 { 429 var db = this.GetDb(); 430 return await db.ListLengthAsync(key); 431 } 432 catch (Exception ex) { } 433 return 0; 434 } 435 436 /// <summary> 437 /// 獲取List數據 438 /// </summary> 439 /// <typeparam name="T"></typeparam> 440 /// <param name="key"></param> 441 /// <returns></returns> 442 public async Task<List<T>> GetList<T>(string key) 443 { 444 var t = new List<T>(); 445 try 446 { 447 var db = this.GetDb(); 448 var _values = await db.ListRangeAsync(key); 449 foreach (var item in _values) 450 { 451 if (string.IsNullOrWhiteSpace(item)) { continue; } 452 t.Add(JsonConvert.DeserializeObject<T>(item)); 453 } 454 } 455 catch (Exception ex) { } 456 return t; 457 } 458 459 /// <summary> 460 /// 獲取隊列出口數據並移除 461 /// </summary> 462 /// <typeparam name="T"></typeparam> 463 /// <param name="key"></param> 464 /// <returns></returns> 465 public async Task<T> GetListAndPop<T>(string key) 466 { 467 var t = default(T); 468 try 469 { 470 var db = this.GetDb(); 471 var _str = await db.ListRightPopAsync(key); 472 if (string.IsNullOrWhiteSpace(_str)) { return t; } 473 t = JsonConvert.DeserializeObject<T>(_str); 474 } 475 catch (Exception ex) { } 476 return t; 477 } 478 479 /// <summary> 480 /// 集合對象添加到list左邊 481 /// </summary> 482 /// <typeparam name="T"></typeparam> 483 /// <param name="key"></param> 484 /// <param name="values"></param> 485 /// <returns></returns> 486 public async Task<long> SetLists<T>(string key, List<T> values) 487 { 488 var result = 0L; 489 try 490 { 491 var jsonOption = new JsonSerializerSettings() 492 { 493 ReferenceLoopHandling = ReferenceLoopHandling.Ignore 494 }; 495 var db = this.GetDb(); 496 foreach (var item in values) 497 { 498 var _str = JsonConvert.SerializeObject(item, jsonOption); 499 result += await db.ListLeftPushAsync(key, _str); 500 } 501 return result; 502 } 503 catch (Exception ex) { } 504 return result; 505 } 506 507 /// <summary> 508 /// 單個對象添加到list左邊 509 /// </summary> 510 /// <typeparam name="T"></typeparam> 511 /// <param name="key"></param> 512 /// <param name="value"></param> 513 /// <returns></returns> 514 public async Task<long> SetList<T>(string key, T value) 515 { 516 var result = 0L; 517 try 518 { 519 result = await this.SetLists(key, new List<T> { value }); 520 } 521 catch (Exception ex) { } 522 return result; 523 } 524 525 526 #endregion 527 528 #region 額外擴展 529 530 public async Task<List<string>> MatchKeys(params string[] paramArr) 531 { 532 var list = new List<string>(); 533 try 534 { 535 var result = await this.ExecuteAsync("keys", paramArr); 536 537 var valArr = ((RedisValue[])result); 538 foreach (var item in valArr) 539 { 540 list.Add(item); 541 } 542 } 543 catch (Exception ex) { } 544 return list; 545 } 546 547 /// <summary> 548 /// 執行redis原生命令 549 /// </summary> 550 /// <param name="cmd"></param> 551 /// <param name="paramArr"></param> 552 /// <returns></returns> 553 public async Task<RedisResult> ExecuteAsync(string cmd, params string[] paramArr) 554 { 555 try 556 { 557 var db = this.GetDb(); 558 return await db.ExecuteAsync(cmd, paramArr); 559 } 560 catch (Exception ex) { } 561 return default(RedisResult); 562 } 563 564 /// <summary> 565 /// 手動回收管理器對象 566 /// </summary> 567 public void Dispose() 568 { 569 this.Dispose(_redis); 570 } 571 572 public void Dispose(ConnectionMultiplexer con) 573 { 574 if (con != null) 575 { 576 con.Close(); 577 con.Dispose(); 578 } 579 } 580 581 #endregion 582 583 #endregion 584 }