首先咱們來定義這樣一個場景:git
商店有10種商品,每種商品有100件庫存。如今有20萬人來搶購這些商品。github
OK,那麼問題來了。要怎樣保證商品不會超賣……(要知道可能會出現20我的同時買A商品(或者更糟糕,畢竟後邊20萬的大軍,隨時可能把商店變成廢墟),怎樣保證A商品的數量絕對安全)數據庫
按照大部分系統的解決方案是這樣的:編程
收到請求放入隊列,而後對隊列順序處理,這樣就避免了系統被瞬間擠爆並且不會超賣。安全
這種處理方式裝換成現實場景是這樣的:客戶到商店先領號,無論買什麼商品,都要排隊,而後一個一個買,直到全部的處理完。服務器
這個是否是弱爆了………………多線程
這個解決方案也就至關於一個售賣窗口,你們在那排隊買,你能受得了嗎?async
先看看現實商店怎樣解決的(存在即合理):客戶太多就加窗口唄,多僱員工,粗暴又簡單的解決了問題(固然你們仍是要排隊,可是不是一個隊了,緩解了壓力提升了速度哦,老闆賺到了更多的錢)ide
Orleans閃亮登場…………ui
首先我要多開幾臺服務器來處理客戶的請求,怎樣分配呢,要知道個人商品庫存數量必須保證安全,若是幾臺服務器操做一個商品那咱們要想辦法作到對象的絕對同步(joab開始也是這樣想的,後來我才知道是我想多了),要知道加的服務器處理數據同步的消耗實在太大得不償失啊(線程之間的數據安全使用線程鎖咱們都閒消耗大,這個誇服務器就更別說了)……
換個思路:加幾臺服務器,每臺服務器買不一樣的商品,例如:1號服務器賣a/b兩種商品,2號服務器賣c/d兩種商品…………以此類推,問題解決了……
客戶消息說買a商品,直接到1號服務器排隊,買c商品就去2號服務器排隊,(固然這裏服務器也要多線程,同樣的解決原理,a商品x線程排隊,b商品y線程排隊)
好了,從場景到解決辦法都出來了,如今要實現:
照例咱們開始搭建環境(事例我就簡單三層了,現實項目你們本身根據項目本身發揮啊)
訪問關係:
Orleans.Samples.HostSilo就是個控制檯應用程序,用於啓動Orleans服務(Silo的啓動)也就至關於售貨的窗口,不一樣服務器啓動Orleans.Samples.HostSilo來處理排隊的請求(配置我就先不貼出來了,不少地方有)
Orleans.Samples.Grains你能夠理解爲商品,它在須要在窗口售賣
Orleans.Samples.StorageProvider這個怎麼說呢,首先Orleans.Samples.Grains是運行在服務端的並且能夠是有狀態的,咱們怎麼來管理他的狀態,StorageProvider就對Grain的狀態作了擴展(本例我就那這個狀態來作商品數據的讀寫,而且對商品扣庫存時也是直接對本Grain的state進行操做)
其它的幾個我就不講了你們一看就知道是什麼了。
關鍵代碼
1、GoodsStorgeProvider
public class GoodsStorgeProvider : IStorageProvider { public Logger Log { get; set; } public string Name { get; set; } public Task ClearStateAsync(string grainType, GrainReference grainReference, IGrainState grainState) { return TaskDone.Done; } public Task Close() { return TaskDone.Done; } public Task Init(string name, IProviderRuntime providerRuntime, IProviderConfiguration config) { this.Name = nameof(GoodsStorgeProvider); this.Log = providerRuntime.GetLogger(this.Name); return TaskDone.Done; } public async Task ReadStateAsync(string grainType, GrainReference grainReference, IGrainState grainState) { Console.WriteLine("獲取商品信息"); var goodsNo = grainReference.GetPrimaryKeyString(); using (var context = EntityContext.Factory()) { grainState.State = context.GoodsInfo.AsNoTracking().FirstOrDefault(o => o.GoodsNo.Equals(goodsNo)); } await TaskDone.Done; } public async Task WriteStateAsync(string grainType, GrainReference grainReference, IGrainState grainState) { var model = grainState.State as GoodsInfo; using (var context = EntityContext.Factory()) { var entity = context.GoodsInfo.FirstOrDefault(o => o.GoodsNo.Equals(model.GoodsNo)); entity.Stock = model.Stock; await context.SaveChangesAsync(); } } }
前邊說過了Grain是有狀態的,我定義了GoodsStorgeProvider管理商品的狀態,商品的讀取我是直接從數據庫讀出而後賦值個它的State,那麼知道這個Grain被釋放,這個State將一直存在,而且惟一,寫入我就直接對商品的Stock進行了賦值而且保存到數據庫(售賣商品,變動的就只有商品的數量)
2、GoodsInfoGrain
[StorageProvider(ProviderName = "GoodsStorgeProvider")] public class GoodsInfoGrain : Grain<GoodsInfo>, IGoodsInfoGrain { public Task<List<GoodsInfo>> GetAllGoods() { using (var context = EntityContext.Factory()) { return Task.FromResult(context.GoodsInfo.AsNoTracking().ToList()); } } public async Task<bool> BuyGoods(int count, string buyerUser) { Console.WriteLine(buyerUser + ":購買商品--" + this.State.GoodsName + " " + count + "個"); if (count>0 && this.State.Stock >= count) { this.State.Stock -= count; OrdersInfo ordersInfo = new OrdersInfo(); ordersInfo.OrderNo = Guid.NewGuid().ToString("n"); ordersInfo.BuyCount = count; ordersInfo.BuyerNo = buyerUser; ordersInfo.GoodsNo = this.State.GoodsNo; ordersInfo.InTime = DateTime.Now; using (var context = EntityContext.Factory()) { context.OrdersInfo.Add(ordersInfo); await context.SaveChangesAsync(); } await this.WriteStateAsync(); Console.WriteLine("購買完成"); return await Task.FromResult(true); } else { Console.WriteLine("庫存不足--剩餘庫存:" + this.State.Stock); return await Task.FromResult(false); } } }
咱們有10種商品因此也就是會有10個Grain的實例保存在服務端,具體哪一個Grain的實例代碼那種商品咱們能夠根據商品編號來劃分,GoodsInfoGrain繼承自IGoodsInfoGrain,IGoodsInfoGrain繼承自IGrainWithStringKey,IGrainWithStringKey的實例化須要一個string類型的key,咱們就用商品的編號做爲這個Grain實例的Key
這裏我指定此Grain的StorageProvider爲GoodsStorgeProvider,那麼當Grain被實例化的時候GoodsStorgeProvider也被實例化而且執行ReadStateAsync,那麼這個商品就在服務端存在了,不用每次去數據庫讀而是一直存在服務端
這裏咱們服務端是不須要特地人爲的進行排隊處理,Grain的實例咱們能夠理解爲是線程安全的(微軟並非使用線程鎖來作的這樣作太浪費資源,有興趣的鞋童能夠研究下源碼,這對你編程水平的提升頗有做用)因此不會出現對象被同時調用,而是順序調用。
客戶端調用:
var grain = GrainClient.GrainFactory.GetGrain<IGoodsInfoGrain>(goods.GoodsNo); bool result = grain.BuyGoods(count, buyerUser).Result; if (result) { Addmsg(buyerUser + "--購買商品" + goods.GoodsName + " " + count + "個"); } else { Addmsg(buyerUser + "--購買商品" + goods.GoodsName + " 庫存不足"); }
你們能夠看到,GrainClient.GrainFactory.GetGrain<IGoodsInfoGrain>(goods.GoodsNo)就是告訴服務端須要用哪一個grain執行個人操做,而後使用這個grain去調用BuyGoods方法購買商品不須要告訴服務端商品的編號,只須要買幾個,購買人是誰就能夠了,由於grain在實例化(固然仍是那句話,Grain是有狀態的不須要每次實例化,)時就已經定了它是哪一種商品。
OK,源碼地址:https://github.com/zhuqingbo/Orleans.Samples
今天舉例的這個場景是有破綻的,例如:有20萬人都是來買一種商品的,那麼就意味着只有一個服務器忙到死,可是其餘的服務器都是空閒的,就像我商場僱了100個銷售人員,只有一我的在賣東西其餘銷售都沒事,顧客要排隊好久…………這個是不容許出現的!!!咱們應該怎麼解決?這個解決辦法我會在下次的事例中和你們分享,你們不妨在留言中提出一些本身的解決辦法,咱們一塊兒研究研究