单机应用架构的一般性设计

1、单机系统应用概述

       几乎所有的互联网企业,在一开始都是从单机系统开始的,最早的淘宝就是基于LAMP的单机系统,数据库和应用服务器部署在一台物理机器上。对于大部分的人来说,单机系统往往很简单,几乎就是对数据库的增删改查,并没有什么架构可言,但我并不这么认为,即使是简单的单机应用系统,也可以有架构,而且,好的架构一定可以减少业务对系统的冲击,减少不必要的返工量。

       对于单机系统来说,本质上它就干了一件事:处理外部系统来的请求,将必要的数据变更保存到存储系统或者根据一定的规则查询数据,然后对外部系统进行响应,返回响应结果。将这件事大致的延展一些,就涉及到三部分内容,第一部分类似于入口,这个入口是需要处理请求,而且肯定不是一种来源的请求,第二是要按照真实业务规则处理请求,第三是必要的数据存取。基于这三部分,我们很容易想到目前比较流行的controller-service-dao的三层架构。其中controller用于处理Web端的HTTP请求,service用于处理业务逻辑,dao用于处理数据库操作。这种三层架构虽然流行,但并非通用的一般架构,就拿controller来说,无论基于spring mvc还是Struts2,它都是处理HTTP请求的,而不是处理TCP请求的。这些MVC框架都是基于tomcat跑的,而tomcat恰恰是用于处理HTTP请求的。如果这时候我们需要处理TCP,这些框架就失效了。下面就说一下我眼中的一般架构:

       总的来说还是三层架构,不过不是上面的那三层,而是更具扩展性的三层架构。第一层是网关层,用于处理不同类型的请求。第二层是业务层,用于处理业务逻辑,第三层是基础层,它不仅仅包含DAO这样的数据操作,还包括其他无关业务的技术组件,比如缓存,调度任务,事务,消息等等。

2、单机应用系统三层架构

2.1、网关层

网关层本质上是对不同的网络协议的请求进行处理,比如HTTP协议,TCP协议,当然,也可以对其他协议进行处理。

2.1.1、HTTP请求

对于处理HTTP请求的方案,业内已经非常成熟了。首先,tomcat容器本身已经把HTTP请求处理的复杂性封装掉了,其次,spring mvc对请求处理提供了RESTful风格的编码方式,大大降低了开发的复杂度。我们要做的就是对controller按照业务领域划分,比如按照订单、会员去划分大的领域,里面的各种方法就是这个领域内的操作。每个controller的方法只做三件事,第一,将请求参数解析出来并组装成内部参数,第二调用下层服务执行业务逻辑,第三组装返回结果,对于异常情况,需要记录异常堆栈日志并转换错误码,堆栈信息不要暴露到调用方。

2.1.2、TCP请求

对于处理TCP请求的方案,业内也已经很成熟了,比如Netty。但是,TCP请求毕竟太底层,我们往往会基于TCP协议去开发自己的协议。另外,很多分布式框架都是基于TCP协议的,比如RPC框架Dubbo,消息框架RocketMQ等等。从单机系统到分布式系统,无非就是网关层多了处理TCP请求的逻辑,理论上底层的业务是无需感知自己到底是出于单机环境还是分布式环境,网关层的作用就是要屏蔽这种不同外部调用源的细节。

2.1.3、小结

网关层本质是对协议进行处理,同时将业务逻辑收敛到网关层,而不是暴露给外部,当内部业务逻辑进行重构的时候,外部调用方就不需要感知这些变化,当外部调用源增加时,内部业务逻辑不需要感知这种变化,从而将外部调用方和内部业务逻辑进行了解耦。

2.2、业务层

业务层是一个系统,无论是单机系统还是分布式系统群中的某个业务系统,业务层都是承载业务流程和规则的地方。业务层从外到内包含三层:第一层是业务服务,第二层是业务流程,第三层是业务组件。

2.2.1、业务服务

业务服务是业务层对外的统一门面,它由三方面组成:业务接口、入参、出参。

a) 业务接口

     一个业务接口代表一个领域的业务服务,比如订单域的业务服务就由接口OrderService表示,会员域的业务服务就由接口MemberService表示。接口可以按照执行性质分为读接口和写接口,比如OrderReadService和OrderWriteService。读写分离的好处是可以对集群进行读写分组,从而管理流量,当然,单机系统读写分离意义不是太大。领域内的操作则以业务接口中的方法的形式体现,比如订单域有下单createOrder,取消订单cancelOrder等等操作。对于这些操作,尽量设计出有业务含义的方法,而不是增删改查,当然,对于一些简单的业务,也只能增删改查。

b)入参

接下来,是入参的设计。入参对于读方法,比较简单,不做讨论。对于写方法,我们将入参设计成有层次的数据模型。首先需要设计出公共的数据模型,比如订单数据模型,商家数据模型,商品数据模型等,然后将这些数据模型和一些特定业务下的个性数据结合,组成Request对象,这个request对象按照不同业务操作不同而不同,对应的返回结果就是response,它也是随着不同业务返回的参数不同。

