从JavaWeb编年史的远古时代,一直到白银时代,我们见证了JavaWeb开发模式的大致变迁。说白了,就是不断解耦合的过程。接下来我们来聊聊项目架构的演变,之所以我把它划到了JavaWeb编年史(黄金时代),是因为在早期的JavaWeb项目中,很少有架构的概念,基本就是一个单体项目,然后不断在已有项目上堆砌新的功能。所以,当我们开始有了架构的概念,其实也是一种非常大的进步,我个人称之为黄金时代,哈哈。

对于小公司来说,大部分项目都比较小,交给一个人开发足以,使用单体项目也无所谓。但是,随着互联网的发展,项目的功能越来越多,就得考虑把项目进行拆分,从而进一步解耦。

JavaWeb编年史 - 黄金时代,就是架构不断升级的年代,各种新的思想层出不穷,但是目的只有一个:拆分项目,进一步解耦。(其实也并没有特别严谨的时间顺序啦,如此行文只是为了统一下文章风格哈)

模块化时代

在解释模块化之前,我们先回顾一下,面向对象的三大特性是什么?

哦,聪明的你肯定立刻就想到继承、封装和多态。面向对象使得Java代码能够轻易被复用,Java程序变得更加灵活。

现在,我们把宾语换成Java项目,就变成了:面向对象使得Java项目能够轻易被复用,Java程序变得更加灵活。

是啊,如果受体变成项目,项目之间也去实现继承,那么很多功能就可以得到复用。而maven可以通过聚合工程,来实现这一点,这个功能是maven本来就具备的。那么,只要我们是用maven来搭建项目,就可以实现真正的模块化开发。

回想一下上一节的springboot项目,结构是这样的:

JavaWeb编年史(黄金时代)_maven

项目根据package来进行模块划分,看似没有问题,但是非常不好维护。

主要体现在:

1.如果其他项目有类似的功能,则需要每一个项目都写重复的代码,比如xxxService、xxxDao等。

2.团队协作的时候,不管你是用SVN还是git,都有极大概率多人操作同一个类,不利于版本管理。

3.项目如果体量越来越大,则文件会越来越多,整体会变得臃肿。

因此,需要用模块化的方法将这些package分化,简单来说就是把一个个package变成一个个module。

moudle叫做组件,或者叫子工程、子项目都可以。

2.演示将单体项目模块化

刚才那张图,我们需要将每一个package拆出来,做成一个module,最终项目结构就变成了这样:

JavaWeb编年史(黄金时代)_maven_02

下面我们依次来介绍各个模块的作用。

web-api

这个是接口模块,主要是提供服务给外界,对应于之前的controller层。

JavaWeb编年史(黄金时代)_maven_03

因为是前后端一体的项目,这边继续保留webapp。如果做的是前后端分离项目,则不需要webapp。

注意,启动配置这边要填写这个,否则访问不了JSP页面。

JavaWeb编年史(黄金时代)_xml_04

然后webapp的目录要进行如下配置,否则也是访问不了页面的。

JavaWeb编年史(黄金时代)_jvm_05

web-api是整个工程的门面。

pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>web1</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>web-api</artifactId>


<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>web-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>web-service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

</project>

从pom中可以看到,web-api需要依赖web-service,web-model。

web-service

这是服务模块,为web-api提供服务支持,对应原来的service。

JavaWeb编年史(黄金时代)_maven_06

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>web1</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>web-service</artifactId>


<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>web-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>web-dao</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

从pom中可以看到,web-service需要依赖web-dao,web-model。

web-dao

这是数据访问模块,对应原来的dao。

JavaWeb编年史(黄金时代)_xml_07

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>web1</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>web-dao</artifactId>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>web-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

</dependencies>

</project>

从pom中可以看到,web-dao需要依赖web-model。

web-common

这是公共模块,主要放置一些工具包和类库,因为项目只是演示,所以目前这是一个空模块。

