分布式一致性的想法

澳门美高梅手机网站 1

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

背景

新近一直在构思,工作这么长年累月下遇到的分布式系统的一弹指间标题,以及针对那几个标题提供的消除方案。
借那么些空子,顺便梳理清楚那块文化,希望同大家一块儿斟酌下

上一篇文章,简单介绍了enode框架内部的全部实现思路,用到了staged
event-driven
architecture的考虑。通过前一篇文章,大家知道了enode内部有二种队列:command
queue、event queue;用户发送的command会进入command queue排队,domain
model发生的domain event会进入event
queue,然后等待被dispatch到具有的event
handlers。本文介绍一下enode框架中那三种音讯队列到底是哪些统一筹划的。

广泛一致性难题

先贴一下enode框架的内部贯彻架构图,那样对我们通晓前面包车型客车分析有帮助。

下订单减仓库储存

在我们做的电商系统中,会有那样的二个现象:用户下单购买有个别商品,然后开展扣减商品仓库储存的情况。

  • 即使先下订单,然后扣减仓库储存,会造成超卖
  • 假定下订单退步,扣减仓库储存成功,那么会促成少卖

那两种情状的爆发都会招致大家系统现身一致性的标题,严重的话,须要对用户做出一定的经济互补。

澳门美高梅手机网站 2

调用超时

事情支出的历程中,肯定会有我们保卫安全的劳务调用别的协会的劳动,即便在机房内部开始展览互连网调用,也或多或少的留存系统调用超时的光景,要是出现如此的光景,大家该怎么消除吗?

我们须求什么的音信队列

enode的计划初衷是在单个进程内提供基于DDD+CQ奥迪Q5S+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占用百分百,导致机器无法工作;
  5. 要支持八个买主线程同时从队列取新闻,不过同3个新闻只可以被三个买主处理,相当于一个新闻不能同时被五个买主取走,也正是要扶助并发的dequeue;
  6. 内需一种设计,完成信息至少会被拍卖贰回;具体指:音讯被消费者取走然后被处理的进度中,若是没有拍卖成功(消费者自己了然有没有处理成功)或许根本没来得急处理(比如那时正好断电了),那必要一种设计,能够大家有空子再度消费该音讯;
  7. 因为大家做不到百分之百不会另行处理1个音信,所以大家的拥有信息消费者要尽量做到帮助等幂操作,正是重复的操作不会唤起副效用;比如插入前先查询是还是不是留存即是一种协理等幂的主意;那一点,框架会尽或者提供帮衬等幂的逻辑,当然,用户本身在设计command
    handler或event handler时,也要尽量考虑等幂的标题。注意:一般command
    handler不用考虑,我们第壹要考虑的是event
    handler。原因,下次文章中再细谈吧。

消除一致性难点的思路

内部存款和储蓄器队列的筹划

内存队列,特点是快。可是大家不仅是索要快,还要能支撑并发的入队和出对。那么看起来ConcurrentQueue<T>就好像能满意大家的需要了,一方面品质还足以,另一方面内置支持了产出操作。可是有一点没知足,那正是大家期待当队列里没有音讯的时候,队列的顾客不可能让CPU空转,CPU空转会直接促成CPU占用百分之百,导致机器不可能工作。幸运的是,.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的架构,全体的政工逻辑依照纯内部存款和储蓄器完成,粗粒度的架构图如下:

澳门美高梅手机网站 3

  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的延迟率比较图:

澳门美高梅手机网站 4

ns是皮秒,大家得以从数额上观望,Disruptor的延迟时间比Array Blocking
Queue快的不是1个数码级。所以,当初LMAX架构出来时,一时万分轰动。笔者早已也对那些架构很愕然,但因为某些细节难点没想清楚,就不敢贸然实施。

由此地点的解析,大家领略,Disruptor也是一种队列,并且也完全能够代替BlockingCollection,然则因为我们的BlockingCollection目前曾经满意大家的内需,且临时不会成为瓶颈,所以,作者一时没有采用Disruptor来促成大家的内部存款和储蓄器队列。关于LMAX架构,大家还足以看一下这篇自个儿从前写的稿子。

酸碱仲阳

ACID : 酸, BASE:碱,其实正是酸碱二月的规律

队列音讯的持久化

大家不光需求2个高质量且支持并发的内部存款和储蓄器队列,还要支撑队列信息的持久化成效,那样大家才能担保消息不会丢掉,从而才能谈消息至少被处理一遍。

