Spring Cloud 入門 之 Eureka 篇(一)


原文地址:Spring Cloud 入門 之 Eureka 篇(一)
博客地址:http://www.extlight.com

一、前言

Spring Cloud 是一系列框架的有序集合。它利用 Spring Boot 的開發便利性巧妙地簡化了分布式系統基礎設施的開發,如服務發現注冊、配置中心、消息總線、負載均衡、斷路器、數據監控等,都可以用 Spring Boot 的開發風格做到一鍵啟動和部署。

本篇介紹 Spring Cloud 入門系列中的 Eureka,實現快速入門。

二、簡單介紹

Eureka 是 Netflix 的子模塊,它是一個基於 REST 的服務,用於定位服務,以實現雲端中間層服務發現和故障轉移。

服務注冊和發現對於微服務架構而言,是非常重要的。有了服務發現和注冊,只需要使用服務的標識符就可以訪問到服務,而不需要修改服務調用的配置文件。該功能類似於 Dubbo 的注冊中心,比如 Zookeeper。

Eureka 采用了 CS 的設計架構。Eureka Server 作為服務注冊功能的服務端,它是服務注冊中心。而系統中其他微服務則使用 Eureka 的客戶端連接到 Eureka Server 並維持心跳連接。

其運行原理如下圖:

由圖可知,Eureka 的運行原理和 Dubbo 大同小異, Eureka 包含兩個組件: Eureka Server 和 Eureka Client。

Eureka Server 提供服務的注冊服務。各個服務節點啟動后會在 Eureka Server 中注冊服務,Eureka Server 中的服務注冊表會存儲所有可用的服務節點信息。

Eureka Client 是一個 Java 客戶端,用於簡化 Eureka Server 的交互,客戶端同時也具備一個內置的、使用輪詢負載算法的負載均衡器。在應用啟動后,向 Eureka Server 發送心跳(默認周期 30 秒)。如果 Eureka Server 在多個心跳周期內沒有接收到某個節點的心跳,Eureka Server 會從服務注冊表中將該服務節點信息移除。

三、搭建注冊中心

創建 Spring Boot 項目,名為 eureka-server,進行如下操作:

3.1 添加依賴

<dependencyManagement>
  	<dependencies>
  		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>Greenwich.RELEASE</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-parent</artifactId>
			<version>2.1.3.RELEASE</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
  	</dependencies>
</dependencyManagement>
  
<dependencies>
	<!-- eureka 服務端 -->
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
	</dependency>
</dependencies>

Spring Boot 與 SpringCloud 有版本兼容關系,如果引用版本不對應,項目啟動會報錯。具體信息查看 Spring 官網

3.2 application.yml 配置參數

server:
    port: 9000

spring:
  application:
    name: EUREKA
    
eureka:
    instance:
        hostname: localhost   # eureka 實例名稱
    client:
        register-with-eureka: false # 不向注冊中心注冊自己
        fetch-registry: false       # 是否檢索服務
        service-url:
            defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/  # 注冊中心訪問地址

3.3 開啟注冊中心功能

在啟動類上添加 @EnableEurekaServer 注解:

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

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

至此,准備工作完成,啟動項目完成后,瀏覽器訪問 http://localhost:9000 ,查看 Eureka 服務監控界面,如下圖:

通過該網址可以查看注冊中心注冊服務的相關信息。當前還沒有服務注冊,因此沒有服務信息。

補充:http://localhost:9000 是 Eureka 監管界面訪問地址,而 http://localhost:9000/eureka/ Eureka 注冊服務的地址。

四、實戰演練

了解 Eureka 的環境搭建后,我們需要進行實戰直觀的感受 Eureka 的真正作用,這樣才能清楚掌握和學習 Eureka 。

下面我們要創建 2 個微服務,一個是商品服務,另一個訂單服務。

測試場景:用戶下訂單時,訂單系統需要調用商品系統獲取商品信息檢查商品是否存在,從而進行其他操作。

服務實例 端口 描述
common-api - 公用的 api,如:實體類
eureka-server 9000 注冊中心(Eureka 服務端)
goods-server 8081 商品服務(Eureka 客戶端)
order-server 8100 訂單服務(Eureka 客戶端)

3.1 商品服務(goods-server)

由於文章內容針對 Eureka 的入門和使用,因此只張貼重要代碼。具體代碼請瀏覽下文提供的源碼地址。

  1. 添加依賴:
<dependencies>
  	<!-- common api -->
  	<dependency>
  		<groupId>com.extlight.springcloud</groupId>
  		<artifactId>common-api</artifactId>
  		<version>${parent-version}</version>
  	</dependency>
  
  	<!-- springmvc -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

	<!-- eureka 客戶端 -->
    <dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
	</dependency>