举个例子,拿下餐饮订单来说,首先,我们应该识别出这些业务流程中一些比较基础的数据模型,比如餐饮领域的菜品、桌位等,这些模型之所以说是基础模型,是因为,不管下什么餐饮订单,菜品和桌位肯定是逃不了的,它们是可以被复用的!因此,我们分别为这些基础模型设计相对于的DO(Domian Object):DishDO(菜品)、BoardDO(桌位)等等,接下来,我们为下餐饮订单设计一个请求对象DishOrderCreateRequest其中DishOrderCreateRequest内部包含了DishDO和BoardDO,另外会包含一些特定的属性,比如人数啊,折扣啊等等,这样一来就能做到通用和灵活兼顾,DishOrderCreateRequest代表的个性化的灵活的业务入参,而DishDO和BoardDO等则代表了不易变化的基础模型。

c) 出参

最后,是出参的设计。对于写方法,一般出参比较简单。对于读方法,出参往往是一个结构与层次比较复杂的组合对象。比如查询一个订单,这个订单有订单基本信息,还有商品信息,收货人地址信息等。在设计出参的时候,结构上要设计成组合对象,但是真正查询的时候,通过查询选择器,去查询不同的组合对象。比如查询选择器设置商品查询为true,地址查询为false,那么这次查询出的订单就只包含商品,而不包含地址。

2.2.2、业务流程

     业务流程其实就是对业务规则的解释,只是这种解释使用代码去实现的,我们要做的其实就是准确翻译这些业务规则,并维护好这些业务规则。

     业务流程中可以大致分为三种动作节点,1、组装参数节点  2、规则判断节点  3、执行动作节点,其中每个动作节点都是一些业务代码的片段。举个例子,下餐饮订单,我们第一步就是将上层传入的参数组装出一个基础的DishOrderDO(组装参数节点),然后按照特定的规则去填充这个DishOrderDO(规则判断节点),然后就是调用DAO去创建DishOrderDO(执行动作节点)。

     业务流程是最容易变化的地方,要想维护好业务流程并不容易,总的思想是将大的业务流程拆分成小的业务流程,抽出每个业务流程中共有的代码片段,变成可维护的业务组件。


2.2.2、业务组件

a) 基础组件

业务组件其实是将一些内聚的可复用的代码片段进行封装。和业务流程中的三种业务节点相对应,业务组件也分为三种:组装参数组件 、规则判断组件 、动作执行业务组件。业务组件的抽象往往是对业务有了深刻理解之后才进行的,盲目地进行业务组件的抽象,往往到头来白忙活。

b) 能力

对业务组件进行进一步抽象,可以得到能力。业务能力是具有一定复用性的组件的组合,比如发短信能力=组装短信参数组件+发短信组件。对于发短信能力,可以被不同的业务流程复用,比如订单下单成功发短信,支付成功发短信,逻辑都是相似的,只有内容不同。能力是一种粒度比较大的组件,粒度越大,往往复用性就越小,对能力的抽取,也是基于对特定业务深刻的理解,没有一劳永逸的银弹。

c)更高纬度的抽象

经过本人的实践,对于互联网这样的需求变化极快的场景,更高纬度的组件抽象往往性价比很低,不建议大家去做。


2.3、基础层

基础层包含两个部分,第一是接口定义,第二是技术组件。

2.3.1、接口定义

接口定义是按照不同的技术框架,同时结合业务需要,设计出合理的接口,对于业务组件来说,它们只会感知技术接口,而不会去感知技术实现,我们也不应该将具体的技术细节向上暴露,这也就是所谓的面向接口编程。技术接口往往是业务与技术之间的桥梁,接口本身是含有业务含义的,最常见的就是DAO接口,我们设计DAO接口的时候,不会设计成insert、update、query这样业务无关的接口,而是设计成insertUser,updateUserById等等和业务相关的接口,同样的道理,设计缓存接口的时候,也不能设计成put、get这样的接口,而应该设计成cacheUser,deprecateUser这样的接口。

2.3.2、技术组件

单机系统的技术组件一般来说分两种,一种是通用的技术组件,比如:数据存储、缓存、消息和调度任务、事务、锁。一种是面向特定领域的技术组件,比如使用个推进行短信推送,使用七牛sdk进行云存储。下面稍微谈谈通用技术组件。

数据存储:数据存储包括关系型数据库、非关系型数据库以及文件存储系统。关系型数据库,比如MySQL,适合存放绝大部分业务数据。非关系型数据库,比如hbase,可以存放历史日志,也可以对历史的MySQL数据进行归档。文件存储系统,一般都是基于Linux文件系统,比如图片、html文件等等,也有基于HDFS的,用于大数据分析。

缓存:缓存按响应时间分,可以分为纳秒级缓存,毫秒级缓存和百毫秒级缓存。纳秒级缓存就是一般的基于本地内存的缓存,比如encache,毫秒级缓存一般是集中式的内存缓存,比如memcache,由于访问时远程调用,因此响应时间会延长到几毫秒,百毫秒级缓存一般是集中式可持久化的缓存,比如redis,由于存在远程访问以及缓存击穿导致的读取持久化记录,它的响应时间会更长些,到几十甚至上百毫秒。单机系统一般用本地内存缓存就够了,当缓存被击穿的时候,直接访问数据库。

