本篇博客主要講解Eureka組件實現服務註冊與發現和Feign組件實現服務間的調用的理論以及實際操作。
Eureka
Eureka是Spring Cloud Netfix微服務套件中的一部分,可以和SpringBoot構建的微服務很容易的整合。SpringCloud將它集成在自己的子項目 spring-cloud-netflix中,實現SpringCloud的服務發現功能。
Eureka包含兩個組件: Eureka Server(服務器端) 和 Eureka Client(客戶端組件)。
Eureka Server(服務器端組件) :也被稱作服務註冊中心,用於提高服務的註冊和發現。各個節點啓動後,會在Eureka Server中進行註冊,這樣EurekaServer中的服務註冊表中將會存儲所有可用服務節點的信息,服務節點的信息可以在界面中直觀的看到。Eureka Server之間通過複製的方式完成數據的同步。
Eureka Client(客戶端組件):用於簡化與Eureka Server的交互,包含服務消費者和服務生產者。客戶端同時也有一個內置的、使用輪詢(round-robin)負載算法的負載均衡器,在應用程序運行時,Eureka客戶端向註冊中心註冊自身提供的服務並週期性的發送心跳來更新它的服務租約,向Eureka Server發送的心跳,默認週期爲30秒,如果Eureka Server在多個心跳週期內沒有 接收到某個節點的心跳,Eureka Server將會從服務註冊表中把這個服務節點移除(默認90 秒)。Eureka還提供了客戶端緩存機制,可以從服務端查詢當前註冊的服務信息並把他們緩存到本地並週期性的刷新服務狀態,即使所有的Eureka Server都掛掉,客戶端依然可以利用緩存中的信息消費其他服務的API。
Eureka支持高可用的配置,當集羣中有分片出現故障時,Eureka就會轉入自動保護模式,它允許分片故障期間繼續提供服務的發現和註冊,當故障分片恢復正常時,集羣中其他分片會把他們的狀態再次同步回來。
Eureka通過心跳檢查、客戶端緩存等機制,確保了系統的高可用性、靈活性和可伸縮性。
Eureka實現服務發現註冊
1.首先搭建Maven項目,後面的模塊都放到它下面
創建完成如下
可以刪除src目錄,因爲這個工程只是爲了放置springcloud的其它模塊。
2.搭建服務註冊中心(eureka-server)
Eureka Server是基於springboot的,只要啓動一個springboot就可以了。start.spring.io提供了一系列啓動模板。創建module,選擇Spring initializer.:
加入Eureka Server組件
創建完成目錄
生成的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.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.changan</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<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>
修改啓動類EurekaServerApplication.java,添加@EnableEurekaServer
package com.szh.ecurekaserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EcurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EcurekaServerApplication.class, args);
}
}
在默認情況下,服務註冊中心也會把自己當做是一個服務,將自己註冊進服務註冊中心,所以我們可以通過配置來禁用他的客戶端註冊行爲,修改application.properties文件
spring.application.name=eureka-server
#服務註冊中心端口號
server.port=8080
#服務註冊中心實例的主機名
eureka.instance.hostname=localhost
#是否向服務註冊中心註冊自己
eureka.client.register-with-eureka=false
#是否檢索服務
eureka.client.fetch-registry=false
#服務註冊中心的配置內容,指定服務註冊中心的位置
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
啓動應用,並訪問http://localhost:8080/即可看到Eureka信息面板,如下:
從上圖看到,在"Instances currently registered with Eureka"信息中,沒有一個實例,說明目前還沒有服務註冊。接下來創建一個服務提供者eureka-client進行註冊測試。
3.創建並註冊服務提供者 Eureka Client
創建方式如eureka-server模塊類似;在選擇組件的時候需要選擇對應的組件
注意要選擇Web組件或者其它能夠持久運行的。不然會註冊失敗
創建後目錄如下
生成的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.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.changan</groupId>
<artifactId>eureka-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-client</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</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-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>
啓動類EurekaClientApplication.java添加@EnableEurekaClient註解以實現Eureka中的DiscoveryClient實現。
package com.xuan.eurekaclient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@SpringBootApplication
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}
@EnableEurekaClient和@EnableDiscoveryClient的區別
spring cloud中discovery service有許多種實現(eureka、consul、zookeeper等等),@EnableDiscoveryClient基於spring-cloud-commons,@EnableEurekaClient基於spring-cloud-netflix。
就是如果選用的註冊中心是eureka,那麼就推薦@EnableEurekaClient,如果是其他的註冊中心,那麼推薦使用@EnableDiscoveryClient。
修改配置文件application.properties
spring.application.name=eureka-client
server.port=8090
# 這個是固定死的,這是我們要註冊到哪裏去
eureka.client.serviceUrl.defaultZone=http://localhost:8080/eureka/
先啓動:eureka-server
然後啓動eureka-client
發現多了一個EUREKA-CLIENT,註冊成功了
其它問題:
運行一段時間後,在http://localhost:8080/出現
如果在Eureka Server的首頁看到以下這段提示,則說明Eureka已經進入了保護模式:
Eureka Server在運行期間,會統計心跳失敗的比例在15分鐘之內是否低於85%,如果出 現低於的情況(在單機調試的時候很容易滿足,實際在生產環境上通常是由於網絡不穩 定導致),Eureka Server會將當前的實例註冊信息保護起來,同時提示這個警告。保護模式主要用於一組客戶端和Eureka Server之間存在網絡分區場景下的保護。一旦進入保護模式,Eureka Server將會嘗試保護其服務註冊表中的信息,不再刪除服務註冊表中的 數據(也就是不會註銷任何微服務)。
停止eureka-client後也沒有刪除節點。
自我保護模式打開時,已關停節點是會一直顯示在 Eureka 首頁的
關閉自我保護模式後,由於其默認的心跳週期比較長等原因,要過一會兒纔會發現已關停節點被自動踢出了
若想盡快的及時踢出,那就只有修改默認的心跳週期參數了
註冊中心eureka-server的配置文件application.properties中修改爲
spring.application.name=eureka-server
#服務註冊中心端口號
server.port=8080
#服務註冊中心實例的主機名
eureka.instance.hostname=localhost
#關閉自我保護
eureka.server.enableSelfPreservation=false
# 續期時間,即掃描失效服務的間隔時間(缺省爲60*1000ms)
eureka.server.eviction-interval-timer-in-ms: 1000
#是否向服務註冊中心註冊自己
eureka.client.register-with-eureka=false
#是否檢索服務
eureka.client.fetch-registry=false
#服務註冊中心的配置內容,指定服務註冊中心的位置
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
Eureka客戶端eureka-client的配置文件application.properties中修改爲
spring.application.name=eureka-client
server.port=8090
# 心跳時間,即服務續約間隔時間(缺省爲30s)
eureka.instance.lease-renewal-interval-in-seconds: 5
# 發呆時間,即服務續約到期時間(缺省爲90s)
eureka.instance.lease-expiration-duration-in-seconds: 15
# 開啓健康檢查(依賴spring-boot-starter-actuator)
eureka.client.healthcheck.enabled:true
eureka.client.serviceUrl.defaultZone=http://localhost:8080/eureka/
eureka-client的pom.xml增加
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
修改後,關閉eureka-client,註冊中心就會很快刪除節點
Feign
Feign是簡化Java HTTP客戶端開發的工具(java-to-httpclient-binder),它的靈感來自於Retrofit、JAXRS-2.0和WebSocket。Feign的初衷是降低統一綁定Denominator到 HTTP API的複雜度,不區分是否爲restful。
Feign實現服務調用
1.創建一個eureka-student和一個eureka-grade
創建方式和ureka-server模塊類似;在選擇組件的時候需要選擇對應的組件
注意要選擇Web組件或者其它能夠持久運行的。不然會註冊失敗
如上再創建一個eureka-grade
完成後目錄如下
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.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.changan</groupId>
<artifactId>eureka-student</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-student</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</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-openfeign</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
啓動類EurekaClientApplication.java添加@EnableDiscoveryClient @EnableFeignClients註解
package com.szh.eurekastudent;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class EurekaStudentApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaStudentApplication.class, args);
}
}
package com.szh.eurekagrade;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class EurekaGradeApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaGradeApplication.class, args);
}
}
eureka-student的application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/studentdb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.application.name=eureka-student
server.port=8091
eureka.client.serviceUrl.defaultZone=http://localhost:8080/eureka/
eureka-grade的application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/studentdb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.application.name=eureka-grade
server.port=8094
eureka.client.serviceUrl.defaultZone=http://localhost:8080/eureka/
在eureka-student src文件下寫client,controller,mapper,pojo,service等包
創建Student實體類
package com.szh.eurekastudent.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Integer id;
private String name;
private String sex;
private Integer gradeId;
}
創建Grade實體類
package com.szh.eurekastudent.pojo;
import lombok.Data;
@Data
public class Grade {
private Integer gradeId;
private String gradeName;
}
創建StudentMapper接口
package com.szh.eurekastudent.mapper;
import com.szh.eurekastudent.pojo.Student;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public interface StudentMapper {
@Select("select * from student")
List<Student> selAllStudent();
}
創建StudentService接口
package com.szh.eurekastudent.service;
import com.szh.eurekastudent.pojo.Student;
import java.util.List;
public interface StudentService {
List<Student> selAllStudent();
}
創建StudentServiceImpl類
package com.szh.eurekastudent.service.impl;
import com.szh.eurekastudent.mapper.StudentMapper;
import com.szh.eurekastudent.pojo.Student;
import com.szh.eurekastudent.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
@Override
public List<Student> selAllStudent() {
return studentMapper.selAllStudent();
}
}
創建StudentController類
package com.szh.eurekastudent.controller;
import com.szh.eurekastudent.client.GradeClient;
import com.szh.eurekastudent.pojo.Grade;
import com.szh.eurekastudent.pojo.Student;
import com.szh.eurekastudent.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class StudentController {
@Autowired
private StudentService studentService;
@Autowired
private GradeClient gradeClient;
@GetMapping("/students")
public List<Student> findStudents(){
return studentService.selAllStudent();
}
@GetMapping("/grades")
public List<Grade> findGrades(){
return gradeClient.findStudents();
}
}
創建GradeClient類
package com.szh.eurekastudent.client;
import com.szh.eurekastudent.pojo.Grade;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@FeignClient("eureka-grade")
public interface GradeClient {
@GetMapping("/grades")
public List<Grade> findStudents();
}
如圖所示:
在eureka-gradesrc文件下寫controller,mapper,pojo,service等包,和eureka-student一樣。
創建Student實體類
package com.szh.eurekagrade.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Integer id;
private String name;
private String sex;
private Integer gradeId;
}
創建Grade實體類
package com.szh.eurekagrade.pojo;
import lombok.Data;
@Data
public class Grade {
private Integer gradeId;
private String gradeName;
}
創建StudentMapper接口
package com.szh.eurekagrade.mapper;
import com.szh.eurekagrade.pojo.Grade;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public interface StudentMapper {
@Select("select * from grade")
List<Grade> selAllGrade();
}
創建StudentService接口
package com.szh.eurekagrade.service;
import com.szh.eurekagrade.pojo.Grade;
import java.util.List;
public interface StudentService {
List<Grade> selAllGrade();
}
創建StudentServiceImpl類
package com.szh.eurekagrade.service.impl;
import com.szh.eurekagrade.mapper.StudentMapper;
import com.szh.eurekagrade.pojo.Grade;
import com.szh.eurekagrade.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
@Override
public List<Grade> selAllGrade() {
return studentMapper.selAllGrade();
}
}
創建StudentController類
package com.szh.eurekagrade.controller;
import com.szh.eurekagrade.pojo.Grade;
import com.szh.eurekagrade.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping("/grades")
public List<Grade> findStudents(){
return studentService.selAllGrade();
}
}
記得要在兩個包的啓動類加上@MapperScan(“com.szh.eurekastudent.mapper”)
package com.szh.eurekastudent;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("com.szh.eurekastudent.mapper")
@SpringBootApplication
public class EurekaStudentApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaStudentApplication.class, args);
}
}
然後從eureka-server開始啓動4個模塊。
去查看結果
查看服務調用結果
在eureka-student調用eureka-grade成功!