ENode 1.0 – 音讯队列的计划思路

开源地址:https://github.com/tangxuehua/enode

Unity3D烘焙技术

直达一致首著作,简单介绍了enode框架内部的圆实现思路,用到了staged
event-driven
architecture的思维。通过前一模一样首稿子,我们知晓了enode内部有星星点点栽阵:command
queue、event queue;用户发送的command会进入command queue排队,domain
model发生的domain event会进来event
queue,然后等待于dispatch到拥有的event
handlers。本文介绍一下enode框架受顿时点儿种植消息队列到底是怎么计划之。

一.Light灯光情景烘焙
1.理论了解:
(1)烘焙背景:在一个场景中,由于灯光组件起至实时渲染的功效,并直跟电脑硬件GPU渲染器进行交互效用,因此对总括机显卡性能差,以至于总计机出现渲染卡帧等现象,为了提高统计机性能,对动,游戏应当开展优化处理,灯光烘焙即凡优化总括机性能的一样种方法,灯光烘焙将灯光由光电热资源转换成为灯光光照贴图从而优化场景
(2)烘焙方式:
烘焙场景无非待拿气象被需要烘焙的地势勾选右上角static开启静态形式要,因为地势不需要活动,当然要用走可关闭static,选中需要烘焙的地貌开展灯光烘焙,点击window中之Light窗口打开灯光烘焙面板,眼睛视角查找到light面板底部的auto,勾选auto表示自动烘焙场景(地形和光)等待一段时间,等待时可查找到light面板底部下方的褐色进度漫漫,这个速度漫漫就表示烘焙场景的状态,烘焙完成后,系统会活动创制场景烘焙的预设资源在project面板,完成烘焙
(3)实时光:
动态发射灯光,简言之,实时灯光指的凡在景被灯光时刻发射灯光,不断充实,不断损耗资源,实时灯光可以随便修改场景中的灯光渲染形式

先贴一下enode框架之里边贯彻架构图,这样对我们知晓前面的解析爆发拉。

(4)静态灯光:
烘焙场景后的光,烘焙场景后,开启static静态形成静态的灯光会在project面板自动生成光照贴图,保存灯光参数属性,使该非可以吃改

图片 1

2.实际上演练:
(1)在气象中初打一个事势并随便笔刷多产

我们得哪的信队列

enode的设计初衷是当么进程内提供依照DDD+CQRS+EDA的拔取开发。只要我们的事务需要同外系统互相,那吧得以,就是经过当event
handler中及任何外部系统互相,比如广播音信出来要调用远程接口,都好。也许未来,enode也相会放襄助远程音信通信的效率。不过未辅助远程通信并无代表enode只好开单机应用了。enode框架需要仓储的数量要出三栽:

  1. 信,包括command新闻和event信息,如今由于性能方面的考虑,是储存于mongodb中;之所以要持久化信息是因信息队列里之新闻未能够少;
  2. 聚合根,聚合根会被体系化,然后存储在内存缓存中,如redis或memcached中;
  3. 事件,就是由于聚合根发生的风波,事件存储在eventstore中,如mongodb中;

好,通过者的剖析,我们清楚enode框架运行时之具备数据,就囤于mongodb和redis这简单单地点。而当时简单种植存储都是安排于独的服务器上,与web服务器无关。所以运行enode框架的各级令web服务器上是凭状态的。所以,我们不怕能利之对web服务器举行集群,大家好随时当用户访问量的多时加新的web服务器,以增强系统的响应能力;当然,当您意识随着web服务器的加码,导致单台mongodb服务器或单台redis服务器处理不东山再起成为瓶颈时,也得以本着mongodb和redis做集群,或者对数据做sharding(当然这片种植做法不是雅好做,需要对mongodb,redis很熟悉才行),这样即使可增长mongodb,redis的吞吐量了。

吓了,下面的辨析重点是为了表达enode框架的应用限制,研究清楚就同碰对咱解析需要什么的音信队列有大酷协助。

