Categories
程式開發

Spring Cloud 微服務實踐(1) – 用Initializr初始化


紙上得來終覺淺,絕知此事要躬行。 這裡我們就直接用Spring Initializr來初始化Spring Cloud項目,然後作一點配置,寫幾句代碼,用比較笨的形式,“徒手”擼一個包含服務發現、網關和業務處理的開發環境版微服務。

1、先看看包含哪些模塊(子項目)

回顧一下我們在《開篇》中提到的簡化版微服務

Spring Cloud 微服務實踐(1) - 用Initializr初始化 1

服務發現與網關幾乎不需要寫代碼,配置一下就可以跑起來,然後業務實現也暫時Say Hello,點到為止,重點是我們怎麼把他們揉到一起。

2、用Spring Initializr 初始化Eureka Server

Spring Initializr 是Spring.io提供的一個Web應用,使用瀏覽器打開https://start.spring.io” 就能使用,它的功能就是為你初始化一個基本的Spring Boot項目,並根據需要來加載用到的依賴。Spring Initializr也可以在Spring Tool Suite中使用,IntelliJ IDEA也做了集成。

為了體現”從零開始“,我們就用瀏覽器訪問https://start.spring.io“來初始化項目。

Spring Cloud 微服務實踐(1) - 用Initializr初始化 2

Project選Maven Project,使用Maven構建項目。 開發語言Language選Java。 Project Metadata可以隨意填,我這裡把Artifact改成eureka-server。 打包方式(Packaging)為Jar。 Java版本選8。 然後依賴(Dependency)增加一個“Eureka Server”和“Spring Boot Actuator”。 最後點擊“GENERATE”按鈕會下載一個zip文件,解壓後就是我們的服務註冊與發現項目。 我這裡因為Artifact填的“eureka-server“,所以得到的文件是eureka-server.zip,後續也會用eureka-server代指這個項目。

解壓後,就是Spring Initializr為我們生成的eureka-server項目的基本文件和結構。

Spring Cloud 微服務實踐(1) - 用Initializr初始化 3

如果熟悉maven和git,解壓後得到的這些文件就無需多說什麼了

這裡我們看一下pom.xml文件,主要是看依賴,中文是我加的註解,其他都是Spring Initializr生成的:

4.0.0

org.springframework.boot
spring-boot-starter-parent

2.3.3.RELEASE

com.example
eureka-server
0.0.1-SNAPSHOT
eureka-server
Demo project for Spring Boot

1.8

Hoxton.SR8

org.springframework.cloud
spring-cloud-starter-netflix-eureka-server

org.springframework.boot
spring-boot-starter-actuator

org.springframework.boot
spring-boot-starter-test
test

org.junit.vintage
junit-vintage-engine

org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import

org.springframework.boot
spring-boot-maven-plugin

熟悉Spring Boot的話,會發現大部分都是Spring Boot相關的配置,只有group id為org.springframework.cloud的才是微服務的內容。

注意:Spring Boot的版本是2.3.3,Spring Cloud的版本是Hoxton.SR8,這兩者的版本一定要匹配,不然容易出一些莫名其妙的問題。 我們可以訪問https://start.spring.io/actuator/info” 這個地址來獲取Spring Boot和Spring Cloud的版本對應關係,比如說:

"Hoxton.SR8":"Spring Boot >=2.2.0.M4 and <2.3.4.BUILD-SNAPSHOT"

3、配置Eureka Server - 實現服務註冊與發現

開源代碼: https://github.com/xiaoboey/from-zero-to-n/tree/master/zero/eureka-server

打開srcmainjavacomexampleeurekaserverEurekaServerApplication.java,添加註解@EnableEurekaServer,開啟註冊中心能力。

Spring Cloud 微服務實踐(1) - 用Initializr初始化 4

把srcmainresourcesapplication.properties刪除,增加文件application.yml:

server:
port: 8761
address: localhost
servlet:
context-path: /

spring:
profiles:
active: dev
application:
name: eureka-server