父项目

父项目就是这个web7,其余5个模块都是在这个项目里面的。

JavaWeb编年史(黄金时代)_jvm_08

体现在pom文件,就是

<modules>
<module>web-common</module>
<module>web-api</module>
<module>web-dao</module>
<module>web-model</module>
<module>web-service</module>
</modules>

然后每一个模块的pom中都有一个parent

<parent>
<artifactId>web1</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>

也就是说,那5个模块都是父项目的子项目,继承自父项目。

打包方式:

<packaging>pom</packaging>

还有,顶层的父项目的打包方式必须为pom,表明这是一个聚合工程。pom工程就像是一个公共类,给下面的子模块提供了maven依赖版本号,方便控制版本。其实也没有什么特别神秘的地方。

模块化的好处

我们一般所说的模块化,就是指的maven聚合项目。

聚合的意义

对于一个大型的项目,如果我们直接作为一个工程开发,由于相互之间的依赖我们只能从头到尾由一组人开发,否则就会出现一个类好多人开发,相互更改的混乱局面,这个时候我们就将项目进行了横向和纵向的拆分。

所谓的横向的拆分就是我们平常说的三层架构,将项目分成了web层,service层、dao层(web层也被叫做表现层,service层也被叫做业务层,dao层也被持久层),可以理解为将一个功能模块的不同调用过程进行了水平方向的拆分。

所谓的纵向拆分就是将一个项目的多个功能模块进行了,可以理解为为了完成一个系统,深度(纵向)分析需要有哪些功能,然后将这些功能独立出来,进行了(纵向)拆分。

横向拆分后,每个功能模块进行了单独的开发之后,项目整合的时候就需要有一个能够整合这些项目或者模块的工程,这就是所谓聚合工程的意义。

模块化与分布式

很多人都有一个误区,觉得上面那种模块化项目就是分布式系统了。其实不然,模块化本质上还是一个单体项目,只是把原来的一个个package转换成一个个module来开发而已。这个最多叫做分模块,而不是分布式。而且,模块之间往往有强依赖性,比如web-dao就必须依赖于web-model,否则连编译都通不过。

因此,模块化这个东西,我们最多就是解决了两个问题:

1.一定程度上让代码得到复用。

2.使得项目变得碎片化、积木化,方便管理和多人协作。

不过呢,很多时候,有人也会为了模块化而去模块化,比如本来一个单体项目就能解决的事情,非要拆成xxx-api,xxx-service,xxx-dao,xxx-model。看起来项目是变得高端了,但其实没有什么作用,只是徒增了系统的复杂度而已。

我第一次接触到模块化开发是在若干年前,看某小破站上黑马的淘淘商城,他就是这样模块化开发的。当时真的觉得好牛逼,没想到项目里面还能套项目,玩的是真滴花。

当时我天真的以为这大概就是分布式了吧,很多人估计跟我一样,对分布式的理解就是把一个项目拆成若干个子项目来开发,这就是分布式。

这种说法也不能说错,甚至我看到有些技术博客确实也是这么写的。

经过这些年技术的沉淀和开发,我才逐渐接触到了真正的分布式开发。

分布式架构

上面的说法其实说对了一半,分布式的概念,按照现在主流的说法,就是把一个大项目拆成若干个子项目。其中任何一个子项目,你可以运用maven聚合工程,也可以不用。然后这些子项目之间,涉及到消息传递的问题,一般是通过RPC远程调用来实现的。

RPC框架

如果是几年前你出去面试,很可能会被问到一个问题,那就是你用过什么RPC远程调用框架?

目前市面上比较常见的,有dubbo,spring系列的SpringCloud,还有重一点的webservice(soap协议)。

RPC 是一个分布式计算的 CS 模式,总是由 Client 向 Server 发出一个请求,Server 接受请求,使用者客户端提供的参数,计算完成之后将结果返回给客户端。

