Orleans初戰(用分佈式解決高併發購物場景)

首先咱們來定義這樣一個場景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個銷售人員,只有一我的在賣東西其餘銷售都沒事,顧客要排隊好久…………這個是不容許出現的!!!咱們應該怎麼解決?這個解決辦法我會在下次的事例中和你們分享,你們不妨在留言中提出一些本身的解決辦法,咱們一塊兒研究研究

相關文章
相關標籤/搜索