eureka:
instance:
hostname: localhost
client:
#这个服务本身是注册中心,不用自己向自己注册
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

management:
endpoints:
web:
exposure:
include: ["health","info", "shutdown"]
endpoint:
health:
show-details: always
shutdown:
enabled: true
server:
port: 7761

4、編譯和運行

Maven項目的編譯都一樣,命令是“mvn install”,我一般習慣再加一個clean先清理一下,所以就是“mvn clean install”。 如果想跳過測試加快編譯速度,則可以加上“-DskipTests”參數,完整的命令就是“mvn clean install -DskipTests”。

Spring Boot項目的運行,測試時可以在命令行或者PowerShell直接用maven運行:

mvn spring-boot:run

編譯成jar文件後,也可以直接運行jar文件,這里以eureka-server為例:

cd target
java -jar eureka-server-0.0.1-SNAPSHOT.jar

5、Eureka Server自帶的UI

eureka-server項目啟動後,可以在http://本地主機:8761"查看註冊到服務中心的微服務的信息。

Spring Cloud 微服務實踐(1) - 用Initializr初始化 5

目前暫時還沒有服務註冊到eureka server,紅色的警告也不用管它,是Eureka的自檢,測試環境容易出現這個問題。

6、網關Spring Cloud Gateway

有了前面eureka-server的搭建經驗後,其他微服務我們就簡化一下,只列出我覺得重要的內容。 開源代碼: https://github.com/xiaoboey/from-zero-to-n/tree/master/zero/gateway

還是用Spring Initializr進行項目初始化,pom.xml如下:

...
gateway
0.0.1-SNAPSHOT
gateway
...

...

org.springframework.cloud
spring-cloud-starter-netflix-eureka-client

org.springframework.cloud
spring-cloud-starter-gateway

...

...

主要是添加了eureka-client和gateway的依賴。

修改GatewayApplication.java,增加服務發現客戶端的註解,表示這是一個Eureka Client,要向服務中心(Eureka Server)註冊,以便其他服務可以發現它。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {

public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}

}

application.yml的配置如下:


server:
port: 8080
address: localhost
servlet:
context-path: /

spring:
profiles:
active: dev
application:
name: gateway
cloud:
gateway:
discovery:
locator:
#自动发现并路由到微服务
#与服务发现Eureka Server进行结合,通过Service Id转发到具体的服务实例,默认为false。
enabled: true

#小写ServiceId,默认是false
#这里是个坑,从Eureka Server注册中心获取的Service Id是大写的
lower-case-service-id: true

eureka:
client:
service-url:
#注册中心的地址,要对应eureka-server的配置
defaultZone: http://localhost:8761/eureka/

management:
endpoints:
web:
exposure:
include: ["health","info", "shutdown"]
endpoint:
health:
show-details: always
shutdown:
enabled: true
server:
port: 7080

網關編譯啟動後,可以去Eureka Server看看網關是否已註冊:

Spring Cloud 微服務實踐(1) - 用Initializr初始化 6

7、微服務Service One

Eureka Server和Gateway都是Spring Cloud自帶的為微服務架構服務的功能,接著我們實現一個“真正的業務”。 開源代碼: https://github.com/xiaoboey/from-zero-to-n/tree/master/zero/service-one

還是用Spring Initializr進行項目初始化,這次是一個Spring MVC應用,pom.xml如下:

...
service-one
0.0.1-SNAPSHOT
service-one
...

...

org.springframework.cloud
spring-cloud-starter-netflix-eureka-client

org.springframework.boot
spring-boot-starter-web

...

...

跟Gateway一樣,都通過註解開啟服務發現客戶端能力,修改ServiceOneApplication.java:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class ServiceOneApplication {

public static void main(String[] args) {
SpringApplication.run(ServiceOneApplication.class, args);
}

}

application.yml:

server:
port: 8081
#把address注释掉,避免网关转发请求过来时,因为地址发生变化而拒绝连接(Connection refused)
#address: localhost
servlet:
context-path: /

