Java Spring Cloud Eureka

Spring Cloud -- Eureka服務治理

黃翰暉 Jacob Huang 2021/06/15 10:00:00
108

前言

      上一篇文章我們討論了Spring Cloud如何建置在父工程底下的Service端微服務及Client端微服務,並且嘗試了直接在Client端打Service端的API,實現簡易的增刪改查的功能,接下來想跟大家分享Spring Cloud的服務治理模組。

      之前我們是實做 "一個服務端" 及 "一個客戶端" 的狀況,但現實中不可能只有一個客戶(不然要賺啥...?),假設,我們的客戶非常之多,每一位客戶都需要使用這一個服務端的功能,當客戶越多,就會出現伺服器無法負荷的現象;那我們試著建立兩個服務端好了,但又會有新的問題出現了,有兩個服務端,代表就要通知客戶端分別連到這兩個""不同位址"的服務端,如果是在公司內部那還好,通知一下哪個部門連哪個服務端就好,但,如果是外部的客戶,誰要連哪個服務端要如何分配?未來如果伺服器又爆了,又要再加新的服務端,要怎麼修正???總不可能每次都個別通知每個客戶端吧?如果有幾十萬幾百萬個客戶,那不是就要瘋了?

      因此,要解決以上的問題,就是要建立一個服務註冊的網站來管制:

1. 若是還存活的服務端 (或新服務端),就向服務註冊的網站註冊該服務端的名稱、實體位址等。

2. 管理需下線服務端的註銷。

3. 提供客戶端可使用的服務,並根據查詢到的服務端實體位置進行連接,達到服務端的負載均衡 (關於如何實現負載均衡,會再之後開一篇文章來討論...)。

      而Spring Cloud的Eureka,就是來提供實現服務註冊服務發現的服務模組。

Spring Cloud Eureka是什麼?

      Spring Cloud Eureka 為 Spring Cloud Netflix微服務套件的一部份子模塊,也是核心模塊之一,提供各個微服務的服務註冊以及服務發現。服務註冊及發現對於微服務架構是非常重要的,有了服務發現與註冊,只需要使用服務的標示符,就可以在不修改服務調用的配置文件狀況下訪問到該服務。

      Eureka擁有三大角色:

1. Eureka Server:提供服務註冊與發現。

2. Service Provider:服務端將自身服務註冊到Eureka,使客戶端能夠找到。

3. Service Consumer:客戶端從客戶端從Eureka獲得註冊服務列表來使用服務。

      他們的關係圖如下:

 

      Eureka包含兩種組件:

1. Eureka Server:提供服務註冊服務。

2. Eureka Client:為一Java客戶端,用於簡化與Eureka Server的互動。

 

建立一個Eureka服務註冊中心

      在上一篇文章(Spring Cloud -- 微服務初探及實作)中,我們已經完成了微服務父工程的構建、共用API、一個Service端及一個Client端~(如下圖),這裡我們會將這包當作基礎,來建立Eureka服務註冊中心。

     1. 首先,我們再創建一個微服務包,父工程包microservicecloud按右鍵 -> New -> Other...

 

      2. 選擇Mavan Module選擇Mavan Module -> Next >

 

      3. 取好微服務包名(Create a simple project選勾,這邊我們都先勾) -> Next >

 

      4. packaging確定設定為jar後按下finish

 

      5. 完成後如下,之後建立微服務的方式都比照方法1~5來做,不會另外再重複演示囉~

 

      6. 打開新微服務中的pom.xml

 

    7. 附上代碼,這邊新版本的Spring Cloud需使用spring-cloud-starter-netflix-eureka-server這個包,如果使用spring-cloud-starter-eureka-server會出錯。

<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-eureka-7001</artifactId>

	<dependencies>
		<!-- eureka-server服務端 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-server</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/resources 下新增一個application.yml

 

      9. application.yml 代碼如下

server:
  port: 7001
  
eureka:
  instance:
    hostname: localhost # eureka服務端的實例名稱
  client:
    register-with-eureka: false #false表示不向註冊中心註冊自己
    fetch-registry: false #false表示自己端就是註冊中心,我的職責就是維護服務實例,並不需要去檢索服務
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #設置與 Eureka Server交互的地址查詢服務和註冊服務都需要依賴這個地址

 

      10. 在src/main/java 中建立一個package

 

      11. 在package中建立一個Eureka的主啟動class,程式如下

package com.jacob.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer //EurekaServer服務器端啟動類,接受其他微服務註冊進來
public class EurekaServer7001_App {

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

}

 

      12. 建立完成後,來做個測試,在eureka 7001上按右鍵 -> Run As -> Spring boot App

      

 

      13. 配置正確的話在console會出現如下圖

 

      14. 再來我們切換到瀏覽器,搜尋網址打上 http://localhost:7001/ 會出現如下圖顯示的頁面,代表我們已成功建立了一個Eureka的服務註冊中心囉~

 

