Spring Cloud 入門教程(一):服務治理(Eureka)
Spring Cloud是一系列框架的集合,其基於Spring Boot的開發便利性巧妙地簡化了分散式系統基礎設施的開發,構建了服務治理(發現註冊)、配置中心、訊息匯流排、負載均衡、斷路器、資料監控、分散式會話和叢集狀態管理等功能,為我們提供一整套企業級分散式雲應用的完美解決方案。
Spring Cloud包含了多個子專案(針對分散式系統中涉及的多個不同開源產品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Zookeeper、Spring Cloud CLI等專案。這些專案是Spring將目前各家公司開發的比較成熟、經得起實際考驗的服務框架組合起來,通過Spring Boot風格進行再封裝遮蔽掉了複雜的配置和實現原理,最終給我們開發者留出了一套簡單易懂、易部署和易維護的分散式系統開發工具包。
Spring Cloud 具有特性,以及適用於哪些場景等包含:
- 基於版本的分散式配置管理
- 服務註冊與發現
- 路由
- 服務之間呼叫(依賴)
- 負載均衡
- 斷路器
- 全域性鎖(分散式鎖)
- 選主以及叢集狀態管理
- 分散式訊息服務
Spring Cloud的核心是服務治理。而服務治理主要通過整合Netflix的相關產品來實現這方面的功能,也就是Spring Cloud Netflix,在該專案中包括用於服務註冊和發現的Eureka,呼叫斷路器Hystrix,呼叫端負載均衡Ribbon,Rest客戶端Feign,智慧服務路由Zuul,用於監控資料收集和展示的Spectator、Servo、Atlas,用於配置讀取的Archaius和提供Controller層Reactive封裝的RxJava。除此之外,針對Feign和RxJava並不是Netiflix的產品,但也被整合到了Spring Cloud Netflix中。
接下來的幾篇我將從Spring Cloud Netflix開始講解如何搭建我們的分散式開發架構。
1. Hello, Spring Cloud!示例工程
我們所要搭建的Hello, Spring Cloud!系統架構圖如下:
從結構圖上可以看出有一下我們所構建的工程中有三種角色:
- Eureka Server: 服務註冊中心,負責服務列表的註冊、維護和查詢等功能;
- Service Provider: 服務提供方,同時也是一個
Eureka Client
,負責將所提供的服務向Eureka Server
進行註冊、續約和登出等操作。註冊時所提供的主要資料包括服務名、機器ip、埠號、域名等,從而能夠使服務消費方能夠找到; - Service Consumer: 服務消費方,同時也是一個
Eureka Client
,同樣也會向Eureka Server
註冊本身所提供的服務。但在本示例工程中更多的是從Eureka Server
中獲取相應的服務列表,以便能夠發起服務呼叫。
Service Provider(服務提供方)和Service Consumer(服務消費方)並不是一個嚴格的概念,往往服務消費方也是一個服務提供方,同時服務提供方也可能會呼叫其它服務方所提供的服務。當然在我們進行微服務構建時還是需要遵守業務層級之間的劃分,儘量避免服務之間的迴圈依賴。
工程結構如下:
Ok! 既然工程結構和系統架構都清楚了,下面讓我們開始擼起袖子寫程式碼。
2. 構建parent
工程
筆者在構建專案的時候喜歡先構建一個parent
工程,該工程僅用來定義一個pom
檔案,後續工程的pom
檔案的皆繼承該pom
。在該pom
中我們將定義各工程所共同使用的第三方依賴及相應版本定義,比如我們接下來的各工程中對Spring Cloud
的依賴等。這樣我們就可以統一對第三方依賴及基礎資訊定義進行管理,後續當我們需要升級第三方依賴時,只需要修改一個地方就可以了。
parent 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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
<groupId>twostepsfromjava.cloud</groupId>
<artifactId>twostepsfromjava-cloud-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
在本系列文章中我們使用的Spring Cloud
的版本為:Dalston.SR1
,Spring Boot
則是1.5.2.RELEASE
。
關於
Spring Cloud
的命名:由於Spring Cloud
是諸多子專案集合的綜合專案,原則上由其子專案維護自己的釋出版本號,也就是我們常用的版本號,如:1.2.3.RELEASE
、1.1.4.RELEASE
等。因此Spring Cloud
為了避免版本號與其子專案的版本號混淆,所以沒有采用版本號的方式,而是採用命名的方式。這些版本名稱採用了倫敦地鐵站的名字,根據字母表的順序來對應版本時間順序。比如,最早的Release版本名稱為Angel
,第二個Release版本的名稱為Brixton
,以此類推……。而我們在本系列文章所使用的版本名稱為:Dalston.SR1
,也就是最新版本。後續版本名稱根據專案中公佈的分別為:Edgware
和Finchley
。另,
Dalston.SR1
中的SR
是service releases
的簡寫,而1
則是該版本名稱中的第1個版本。具體關於
Spring Cloud
版本的命名說明可以參考這裡.
3. 構建Eureka Server
3.1 編寫pom.xml檔案
我們將繼承parent
專案的pom.xml
,並把artifactId
定義為:service-discovery
。
<?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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>twostepsfromjava.cloud</groupId>
<artifactId>twostepsfromjava-cloud-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../parent</relativePath>
</parent>
<artifactId>service-discovery</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
這裡我們直接繼承
parent
專案中的pom
,所以只需要宣告我們需要的新增的spring-cloud-starter-eureka-server
依賴即可。
3.2 編寫啟動類
@EnableEurekaServer
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
說明: 這裡核心就是在啟動類上新增
@EnableEurekaServer
,宣告這是一個Eureka伺服器。
3.3 編寫配置檔案
配置檔案在resources
目錄下,預設名稱為:application.properties
(本系列中將採用properties
檔案格式,你也可以使用另外一種格式:yml
)。
server.port=8260
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
這裡為什麼這麼配,暫時先不解釋,後續我會進行相關配置引數的解釋。
3.4 啟動伺服器
接下來你可以在你的IDE中啟動該服務。當然你也可以將該伺服器打包成一個Fat Jar,然後通過java -jar
的命令啟動,如:
java -jar service-discovery-1.0.0-SNAPSHOT.jar
說明: 如果需要打包成一個Fat Jar你需要修改pom.xml
中的配置,增加如下內容:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
增加一個Spring Boot
打包外掛。這樣編譯出來的Jar包就可以通過上述命令直接執行。
3.5 檢視伺服器
在Instance currently registered with Eureka部分可以看到現在尚未有任何例項註冊進來。
4. 構建Eureka Client
Eureka伺服器我們已經編寫好了,接下來我們就可以編寫一個Eureka的客戶端了。這個客戶端可能是一個服務提供者,也可能是一個服務消費者,甚至兩者都是。
我們先編寫一個簡單的Eureka Client,該客戶端提供一個簡單的服務,就是呼叫/hello
服務端點(EndPoint)時返回一個字串Hello, Spring Cloud!
。
4.1 編寫pom.xml檔案
同樣,我們繼承自parent
專案的pom.xml
,這裡將artifactId
定義為:service-hello
,也就是提供Hello服務。
<?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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>twostepsfromjava.cloud</groupId>
<artifactId>twostepsfromjava-cloud-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../parent</relativePath>
</parent>
<artifactId>service-hello</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.2 編寫啟動類
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
說明: 這裡與service-discovery
的唯一區別就是啟動類上註解變成了@EnableDiscoveryClient
,宣告這是一個Eureka Client。
4.3 編寫一個簡單的API服務
@RestController
public class HelloEndpoint {
protected Logger logger = LoggerFactory.getLogger(HelloEndpoint.class);
@Autowired
private EurekaInstanceConfig eurekaInstanceConfig;
@Value("${server.port}")
private int serverPort = 0;
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello() {
this.logger.info("/hello, instanceId:{}, host:{}", eurekaInstanceConfig.getInstanceId(), eurekaInstanceConfig.getHostName(false));
return "Hello, Spring Cloud! My port is " + String.valueOf(serverPort);
}
}
該服務僅提供一個/hello
服務端點,呼叫該服務後將返回一個字串Hello, Spring Cloud!
。
4.4 編寫配置檔案
server.port=2100
spring.application.name=SERVICE-HELLO
eureka.client.service-url.defaultZone=http://localhost:8260/eureka
說明: 這裡spring.application.name
必須要設定,服務消費者將通過該名稱呼叫所提供的服務。 eureka.client.service-url
也必須設定,表示我們要向那些Eureka伺服器進行服務註冊,這裡可以宣告多個Eureka伺服器,具體我們將在後面關於Eureka高可用相關章節中進行詳細說明。
4.5 啟動伺服器
同樣啟動該伺服器。啟動成功後,我們將在控制檯上看到這麼一句日誌:
[DiscoveryClient-InstanceInfoReplicator-0] INFO c.netflix.discovery.DiscoveryClient - DiscoveryClient_SERVICE-HELLO/192.168.0.105:SERVICE-HELLO:2100 - registration status: 204
說明我們的服務已經在Eureka伺服器上註冊成功。
5. 構建服務消費者
到上一小節其實一個最簡單的Eureka伺服器和客戶端就已經構建完畢了。為了讓我們更能夠體會到Eureka所發揮的作用,我們下面來構建一個服務消費者,該服務消費者將呼叫SERVICE-HELLO
所提供的服務。
5.1 編寫pom.xml檔案
同樣,我們繼承自parent
專案的pom.xml
,這裡將artifactId
定義為:consumer-hello
,也就是Hello服務消費者。
<?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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>twostepsfromjava.cloud</groupId>
<artifactId>twostepsfromjava-cloud-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../parent</relativePath>
</parent>
<artifactId>consumer-hello</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
</dependencies>
</project>
這裡需要注意的是我們除了依賴spring-cloud-starter-eureka
,還依賴了Spring Cloud
中的另外一個子專案spring-cloud-starter-ribbon
,該子專案提供客戶端負載均衡功能,可以自動從Eureka
伺服器中獲取服務提供者的地址列表,從而能夠發起相應的呼叫。這個後面我們將詳細進行說明,這裡先引入進來就可以了。
5.2 編寫啟動類
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
同Service-Hello
一樣在啟動類上註解了@EnableDiscoveryClient
,說明這也是一個Eureka Client。
5.3 編寫服務呼叫
@RestController
public class HelloController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello() {
return restTemplate.getForEntity("http://SERVICE-HELLO/hello", String.class).getBody();
}
}
該服務呼叫時一個標準的controller
,hello()
方法將通過restTemplate
呼叫SERVICE-HELLO/hello
服務並返回。
5.4 編寫配置檔案
server.port=8080
spring.application.name=consumer-hello
eureka.client.service-url.defaultZone=http://localhost:8260/eureka
5.5 啟動伺服器
啟動成功後,同樣我們將在控制檯上看到這麼一句日誌:
[DiscoveryClient-InstanceInfoReplicator-0] INFO c.netflix.discovery.DiscoveryClient - DiscoveryClient_CONSUMER-HELLO/192.168.0.105:consumer-hello:8080 - registration status: 204
說明我們的兩個服務都已經在Eureka伺服器上註冊成功。
5.6 驗證服務呼叫
在瀏覽器中,我們輸入http://localhost:8080/hello,也就是該服務所定義的埠server.port=8080
,將會看到如下介面:
同時在Service-Hello
的控制檯中會列印下面一句日誌:
[http-nio-2100-exec-1] INFO i.t.c.s.hello.api.HelloEndpoint - /hello, instanceId:cd826dembp.lan:SERVICE-HELLO:2100, host:192.168.0.105
Ok,到這裡為止,我們的Hello, Spring Cloud!示例工程搭建完畢。