spring:
profiles:
active: dev
application:
name: service-one

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/

management:
endpoints:
web:
exposure:
include: ["health","info", "shutdown"]
endpoint:
health:
show-details: always
shutdown:
enabled: true
server:
port: 7081

寫一個Controller,實現一個hello方法:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OneController {
@RequestMapping("/hello")
public String hello() {
return "Hello!";
}
}

編譯啟動後,訪問http:// localhost:8081 / hello" ,不出意外的話,Service One會立馬響應一個“Hello! ”。

再訪問http:// localhost:8080 / service-one / hello" 看看,是不是一樣的效果?說明網關自動把請求轉發過來了,而Service One這個​​應用的信息,網關Gateway是從Eureka Server上的註冊信息裡獲取到的。

8、網關的負載均衡

到目前為止,我們搭建了一個最簡單的微服務“集群”,網關(Gateway)接受外部的請求,然後轉發到內部的業務實現(Service One)上進行處理,並將結果反饋回去。 如果只是這樣的話,微服務的優勢在哪裡呢? 這個Service One直接暴露給外部調用也是OK的呀?

我們再擴展一下Service One,提供多一點服務,並且啟動多個Service One來看一下。

增加getPort方法,返回服務的端口(application.yml中的server.port):

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OneController {
@RequestMapping("/hello")
public String hello() {
return "Hello!";
}

@Value(("${server.port}"))
private int port;

@RequestMapping("/getPort")
public int getPort() {
return port;
}
}

編譯後啟動,訪問http:// localhost:8080 / service-one / getPort" ,得到一個端口號,這是application.yml中配置的端口(server.port)。

開啟一個新的cmd或者shell,進入到service-one項目的pom.xml文件那級路徑,再啟動一個Service One,並指定不同的端口:server.port=8082,management.server.port=7082

mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8082 --management.server.port=7082"
#如果是PowerShell,带=号的参数要用单引号引起来才能正确传递
mvn spring-boot:run -D'spring-boot.run.arguments="--server.port=8082 --management.server.port=7082"'

現在在瀏覽器裡多刷新幾次http:// localhost:8080 / service-one / getPort" ,可以發現返回的端口號發生了變化,說明網關把請求轉發到Service One的每個實例上。我們可以繼續啟動更多的服務實例,並模擬大量的請求來測試。測試的結果是網關( Gateway)把請求平均分發給每個實例,體現了微服務在“彈性伸縮、獨立部署”上的優勢。

Spring Cloud 微服務實踐(1) - 用Initializr初始化 7

9、宕機測試

除了有計劃的停止,在實際的生產環境,服務器宕機或其他故障也可能會讓服務啞火,失去響應。

前面說“彈性伸縮”,其實我們只測試了“伸”(增加更多的服務實例),還沒測試“縮”(減少實例),那就試試停止一個Service One,再去反复刷新getPort看看會發生什麼。

反复刷新的情況下,有時會出現響應變慢,然後錯誤“Internal Server Error, status=500”。 這是因為Client失聯或者停止後,Eureka Server還沒get到這個狀態,Gateway繼續往停擺的服務轉發請求,導致錯誤。 直到註冊中心把這個失聯的服務剔除掉並通知網關,一切才恢復正常。

宕機導致的服務異常,對終端用戶的使用是有影響的,需要解決。 並且Spring Cloud Gateway代理轉發請求給後端的服務實例,這個工作其實nginx也可以勝任,在某些方面甚至更好。 所以到目前為止,微服務的優勢還沒體現出來。

所謂“師傅領進門,修行在個人”,通過這篇“入門”上手找到感覺之後,剩下的就是針對具體的需求和問題,去慢慢探索解決,積累經驗,踐行微服務開發方法。

上一篇: 《Spring Cloud 微服務實踐(0) - 開篇閒話"》下一篇: 《Spring Cloud 微服務實踐(2) - Gateway重試機制 “》