使用最广泛的 Spring Cloud,基于 Spring Boot 特性整合了开源行业中优秀的组件,整体对外提供了一套在微服务架构中服务治理的解决方案。

国内开源的框架中,使用比较广泛的有阿里的 Dubbo,后来捐献给了 Apache。还有腾讯的 Tars 框架,还有 Thrift 框架,也有基于 Thrift 二次开发的 RPC 框架,比如美团的 Mtthrift。

这些 RPC 大致原理基本都是一样的。

JavaWeb编年史(黄金时代)_java_09

服务集成 RPC 后,服务(这里的服务就是图中的 Provider,服务提供者)启动后会通过 Register(注册)模块,把服务的唯一 ID 和 IP 地址,端口信息等注册到 RPC 框架注册中心(图中的 Registry 部分)。

当调用者(Consumer)想要调用服务的时候,通过 Provider 注册时的的服务唯一 ID 去注册中心查找在线可供调用的服务,返回一个 IP 列表(3.notify 部分)。

第三步 Consumer 根据一定的策略,比如随机 or 轮训从 Registry 返回的可用 IP 列表真正调用服务(4.invoke)。

最后是统计功能,RPC 框架都提供监控功能,监控服务健康状况,控制服务线上扩展和上下线(5.count)

JavaWeb编年史(黄金时代)_jvm_10

Container是服务容器,常见的有Tomcat,Weblogic,Jetty。

rpc 与 http的区别

http是协议,rpc是方法,rpc的实现可能也会用到http(比如httpClient)。

http在应用层,rpc在传输层(长连接,少了三次握手,不过http2.0也可以链接复用了)

http中所使用的报文中有效字节数仅仅占约 30%,也就是70%的时间用于传输元数据废编码。当然实际情况下报文内容可能会比这个长,但是报头所占的比例也是非常可观的。而rpc仅通过序列化发送有效数据,省去了很多无效的数据,提高传输效率。 http需要可读性强,包括输入、输出,解析等。rpc就像调用方法一样调用,很简单。

soap 与 http的区别

上面我们说到,webservice也是一种常见的rpc远程调用框架,它用的是soap协议。那么soap协议与http又有上面区别呢?

JavaWeb编年史(黄金时代)_maven_11

分布式架构案例

分布式架构,就是把一个大项目拆成若干个子项目,然后这些子项目之间通过RPC远程调用来实现消息传递。这就是分布式架构。

举一个例子,有一个信用卡系统,因为信用卡是一个比较大型的项目,所以没可能做成一个单体项目。所以第一步,把项目拆成若干个子项目。

JavaWeb编年史(黄金时代)_面试_12

假设信用卡系统分为3个子系统,rpc框架采用webservice,那么每一个子系统自己即可能是服务的提供方(Server),也可能是服务的调用方(Client)。比如信用卡客服需要查询客户信息的时候,可能只需要通过webservice去远程调用核心服务系统的客户查询服务即可。

这就是分布式架构,但是,以上架构模式至少存在以下三个问题。

1.项目中服务过多,就会导致出现管理混乱的问题。

比如核心服务已经有了客户查询的服务,然后张三需要在客服系统增加一个维护客户资料的功能,但是他并不知道核心服务已经有接口能提供客户资料的CRUD操作了,于是他就自己写了service,dao,甚至自己写了个jdbc把功能实现了。(管它三七二十一,完成了需求就行)

2.项目中服务对其他系统不透明,协同开发困难

假如有一天,其他部门的某某系统需要查询信用卡的相关信息,但是不清楚信用卡这边有没有对应的接口,就得跑来问具体的负责人。一般呢,部门也会有一个word文档,记录这边系统对外的webservice接口。可是呢,谁又能保证word文档一定没问题,一定是最新的。可能张三改了某个接口,忘记更新文档了,这样的事情太正常。

3.接口调用复杂度高

