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个销售人员,只有一我的在卖东西其余销售都没事,顾客要排队好久…………这个是不容许出现的!!!咱们应该怎么解决?这个解决办法我会在下次的事例中和你们分享,你们不妨在留言中提出一些本身的解决办法,咱们一块儿研究研究

相关文章
相关标签/搜索