本我们领略,大家完全不需要分布式的新闻队列了,比如不需MSMQ、RabbitMQ,等重级成熟的匡助远程音信传递的消息队列了。我们得之消息队列的表征是:

  1. 据悉内存的音讯队列;
  2. 虽说因内存,但新闻不可知少,也不怕是信而协理持久化;
  3. 信息队列要性能尽量高;
  4. 信息队列里从未消息的时候,队列的顾客莫可以让CPU空转,CPU空转会直接造成CPU占用100%,导致机器无法工作;
  5. 假诺协理多单顾客线程同时自队列废除息,可是同一个音信只好吃一个买主处理,也即便是一个信不克以于简单个买主得到走,也即是使帮助并发的dequeue;
  6. 用平等栽设计,实现音信至少会让拍卖同软;具体指:信息给消费者得到走然后给拍卖的历程遭到,如果无处理成(消费者自己领悟暴发没有发生处理成)或者向无来得急处理(比如这时正断电了),这要同种植设计,可以大家出会又消费该音信;
  7. 因我们做不交100%不谋面又处理一个消息,所以大家的有着消息消费者如尽可能做到襄助等幂操作,就是重复的操作不碰面引起副成效;比如插入前先查询是否存在即是同种协理等覆盖的措施;这或多或少,框架会尽量提供支撑等覆盖的逻辑,当然,用户自己于设计command
    handler或event handler时,也即使尽量考虑优良覆盖的题材。注意:一般command
    handler不用考虑,大家第一要考虑的凡event
    handler。原因,下次章中还仔细谈吧。

(2)在万象被新修建一个光,假使发生光则不用新增

外存队列的筹划

内存队列,特点是尽早。可是大家不仅是内需及早,还要会支撑并发的入队跟出对。那么看起ConcurrentQueue<T>似乎会知足大家的求了,一方面性能仍可以够,另一方面内置帮助了出现操作。可是有同一点没有知足,这就是是我们盼望当排里不曾信息的当儿,队列的消费者未克为CPU空转,CPU空转会直接造成CPU占用100%,导致机器不能工作。幸运的是,.net中呢闹一个扶助这种效益的集纳,这便是:BlockingCollection<T>,这种集能提供在列内无元素的时候block当前线程的职能。大家得以据此以下的办法来实例化一个队:

private BlockingCollection<T> _queue = new BlockingCollection<T>(new ConcurrentQueue<T>());

并发入队的时光,我们假诺写上边的代码即可:

_queue.Add(message);

连发出队的下,只要:

_queue.Take();

大家不难看出,ConcurrentQueue<T>是供了班列加并发访问的协理,而BlockingCollection<T>是于斯基础及重复添blocking线程的效率。

凡未是万分简单,经过自家之测试,BlockingCollection<T>的性质就丰裕好,每秒10万坏入队出对得没有问题,所以不必担心成为瓶颈。

关于Disruptor的调研:

了解过LMAX搭的朋友应该听说过Disruptor,LMAX架构能扶助各国秒处理600W订单,而且是单线程。这多少个速度是不是老震惊?大家有趣味的可去打听下。LMAX架构是意in
memory的架构,所有的事务逻辑按照纯内存实现,粗粒度的架构图如下:

图片 2

  1. Business Logic Processor完全在in memory中跑,简称BLP;
  2. Input Disruptor是相同种特此外冲内存运行的环形队列(基于相同种植被Ring
    Buffer的环形数据结构),负责接消息,然后叫BLP处理音信;
  3. Output
    Disruptor也是一致的行,负责将BLP暴发的风波发表出来,给外部组件消费,外部组件消费后可能以会来新的消息塞入到Input
    Disruptor;

LMAX架构之所以会如此快,除了全按照in
memory的架外,还归功给延迟率在皮秒级别之disruptor队列组件。下边是disruptor与java中的Array
Blocking Queue的延迟率比较图:

图片 3

ns是皮秒,我们能够自数据上见到,Disruptor的延迟时间比Array Blocking
Queue快的非是一个数级。所以,当初LMAX架构下时,一时不行轰动。我都为本着之架构很怪,但坐微微细节问题尚未想精晓,就非敢造次实施。

通过者的剖析,大家清楚,Disruptor也是一律种队列,并且也截然可取代BlockingCollection,不过坐我们的BlockingCollection如今一度满意我们的需要,且少无相会成为瓶颈,所以,我小没有用Disruptor来兑现我们的内存队列。关于LMAX架构,我们还好扣押一下这篇本身此前写的著作。

(3)设置灯光和地形在inspector面板中的static静态为勾选状态

