精华内容
下载资源
问答
  • 关于面向对象与面向过程分析与设计方法的思考,杨彤骥,杨红玉,摘要: 面向对象与面向过程是信息系统的两种分析与设计方法,本文通过一实例对比了两种方法的特点,在开发实践中对面向对象与面�
  • 原文:关于领域驱动设计(DDD)中聚合设计的一些思考 关于DDD的理论知识总结,可参考这篇文章。 DDD社区官网上一篇关于聚合设计的几个原则的简单讨论: 文章地址:http://dddcommunity.org/library/vernon_2011/,该...
    原文:关于领域驱动设计(DDD)中聚合设计的一些思考

    关于DDD的理论知识总结,可参考这篇文章。

    DDD社区官网上一篇关于聚合设计的几个原则的简单讨论:

    文章地址:http://dddcommunity.org/library/vernon_2011/,该地址中包含了一篇关于介绍如何有效的设计聚合的一些原则,共3个pdf文件。该文章中指出了以下几个聚合设计的原则:

    1. 聚合是用来封装真正的不变性,而不是简单的将对象组合在一起;
    2. 聚合应尽量设计的小;
    3. 聚合之间的关联通过ID,而不是对象引用;
    4. 聚合内强一致性,聚合之间最终一致性;

    上面这几条原则,作者通过一个例子来逐步阐述。下面我按照我的理解对每个原则做一个简单的描述。

    聚合是用来封装真正的不变性,而不是简单的将对象组合在一起

    这个原则,就是强调聚合的真正用途除了封装我们本身所关心的信息外,最主要的目的是为了封装业务规则,保证数据的一致性。在我看来,这一点是设计聚合时最重要和最需要考虑的点;当我们在设计聚合时,要多想想当前聚合封装了哪些业务规则,实现了哪些数据一致性。所谓的业务规则是指,比如一个银行账号的余额不能小于0,订单中的订单明细的个数不能为0,订单中不能出现两个明细对应的商品ID相同,订单明细中的商品信息必须合法,商品的名称不能为空,回复被创建时必须要传入被回复的帖子(因为没有帖子的回复不是一个合法的回复),等;

    聚合应尽量设计的小

    这个原则,更多的是从技术的角度去考虑的。作者通过一个例子来说明,该例子中,一开始聚合设计的很大,包含了很多实体,但是后来发现因为该聚合包含的东西过多,导致多人操作时并发冲突严重,导致系统可用性变差;后来开发团队将原来的大聚合拆分为多个小聚合,当然,拆分为小聚合后,原来大聚合内维护的业务规则同样在多个小聚合上有所体现。所以实现了既能解决并发冲突的问题,也能保证让聚合来封装业务规则,实现模型级别的数据一致性;另外,回复中的一位道友“殇、凌枫”提到,聚合设计的小还有一个好处,就是:业务决定聚合,业务改变聚合。聚合设计的小除了可以降低并发冲突的可能性之外,同样减少了业务改变的时候,聚合的拆分个数,降低了聚合大幅重构(拆分)的可能性,从而能让我们的领域模型更能适应业务的变化。

    聚合之间通过ID关联

    这个原则,是考虑到,其实聚合之间无需通过对象引用的方式来关联;

    1. 首先通过引用关联,会导致聚合的边界不够清晰,如果通过ID关联,由于ID是值对象,且值对象正好是用来表达状态的;所以,可以让聚合内只包含只属于自己的实体或值对象,那这样每个聚合的边界就很清晰;每个聚合,关心的是自己有什么信息,自己封装了什么业务规则,自己实现了哪些数据一致性;
    2. 如果通过引用关联,那需要实现LazyLoad的效果,否则当我们加载一个聚合的时候,就会把其关联的其他聚合也一起加载,而实际上我们有时在加载一个聚合时,不需要用到关联的那些聚合,所以在这种时候,就给性能带来一定影响,不过幸好我们现在的ORM都支持LazyLoad,所以这点问题相对不是很大;
    3. 你可能会问,聚合之间如果通过对象引用来关联,那聚合之间的交互就比较方便,因为我可以方便的直接拿到关联的聚合的引用;是的,这点是没错,但是如果聚合之间要交互,在经典DDD的架构下,一般可以通过两种方式解决:1)如果A聚合的某个方法需要依赖于B聚合对象,则我们可以将B聚合对象以参数的方式传递给A聚合,这样A对B没有属性上的关联,而只是参数上的依赖;一般当一个聚合需要直接访问另一个聚合的情况往往是在职责上表明A聚合需要通知B聚合做什么事情或者想从B聚合获取什么信息以便A聚合自己可以实现某种业务逻辑;2)如果两个聚合之间需要交互,但是这两个聚合本身只需要关注自己的那部分逻辑即可,典型的例子就是银行转账,在经典DDD下,我们一般会设计一个转账的领域服务,来协调源账号和目标账号之间的转入和转出,但源账号和目标账号本身只需要关注自己的转入或转出逻辑即可。这种情况下,源账号和目标账号两个聚合实例不需要相互关联引用,只需要引入领域服务来协调跨聚合的逻辑即可;
    4. 如果一个聚合单单保存另外的聚合的ID还不够,那是否就需要引用另外的聚合了呢?也不必,此时我们可以将当前聚合所需要的外部聚合的信息封装为值对象,然后自己聚合该值对象即可。比如经典的订单的例子就是,订单聚合了一些订单明细,每个订单明细包含了商品ID、商品名称、商品价格这三个来自商品聚合的信息;此时我们可以设计一个ProductInfo的值对象来包含这些信息,然后订单明细持有该ProductInfo值对象即可;实际上,这里的ProductInfo所包含的商品信息是在订单生成时对商品信息的状态的冗余,订单生成后,即便商品的价格变了,那订单明细中包含的ProductInfo信息也不会变,因为这个信息已经完全是订单聚合内部的东西了,也就是说和商品聚合无关了。
    5. 实际上通过ID关联,也是达到设计小聚合的目标的一种方式;

    聚合内强一致性,聚合之间最终一致性

    这个原则主要的背景是:如果用CQRS+Event Sourcing的架构来实现DDD,那聚合之间因为通过Domain Event(领域事件)来实现交互了,所以同样也不需要聚合与聚合之间的对象引用,同时也不需要领域服务了,因为领域服务已经被Process(流程聚合根)和Process Manager(流程管理器,无状态)所替代。流程聚合根,负责封装流程的当前状态以及流程下一步该怎么走的逻辑,包括流程遇到异常时的回滚处理逻辑;流程管理器,无状态。负责协调流程中各个参与者聚合根之间的消息交互,它会接受聚合根产生的domain event,然后发送command另外一方面,由于CQRS的引入,使得我们的domain只需要处理业务逻辑,而不需要应付查询相关的需求了,各种查询需求专门由各种查询服务实现;所以我们的domain就可以非常瘦身,仅仅只需要通过聚合根来封装必要的业务规则(保证聚合内数据的强一致性)即可,然后每个聚合根做了任何的状态变更后,会产生相应的领域事件,然后事件会被持久化到EventStore,EventStore用来持久化所有的事件,整个domain的状态要恢复,只需要通过Event Sourcing的方式还原即可;另外,当事件持久化完成后,框架会通过事件总线将事件发布出去,然后Process Manager就可以响应事件,然后发送新的command去通知相应的聚合根去做必要的处理;

    上面这个过程可以在任何一个CQRS的架构图(包括enode的架构图)中找到,我这里就不贴图了。enode中对经典的转账场景用这种思路实现了一下,有兴趣可以去下载enode源代码,然后看一下其中的BankTransferSample这个例子就清楚了。另外,因为事件的响应和Command的发送是异步的,所以,这种架构下,聚合根的交互是异步的;

    需要再次强调的一点是,聚合如果只需要关注如何实现业务规则而不需要考虑查询需求所带来的好处,那就是我们不需要在domain里维护各种统计信息了,而只要维护各种业务规则所潜在的必须依赖的状态信息即可;举个例子,假如一个论坛,有版块和帖子,以前,我们可能会在版块对象上有一个帖子总数的属性,当新增一个帖子时,会对这个属性加1;而在CQRS架构下,domain内的版块聚合根无需维护总帖子数这个统计信息了,总帖子数会在查询端的数据库独立维护;

    从聚合和哲学的角度思考,为什么需要状态?

    聚合的角度

    首先,什么是状态?很简单,比如一个商品的库存信息,那么该库存信息有一个商品的数量这个属性,表示当前商品在库存中还有多少件;那么我们为什么需要记录该属性呢?也就是为什么需要记录这个状态呢?因为有业务规则的存在。以这个例子为例,因为存在“商品的库存不能为负数”这样的一个业务规则,那这个规则如果要能保证,首先必须先记录商品的库存数量;因为商品的库存数量是会随着商品的卖出而减少的,而减少就是通过:Product.Count = Product.Count - 1这样的逻辑运算来实现;这个逻辑运算要能运行的前提就是商品要有库存信息。从这个例子我们不难理解,一个聚合根的很多状态,不是平白无辜设计上去的,而是某些业务规则潜在的要求,必须要设计这些状态才能实现相应的业务规则;这样的例子还有很多,比如银行账号的余额不能小于0,导致我们的银行账号必须要设计一个当前余额的属性;

    另外一个原因是,看起来像是废话,呵呵。就是:因为我们关心这些信息,所以需要设计在当前聚合上;比如,以一个论坛的帖子为例,作为一个帖子,我们通常都会关心帖子的标题、描述、发帖人、发帖时间、所属版块(如果论坛有版块这个概念的话);所以,我们就会在帖子聚合根上设计出这些属性,以表达我们所关心的这些信息的状态;

    哲学的角度

    下面在从偏哲学的角度表达一下对象的概念吧:

    人类永远无法认识完整的事物,因为我们认识到的总是事物的某一方面。我们所说的对象实际上是客观事物在人头脑里的反应,而事物则是不因人的认识发生改变的客观存在。同样一根铁棒,在钢材生产厂家看来,它是成品;在机械加工厂家看来,它是原料;在废品站看来,他是商品。成品、原料、商品,这三者拥有不同的属性,有本质的不同。为什么同一事物在不同人的眼里就截然不同了呢?这是因为我们总是取对我们有用的方面来认识事物。当这根铁棒作为商品时,它的原料属性依然存在,只是我们不关心了。
     
    所以,总结出来就是,因为我们关心一个对象的某些方面,所以我们才会为他设计某些状态属性;

    关于聚合的设计的一些思考

    上面只是简单提到,聚合的设计应该多考虑它封装了哪些业务规则这个问题。下面我想再多讲一点我的一些想法:

    关于GRASP九大模式中的最重要模式:信息专家模式

    还是以论坛的帖子为例,创建一个帖子时,有一个业务规则,那就是帖子的发帖人、标题、描述、所属板块(如果论坛有板块这个概念的话)都不能为空或无效的值,因为这些信息只要有任何一个无效,那就意味着被创建出来的帖子是无效的,那就是没有保证业务规则,也就没办法谈领域模型的数据一致性了;如果像以往的三层贫血架构,那帖子只是一个数据的载体,不包含任何业务规则,帖子会先被构造一个空的帖子对象出来,然后我们给这个空帖子对象的某些属性赋值,然后保存该帖子对象到数据库;这种设计,帖子对象只是一个数据的容器,它完全控制不了自己的状态,因为它的状态都是被别人(如service)去修改的;这样的设计,相当于是没有把业务规则封装在业务对象内部,而是转移到了外部service中,虽然这样通常也没问题,事实上我们大部分人都一直在这么干,因为这样干写代码很随意,也很高效,呵呵。

    GRASP九大模式中有一个面向对象的模式叫信息专家模式,不知道大家有了解过没有,该模式的描述是:将职责分配给拥有执行该职责所需信息的对象;这个模式告诉我们,如果一个对象负责维护一些信息,那它就有职责维护好这些信息。体现到对象的属性上,那就是这个对象的属性不能被外部随便更改,对象自己的属性必须自己负责维护修改。构造函数和普通的方法都会改变对象的状态,所以,我们对构造函数和对象普通的公共方法,都要秉持这个原则;这点非常重要,否则,如果像贫血模型那样,那对象就不叫对象了,而只是一个普通的容纳数据的容器而已,和数据库里的一条记录也无本质差别了。实际上,在我看来,这也是DDD中的聚合区别于贫血模型中的实体的最大的地方。聚合不仅有状态,还有严格维护好自己状态的各种方法,包括构造函数在内;而贫血模型,则只有状态,没有行为;

    关于DDD中一个领域对象是否是聚合根的考虑

    这个问题,没有非常清晰的放之四海而皆准的确定方法,我的想法是:

    1. 首先从我们对领域的最基本的常识方面的理解去思考,该对象是否有独立的生命周期,如果有,那基本上是聚合根了;
    2. 如果领域内的一个对象,我们会在后台有一个独立的模块去管理它,那它基本上也是聚合根了;
    3. 是否有独立的业务场景会去创建或修改一个对象;
    4. 如果对象有全局唯一的标识,那它也是聚合根了;
    5. 如果你不能确定一个对象是否是聚合根的的时候,就先放一下,就先假定它是聚合根也无妨,然后可以先分析一下你已经确定的那些聚合根应该具体聚合哪些信息;也许等你分析清楚其他的那些聚合的范围后,也推导出了你之前不确定是否是聚合根的那个对象是否应该是聚合根了呢。

    关于一个聚合内应该聚合哪些信息的思考

    1. 把我们所需要关心的属性设计进去;
    2. 分析该聚合要封装和实现哪些业务规则,从而像上面的例子(商品库存)那样推导出需要设计哪些属性状态到该聚合内;
    3. 如果我们在创建或修改一个对象时,总是会级联创建或修改一些级联信息,比如在一个任务系统,当我们创建一个任务时,可能会上传一些附件,那这些附件的描述信息(如附件ID,附件名称,附件下载地址)就应该被聚合在任务聚合根上;
    4. 聚合内只需要值对象和内部的实体即可,不需要引用其他的聚合根,引用其他的聚合根只会让当前聚合的边界模糊;

    关于如何更合理的设计聚合来封装各种业务规则的思考

    这一点在最上面的几个原则中,实际上已经提到过一点,那就是尽量设计小聚合,这里的出发点主要是从技术的角度去思考,为了降低对公共对象(大聚合)的并发修改,从而减小并发冲突的可能性,从而提高系统的可用性(因为系统用户不会经常因为并发冲突而导致它的操作失败);关于这一点,我还想再举几个例子,来说明,其实要实现各种业务规则,可以有多种聚合的设计方式,大聚合只是其中一种;

    比如,帖子和回复,大家都知道一个帖子有多个回复,没有帖子,回复就没有意义;所以很多人就会认为帖子应该聚合回复;但实际上不需要这样,如果你这样做了,那对于一个论坛来说,同一个帖子被多个人同时回复的可能性是非常高的,那这样的话,多个人同时回复一个帖子,就会导致多个人同时修改同一个帖子对象,那就导致大家都回复不了,因为会有并发冲突或者数据库事务的等待超时,因为大家都在修改同一个帖子聚合根;实际上如果我们从业务规则的角度去思考一下,那可以发现,其实帖子和回复之间,只有一个简单的规则,那就是回复一旦被创建,那他所对应的帖子不能被修改即可;这样的话,要实现这个规则其实很简单,把回复作为聚合根,然后把帖子传入回复聚合根的构造函数,然后回复保存帖子ID,然后回复将帖子ID设置为不允许外部修改(private set;即可),这样我们就实现了这个业务规则,同时还做到了多人同时推一个帖子回复时,不会对同一个帖子对象就并发修改,而是每个回复都是并行的往数据库插入一条回复记录即可;

    所以,通过这个例子,我们发现,要实现领域模型内的各种业务规则,方法不止一种,我们除了要从业务角度考虑对象的内聚关系外,还要从技术角度考虑,但是不管从什么角度考虑,都是以实现所要求的业务规则为前提;

    从这个例子,我们其实还发现了另外一件有意义的事情,那就是一个论坛中,发表帖子和发表回复是两个独立的业务场景;一个人发表了帖子,然后可能过了一段时间,另一个人对该帖子发表了回复;所以将帖子和回复都设计为独立的很容易理解;这里虽然帖子和回复是一对多,回复离开帖子确实也没意义,但是将回复设计在帖子内没任何好处,反而让系统的可用性降低;相反,像上面提到的关于创建任务时同时上传一些附件的例子,虽然一个任务也是对应多个附件信息,但是我们发现,人物的附件信息总是随着任务被创建或修改时,一起被修改的。也就是说,我们没有独立的业务场景需要独立修改任务的某个附件信息;所以,没有必要将任务的附件信息设计为独立聚合根;

    ENode框架对聚合设计和聚合之间交互的支持

    enode提供了一个基于DDD+CQRS+Event Sourcing+In Memory+EDA这些技术的应用开发架构;

    1. enode在框架层面就限制了一个command只能修改一个聚合根,这就杜绝了我们使用Unit of Work的模式来以事务的方式来一次性修改多个聚合根;
    2. enode提供了可靠的原子操作和并发冲突检测机制,来保证对单个聚合的操作的强一致性;
    3. enode提供了可靠的事件机制,来保证我们的domain中的聚合之间数据交互可以通过事件异步通信的方式来实现聚合之间的最终一致性;如果有些复杂业务场景是一个流程,那我们可以通过Process+Process Manager的思想来实现流程状态的跟踪和流程的流转;
    4. enode因为基于domain event,所以,我们的聚合根不需要引用,每个聚合根只需要负责自己的状态更新,然后更新完后产生相应的domain event即可,这本质就是就是实现了:Don’t Ask, Tell这个设计原则;
    5. enode提供了可靠的事件发布机制,可以确保command side和query side的数据最终一定是一致的;
    6. enode提供了in memory的设计,使得我们的domain可以非常高效的运行,持久化事件不需要事务,获取聚合根直接从in memory获取;
    7. enode提供了很多设计,可以让我们最大化的对不同的聚合根实例做并行操作,从而提高整个系统的吞吐量;

    使用enode,将会迫使你思考如何设计聚合,如何通过流程实现聚合之间的异步交互;迫使你思考如何定义domain event,将领域内的状态更改显式化;迫使你将外部对领域的各种操作显式化,即定义出各种command;迫使你将command side和query side的数据分离和架构分离,技术分离。减少的是,我们不必再设计unit of work,不必设计domain service,不必让聚合设计各种非第一手的冗余的统计信息;

    展开全文
  •  (代码基于Spark-core 1.2.0)  本来这篇想结合自己经验讨论shuffle,但是shuffle讨论...0、 http://www.cs.berkeley.edu/~matei/papers/2012/nsdi_spark.pdf Spark paper 这个是设计时候paper 1、 h...

         (代码基于Spark-core 1.2.0)

        本来这篇想结合自己的经验讨论shuffle,但是shuffle讨论之前还是准备先讨论一下关于RDD的问题。 网上介绍RDD的我看过的有:

    0、 http://www.cs.berkeley.edu/~matei/papers/2012/nsdi_spark.pdf Spark paper 这个是设计时候的paper

    1、 https://www.zybuluo.com/jewes/note/35032 

    2、 一个介绍RDD甚好的PPT,之后我会发在我的云盘。 

     

        RDD学名Resilient Distributed Dataset,"Resillient"表示它的容错能力,“Distributed”说明它是分布式的实现,"Dataset"说明它是基于集合操作的, RDD更准确的说是一个接口,主要由5个接口或者说特性组成:

     

    compute             :在Action调用,生成一个Job时,RDD如果没有Cache的时候调用这个函数

    getPartitions       :Array[Partitions] 每个分区由 index标示

    getDependencies    : RDD的父RDDs, 通常RDD有parent RDD,除了HadoopRDD以及类似RDD,这些是数据源头,所有RDD的关系构成RDD的lineage,一般翻译成血缘关系

    getPreferredLocations 

    partitioner

     

        在这里不会详细介绍RDD的方法面面,Spark的运行启动时的先后顺序,以及运行时各个模块的交互在网上能找到很多的文章,如果有时间,不妨认真看看51CTO上王家林老师的免费课程:

    http://edu.51cto.com/index.php?do=lession&id=30815 我刚接触Spark时看了这个视频,收获很多。

     

        总体而言,Spark的主要接口分为transformation以及action,action触发Job的执行。 job执行时

    1、sparkContext调用dagscheduler划分Stage,Stage划分的依据是shuffle,shuffle前后属于不同的Stage。 2、Stage封装成TaskSet,提交给taskScheduler。

    3、taskScheduler调用backend,获取可用的Executor信息, 对于每一个TaskSet中的Task分配一个Executor的core。

    4、CoarseGrainedSchedulerBackend调用launchTasks(tasks: Seq[Seq[TaskDescription]]),每个ExecutorActor会收到launchTask消息,在线程池中增加执行这个Task的TaskRunner线程。

    5、TaskRunner反序列化TaskDescription,执行Task, Task的子类包括ResultTask,和ShuffleMapTask, ResultTask 的主要工作是执行 func(context, rdd.iterator(partition, context)) func是我们写的Spark程序的action函数。ShuffleMapTask则是拉取Shuffle保存的数据,主要的逻辑是 fetchIterator-> iterator.read。

     

        每个人看源码时碰到想到的问题会是不一样的,我碰到的两个具体问题分别是:

    1、 每个rdd的compute方法是如何工作的。 

    2、 每个stage有parent stages, 但是为何stage包含的是Option[shuffleDependency],而rdd包含一个对象Seq[Dependency]

     

     

     

     

     

     

        这两个问题难度一般,主要是对于rdd和stage生成过程不清晰导致的。 rdd的生成过程是从前向后

    val text = sc.textFile("some/path/hold/data")
    val rdd 1 = text.flatmap(_.split(" "))
    val rdd2 = rdd1.map(w=>(w,1))
    val rdd3 = rdd2.reduceByKey(_+_)

        那么存在 text->rdd1->rdd2->rdd3的lineage。而stage的产生是DagScheduler从后向前划分产生的。从后向前的代码依次为: 

    var finalStage: Stage = null
        try {
          // New stage creation may throw an exception if, for example, jobs are run on a
          // HadoopRDD whose underlying HDFS files have been deleted.
          finalStage = newStage(finalRDD, partitions.size, None, jobId, callSite)
    
    private def newStage(
          rdd: RDD[_],
          numTasks: Int,
          shuffleDep: Option[ShuffleDependency[_, _, _]],
          jobId: Int,
          callSite: CallSite)
        : Stage =
      {
        val parentStages = getParentStages(rdd, jobId)
        val id = nextStageId.getAndIncrement()
        val stage = new Stage(id, rdd, numTasks, shuffleDep, parentStages, jobId, callSite)
        stageIdToStage(id) = stage
        updateJobIdStageIdMaps(jobId, stage)
        stage
      }
    
    

     

    private def getParentStages(rdd: RDD[_], jobId: Int): List[Stage] = {
        val parents = new HashSet[Stage]
        val visited = new HashSet[RDD[_]]
        // We are manually maintaining a stack here to prevent StackOverflowError
        // caused by recursively visiting
        val waitingForVisit = new Stack[RDD[_]]
        def visit(r: RDD[_]) {
          if (!visited(r)) {
            visited += r
            // Kind of ugly: need to register RDDs with the cache here since
            // we can't do it in its constructor because # of partitions is unknown
            for (dep <- r.dependencies) {
              dep match {
                case shufDep: ShuffleDependency[_, _, _] =>
                  parents += getShuffleMapStage(shufDep, jobId)
                case _ =>
                  waitingForVisit.push(dep.rdd)
              }
            }
          }
        }
        waitingForVisit.push(rdd)
        while (!waitingForVisit.isEmpty) {
          visit(waitingForVisit.pop())
        }
        parents.toList
      }

     

     

     private def getShuffleMapStage(shuffleDep: ShuffleDependency[_, _, _], jobId: Int): Stage = {
        shuffleToMapStage.get(shuffleDep.shuffleId) match {
          case Some(stage) => stage
          case None =>
            // We are going to register ancestor shuffle dependencies
            registerShuffleDependencies(shuffleDep, jobId)
            // Then register current shuffleDep
            val stage =
              newOrUsedStage(
                shuffleDep.rdd, shuffleDep.rdd.partitions.size, shuffleDep, jobId,
                shuffleDep.rdd.creationSite)
            shuffleToMapStage(shuffleDep.shuffleId) = stage
     
            stage
        }
      }

     

    private def newOrUsedStage(
          rdd: RDD[_],
          numTasks: Int,
          shuffleDep: ShuffleDependency[_, _, _],
          jobId: Int,
          callSite: CallSite)
        : Stage =
      {
        val stage = newStage(rdd, numTasks, Some(shuffleDep), jobId, callSite)
      // (之后代码省略)

         从代码可以看出 Stage类里的shuffleDep从后往前生成的逻辑,结论是 newStage完成了所有stage的划分逻辑! 看了这个函数名第一反应以为只是new Stage的作用。 这样逻辑大致理清了,剩下的关键点是 shuffleDependency是谁创建的。 shuffleDependency一定和rdd的生成有关,所以随便打开一个发生了shuffle的RDD类 就看到: 

    (CoGroupedRDD)
    override def getDependencies: Seq[Dependency[_]] = {
        rdds.map { rdd: RDD[_ <: Product2[K, _]] =>
          if (rdd.partitioner == Some(part)) {
            logDebug("Adding one-to-one dependency with " + rdd)
            new OneToOneDependency(rdd)
          } else {
            logDebug("Adding shuffle dependency with " + rdd)
            new ShuffleDependency[K, Any, CoGroupCombiner](rdd, part, serializer)
          }
        }
      }

        至此,DAGScheduler生成Stage问题梳理清楚了。 

     

         第二个问题是compute的作用。这个比较简单

    (RDD)
    final def iterator(split: Partition, context: TaskContext): Iterator[T] = {
        if (storageLevel != StorageLevel.NONE) {
          SparkEnv.get.cacheManager.getOrCompute(this, split, context, storageLevel)
        } else {
          computeOrReadCheckpoint(split, context)
        }
      }
    
    private[spark] def computeOrReadCheckpoint(split: Partition, context: TaskContext): Iterator[T] =
      {
        if (isCheckpointed) firstParent[T].iterator(split, context) else compute(split, context)
      }

       

         比如MappedRDD 

    override def compute(split: Partition, context: TaskContext) =
        firstParent[T].iterator(split, context).map(f)
    }

        又如ShuffledRDD

    override def compute(split: Partition, context: TaskContext): Iterator[(K, C)] = {
        val dep = dependencies.head.asInstanceOf[ShuffleDependency[K, V, C]]
        SparkEnv.get.shuffleManager.getReader(dep.shuffleHandle, split.index, split.index + 1, context)
          .read()
          .asInstanceOf[Iterator[(K, C)]]
      }

     

         这里插一句 shuffledRDD是shuffle完成之后,向shuffleManager去取数据,而写数据则是在ShuffleMapTask中。 这将在以后一节详细介绍。

     

    下节介绍Spark的Shuffle过程

     

        

     

    展开全文
  • 书中将framework的设计概念呈现在读者眼前,让读者了解如何设计与开发一个framework。  本书从概念、设计、强化到实践,一应俱全。全书分成三个部分:  ·windows forms开发基础知识  讨论关于windowsforms开发...
  • 7.5 关于设计规范 第 8章 项目跟进— 保障设计效果实现 8.1 做设计评审主导者 8.2 如何审核视觉稿 8.3 开发阶段,设计师该做些什么 第 9章 成果检验— 设计优劣可以判断 9.1 可用性测试 9.2 A/B 测试 9.3 定性...
  • 第二节 设备设计的思考方法和决策过程 设备的功能模块划分和布局基础 设备模块设计思路和方法 设备方案失效模式分析方法 附: 设备设计术语英文单词 第五章 项目管理基础 第一节 项目管理的基本内容 第二节 项目...
  • 下载地址:网盘下载《经济学人》杂志年度推荐的三大可视化图书之一 《大数据》作者、《经济学人》大数据主编肯尼思·库克耶倾情推荐,称赞其为“关于数据呈现的思考和方式的颠覆之作” 亚马逊数据和信息可视化类图书...

    下载地址:网盘下载

     

     

     

    《经济学人》杂志年度推荐的三大可视化图书之一 《大数据》作者、《经济学人》大数据主编肯尼思·库克耶倾情推荐,称赞其为“关于数据呈现的思考和方式的颠覆之作” 亚马逊数据和信息可视化类图书排名第3位 畅销书《鲜活的数据》作者最新力作及姐妹篇 第一本系统讲述数据可视化过程的的普及图书 这是一本教我们如何制作完美可视化图表,挖掘大数据背后意义的书。作者认为,可视化是一种媒介,向我们揭示了数据背后的故事。他循序渐进、深入浅出地道出了数据可视化的步骤和思想。本书让我们知道了如何理解数据可视化,如何探索数据的模式和寻找数据间的关联,如何选择适合自己的数据和目的的可视化方式,有哪些我们可以利用的可视化工具以及这些工具各有怎样的利弊。 作者给我们提供了丰富的可视化信息以及查看、探索数据的多元视角,丰富了我们对于数据、对于可视化的认知。对那些对设计和分析过程感兴趣的人,本书无疑就是一本必读书。

     

     

     

    下载地址:网盘下载

     

    转载于:https://www.cnblogs.com/long12365/p/9730568.html

    展开全文
  • 尽管技术如此重要,却少有人在快节奏生活中停下来深入思考技术。我们了解技术原理,却不知道它们从何而来。我们深思技术意义,追问技术到底能否决定人类历史,但是关于“技术”到底是什么,它是如何形成,...
  • 四色原型图的思考

    2009-06-01 16:07:07
    学习四色原型图有一段时间了,磕磕碰碰一路走来,更有理解严重偏差时候,不过近日总算有所领悟了...(关于四色原型图信息,大家可以自行google,有一个英文版的pdf文件。) 1、Moment-interval 这个图被作...
    学习四色原型图有一段时间了,磕磕碰碰一路走来,更有理解严重偏差的时候,不过近日总算有所领悟了,把一些想法写在这里,供大家共同讨论。

    不过说到四色原型图,大家更愿意一起比较的是“领域驱动设计”,简称 DDD,因此这个讨论也就把 DDD 掺合在一起了。

    (关于四色原型图的信息,大家可以自行google,有一个英文版的pdf文件。)

    1、Moment-interval

    这个图被作为是四色原型图中最重要的一个部分。我想也是最难理解的一个图。原文对它的定义是:某一个时刻,或者某一段时间内发生的业务。这种定义非常非常的抽象,导致我们在使用四色原型图去分析业务的时候,很难确定到底什么是 MI。

    那么,到底什么是 MI 呢?去除抽象的定义,以及其他挠头的文字,揭开它的本质,其实它就是业务领域的[b]关键性动词[/b]!

    例如,销售,报告,结算,确认订单 等等,这样关键性的动词,其实都表示一个 MI。

    所以,我们也就知道如何发现一个 MI了,在一堆堆的需求文字中,我们要找到关键性的动词,那么它就是 MI,也就是当前这个业务的核心了。围绕着这个 MI,我们会继续发现 ppt,role,desc 等等。所以 MI 神奇吗?一点都不神奇。深奥吗?一点不深奥。

    值得一提的是,虽然第一次找到了几个 MI,再经过分析后,很可能这些 MI 会合并到一起--这根据你的需求来设计。

    另外一个网上流传甚广,害人不倦的信息是:[color=red]很多人都说 MI 相当于 DDD 中的 service[/color]。这是绝对的误人子弟!通过上面的分析,我们已经知道 MI 必然包含业务的关键性动作,而这类关键性动作,通常是作为领域模型的一个方法,很少会作为 service 的方法。所以,说 MI 相当于 DDD 中的service是绝对的错误,正确的说来,应该是 MI 可能是 DDD 中的 service,更可能是 DDD 中的领域模型。

    2、ppt(Party, Place, Thing) 和 role

    对于 ppt ,大家都有一个共识,就是 ppt 一般相当于领域模型,而寻找ppt的方式也无外乎就是归纳名词的方式。不过,之前我们一定会先找到 MI,所以这个 ppt 也是围绕着 MI归纳出来的。

    Role 是个迷惑大家的东西。因为一提到 role,大家基本都联想到“人”之类的有生命的东西。可是在四色原型里,Role也可能是没有生命的物体。例如,车是一个 ppt,坏掉的车则是车的一个role,良好的车也是车的一个role。虽然我们通常会认为这是车的两个状态,但是这里我们将它设计为车的两个Role。

    3、desc
    desc这个图的定义是类似目录的结构,起到描述性的作用。大家的共识是它相当于领域模型的值对象----我的理解也就达到这个程度,我觉得用值对象来类比它非常合适,容易让人理解。

    4、四色原型,DDD 到底应该选择哪个?

    四色原型其实正如其名字一样,[b]是一种分析模式[/b],而不是设计模式。
    所以,分析阶段采用四色原型,而在设计阶段采用 DDD 应该是可以的。

    其实四色原型也没有什么特别深奥的地方,面对需求文档,我们先找关键性动词,围绕它去找关键性名词,理清业务的逻辑。这是我们常用的思维方式,不过四色原型将这个过程更加科学化,并且用图的方式让这个过程更加清晰。

    打个比方:以前我们分析需求是凭感觉,现在则是遵循一定的步骤做。

    所以,需求分析,不用四色原型也ok,但是使用四色原型,会更加顺利和清晰。


    以上是我对四色原型的思考,欢迎大家来一起讨论。
    展开全文
  • 编程之美,pdf电子书

    2019-04-20 12:59:41
    编程之美pdf 这本书收集了约60道算法和程序设计题目,这些题目大部分在近年笔试、...这本书很多题目会出现在IT 行业各种笔试、面试中,但这本书更深层意义在于引导读者思考,和读者共享思考之乐,编程之美。
  • 本书中的主题取代了IT界目前的思维方式,并涉及到在UX教育或工作场所中不常考虑的话题,这是专为任何从事UX工作的人而设计的-从开发人员到招聘经理。每一次讨论都为你自己的思考和理解提供了一个启动器。这本书是由...
  • 编程之美PDF

    2016-03-17 16:09:18
    这本书收集了约60道算法和程序设计题目,这些题目大部分在近年笔试、面试中出现过,或者是被微软员工热烈讨论过。作者试图从书中各种有趣问题出发,引导读者发现问题,分析问题,解决问题,寻找更优解法。本书...
  • 第一部分关于计算机的思考 第0章计算机科学研究 第二部分开始编程 第1章入门 第2章控制语句 第3章算法和程序开发 第三部分组织:数据结构和函数 第4章字符串 第5章函数快速入门 第6章列表和元组 第7章深入...
  • 《仙剑奇侠传》之父姚壮宪热情推荐,技术...13.3 对游戏开发方法一些思考 369 第14章 编程和游戏 373 14.1 操作 376 14.2 角色设定 378 14.3 再谈技术 379 14.4 浅谈网络游戏 381 14.5 小结 382 后记 383 致谢 385
  • 《仙剑奇侠传》之父姚壮宪热情推荐,技术...13.3 对游戏开发方法一些思考 369 第14章 编程和游戏 373 14.1 操作 376 14.2 角色设定 378 14.3 再谈技术 379 14.4 浅谈网络游戏 381 14.5 小结 382 后记 383 致谢 385
  • 这个是完整扫描版,不是那种一页页合集。 这本书收集了约60道算法和程序设计题目,...这本书很多题目会出现在IT 行业各种笔试、面试中,但这本书更深层意义在于引导读者思考,和读者共享思考之乐,编程之美。
  • 吴永和:e-Learning技术系统演化...姜昌华:关于开源协作学习环境Sakai学习与思考.pdf 李健伟:北邮远程教育平台设计理念与整体架构1.pdf 李江涛:使用Sakai构建开放式教学平台.pdf 苏占玖:开放式插件系统研究.pdf
  • 编程之美(高清版)pdf

    2018-03-13 15:46:18
    这个是完整扫描版,不是那种一页页合集。 这本书收集了约60道算法和程序设计题目,...这本书很多题目会出现在IT 行业各种笔试、面试中,但这本书更深层意义在于引导读者思考,和读者共享思考之乐,编程之美。
  • 编程之美收集了约60道算法和程序设计题目,这些题目大部分在近年笔试、面试中出现过,或者是被微软员工热烈讨论过。作者试图从书中各种有趣问题出发,引导读者发现问题,分析问题,解决问题,寻找更优解法。 ...
  • 第3~8章分别从网络架构和接入技术两方面对5G潜在关键技术展开具体阐述,其中接人技术包括5G的热门研究方向:大规模天线、超密集组网、高频应用、新型物理接人技术等,系统呈现了关于5G演进的思考。  《5G:关键...
  • 完整版本编程之美PDF,以下是目录: 第1章 游戏之乐——游戏中碰到题目 1.1 让CPU占用率曲线听你指挥 1.2 中国想起将帅问题 1.3 一摞烙饼排序 1.4 买书问题 1.5 快速找出故障机器 1.6 饮料供货 1.7 光阴切割...
  • 1. 是自然语言处理重要应用也可以说是最基础应用3.0 分 A.文本识别 B.机器翻译 C.文本分类 D.问答系统 我答案C 答对 2.关于专用人工智能与通用...通用人工智能可处理视觉听觉判断推理学习思考规划设计等各类问
  • 1. 是自然语言处理重要应用也可以说是最基础应用3.0 分 A.文本识别 B.机器翻译 C.文本分类 D.问答系统 我答案C 答对 2.关于专用人工智能与通用...通用人工智能可处理视觉听觉判断推理学习思考规划设计等各类问

空空如也

空空如也

1 2 3 4
收藏数 71
精华内容 28
关键字:

pdf关于设计的思考