在企業級軟件的架構模型上,我們主要討論下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即可。