排音讯的持久化

大家不仅要一个胜似性能都援助并发的内存队列,还要补助排信息的持久化功效,这样我们才会确保音信不会面少,从而才可以讲音信至少为处理同不善。

这就是说信啊时持久化?

当我们发送一个信让班,一旦有成功,我们得认为信都不谋面丢了。所以,很明朗,信息队列中肯定是如果于吸收至入队之信息时优先持久化该音信,然后才可以回去。

这怎么着急速的持久化呢?

第一独想法:

基于txt文本文件之次第写。原理是:当音信入队时,将音讯连串化为文本,然后append到一个txt1文件;当音讯被拍卖完毕将来,再管该信息append到其他一个txt2文件;然后,假使手上机械没再开,这内存队列里时有的音信就是是还未为拍卖的信息;如若机器还开了,这怎么精通怎么着音讯还没有给处理?很粗略,就是针相比txt1,txt2随即有限单文件文件,然后使是txt1遇有,不过txt2壬子设有的音信,就看是从未为拍卖过,这用在enode框架启动时读取txt1惨遭那么些从没给拍卖的信息文本,反体系化为消息对象,然后又放入内存队列,然后起拍卖。那一个思路其实挺好,关键之一点,这种做法性质特别高。因为我们精通各样写文本文件是非常急匆匆之,经过自己的测试,每秒200W行普通音信之文书不以话下。这意味大家每秒可以持久化200W独信息,当然实际上我们定及不至之高的快慢,因为信息之体系化性能及不交者速度,所以瓶颈是当系列化下面。但是,通过那种持久化音讯之思绪,也会有这个细节问题较麻烦化解,比如txt文件进一步深,怎么惩罚?txt文件不佳管理以及保障,万一不小心给人去了吧?还有,怎么着相比较及时片单txt文件?按行相比较也?不行,因为新闻入队的各样及拍卖的各样不肯定同,比如command就是这般,当用户发送一个command到行列,可是处理的时节发现第一不佳出于现身顶牛,导致command执行没有成功,所以会重试command,假如重试成功了,然后持久化该command,可是我们了然,此时持久化的时光,它的一一也许已经当背后的command的尾了。所以,大家无可知按行相比较;那么就算尽管准音信的ID相比了?虽然会成功,这这相比过程也是特别耗时的,假使txt1生出100W个信息;txt2蒙生80W单音讯,这使按ID来相比较txt1遇啦20W独信息还不曾叫处理,有啊算法能高效相比出来吧?所以,大家发现,这个思路或有广大细节问题待考虑。

仲独想法:

行使NoSQL来囤积音信,通过一些思考与比晚,觉得要MongoDB比较适当。一方面MongoDB实际上有的存取操作优先选取内存,也就是说不相会这持久化到磁盘。所以性能好快。另一方面,mongodb辅助保险的持久化效率,可以放心的所以来持久化消息。性能方面,即使尚未写txt那么快,但为基本能领了。因为我们总非是举网站的保有用户要的command都是坐落一个列,假使大家的网站用户量很挺,这必会由此web服务器集群,且每个集群机器上且晤面出不止一个command
queue,所以,单个command
queue里的音讯我们能够决定为非会晤无限多,而且,单个command
queue里的音讯如故在不同之mongodb
collection中贮存;当然持久化瓶颈永远是IO,所以的确要快,这只能一个独门的mongodb
server上设计一个collection,该collection存放一个command
queue里的音信;其他的command
queue的信息就为应用这样的做法在此外的mongodb
server上;这样就是可知就IO的互动,从而从达加强持久化速度。不过如此做代价相当怪之,可能用过多机器也,整个体系爆发稍许个queue,这便待有些台机器,呵呵。综上可得,持久化方面,我们尚是生一部分艺术可以去尝试,还有优化的退路。

再一次回过头来简单说一下,采取mongodb来持久化音信之落实思路:入队之时持久化音信,出队的时去该新闻;这样当机还开时,要翻开有队列有微微音讯,只要透过一个简单易行的询问重回mongodb
collection中即是的音讯即可。这种做法设计简约,稳定,性能方面即当还得领。所以,近日enode就是动这种艺术来持久化所有enode用到的内存队列的信。

代码示意,有趣味之雅观看:

图片 4图片 5

    public abstract class QueueBase<T> : IQueue<T> where T : class, IMessage
    {
        #region Private Variables

        private IMessageStore _messageStore;
        private BlockingCollection<T> _queue = new BlockingCollection<T>(new ConcurrentQueue<T>());
        private ReaderWriterLockSlim _enqueueLocker = new ReaderWriterLockSlim();
        private ReaderWriterLockSlim _dequeueLocker = new ReaderWriterLockSlim();

        #endregion

        public string Name { get; private set; }
        protected ILogger Logger { get; private set; }

        public QueueBase(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            Name = name;
            _messageStore = ObjectContainer.Resolve<IMessageStore>();
            Logger = ObjectContainer.Resolve<ILoggerFactory>().Create(GetType().Name);
        }

        public void Initialize()
        {
            _messageStore.Initialize(Name);
            var messages = _messageStore.GetMessages<T>(Name);
            foreach (var message in messages)
            {
                _queue.Add(message);
            }
            OnInitialized(messages);
        }
        protected virtual void OnInitialized(IEnumerable<T> initialQueueMessages) { }

        public void Enqueue(T message)
        {
            _enqueueLocker.AtomWrite(() =>
            {
                _messageStore.AddMessage(Name, message);
                _queue.Add(message);
            });
        }
        public T Dequeue()
        {
            return _queue.Take();
        }
        public void Complete(T message)
        {
            _dequeueLocker.AtomWrite(() =>
            {
                _messageStore.RemoveMessage(Name, message);
            });
        }
    }

View Code

(4)打开window中的light窗口

争保管音信至少给处理同次

思路应当好容易想到,就是事先将信于内存队列dequeue出来,然后交到消费者处理,然后由消费者告知我们脚下音讯是否受拍卖了,假如无让处理好,这要尝试重试处理,假使重试几涂鸦后要挺,这也未可知拿音讯摒弃了,但为不克随便停歇的直接就处理这音信,所以用把该音信丢到其余一个专门用于拍卖要重试的地面纯内存队列。假如音信让拍卖成了,那即使管该消息于持久化设备遭逢剔除即可。看一下代码相比较清楚吧:

    private void ProcessMessage(TMessageExecutor messageExecutor)
    {
        var message = _bindingQueue.Dequeue();
        if (message != null)
        {
            ProcessMessageRecursively(messageExecutor, message, 0, 3);
        }
    }
    private void ProcessMessageRecursively(TMessageExecutor messageExecutor, TMessage message, int retriedCount, int maxRetryCount)
    {
        var result = ExecuteMessage(messageExecutor, message); //这里表示在消费(即处理)消息

        //如果处理成功了,就通知队列从持久化设备删除该消息,通过调用Complete方法实现
        if (result == MessageExecuteResult.Executed)
        {
            _bindingQueue.Complete(message);
        }
        //如果处理失败了,就重试几次,目前是3次,如果还是失败,那就丢到一个重试队列,进行永久的定时重试
        else if (result == MessageExecuteResult.Failed)
        {
            if (retriedCount < maxRetryCount)
            {
                _logger.InfoFormat("Retring to handle message:{0} for {1} times.", message.ToString(), retriedCount + 1);
                ProcessMessageRecursively(messageExecutor, message, retriedCount + 1, maxRetryCount);
            }
            else
            {
                //这里是丢到一个重试队列,进行永久的定时重试,目前是每隔5秒重试一下,_retryQueue是一个简单的内存队列,也是一个BlockingCollection<T>
                _retryQueue.Add(message);
            }
        }
    }

代码应该怪亮了,我就不多开表达了。

(5)选中需要烘焙的对象

总结:

本文首要介绍了enode框架中信息队列的设计思路,因为enode中出command
queue和event
queue,两种植queue,所以逻辑是看似的;所以当还眷恋探究一下如何抽象和计划性这多少个queue,已失去丢重代码。但日子未早了,下次再也详尽讲吧。

(6)鼠标左键单机light面板右下角的bake按钮举行烘焙生成

(7)烘焙完成后系自动生成光照贴图

图片 6图片 7

形成后,假使我们对著作发生质疑,或针对unity有趣味之小伙伴可互补加QQ群579849714一同来琢磨unity!

发表评论

电子邮件地址不会被公开。 必填项已用*标注