消息和调度任务:消息和调度任务本质都是一种异步化的手段,区别在于消息无法控制异步的时间,而调度任务可以。一般,消息发送出去后,监听消息的系统会立即收到消息,从而立即触发业务逻辑的执行,而调度任务则会按照调度规则,一次或者多次的执行业务逻辑。单机系统中消息和调度任务用到的比较少,在做日志监控的时候可能会用到消息,在进行数据报表统计的时候可能会用到调度任务。

事务:事务本质都是基于数据库去实现的,单机系统的事务就是依赖数据库的事务,我们可以使用spring-tx的事务模板进行事务操作,在业务逻辑开发中,一定要把握事务的大小,建议把业务比较紧密的一堆数据库操作放在一个事务里,不要随意的为每个方法都开启事务。

锁:单机系统中主要用到两种锁:乐观锁和悲观锁。乐观锁依靠在数据库的业务表加版本字段来实现,每次更新都会去判断版本是否变化,如果变化则需要重试,这种锁的粒度比较小。悲观锁是基于JDK的Lock接口的,对一个业务流程进行加锁和释放锁的操作,锁的粒度比较粗。

3、当单机变成分布式,上面的架构会变成什么样?

从单机系统到分布式改造,最常见的改造就是服务化、消息异步化和数据库分库分表,下面看看从单机系统到分布式系统演变过程中,上面的架构会有什么变化。

3.1、服务化

服务化简单的说就是,原来是单机系统A,现在从A分离出一个B,B承载了A中的一部分功能,原来只要在A内部完成的功能,这时候就需要一次远程调用去调B完成。当进行了服务化改造以后,单机系统的架构会怎么变化呢?

3.1.1、网关层改造

首先B系统也会有网关层,B会在网关层提供Dubbo服务,并注册到zk,A会在基础层启动一个Dubbo客户端,A在业务流程中调用基础层的Dubbo客户端进行B服务的调用。

3.1.2、业务层改造

A在业务流程中,原来是本地操作,这时候需要调用基础层的Dubbo客户端进行远程操作,其他地方不会变化。

3.1.3、基础层改造

A会在基础层新增Dubbo客户端的技术组件,这个技术组件主要是对Dubbo client进行封装,处理一些异常,对内暴露合理的接口。

从上面来看,从单机架构变成SOA架构,变化不是太多,原有的架构受到的冲击没有想象中的大,A中的业务逻辑对SOA化感知不大。当然,以上只是架构变化不大,开发、运维和测试的改变其实挺大的,在此不谈这些。

3.2、消息异步化

消息异步化是在SOA化基础上做的,SOA化后,一次调用后台可能会经过很多系统,这些系统如果都是远程调用的话就会有问题,一个问题是调用耗时太长,容易超时,第二个是系统调用中的强弱依赖倒置,一条链路经过A-B-C-D,如果C是弱依赖(比如调用C就是记录一下流水日志),其他都是强依赖,那么只要C挂了,整条链路就挂了,因此,我们需要把C解耦出去,解耦方法就是消息异步化,链路就变成了A-B-D----C,其中虚线表示异步解耦。下面看看消息异步化后架构的改造

3.2.1、网关层改造

网关层主要是对消息的接受,C需要在网关层引入消息监听器,监听主链路发出的消息。

3.2.2、业务层改造

D需要在业务层调用基础层的消息发送组件,进行消息的广播,其实对于D来说,就是多了一个动作执行节点,其他业务流程不会受到影响。

3.2.3、基础层改造

D需要在基础层对构建发消息的技术组件,对业务层暴露发消息的接口。

和SOA化一样,消息异步化对原有架构有一定影响,但影响不大。当然,对开发测试运维影响还是挺大的。。。

3.3、数据库分库分表

数据库分库分表主要是数据库压力太大,撑不住应用集群的调用。一般中前期都是进行数据库分库,也就是把原来一个库中的几十张表,分成多个库,每个库中的表都是内聚的,这些内聚的表单独部署一个数据库。当需要分表的时候,往往业务量已经很大了(至少也要C轮了吧。。)。数据库分库分表本质是数据存取方式的改造,因此不涉及网关层和业务层,只涉及基础层,基础层可能会引入类似TDDL这样的中间件,不过改动也不是太大,业务往往感受不到,当然,从单机数据库到分库分表,DB运维一定会有质的变化,这时候就会有专门的DBA来维护DB了。。。

4、总结

扯了这么多,最后只想说一句话,系统架构这东西,其实很多时候都是面向业务的,和技术关系不是太大。。。因此,喜欢研究架构的同学,可以更往前走一步,去研究一下产品架构,你会发现,平日里吵的不可开交的产品和技术,在架构这条路上,其实是穿一条裤子的,所谓的相爱相杀,大抵如此。。。


版权印为您的作品印上版权

获取授权

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容