那消息什么日期持久化?

当我们发送二个新闻给队列,一旦产生成功,大家终将认为音讯一度不会丢了。所以,很扎眼,新闻队列之中肯定是要在收受到入队的信息时先持久化该信息,然后才能回到。

那么哪些火速的持久化呢?

首先个想法:

基于txt文本文件的逐一写。原理是:当音讯入队时,将音信体系化为文本,然后append到三个txt1文书;当音讯被处理完事后,再把该新闻append到另三个txt2文件;然后,假若当前机械没重启,那内部存储器队列里当前留存的音信正是还未被拍卖的音讯;假诺机注重启了,那什么样晓得什么样音讯还没被处理?很简短,就是比较txt1,txt2那四个公文文件,然后一旦是txt第11中学设有,不过txt第22中学不存在的信息,就觉得是没被处理过,那供给在enode框架运营时读取txt第11中学那个没被拍卖的音讯文本,反种类化为新闻对象,然后再一次放入内部存储器队列,然后开首次拍卖卖。这些思路其实挺好,关键的有些,那种做法性质特别高。因为大家清楚各类写文本文件是尤其快的,经过自家的测试,每秒200W行普通音信的文书不在话下。这意味着大家每秒能够持久化200W个信息,当然实际上大家肯定达不到这几个高的快慢,因为音讯的种类化品质达不到那么些速度,所以瓶颈是在体系化上面。可是,通过那种持久化音信的思路,也会有比比皆是细节难题比较难消除,比如txt文件越来越大,如何是好?txt文件不佳管理和保卫安全,万一十分的大心被人删除了呢?还有,怎样相比较那八个txt文件?按行相比呢?不行,因为音信入队的逐条和处理的依次不必然相同,比如command正是那样,当用户发送叁个command到行列,可是处理的时候发现第二次出于出现冲突,导致command执行没得逞,所以会重试command,倘诺重试成功了,然后持久化该command,不过大家了然,此时持久化的时候,它的一一可能已经在前面包车型大巴command的后边了。所以,大家不能够按行相比较;那么就要按新闻的ID相比了?就算能不负众望,那这些相比较进程也是很耗费时间的,固然txt1有100W个消息;txt第22中学有80W个音讯,那假如根据ID来相比txt第11中学哪20W个新闻还没被拍卖,有怎样算法能高效相比较出来呢?所以,大家发现,那个思路如故有为数不少细节难题须要考虑。

第①个想法:

动用NoSQL来存款和储蓄音信,通过某些思考和比较后,觉得依旧MongoDB比较合适。一方面MongoDB实际上全部的存取操作优先选择内部存储器,也正是说不会立即持久化到磁盘。所以品质不慢。另一方面,mongodb援助有限援助的持久化功效,能够放心的用来持久化音讯。品质方面,就算并未写txt那么快,但也基本能接受了。因为大家终究不是百分百网站的装有用户请求的command都是位于一个类别,如若大家的网站用户量十分大,那必将会用web服务器集群,且每种集群机器上都会有不止3个command
queue,所以,单个command
queue里的音讯大家能够操纵为不会太多,而且,单个command
queue里的音讯都以位于不一致的mongodb
collection中储存;当然持久化瓶颈永远是IO,所以的确要快,那只好一个独立的mongodb
server上设计一个collection,该collection存放三个command
queue里的音信;其余的command
queue的消息就也应用那样的做法放在此外的mongodb
server上;那样就能成就IO的并行,从而根本上增强持久化速度。可是那样做代价十分的大的,可能必要过多机械呢,整个连串有多少个queue,这就须要有些台机器,呵呵。简单来讲,持久化方面,大家还是有部分办法能够去尝试,还有优化的退路。

再回过头来不难说一下,选用mongodb来持久化音讯的兑现思路:入队的时候持久化新闻,出队的时候删除该新闻;那样当机珍视启时,要翻看某些队列有多少信息,只要经过3个简单的查询重临mongodb
collection中当前设有的音讯即可。那种做法设计不难,稳定,品质方面近年来理应还足以承受。所以,近日enode正是应用这种艺术来持久化全体enode用到的内部存款和储蓄器队列的音讯。

代码示意,有趣味的可以看看:

澳门美高梅手机网站 5澳门美高梅手机网站 6

    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

1. ACID