我们可以提供接口供外部系统调用,这就涉及一个鉴权,监控等问题。并不是谁都能调用接口的,作为服务端,每次对接一个新系统,可能就要单独开发一套东西来用于鉴权,很麻烦。

所以,分布式架构不得不面临一个棘手的问题,那就是服务治理。

SOA面向服务架构

正因为普通分布式架构有着上面的三大难题,所以很自然的,分布式架构必须解决服务治理的问题。

还拿上面的信用卡系统举例子,所谓的服务治理,就是专门引入一个叫做ESB企业服务总线的东西,由它去注册和管理所有的webservice服务。

一个单体项目,比如信用卡核心服务系统,存在着多个webservice服务,这些服务都应该注册到ESB服务总线。

然后,其他外部系统想要调用其中的某一个接口,就需要从ESB中转,ESB需要管理这些调用方和具体的调用过程。

ESB扮演的角色就是RPC远程框架的注册中心和监控中心。

SOA 是面向服务的架构,即企业的 IT 系统是由服务组成的,也即企业的各个应用系统是由许多标准的服务件“组装”起来的,组成应用系统中的各个服务之间是一种非常松耦合的关系。

引入ESB企业服务总线后, 上面的架构就变成了这样。

JavaWeb编年史(黄金时代)_jvm_13

这样以来,这个系统就是分布式面向服务架构。

再说明一点,分模块和分布式没多少关系,分布式系统之间是相对独立的,系统之间通过rpc远程调用来传递消息而已。而分模块的话,模块之间往往强依赖的,不可分割的,若干个模块就好像积木一样,最终组成一个单体项目。

也就说,分布式系统的每一个系统,你可以选择模块化来做,也可以不做,这是看具体需求的。

如果上图中的几个系统是模块化开发的,你就可以说,信用卡业务系统采用的是分布式面向服务架构+模块化开发。

微服务架构

上面的分布式面向服务架构,已经是现在很多大企业的主流方案了,但是依然存在一些问题。

1.以单体项目为单位,粒度太粗,项目一多难免会有一些代码重复。比如,你要做一个功能,自己直接查一下数据库就over了,可是呢另一个系统有一个接口有类似的功能。用是能用,但是你去调用的话,还得转换一下才行,很麻烦。而且,因为这个功能并非必要,你没法让对方系统因为你要做的这个需求,去新增或者修改接口。这就是粒度太粗导致的问题。

2.SOA现在用的rpc框架太重,一般还是用webservice,因为用的是xml传输,效率肯定会慢一点。

于是,就有了微服务架构。

微服务架构是分布式面向服务架构的一个分支,本身也属于分布式面向服务架构。

JavaWeb编年史(黄金时代)_xml_14

SOA 和微服务架构的差别

1.更轻了,只关注http+json数据传输,也更快了。

2.粒度更细了,原子单位是一组强相关的接口集合,微服务通常是 单用途 的服务,它们可以非常非常好地完成一件事情。

3.微服务去中心化,去掉ESB企业总线。微服务不再强调传统SOA架构里面比较重的ESB企业服务总线,同时SOA的思想进入到单个业务系统内部实现真正的组件化.

4.Docker容器技术的出现,为微服务提供了更便利的条件,比如更小的部署单元,每个服务可以通过类似Node或者Spring Boot等技术跑在自己的进程中。

5.SOA注重的是系统集成方面,而微服务关注的是完全分离。因此微服务的系统是弱相关,可独立运行的。

总结一下,并不能说微服务架构就比SOA架构高级,对于面向系统集成的大型项目,还是适合用SOA。而对于那些只有http轻量级协议传输的短平快项目,就适合用微服务架构。

微服务比较考验架构师的水准,如果拆的不好反而影响开发效率。我就见过一个小项目结果硬是拆了几百个服务的情况,其中很多服务的代码都是高度重复的。这就是典型的为了用微服务而去搞微服务,最终得不偿失。