</dependencies>
  1. 配置參數:
server:
    port: 8081

spring:
    application:
        name: GOODS
    
eureka:
    instance:
        instance-id: goods-api-8081
        prefer-ip-address: true # 訪問路徑可以顯示 IP
    client:
        service-url:
            defaultZone: http://localhost:9000/eureka/  # 注冊中心訪問地址

注意:http://localhost:9000/eureka/ 就是第三節中配置的注冊中心的地址。

  1. 服務接口:
public interface GoodsService {

	Goods findGoodsById(String goodsId);
}
@Service
public class GoodsServiceImpl implements GoodsService{
	
	// 模擬數據庫
	private static Map<String, Goods> data;
	
	static {
		data = new HashMap<>();
		data.put("1", new Goods("1", "手機", "國產手機", 8081));
		data.put("2", new Goods("2", "電腦", "台式電腦", 8081));
	}

	@Override
	public Goods findGoodsById(String goodsId) {
		return data.get(goodsId);
	}

}
@RestController
@RequestMapping("/goods")
public class GoodsController {

	@Autowired
	private GoodsService goodsService;
	
	@RequestMapping("/goodsInfo/{goodsId}")
	public Result goodsInfo(@PathVariable String goodsId) {
		
		Goods goods = this.goodsService.findGoodsById(goodsId);
		return Result.success(goods);
	}
}
  1. 開啟服務注冊功能:

在啟動類上添加 @EnableEurekaClient 注解:

@EnableEurekaClient
@SpringBootApplication
public class GoodsServerApplication {

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

啟動項目完成后,瀏覽器訪問 http://localhost:9000 查看 Eureka 服務監控界面 ,如下圖:

從圖可知,商品服務已經注冊到 Eureka 服務中了。

補充:在上圖中,我們還看到一串紅色的字體,那是因為 Eureka 啟動了自我保護的機制。當 EurekaServer 在短時間內丟失過多客戶端時(可能發生了網絡故障),EurekaServer 將進入自我保護模式。進入該模式后,EurekaServer 會保護服務注冊表中的信息不被刪除。當網絡故障恢復后,EurekaServer 會自動退出自我保護模式。

3.2 訂單服務(order-server)

  1. 添加依賴:
<dependencies>
    <!-- common api -->
  	<dependency>
  		<groupId>com.extlight.springcloud</groupId>
  		<artifactId>common-api</artifactId>
  		<version>${parent-version}</version>
  	</dependency>
  
  	<!-- springmvc -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

	<!-- eureka 客戶端 -->
    <dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
	</dependency>
 </dependencies>
  1. 配置參數:
server:
    port: 8100

spring:
    application:
        name: ORDER

eureka:
    instance:
        instance-id: order-api-8100
        prefer-ip-address: true # 訪問路徑可以顯示 IP
    client:
        service-url:
            defaultZone: http://localhost:9000/eureka/  # 注冊中心訪問地址
  1. 服務接口:
@Configuration
public class RestConfiguration {

	@Bean
	public RestTemplate getRestTemplate() {
		return new RestTemplate();
	}
}
public interface OrderService {

	void placeOrder(Order order) throws Exception;
}
@Service
public class OrderServiceImpl implements OrderService{
	
	@Autowired
	private RestTemplate restTemplate;
	
	@Autowired
	private DiscoveryClient client;

	@Override
	public void placeOrder(Order order) throws Exception{
		
		// 獲取商品服務地址列表
		List<ServiceInstance> list = this.client.getInstances("GOODS");
		String uri = "";
	    for (ServiceInstance instance : list) {
	        if (instance.getUri() != null && !"".equals(instance.getUri())) {
	        	uri = instance.getUri().toString();
	        	break;
	        }
	    }
	    
		Result result = restTemplate.getForObject(new URI(uri + "/goods/goodsInfo/" + order.getGoodsId()), Result.class);
		
		if (result != null && result.getCode() == 200) {
			System.out.println("=====下訂單====");
			System.out.println(result.getData());
		}
	}

}

注意:此處只是為了體現服務發現的效果,實際開發中不使用 DiscoveryClient 查詢服務進行調用!至於如何進行服務發現和調用請讀者等待和瀏覽后續發布的 Ribbon 文章。

  1. 客戶端:
@RestController
@RequestMapping("/order")
public class OrderController {

	@Autowired
	private OrderService orderService;
	
