在企业级软件的架构模型上,我们主要讨论下SOA与微服务架构。
SOA的全称是Service-Oriented Architecture,可译为“面向服务的架构”,它是一个组件模型,将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在各种各样的系统中的服务可以以一种统一和通用的方式进行交互。
SOA是一种粗粒度、松耦合服务架构,服务之间通过简单、精确定义接口进行通讯,不涉及底层编程接口和通讯模型。SOA可以看作是B/S模型、XML(标准通用标记语言的子集)/Web Service技术之后的自然延伸。
SOA服务具有平台独立的自我描述XML文档。Web服务描述语言(WSDL, Web Services Description Language)是用于描述服务的标准语言。
SOA 服务用消息进行通信,该消息通常使用XML Schema来定义(也叫做XSD, XML Schema Definition)。消费者和提供者或消费者和服务之间的通信多见于不知道提供者的环境中。服务间的通讯也可以看作企业内部处理的关键商业文档。
在一个企业内部,SOA服务通过一个扮演目录列表(directory listing)角色的登记处(Registry)来进行维护。应用程序在登记处(Registry)寻找并调用某项服务。统一描述,定义和集成(UDDI, Universal Description, Definition, and Integration)是服务登记的标准。
每项SOA服务都有一个与之相关的服务品质(QoS, quality of service)。QoS的一些关键元素有安全需求(例如认证和授权),可靠通信(译注:可靠消息是指,确保消息“仅且仅仅”发送一次,从而过滤重复信息。),以及谁能调用服务的策略。
要运行,管理SOA应用程序,企业需要SOA基础,这是SOA平台的一个部分。SOA基础必须支持所有的相关标准,和需要的运行时容器。SOA基础结构示意图如下:
上图中,WSDL,UDDI和SOAP是SOA基础的基础部件。WSDL用来描述服务;UDDI用来注册和查找服务;而SOAP,作为传输层,用来在消费者和服务提供者之间传送消息。SOAP是Web服务的默认机制。一个消费者可以在UDDI注册表(registry)查找服务,取得服务的WSDL描述,然后通过SOAP来调用服务。
WS-I Basic Profile,由Web服务互用性组织(Web Services Interoperability Organization)提供,是SOA服务测试与互用性所需要的核心构件。服务提供者可以使用Basic Profile测试程序来测试服务在不同平台和技术上的互用性。
尽管J2EE和.NET平台是开发SOA应用程序常用的平台,但SOA不仅限于此。像J2EE这类平台,不仅为开发者自然而然地参与到SOA中来提供了一个平台,还通过他们内在的特性,将可扩展性,可靠性,可用性以及性能引入了SOA世界。新的规范,例如 JAXB(Java API for XML Binding),用于将XML文档定位到Java类;JAXR(Java API for XML Registry)用来规范对UDDI注册表(registry)的操作;XML-RPC(Java API for XML-based Remote Procedure Call)在J2EE1.4中用来调用远程服务,这使得开发和部署可移植于标准J2EE容器的Web服务变得容易,与此同时,实现了跨平台(如.NET)的服务互用。
在企业中,关键任务系统(mission-critical system,是指如果一个系统的可靠性对于一个组织是至关重要的,那么该系统就是该企业的关键任务系统)用来解决高级需求,例如安全性,可靠性,事物。当一个企业开始采用服务架构作为工具来进行开发和部署应用的时候,基本的Web服务规范,像WSDL,SOAP,以及UDDI就不能满足这些高级需求。这些需求也称作服务品质(QoS,quality of services)。与QoS相关的众多规范已经由一些标准化组织(standards bodies)提出,像W3C(World Wide Web Consortium)和OASIS(the Organization for the Advancement of Structured Information Standards)。
在了解SOA后,什么是微服务(micro service)呢?微服务可以在“自己的程序”中运行,并通过“轻量级设备与HTTP型API进行沟通”。微服务不需要像普通服务那样成为一种独立的功能或者独立的资源。微服务主要围绕业务领域组件来创建应用,这些应用可独立地进行开发、管理和迭代。微服务的本质是用一些功能比较明确、业务比较精练的服务去解决更大、更实际的问题。
微服务(Microservice)这个概念是2012年出现的,作为加快Web和移动应用程序开发进程的一种方法,2014年开始受到各方的关注,而2015年,可以说是微服务的元年,越来越多的论坛、社区、blog以及互联网行业巨头开始对微服务进行讨论、实践,可以说这样更近一步推动了微服务的发展和创新。
我们接下来以Java web项目为例,比较传统开发模式与微服务开发模式的区别。
在传统开发模式里,所有的功能打包在一个 WAR包里,基本没有外部依赖(除了容器),部署在一个JEE容器(Tomcat,JBoss,WebLogic)里,包含了 DO/DAO,Service,UI等所有逻辑,如下图所示:
上述传统开发模式的优点是:
①开发简单,集中式管理;
②基本不会重复开发;
③功能都在本地,没有分布式的管理和调用消耗。
缺点是:
①效率低:开发都在同一个项目改代码,相互等待,冲突不断;
②维护难:代码功功能耦合在一起,新人不知道何从下手;
③不灵活:构建时间长,任何小修改都要重构整个项目,耗时;
④稳定性差:一个微小的问题,都可能导致整个应用挂掉;
⑤扩展性不够:无法满足高并发下的业务需求。
一般来说,常见的系统架构遵循的三个标准和业务驱动力有:
①提高敏捷性:及时响应业务需求,促进企业发展;
②提升用户体验:提升用户体验,减少用户流失;
③降低成本:降低增加产品、客户或业务方案的成本。
相对来说,基于微服务的设计可以有效的拆分应用,实现敏捷开发和部署。示意图如下:
微服务的具体特征有:①由一些独立的服务共同组成系统;②单独部署,跑在自己的进程中;③每个服务为独立的业务开发;④分布式管理;⑤非常强调隔离性。
那么,SOA和微服务有哪些区别呢?
①SOA喜欢重用,微服务喜欢重写。
SOA的主要目的是为了企业各个系统更加容易地融合在一起。SOA里有一个重要的ESB(Enterprise Service Bus,即企业服务总线)机制,ESB是传统中间件技术与XML、Web服务等技术结合的产物。ESB提供了网络中最基本的连接中枢,是构筑企业神经系统的必要元素。我们可以把ESB想象成一个连接所有企业级服务的脚手架。SOA还可以把一个服务路由到另一个服务上,也可以集中化管理业务逻辑,规则和验证等。它还有一个重要功能是消息队列和事件驱动的消息传递,比如把JMS服务转化成SOAP协议。各服务间可能有复杂的依赖关系。
微服务通常由重写一个模块开始。我们向微服务迁移的时候通常从耦合度最低的模块或对扩展性要求最高的模块开始,把它们一个一个剥离出来敏捷地重写,可以尝试最新的技术、语言和框架,然 后单独布署。它通常不依赖其他服务。微服务中常用的API Gateway的模式主要目的也不是重用代码,而是减少客户端和服务间的往来。
②SOA喜欢水平服务,微服务喜欢垂直服务。
SOA设计喜欢给服务分层(如Service Layers模式)。如对于一个Entity服务层的设计,可能美其名曰Data Access Layer。 这种设计要求所有的服务都通过这个Entity服务层来获取数据。这种设计非常不灵活,比如每次数据层的改动都可能影响到所有业务层的服务。而每个微服务通常有它自己独立的data store。 我们在拆分数据库时可以适当的做些去范式化(denormalization),让它不需要依赖其他服务的数据。
微服务通常是直接面对用户的,每个微服务通常直接为用户提供某个功能。
③SOA喜欢自上而下,微服务喜欢自下而上。
SOA架构在设计开始时会先定义好服务合同(service contract)。 它喜欢集中管理所有的服务,包括集中管理业务逻辑,数据,流程,schema等。 它使用Enterprise Inventory和Service Composition等方法来集中管理服务。 SOA架构通常会预先把每个模块服务接口都定义好。 模块系统间的通讯必须遵守这些接口,各服务是针对他们的调用者。
微服务则敏捷得多。只要用户用得到,就先把这个服务挖出来。然后针对性的,快速确认业务需求,快速开发迭代。
所有的微服务都是独立的Java进程跑在独立的虚拟机上,所以服务间的通信就是IPC(inter process communication)。
相比较而言,SOA更关注企业规模范围,微服务架构则更关注应用规模范围。
总的来说,微服务架构继承了面向服务架构(SOA)的整体思路,将应用分割为一系列细小的服务,每个服务专注于单一的功能,运行在独立的进程中,服务之间的边界清晰,采用轻量级通信机制相互沟通、配合来实现完整的应用,满足业务和用户的需求。微服务架构更多是属于应用技术架构。我们接下来看一个微服务架构的案例SpringBoot。
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot几乎整合了所有的框架,
一般来说或,我们平时搭建一个spring web项目,往往要这样做:
①配置web.xml,加载spring和spring mvc;
②配置数据库连接、配置spring事务;
③配置加载配置文件的读取,开启注解;
④配置日志文件
...
配置完成之后再部署tomcat,调试等。
好了,下面我用Spring Boot创建一个案例。
pom.xml文件:
<?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"> <!--模块版本--> <modelVersion>4.0.0</modelVersion> <!--组,项目id,版本,打包类型--> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <!--项目名称,描述--> <name>demo</name> <description>Demo project for Spring Boot</description> <!--父模块--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <!--属性配置--> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <!--依赖--> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!--配置mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--c3p0配置--> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> <!--添加对tomcat的支持--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> <!--添加对jsp的支持;tomcat插件,默认端口8080--> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <!--此处的<scope></scope>一定不要加上,作用域为provided,可以为compile或缺省--> </dependency> </dependencies> <build> <!--插件配置--> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
application.properties文件:
#jsp视图配置,如果报错,可修改为:spring.view.prefix和spring.view.suffix,这个和spring boot的版本有关 spring.mvc.view.prefix=/WEB-INF/ spring.mvc.view.suffix=.jsp c3p0.jdbcUrl=jdbc:mysql://localhost:3306/itszt4 c3p0.user=root c3p0.password=2018 c3p0.driverClass=com.mysql.jdbc.Driver c3p0.minPoolSize=2 c3p0.maxPoolSize=10 c3p0.maxIdleTime=1800000 c3p0.acquireIncrement=3 c3p0.maxStatements=1000 c3p0.initialPoolSize=3
DemoApplication.java文件:
package com.example.demo; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.web.servlet.DispatcherServlet; import javax.sql.DataSource; @SpringBootApplication @Configuration @MapperScan("com.example.demo.dao") public class DemoApplication { @Bean(name = "dataSource") @Qualifier(value = "dataSource") @Primary @ConfigurationProperties(prefix = "c3p0") public DataSource dataSource() { return DataSourceBuilder.create().type(com.mchange.v2.c3p0.ComboPooledDataSource.class).build(); } //分发中心配置返回视图的规则 @Bean public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) { ServletRegistrationBean reg = new ServletRegistrationBean(dispatcherServlet); reg.getUrlMappings().clear(); reg.addUrlMappings("*.html"); reg.addUrlMappings("*.json"); return reg; } public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
接下来是domain,dao,service,controller层的文件:
//domain实体类 package com.example.demo.domain; /** * 实体类,映射数据库里的表 */ public class User { private String username, userpwd; public User() { } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getUserpwd() { return userpwd; } public void setUserpwd(String userpwd) { this.userpwd = userpwd; } public User(String username, String userpwd) { this.username = username; this.userpwd = userpwd; } } ---------------------------------------------------------------- dao层: package com.example.demo.dao; import com.example.demo.domain.User; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.springframework.stereotype.Repository; /** * 访问,操作数据库 */ @Repository public interface UserDao { @Select("select * from user where username=#{username} and userpwd=#{userpwd}") public User queryUser(@Param("username") String username,@Param("userpwd") String userpwd); } ---------------------------------------------------------------- service层: package com.example.demo.service; /** * 业务接口 */ public interface UserService { boolean doLogin(String username,String userpwd); } package com.example.demo.service; import com.example.demo.dao.UserDao; import com.example.demo.domain.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * 引入userDao */ @Service public class UserServiceImpl implements UserService{ @Autowired private UserDao userDao; @Override public boolean doLogin(String username, String userpwd) { User user = userDao.queryUser(username, userpwd); if(user==null){ return false; } return true; } } --------------------------------------------------------------- controller层: package com.example.demo.controller; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; /** * 引入业务实现类,判断是否登录成功 * 传递用户名和密码两个参数 */ @RequestMapping("/userCenter") @Controller public class UserController { @Autowired private UserService userService; @RequestMapping("/login.html") public String doLogin(@RequestParam("username") String username,@RequestParam("userpwd") String userpwd){ boolean boo = userService.doLogin(username, userpwd); if(boo){ return "usercenter"; } return "redirect:/index.jsp?errorInfo=登录错误!"; } }
web.xml文件:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <display-name>demo</display-name> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
两个前端页面:
index.jsp <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <form action="/userCenter/login.html"> <input type="text" name="username" value="admin"> <hr> <input type="text" name="userpwd" value="admin"> <hr> <input type="submit" value="登录"> <hr> </form> <span style="color: red;font-weight: bold">${param.errorInfo}</span> </body> </html> ------------------------------------------------------- usercenter.jsp <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> 我是usercenter123456789。。 </body> </html>
启动DemoApplication.java中的main()函数,在浏览器中访问http://localhost:8080/index.jsp即可。