ACID,是指数据库管理体系在写入数据经过中,为保证工作是天经地义可信赖性。
所必须拥有的八个特征:

  • A: Atomicity 原子性
    一个事情(transaction)中的全部操作,要么全体成就,要么全体不做到,不会终结在中等有些环节
  • C: Consistency 一致性
    事务开始在此之前和事务结束之后,数据库的完整性没有被弄坏
  • I: Isolation 隔绝性
    数据库允许四个冒出事务同时对其数额进行读写和修改的力量,隔开性能够预防多少个事情并发执行时由于交叉执行而导致数据的不等同。事务隔开分为分歧级别,包含读未提交(Read
    uncommitted)、读提交(read committed)、可重复读(repeatable
    read)和串行化(Serializable)
  • D: Durability 持久性
    事务处理终结后,对数据的改动正是永远的,固然系统故障也不会丢掉

像 MySQL、Oracle 那样关系型数据库是接济 ACID
个性的强一致性须要的。本人强一致性就不容许出现分裂性的题目,底层都以透过
MVCC 来控制完成的

正好上边提到的下订单减仓库储存就足以关系型数据库的强一致性来消除。将订单表与库存表放在3个数据库
Instance 中,通过数据库 ACID 的性情来消除少卖或然超卖的题材。

唯独假诺蒙受数据量相比较大的图景怎么做?订单表有多张,大家该怎么解决吧?

实际,纵然遇见订单表展开拆分,大家能够一如既往采用数据库 ACID
的性状来缓解。怎么弄?我们得以将订单表的拆表维度与仓库储存表的拆分维度控制在1个多少分片中,然而现实怎么拆分呢?须要各位依据自个儿的事务规则来划分开来

什么保管音讯至少被处理三回

思路应该很简单想到,就是先把消息从内部存储器队列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);
            }
        }
    }

代码应该很精通了,小编就不多做表明了。

2. BASE

BASE 思想化解了 CAP 建议的分布式一致性与可用性不能够同时全职的题材。BASE
思想与 ACID 思想完全分歧,它实在是满意 CAP
理论,通过捐躯强一致性来换取可用性。
BASE 理论:

  • BA:Basically Available,基本可用性
  • S: Soft State 软状态 接受状态在一段时间内部同步
  • E:伊芙ntually Consistent 最后一致性
    在一定的时刻窗口中,最后数额完毕一致即可

软状态是落到实处 BASE 的措施,基本可用域最后一致性是必须到达的目的。以 BASE
的合计由于不保险强一致性,全部接受系统在必然时间内数据存在不雷同,但是在处理请求的经过中,需求记录领会每趟请求的事态,未来出现难题的时候,回滚到中间任何权且气象,达到最终一致性

总结:

正文首要介绍了enode框架中国国投息队列的宏图思路,因为enode中有command
queue和event
queue,二种queue,所以逻辑是看似的;所以自然还想探究一下如何抽象和安顿这一个queue,已去掉重复代码。但日子不早了,下次再详尽讲吧。

3. CAP

当大家服务发展更多,是不可转败为胜就会须要将劳动拓展拆分。一旦服务进行拆分后,它就不在是3个单机的系统,而是通俗意义上的分布式系统。说到分布式系统,大家肯定要说下最为经典的罪名理论。要是自身都没有传闻过帽子理论,小编出门都不佳意思打招呼。
分布式系统 CAP 理论:

  • C: Consistence 一致性 全数节点访问同一份最新的多寡副本
  • A: Availability 可用性
    每趟请求都能在少数的时刻内获取到响应——可是不保证收获的数额为新型数据
  • P: Network partitioning 分区容错性
    即使互联网上有部分信息丢失,但依然能够三番五次工作

CAP 原理注明:分布式系统只可以满意三项中的两项而非常小概满意全部三项。领会CAP 理论的最简易方法便是想象五个节点处在 三个机房中。允许至少二个节点更新情状会促成数据差异,即丧失了 C
性质。要是为了保障数据一致性,将分区一侧的节点设置为不可用,那么又丧失了
A 性质。除非七个节点可以相互通信,才能既保险C 又保险A,那又会促成丧失P性质。

总结

本次小说介绍 ACID、CAP 和 BASE 思想。在价值观数据库领域中动用 ACID
理论,追求强一致性。可是在巨型分布式系统中,采取 BASE
的规划思想,通过就义强一致性来赢得高可用性及最终的一致性。三种设计理念完全差别,我们须求基于自个儿的事体场景,来支配到底哪用艺术。

参考

  • 分布式服务框架结构原理、设计与实战

相关文章

发表评论

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