將Server端的微服務註冊進Eureka中

      接下來~我們要將我們原本已有的Service端的微服務(microservicecloud-provider-dept-8001)註冊進我們建立好的Eureka註冊中心~

      1. 從我們剛剛建好的Eureka註冊中心中,我們可以看到在下方有一Application列表中,出現了一行字"No instances available",代表這個註冊中心還沒有任何的服務被註冊進來。

 

      2. 我們先到microservicecloud-provider-dept-8001包中的pom.xml中,加入Eureka client的配置,修正後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>
		<!-- 將微服務provider註冊進eureka -->
		<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-config</artifactId>
		</dependency>
		<!-- 監控信息完善 -->
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </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>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
	</dependencies>
</project>

 

      3. 設定8001的application.yml檔案,加入註冊進入Eureka的目標位址,代碼修改如下

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: Jacob
    password: 1234
    dbcp2:
      min-idle: 5                                        #資料庫連接池的最小維持連接數
      initial-size: 5                                    #初始化連接數
      max-idle: 5                                        #最大連接數
      max-wait-millis: 200                               #等待連接獲取最大超時時間
      
eureka:
  client:
    register-with-eureka: true  #註冊到Eureka註冊中心
    fetch-registry: true  #開啟檢索服務
    service-url:
      defaultZone: http://localhost:7001/eureka/    
  instance:
    instance-id: microservicecloud-dept8001 #status的默認名稱修改
    prefer-ip-address: true #訪問路徑可以顯示IP地址
    
info: #Eureka中針對本服務端的信息描述
  app:
    name: jacob-microservicecloud
  company:
    name: www.jacob.com
  build:
    artifactId: $project.artifactId$
    version: $project.version$

 

      4. 將Eureka client的註解加進8001主啟動class,修正後程式如下

package com.jacob.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient //本服務啟動後會自動註冊進eureka服務中
public class DeptProvider8001_App {

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

}

 

      5. 完成上述設置之後,我們就可以來測試看看,首先先啟動Eureka 7001的微服務,再啟動Service端的8001服務,啟動完成之後,回到瀏覽器,輸入localhost:7001,可以看到Application的表格多了一列MICROSERVICECLOUD-DEPT的服務,這個就是我們註冊進入Eureka的Service端服務名稱

 

      至於MICROSERVICECLOUD-DEPT這個名字是從哪來的呢?大家可以回到Service端8001的application.yml中找到,該微服務名是從spring.application.name過來的,也就是這個地方決定註冊進Eureka及對外公布的的名稱叫做什麼名字(Eureka會自動將名稱改為大寫)

spring:
  application:
    name: microservicecloud-dept

 

      

Eureka的服務發現

      做完Eureka的服務註冊之後,再來我們要來做Eureka的服務發現。Eureka的服務發現可以讓Service端的服務顯示在客戶消費端中,供客戶消費使用。

      1. 首先,需在Service端8001這一包的DeptController中,添加一包服務發現接口,來獲得註冊進eureka裡面的微服務信息,程式修改如下

package com.jacob.springcloud.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
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;
	
	@Autowired
	private DiscoveryClient client;
	
	@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();
	}
	
	@RequestMapping(value = "/dept/discovery", method = RequestMethod.GET)
	public Object discovery() { //服務發現 對於註冊進eureka裡面的微服務,可以通過服務發現來獲得該服務的信息
		List<String> list = client.getServices();
		System.out.println("**********" + list);
		
		List<ServiceInstance> srvList = client.getInstances("MICROSERVICECLOUD-DEPT");
		for (ServiceInstance element : srvList) {
			System.out.println(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t" + element.getUri());
		}
		
		return this.client;
	}
	
}

 

      2. 再來針對8001的主啟動class DeptProvider8001_App導入服務發現Annotation,程式修改如下

package com.jacob.springcloud;

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

@SpringBootApplication
@EnableEurekaClient //本服務啟動後會自動註冊進eureka服務中
@EnableDiscoveryClient //服務發現 對於註冊進eureka裡面的微服務,可以通過服務發現來獲得該服務的信息
public class DeptProvider8001_App {

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

}

 

      3. 先來做Service端的自測,測試時我們啟動微服務的順序為Eureka微服務,再來才是Service端微服務,啟動後我們在瀏覽器上輸入localhost:8001/dept/discovery,可以發現我們頁面上已有顯示Service端的微服務訊息,這個是讓Service端確保我們的信息暴露在消費端是否正確,回到console上,我們也可以看到也可以針對相關的為服務訊息做一些log

 

 

      4. 在消費者客戶端80 的 class DeptController_Consumer加上針對Service端的服務發現接口,程式修改如下

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);
	}

	// 測試@EnableDiscoveryClient,消費端可以調用服務發現
	@RequestMapping(value = "/consumer/dept/discovery")
	public Object discovery() {
		return restTemplate.getForObject(REST_URL_PREFIX + "/dept/discovery", Object.class);
	}

}

 

      5. 回到瀏覽器上,啟動客戶端微服務,搜尋網址打上http://localhost/consumer/dept/discovery,可以看到從客戶端我們也可以藉由服務發現來取得Service端在提供的公開Service端的訊息

 

      以上為Eureka的服務註冊以及服務發現的演示,下一篇文章將會針對Eureka的集群配置來做討論,文章若有不正確之處,還請大家多多指教,謝謝大家~

 

參考文獻

Spring Cloud Eureka原理知多少

服務註冊伺服器

尚硅谷Spring Cloud教程

 

黃翰暉 Jacob Huang