Spring Cloud -- 微服務初探及實作
前言
微服務架構(Microservice Architecture)最早在2014年由Martin Fowler (https://martinfowler.com/articles/microservices.html)提出,他指出:微服務架構是一種架構模式,或著說是一種架構風格,他提倡將單一應用程序劃分成一組小的服務,每個服務運行在其獨立、自身的程序中,服務之間互相協調,互相配合,為用戶提供最終價值。服務之間採輕量級的通信機制互相溝通(通常基於HTTP的RESTful API)。每個微服務都圍繞著具體的業務進行建構,並且能夠被獨立地部屬到生產環境。另外,應盡量避免統一的、集中式的服務管理機制,對具體的一個服務而言,應該根據業務的上下游資料,選擇合適的語言及工具進行構建,以一個非常輕量級的集中式管理來協調這些服務,可以使用不同的語言來編寫服務,也可以使用不同的數據存取。
因此,微服務化的核心,就是將傳統的一站式應用,根據業務拆分成一個一個的服務,徹底地將每個服務去耦合,每個微服務提供單個業務功能服務,一個服務做一件事,能夠自行單獨啟動或銷毀,並擁有自己的資料庫。
接下來會在文章中跟大家分享,如何使用Spring cloud建立一個簡易的微服務架構。
Spring cloud是什麼?
Spring cloud是基於Spring Boot提供的一套微服務解決方案,他為開發人員提供了許多工具,達到快速構建分布式系統中的一些常見模式,其內容較常用的有服務註冊與發現、配置管理、服務熔斷器、全鍊路監控、服務網關、負載均衡等組件,它們都可以利用Spring Boot的開發風格做到一鍵部屬或啟動,是一套讓開發者感到簡單易懂、易部屬及易維護的分布式系統開發套件。
為什麼要用Spring cloud?
Spring cloud擁有完整的微服務框架所需要的工具,支持Rest、多語言、高可用及高容錯性、容易學習,已成為許多大公司愛用的架構。
開始建立Spring Cloud整體父工程project
接下來,我們開始建立第一個Spring Cloud的父工程project吧~
本範例會帶大家建立一個整體的父工程project、一個共用的API服務、一個Service端微服務及一個Client端微服務,來模擬客戶端的介面操作增刪改查,去打服務端的API,所使用的環境為Java 8、MySQL Router 8.0,所需要的背景知識為Spring boot、mybatis、MVC架構等等。
1.首先,開啟Eclipse,右鍵 New -> Other...
2.選擇Mavan -> Mavan Project (建立父工程使用Mavan Project,微服務建立使用Mavan Module) -> 按 Next >
3.這邊先勾選Create a simple project
4.填上Group Id、Artifact Id、Packaging選擇pom
5.完成父工程的構建,會看到有一個src的資料夾及一個pom.xml文件
6.再來我們要添加所需要用到的jar到pom.xml,配置如下
以下為目前配置過可以使用的spring cloud的版本,每種Spring boot的版本都有相對應的spring cloud版本做搭配,搭配錯誤會無法啟動
本範例是用spring boot 2.2.2 Release 及spring cloud Hoxton.SR1做搭配
詳細文件在spring的官網可以查到 (https://spring.io/projects/spring-cloud)
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jacob.springcloud</groupId>
<artifactId>microservicecloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<slf4j.version>1.7.30</slf4j.version>
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
<spring-boot.version>2.2.2.RELEASE</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>microservicecloud</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<delimiters>
<delimit>$</delimit>
</delimiters>
</configuration>
</plugin>
</plugins>
</build>
</project>
到此,父工程建立完成。
建立Spring Cloud Microservicecloud-API
接下來,要建立微服務的一些共用的API服務
1.在父工程上右鍵 -> New -> Other...
2.選擇 Mavan -> Mavan module 後按 Next >
3.勾選Create a simple project,Module Name取名後按 Next >
4.Packaging選擇jar後,按Finish
5.完成建立共用的API服務
6.這時進到父工程的pom.xml,應可看到下方出現了子工程的modules
<modules>
<module>microservicecloud-api</module>
</modules>
7.進到microservicecloud-api中,可看到JRE也是直接繼承JavaSE-1.8
8.附上microservicecloud-api中 pom.xml設定
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent> <!-- 子類別中顯示聲明才能有明確繼承的表現,通常為父類別的預設版本,也可自行設定 -->
<groupId>com.jacob.springcloud</groupId>
<artifactId>microservicecloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>microservicecloud-api</artifactId> <!-- 當前Module我自己叫什麼名字 -->
<dependencies> <!-- 當前Module所需用的jar包,若在父類別中已有定義則不需再寫版本號 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
9.再來我們建立一個模擬部門的Entity,右鍵 -> New -> package,取package名稱
10.在package中建立一個class Dept.java
package com.jacob.springcloud.entities;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@SuppressWarnings("serial")
@AllArgsConstructor
@NoArgsConstructor
@Data
@Accessors(chain = true)
public class Dept implements Serializable{ // Dept(Entity) orm 在mysql中也有一個Dept(table) 為類表關係映射 //必須序列化
private Long deptno; //主鍵
private String dname; //部門名稱
private String db_source; //來自那個資料庫,因為微服務架構可以一個服務對應一個資料庫,同一個信息被儲存到不同資料庫
}
11.建完之後,回到microservicecloud-api在local端生成最新的jar包 右鍵 -> Run as -> Mavan clean
12.右鍵 -> Run as -> Mavan install
13.成功畫面如下
14.這樣未來後續其他微服務若想使用該API,直接在pom.xml調用即可,不需再自行新增~
<groupId>com.jacob.springcloud</groupId>
<artifactId>microservicecloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
建立Spring Cloud 微服務Service端
接下來來建立在這個微服務中提供服務的Service端~
1.在父工程上右鍵 -> New -> Other...
2.選擇 Mavan -> Mavan module 後按 Next >
3.勾選Create a simple project,Module Name取名後按 Next >
4.Packaging選擇jar後,按Finish
5.完成建立Service端微服務包
6.回到父工程的pom.xml,可以發現modules多增加一個Service端的服務繼承了父工程
7.針對Service端的服務建置pom.xml,提供配置如下
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jacob.springcloud</groupId>
<artifactId>microservicecloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>microservicecloud-provider-dept-8001</artifactId>
<dependencies>
<dependency><!-- 自己定義的api通用包 -->
<groupId>com.jacob.springcloud</groupId>
<artifactId>microservicecloud-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
</dependencies>
</project>
8.再來在src/main/recouse在src/main/resources 中,建立application建立application.yml,配置如下
server:
port: 8001
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml #mybatis配置文件所在路徑
type-aliases-package: com.jacob.springcloud.entities #所有Entity別名類所在package
mapper-locations:
- classpath:mybatis/mapper/**/*.xml #mapper映射文件
spring:
application:
name: microservicecloud-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource #當前資料源操作類型
driver-class-name: org.gjt.mm.mysql.Driver #mysql驅動包
url: jdbc:mysql://localhost:3306/cloudDB01?useSSL=false #資料庫名稱,請自訂
username: xxxx #資料庫帳號,請自訂
password: 1111 #資料庫密碼,請自訂
dbcp2:
min-idle: 5 #資料庫連接池的最小維持連接數
initial-size: 5 #初始化連接數
max-idle: 5 #最大連接數
max-wait-millis: 200 #等待連接獲取最大超時時間
在此使用mybatis來做資料庫的操作,因為主題為微服務,在此僅提供建立的文件,不探討mybatis的原理
9.在resources中建立一個mybatis folder,裡面放mybatis.cfg.xml文件,內容如下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/><!-- 二級緩存開啟-->
</settings>
</configuration>
10.再來我們建立一些DB的資料
create database cloudDB01 character set utf8;
use cloudDB01;
create table dept
(
deptno bigint not null primary key auto_increment,
dname varchar(60),
db_source varchar(60)
);
insert into dept(dname, db_source) values('開發部',database());
insert into dept(dname, db_source) values('人事部',database());
insert into dept(dname, db_source) values('財務部',database());
insert into dept(dname, db_source) values('市場部',database());
insert into dept(dname, db_source) values('維運部',database());
select *
from dept
資料狀況如下
11.回到Service端微服務,建立一個dao層的 package ,並建立簡易的增刪改查功能
package com.jacob.springcloud.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.jacob.springcloud.entities.Dept;
@Mapper
public interface DeptDao {
public boolean addDept(Dept dept);
public Dept findById(Long id);
public List<Dept> findAll();
}
12.在resources/mybatis 中建一個mapper folder,再建立一個DeptMapper.xml,內容如下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jacob.springcloud.dao.DeptDao">
<select id="findById" resultType="Dept" parameterType="Long">
select deptno,dname,db_source from dept where deptno=#{deptno};
</select>
<select id="findAll" resultType="Dept">
select deptno,dname,db_source from dept;
</select>
<insert id="addDept" parameterType="Dept">
INSERT INTO dept(dname, db_source) VALUES(#{dname}, DATABASE());
</insert>
</mapper>
目前建立完應該是這樣
13.建立service層 package及程式,程式如下
package com.jacob.springcloud.service;
import java.util.List;
import com.jacob.springcloud.entities.Dept;
public interface DeptService {
public boolean add(Dept dept);
public Dept get(Long id);
public List<Dept> list();
}
14.建立實現service的impl package及程式,程式如下
package com.jacob.springcloud.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.jacob.springcloud.dao.DeptDao;
import com.jacob.springcloud.entities.Dept;
import com.jacob.springcloud.service.DeptService;
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptDao dao;
@Override
public boolean add(Dept dept) {
return dao.addDept(dept);
}
@Override
public Dept get(Long id) {
return dao.findById(id);
}
@Override
public List<Dept> list() {
return dao.findAll();
}
}
15.最後完成controller層的package及增刪改查API,如下
package com.jacob.springcloud.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.jacob.springcloud.entities.Dept;
import com.jacob.springcloud.service.DeptService;
@RestController
public class DeptController {
@Autowired
private DeptService service;
@RequestMapping(value = "/dept/add", method = RequestMethod.POST)
public boolean add(@RequestBody Dept dept) {
return service.add(dept);
}
@RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET)
public Dept get(@PathVariable("id") Long id) {
return service.get(id);
}
@RequestMapping(value = "/dept/list", method = RequestMethod.GET)
public List<Dept> list() {
return service.list();
}
}
16.MVC完成後,在com.jacob.springcloud下建立一個主啟動程式,如下
package com.jacob.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DeptProvider8001_App {
public static void main(String[] args) {
SpringApplication.run(DeptProvider8001_App.class, args);
}
}
完成後的package如下
17.在DeptProvider8001_App上 右鍵 -> Run As -> Spring Boot App
成功啟動,會看到如下顯示
18.上述文件建立完成無誤後,可啟動,並至瀏覽器上輸入http://localhost:8001/dept/list,可成功查詢到DB的資料,Service端建立完成
建立Spring Cloud 微服務Client端
接下來來建立在這個微服務中提供服務的Client端~
1.請先按照上面建立Service端微服務的1~5的步驟建立一個Client端的微服務,建立完如下圖顯示
2.建立pom.xml,配置如下
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jacob.springcloud</groupId>
<artifactId>microservicecloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>microservicecloud-consumer-dept-80</artifactId>
<description>部門微服務消費者</description>
<dependencies>
<dependency><!-- 自己定義的api -->
<groupId>com.jacob.springcloud</groupId>
<artifactId>microservicecloud-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 修改後立即生效,熱部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
</dependencies>
</project>
3.建立application.yml檔於resources中,配置如下
server:
port: 80
4.建立ConfigBean配置文件於com.jacob.springcloud.cfgbeans的package下
package com.jacob.springcloud.cfgbeans;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConfigBean {//@Configuration + ConfigBean 等於applicationContext.xml文件
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
因為這部分為Client端,為消費者所使用的端口,故不應會有Service層,在此應用RestTemplate來做接口的調用
RestTemplate提供了多種便捷訪問遠端Http服務的方法,是一種簡單便捷的訪問restful服務的class,為Spring提供用於訪問Rest服務的客戶端模板工具集。
使用上非常簡單,它的方法postForObject共有三個參數->(url, requestMap, ResponseBean.class),分別代表->(Rest請求地址, 請求參數, Http響應轉換被轉
換的對象類型)、getForObject共有兩個參數->(url, ResponseBean.class),分別代表->(Rest請求地址, Http響應轉換被轉換的對象類型)
5.建立Controller
package com.jacob.springcloud.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import com.jacob.springcloud.entities.Dept;
@RestController
public class DeptController_Consumer {
private static final String REST_URL_PREFIX = "http://localhost:8001";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value="/consumer/dept/add")
public boolean add(Dept dept) {
return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
}
@RequestMapping(value="/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
}
@SuppressWarnings("unchecked")
@RequestMapping(value="/consumer/dept/list")
public List<Dept> list() {
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
}
}
6.建立Client端主啟動類
package com.jacob.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DeptConsumer80_App {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer80_App.class, args);
}
}
完成後Client端配置如下
至此,我們已經完成了微服務的Service端及Client端的配置,現在我們來測試看看同時啟動的狀態,分別啟動microservicecloud-provider-dept-8001及microservicecloud-consumer-dept-80,我們使用Client端的Controller方法來看看是否能打到Service端提供的增刪改查服務API
1.開啟瀏覽器,輸入"localhost/consumer/dept/get/2",可看到能順利查到id為2的那筆資料
2.輸入"localhost/consumer/dept/list",可查到所有資料
3.輸入"localhost/consumer/dept/add?dname=大數據",頁面顯示true,表示資料已成功insert進入DB中,使用localhost/consumer/dept/list可查到,如下圖
以上,我們成功建立了一個模擬的Service端及Client端的微服務,未來將繼續跟大家分享Spring Cloud的其他工具,若其中有疏漏,也歡迎各位前輩不吝指教,謝謝大家~
參考文獻
https://spring.io/projects/spring-cloud
https://martinfowler.com/articles/microservices.html
急速開發JAVA大系統 Spring Boot 又輕又快又好學
尚硅谷Spring Cloud教程