	@RequestMapping("/place")
	public Result placeOrder(Order order) throws Exception {
		
		this.orderService.placeOrder(order);
		return Result.success();
	}
}
  1. 開啟服務發現功能:

在啟動類上添加 @EnableEurekaClient 注解:

@EnableEurekaClient
@SpringBootApplication
public class OrderServerApplication {

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

啟動項目后,使用 PostMan 模擬用戶下單,運行結果如下:

至此,Eureka 的服務注冊和發現演示完畢。

本系列的后續文章會基於該案例進行介紹和實戰演練。

四、Eureka 集群

Eureka 作為注冊中心,保存了系統服務的相關信息,如果注冊中心掛掉,那么系統就癱瘓了。因此,對 Eureka 做集群實現高可用是必不可少的。

本次測試使用一台機器部署 Eureka 集群,通過名字和端口區分不同的 eureka 服務。

Eureka 名稱 端口號
eureka01 9001
eureka02 9002
  1. 由於使用一台機器,使用兩個名稱還需要修改 C:\Windows\System32\drivers\etc 下的 host 文件,添加如下配置:
127.0.0.1  eureka01
127.0.0.1  eureka02
  1. application.yml 文件需要進行如下修改:
server:
    port: 9001
    
eureka:
    instance:
        hostname: eureka01   # eureka 實例名稱
    client:
        register-with-eureka: false # 不向注冊中心注冊自己
        fetch-registry: false       # 表示自己就是注冊中心
        service-url:
            defaultZone: http://eureka01:9001/eureka/,http://eureka02:9002/eureka/

兩個 eureka 服務實例的配置文件修改方式類似,將名稱和端口進行修改即可。

  1. 服務注冊的項目中,將 eureka.client.service-url.defaultZone 改成集群的 url 即可。

啟動效果如下圖:

五、Eureka 與 Zookeeper 的區別

兩者都可以充當注冊中心的角色,且可以集群實現高可用,相當於小型的分布式存儲系統。

5.1 CAP 理論

CAP 分別為 consistency(強一致性)、availability(可用性) 和 partition tolerance(分區容錯性)。

理論核心:一個分布式系統不可能同時很好的滿足一致性、可用性和分區容錯性這三個需求。因此,根據 CAP 原理將 NoSQL 數據庫分成滿足 CA 原則、滿足 CP 原則和滿足 AP 原則三大類:

CA:單點集群,滿足一致性,可用性的系統,通常在可擴展性上不高
CP: 滿足一致性,分區容錯性的系統,通常性能不是特別高
AP: 滿足可用性,分區容錯性的系統,通過對一致性要求較低

簡單的說:CAP 理論描述在分布式存儲系統中,最多只能滿足兩個需求。

5.2 Zookeeper 保證 CP

當向注冊中心查詢服務列表時,我們可以容忍注冊中心返回的是幾分鍾前的注冊信息,但不能接受服務直接掛掉不可用了。因此,服務注冊中心對可用性的要求高於一致性。

但是,zookeeper 會出現一種情況,當 master 節點因為網絡故障與其他節點失去聯系時,剩余節點會重新進行 leader 選舉。問題在於,選舉 leader 的時間較長,30 ~ 120 秒,且選舉期間整個 zookeeper 集群是不可用的,這期間會導致注冊服務癱瘓。在雲部署的環境下,因網絡問題導致 zookeeper 集群失去 master 節點的概率較大,雖然服務能最終恢復,但是漫長的選舉時間導致注冊服務長期不可用是不能容忍的。

5.3 Eureka 保證 AP

Eureka 在設計上優先保證了可用性。EurekaServer 各個節點都是平等的,幾個節點掛掉不會影響正常節點的工作,剩余的節點依然可以提供注冊和發現服務。

而 Eureka 客戶端在向某個 EurekaServer 注冊或發現連接失敗時,會自動切換到其他 EurekaServer 節點,只要有一台 EurekaServer 正常運行,就能保證注冊服務可用,只不過查詢到的信息可能不是最新的。

除此之外,EurekaServer 還有一種自我保護機制,如果在 15 分鍾內超過 85% 的節點都沒有正常的心跳,那么 EurekaServer 將認為客戶端與注冊中心出現網絡故障,此時會出現一下幾種情況:

EurekaServer 不再從注冊列表中移除因為長時間沒有收到心跳而應該過期的服務

EurekaServer 仍然能夠接收新服務的注冊和查詢請求,但不會被同步到其他節點上

當網絡穩定時,當前 EurekaServer 節點新的注冊信息會同步到其他節點中

因此,Eureka 可以很好的應對因網絡故障導致部分節點失去聯系的情況,而不會向 Zookeeper 那樣是整個注冊服務癱瘓。

六、案例源碼

Eureka demo 源碼

七、參考資料

Springcloud 中文網

ZooKeeper、Eureka對比


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM