美文网首页
分类信息主站的近领域设计

分类信息主站的近领域设计

作者: kevinfuture | 来源:发表于2018-09-05 18:10 被阅读0次

    参考:

    1、IO模型

    2、Java锁源码

    3、事件驱动、观察者与nio的应用

    鉴于昨晚的经历,别人问到我,我竟然不知道怎么回答,我觉得有必要写一些关于领域驱动模型的东西了!

     第一次接触领域模型,是去年看到美团点评的文章,刚好是在把分类信息listing架构优化后的时间看到的,然后当我看到他们写的那篇文章后,加一印证,发现原来当时的设计思想就贴合这种领域驱动的模型方式!

            遗憾的是自己并没有深入到理论知识上去,所以也没有过多的总结,跟别人说的时候,就说直接看领域驱动模型!

            到昨晚才发现,原来自己只是知道怎么去做,却不知道怎么跟别人讲述如何去做!

            好吧,接下来,我会根据我的实际经验与编码以及思考的问题,具体的出具一篇关于之前,贴合领域驱动设计的架构优化的文章,记录下来,后续编写文章以分享!

    问题分析

    一、以前仅一个需求,就需要反复查看大量代码,然后才补充了一小段代码,无用功上浪费了大量时间;

    二、代码复用性差;

    三、大量耦合;

    四、重复性代码(过分冗余);

    五、传输了重复性数据;

    最终导致了响应效率奇差;机器无端浪费硬件性能;gc间隔端时间长;更由于难以阅读的原因,维护人员近20个;虽然有项目规范,但是不能有效的抵制代码腐败!

    要达成的结果

    1、结构化代码,抵制代码腐败;

    2、解耦代码,提高复用性;

    3、提升响应效率;

    4、延长gc时间;

    5、节省硬件开支,充分利用机器性能;

    6、提高人效,降低成本;

    最终成果

    只能以文字叙述!大体来说:响应效率从原来的2s左右降低到110ms左右;大体情况看图;主体服务机器负载情况从原来的13~16%降低到7~8%(这还是将大量逻辑聚合到了该主体服务);web端降低更加明显,开发人员也大量减少,并且更容易开发等等。

    一期的对比图找不到了,这是二期改造后的对照,分别表示一个集群状况,深浅分别表示前后对比!实际上一期的效果要更明显一些!!!

    需求分析

    1)、主体信息查询;

    2)、验证是否需要,补充推荐信息,如需要,根据地域类别再查询,综合得到allinfoList;

    3)、处理,当前信息可直接用于展示的数据;

    4)、根据allinfoList,查询各种类型的其他第三方扩展数据(各不相同);

    5)、将第三方数据合并到allinfoList,形成输出数据;

    6)、各端甚至类别与地域不同,导致key值不同,需再处理输出;

    架构设计方案

    首先解释一下,由于当时并没有接触领域模型,很多都是慢慢思索出来的!所以对于建模的思路可能也并不那么相似!

    首先,尽量复合软件设计的七大原则:单一职责、开放封闭、依赖倒转、里氏替换、接口隔离、聚合。也就是尽量在设计的时候抽象化、保证职责单一性、扩展性尽可能高、保证基类可以被替换扩展、细分功能、将所有的功能尽量对抽象进行依赖,同时对逻辑进行聚合的操作。

    其次,如何思索聚合的方式!如何分配出合理的模块。也就是如何将后期可能出现的需求、现有的需求,有机的整合起来。达到更高更好的兼容性与扩展性。

    第三,如何更合理、更方便同时能给开发人员带来更少思索多线程方式的并发事件!实现一个消息接收形式的、或者以访问事件驱动的多线程扩展服务模块。

    第四,涉及到具体当前场景。比如多端一致性与不一致性,如何聚合统一与差异区分。

    第五,可能就是怎么样去解耦整体代码结构了,实际上我认为是非常重要的一个环节。

    第六,将散布在各个容器外的逻辑整合到一个一种容器内。

    设计与分析

    MVC:由于当前微服务,没有一套可以解耦的方案框架,所以自己按照mvc的设计思想,实现了一套针对当前业务领域比较合适的mvc,并非框架,而是嵌入到业务内部的mvc,也就是系统架构与业务架构融合在了一块,当然主要是用于路由转发各种接口!

    “付出”与“回报”:简单来说就是入参的目的,与最终期望得到的结果。再一个,入参与回参需要尽量简单化,不能说是返回的结果,还需要客户端继续一些处理才能使用!这里同样有一些需要注意的地方:比如说我们返回的数据肯定是各种service获取来拼装的数据,而且这些拼装数据,有可能会前后有关联才能最终拼装成一个完整状态数据,甚至有一些扩展数据。所以需要有一个数据对象存储在各个阶段处理好的数据,然后一次性返回。

    业务建模

    我将这些信息分成即个的模块,数据模块为:主体数据模块、扩展数据模块;行为模块:路由模块、数据拆分模块、数据合并模块、数据转换模块;结果模块:强约束结果模块!大体模块是这么区分的,里面还涉及到一些更小的单元模块!各个模块之间尽量不要有耦合的关系,也就是缺少某些模块也是可以使用的,除了数据行为模块。

    如何把介于模式边界之外的内容聚合起来,同时能够分模块(那时候还没有想到模型建立这样的词)有机的整合,才是最关键的部分!

    主体数据模块

    主体数据细分的话,有好多部分组成,而且相当复杂!可以确认的是,要对主体数据中的一些信息,进行加工或者提炼之后,作为一部分的数据返回。那么为了避免返回重复的信息,采用对象中含map的形式避免有重复数据,而这个对象将会是贯穿到最后返回。会对该对象集合进行不断修饰的一个对象集合。

    不管如何就算是其他业务代码都不能执行,必须保证主体数据是可以返回结果的!在松耦合方面,关联关系的处理尤为重要,必须不能相互影响。

    实际上为了使业务端更易扩展,在分析需求与业务的时候,我更深层、更细致的进行了,各种信息的抽象,形成了仅一种信息就会有一种模块的抽象实现代码。这种更细致的方式最终导致可能出现类爆炸的情况,也就是如果处理不当,很有可能每次调用都会大量对象的创建对象,频繁的回收,耗时也会受到影响。所以引入了享元(领域中叫做in-memeroy)!

    这样也会有个问题,就是如果存在有状态的代码,那么高并发、多线程访问的情况下,就会出现数据前后无法衔接,我称其为数据“追尾”现象!

    为了解决这一问题,同时考虑到当前系统的目的,与要最重要保证的效率,所以基本上设计的所有模块结构以及类,都是无状态的,这样保证了不会出现高并发情况下的追尾情况。

    那么如果仅仅只是无状态可以吗?如果不追求一些极致的话,没有问题。但是我在意,因为无状态的类与代码,确实没有gc root的关联了,但是还是会频繁造成young gc,如果能避免的话,还是要尽量避免。

    所以此类业务需求,要结合无状态模型+享元的方式实现!

    扩展数据模块

    调用第三方服务,io操作,以前都是串行操作的!先不说响应效率问题,仅仅串行的代码,就很难维护!那么有没有什么方式可以很好的加以维护呢?这块内容考虑了很久,完全可以使用组合模式进行拼装,但是如何分模块抽象出来呢?因为我们的业务非常复杂,可能我描述的不是很清楚,但是如果做过这块的人就知道复杂的难以想象!

    所以必须针对每个点、每个服务都进行业务拆分模块划分,在领域驱动上就叫做业务建模吧!

    同时如何更合理的进行并发访问呢?因为对于工程项目来说,很多耗时都浪费在了io请求上!如果能够并行操作的话,实际上能节省大量时间跟资源!但是我们都知道,线程代码必须要慎重思索考虑,不然的话也很容易出问题。所以如何让维护人员,后续能尽量少的考虑线程问题,也在我的考虑范围内!

    最终,我决定使用观察者的方式,隐藏所有线程的细节,让后续开发维护人员只需要关注需求点即可,同时还保证了并行的效率。还有另一个原因,就是可以通过这种方式,实现唤醒通知观察者的形式。也让这种访问形式,更像事件驱动的方式。

    涉及到多线程,我尽量又将线程做成一个模块,使用者只需要将需要进行多线程的任务,丢进这个模型中,随后就可以得到对应的多线程任务。这里的超时时间,注意也需要根据具体业务场景设置!

    第三方服务,又进行了不同的抽象模块设计!这时候,就是将数据的行为方式与展示方式分开适配了。包括地域模块、类别模块、会员模块、敏感词模块、企业信息模块等等。太多了,每个模块又不相同,不再描述!

    路由模块

    这个模型的建立,完全是参考了mvc的方式,其实就是一个责任链的执行模式!主要考虑如何更合适的解耦、更适配当前的系统与业务架构!

    之所以做这么个路由转发(实际上并不是最好的设计方案,后续可以再改进一下),是因为要考虑到其他接口、或者业务接口的调用方式的不同:

    数据拆分模块

    拆分模块,是位于查到所有数据信息之后。并不是以什么对象形式进行拆分的,主要是种想法,可以认为是种抽象形式!因为主体信息处理,扩展数据处理是两种不同的行为方式!主体信息处理,基本上都只是一些操作指令;而扩展数据则是一些io操作。所以两种模型的处理方式上要分开,这样才更贴切实际!

    既然拆分,那么就得有处理过的数据的合并!所以这块有采用了一些比较巧妙的方式进行了数据合并!

    主要思想就是fork与join,参考如下图!

    数据合并模块

    这个是一个对象的行为,因为前边使用map保证了唯一性,所以又实现了 一个MapBuilder,用来合并各个扩展数据!

    通过调用MapBuilder扩展的append方法,对要合并的Map进行键值去重,同时合并成一个Map,以对象的方法行为合并不同Map!

    数据转换模块

    以前数据转换,除了要处理成当前业务线要使用的通用数据,还需要再根据不同的端或者类别分别在转换一次!既麻烦又丑陋!

    建模之后,首先就对这两种转换方式进行了约束与剔除,目前复杂的转换方式,都结合到了,各层中的系统结构当中。

    但是仍然需要再进行一次转换,这主要是历史原因导致的,但是又需要兼容。所以这块仍然存在一些键值的匹配问题!

    所以再次对该模块进行深度抽象,分别按照类别与端抽象出不同的模式来!而为了减少判断,又增加了责任式处理的方式。在抽象层写统一性的代码,在各个实现层处理匹配问题。

    其实保留这块也是有好处的,比如再次进行垂直扩展的时候,可以继续对这块进行抽象隔离!

    强约束结果模块

    如果仅仅是制定了编码规范的话,仍然不能很好的控制代码腐败的进程!

    所以像阿里他们更狠,直接出了插件检测,就是为了规范你的代码!但是我认为,一份好的代码,更应该在代码层面做好控制(实际上仍然会有人别处心裁去腐败,这时候规范就很重要了)。

    而我们的模型中,实际上不仅仅在这个模块上进行了约束!在整个宏观项目结构上,也做了大量的约束。此处只是以此模块作为分享探讨!

    以前的情况:往往是一个新人并不知道,到底针对一个需求是不是有过对应的值返回给过客户端,所以再次新增一个不一样的key值交付过去。长此以往,会有越来越多的重复数据做了无效传输,而代码也就越来越臃肿。

    为了杜绝这种情况,对返回的数据单元,也做了模块划分。抽象出来了一个枚举类型的接口INameEnum。同时也设置了两种枚举类型,实现了该接口。对于使用实现类的使用者来说,就必须使用返回结果为Enum的这两个枚举类  ExtendEnumImpl 和MajorEnumImpl,不使用这种代码,则无法返回给客户端数据。

    而比如,不同端、不同类别寻找哪个枚举值,也在这两个枚举类的抽象中实现了,使用者不用关心,到底需要如何区分不同的端!

    这样,是非常贴合,数据驱动行为的方式的!

    最终流程

    通过如上调整,我们就得到了一个比较高性能的业务项目!当完成优化之后,后来接触到领域驱动的概念,发现非常符合,其他一些公司的项目的设计!

    按照网上一些说法,可能我属于实战派吧!概念的话只能大概明白,还是要具体到项目的业务需求分析上,才能给出更好的模型设计!

    相关文章

      网友评论

          本文标题:分类信息主站的近领域设计

          本文链接:https://www.haomeiwen.com/subject/yoczwftx.html