目錄
Spring Cloud Eureka是Spring Cloud的服務治理組件,本文演示其服務註冊中心、服務提供者和服務消費者的搭建過程。在其中消費者採用Spring Cloud Ribbon來實現,Ribbon是一個基於HTTP和TCP的客戶端負載均衡器,它可以負載均衡地調用服務,Ribbon負載均衡的默認方式爲輪詢。同時消費者會整合Spring Cloud Hystrix,Hystrix是容錯保護的斷路器。當服務因爲某種原因關閉或者服務阻塞等原因而變得不能訪問的情況下,該組件可以立即返回一個錯誤響應,避免整個系統的故障蔓延。
筆者使用的Java版本是jdk-8u201,IDE使用的是IntelliJ IDEA 2019.2 x64,Spring Cloud的版本是Greenwich.SR2。
1 服務註冊中心
創建一個Spring Boot項目,命名爲eureka-server。
1.1 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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hys</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Cloud</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<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>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.2 啓動類
package com.hys.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
1.3 application.properties
註冊中心的搭建過程可以是單點搭建,也可以是集羣搭建。單點搭建的配置文件示例代碼如下所示:
server.port=1111
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://localhost:1111/eureka/
注意其中eureka.client.register-with-eureka和eureka.client.fetch-registry配置項一定要配置成false,前者表示不向註冊中心註冊自己,後者表示不需要檢索服務。
本文采用模擬三個節點的集羣搭建方式來確保高可用,當一個註冊中心掛了的時候,另外兩個可以繼續使用。分別創建三個配置文件application-peer1.properties、application-peer2.properties和application-peer3.properties:
spring.application.name=eureka-server
server.port=1111
eureka.instance.hostname=peer1
eureka.client.service-url.defaultZone=http://peer2:1112/eureka/,http://peer3:1113/eureka/
spring.application.name=eureka-server
server.port=1112
eureka.instance.hostname=peer2
eureka.client.service-url.defaultZone=http://peer1:1111/eureka/,http://peer3:1113/eureka/
spring.application.name=eureka-server
server.port=1113
eureka.instance.hostname=peer3
eureka.client.service-url.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
其中每個節點的eureka.client.service-url.defaultZone項都配置成其他兩個節點的服務地址,用逗號分隔。需要注意的是,和單點的搭建方式不同,這裏的eureka.client.register-with-eureka和eureka.client.fetch-registry配置項一定要配置成true,或者不寫,默認值就爲true。
1.4 host文件
接着在本地的host文件(C:\Windows\System32\drivers\etc)中加上下面的三行代碼,使得serviceUrl可以正確訪問:
127.0.0.1 peer1
127.0.0.1 peer2
127.0.0.1 peer3
1.5 運行及結果
然後將該項目打包成jar,通過以下的三條命令來啓動:
java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1
java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer2
java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer3
注意,一開始啓動第一個jar包的時候會因爲其他兩個節點還沒有啓動而報一些錯,這都是正常的,只要先後啓動這三個jar包就可以了。在啓動第三個jar包的時候就可以發現不會報相關錯誤了。
除了上述打成jar包的方式啓動之外,也可以在IDE中啓動並通過不同的參數來區分。當三個節點都運行起來後,分別訪問http://localhost:1111/、http://localhost:1112/和http://localhost:1113/,可以看到Eureka的相關信息面板:
由上可以看到,每個節點都可以看到另外兩個註冊的節點,並且這些節點都在可用分片(available-replicas)之中。
2 服務提供者
創建一個Spring Boot項目,命名爲hello-service。
2.1 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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hys</groupId>
<artifactId>hello-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>hello-service</name>
<description>Demo project for Spring Cloud</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<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>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2 啓動類
package com.hys.helloservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class HelloServiceApplication {
public static void main(String[] args) {
SpringApplication.run(HelloServiceApplication.class, args);
}
}
2.3 application.properties
spring.application.name=hello-service
eureka.client.service-url.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/,http://peer3:1113/eureka/
2.4 Controller
package com.hys.helloservice.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Random;
@RestController
public class HelloController {
@Value("${server.port}")
private String port;
@GetMapping("/hello")
public String index() {
return "port:" + port;
}
@GetMapping("/hello2")
public String index2() throws InterruptedException {
Thread.sleep(new Random().nextInt(3000));
return "port:" + port;
}
}
其中index方法是用來測試負載均衡的方法,通過刷新頁面,查看端口號是否發生改變即可看出有沒有實現負載均衡。而index2方法不僅是用來測試負載均衡,更多的是用來測試斷路器的效果。因爲Hystrix的默認超時時間是1秒,所以通過休眠一個小於3秒的隨機時間,如果當前休眠的時間小於1秒,則能成功訪問服務;如果當前休眠的時間大於1秒,則過了一秒後,會立即返回錯誤反饋,而不是會等到休眠時間到了爲止。
2.5 運行及結果
和上面相似,可以將該項目打成jar包或者直接運行IDE的方式來運行,打成jar包的命令如下所示:
java -jar hello-service-0.0.1-SNAPSHOT.jar --server.port=8081
java -jar hello-service-0.0.1-SNAPSHOT.jar --server.port=8082
啓動兩個hello-service項目,通過端口號來區分。Eureka的信息頁面可以看到相關的變化:
3 服務消費者
消費者通過Ribbon來實現消費,創建一個Spring Boot項目,命名爲ribbon-consumer。
3.1 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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hys</groupId>
<artifactId>ribbon-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ribbon-consumer</name>
<description>Demo project for Spring Cloud</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<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>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.2 啓動類
package com.hys.ribbonconsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringCloudApplication
public class RibbonConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonConsumerApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
其中@LoadBalanced註解是用來開啓客戶端的負載均衡。而@SpringCloudApplication註解是整合了@SpringBootApplication、@EnableDiscoveryClient和@EnableCircuitBreaker這三個子註解的註解。其中第一個註解表示這是一個Spring Boot項目,第二個註解用來註冊Eureka服務,而第三個註解是用來開啓斷路器的功能。
3.3 application.properties
logging.level.com.hys=debug
logging.path=E:/logs
spring.application.name=ribbon-consumer
server.port=9000
eureka.client.service-url.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/,http://peer3:1113/eureka/
3.4 Controller
package com.hys.ribbonconsumer.controller;
import com.hys.ribbonconsumer.service.IHelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ConsumerController {
@Autowired
private IHelloService helloService;
@GetMapping("/ribbon-consumer")
public String helloConsumer() {
return helloService.helloService();
}
@GetMapping("/ribbon-consumer2")
public String helloConsumer2() {
return helloService.helloService2();
}
}
3.5 Service
package com.hys.ribbonconsumer.service;
public interface IHelloService {
String helloService();
String helloService2();
}
package com.hys.ribbonconsumer.service.impl;
import com.hys.ribbonconsumer.service.IHelloService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class HelloServiceImpl implements IHelloService {
private static final Log logger = LogFactory.getLog(HelloServiceImpl.class);
@Autowired
private RestTemplate restTemplate;
@Override
public String helloService() {
return restTemplate.getForObject("http://HELLO-SERVICE/hello", String.class);
}
@HystrixCommand(fallbackMethod = "helloFallback")
@Override
public String helloService2() {
long start = System.currentTimeMillis();
String forObject = restTemplate.getForObject("http://HELLO-SERVICE/hello2", String.class);
long end = System.currentTimeMillis();
if (logger.isDebugEnabled()) {
logger.debug("花費" + (end - start) + "毫秒");
}
return forObject;
}
private String helloFallback() {
return "訪問超時,請重新再試!";
}
}
helloService方法實現對上面創建的HELLO-SERVICE服務的/hello接口進行調用的效果,而helloService2方法實現對上面創建的HELLO-SERVICE服務的/hello2接口進行調用的效果,也就是測試斷路器是否成功。
3.6 運行及結果
運行該項目,Eureka的信息頁面中可以看到該項目已經註冊進去了:
同時頁面訪問http://localhost:9000/ribbon-consumer,頁面結果如下所示:
不斷刷新該頁面,可以看到port一直在8081和8082之間切換(因爲默認的負載均衡是通過輪詢的方式),可以驗證負載均衡是成功的。
而Hystrix斷路器的演示通過訪問http://localhost:9000/ribbon-consumer2,