1.初識SpringCloud
微服務是一種架構方式,最終肯定需要技術架構去實施。
微服務的實現方式很多,但是最火的莫過於Spring Cloud了。爲什麼?
- 後臺硬:作爲Spring家族的一員,有整個Spring全家桶靠山,背景十分強大。
- 技術強:Spring作爲Java領域的前輩,可以說是功力深厚。有強力的技術團隊支撐,一般人還真比不了
- 羣衆基礎好:可以說大多數程序員的成長都伴隨着Spring框架,試問:現在有幾家公司開發不用Spring?SpringCloud與Spring的各個框架無縫整合,對大家來說一切都是熟悉的配方,熟悉的味道。
- 使用方便:相信大家都體會到了SpringBoot給我們開發帶來的便利,而SpringCloud完全支持SpringBoot的開發,用很少的配置就能完成微服務框架的搭建
1.1.簡介
SpringCloud是Spring旗下的項目之一,官網地址:http://projects.spring.io/spring-cloud/
Spring最擅長的就是集成,把世界上最好的框架拿過來,集成到自己的項目中。
SpringCloud也是一樣,它將現在非常流行的一些技術整合到一起,實現了諸如:配置管理,服務發現,智能路由,負載均衡,熔斷器,控制總線,集羣狀態等等功能。其主要涉及的組件包括:
netflix
- Eureka:註冊中心
- Zuul:服務網關
- Ribbon:負載均衡
- Feign:服務調用
- Hystix:熔斷器
以上只是其中一部分,架構圖:
1.2.版本
SpringCloud的版本命名比較特殊,因爲它不是一個組件,而是許多組件的集合,它的命名是以A到Z的爲首字母的一些單詞組成:
我們在項目中,會是以Finchley的版本。
其中包含的組件,也都有各自的版本,如下表:
Component | Edgware.SR3 | Finchley.RC1 | Finchley.BUILD-SNAPSHOT |
---|---|---|---|
spring-cloud-aws | 1.2.2.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-bus | 1.3.2.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-cli | 1.4.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-commons | 1.3.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-contract | 1.2.4.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-config | 1.4.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-netflix | 1.4.4.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-security | 1.2.2.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-cloudfoundry | 1.1.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-consul | 1.3.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-sleuth | 1.3.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-stream | Ditmars.SR3 | Elmhurst.RELEASE | Elmhurst.BUILD-SNAPSHOT |
spring-cloud-zookeeper | 1.2.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-boot | 1.5.10.RELEASE | 2.0.1.RELEASE | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-task | 1.2.2.RELEASE | 2.0.0.RC1 | 2.0.0.RELEASE |
spring-cloud-vault | 1.1.0.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-gateway | 1.0.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-openfeign | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
接下來,我們就一一學習SpringCloud中的重要組件。
2.微服務場景模擬
首先,我們需要模擬一個服務調用的場景。方便後面學習微服務架構
2.1.服務提供者
我們新建一個項目,對外提供查詢用戶的服務。
2.1.1.Spring腳手架創建工程
藉助於Spring提供的快速搭建工具:
填寫項目信息:
添加web依賴:
添加mybatis依賴:
填寫項目位置:
生成的項目結構:
依賴也已經全部自動引入:
<?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>
<groupId>com.leyou.demo</groupId>
<artifactId>user-service-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>user-service-demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<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>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
當然,因爲要使用通用mapper,所以我們需要手動加一條依賴:
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>
非常快捷啊!
2.1.2.編寫代碼
添加一個對外查詢的接口:
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return this.userService.queryById(id);
}
}
Service:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User queryById(Long id) {
return this.userMapper.selectByPrimaryKey(id);
}
}
mapper:
@Mapper
public interface UserMapper extends tk.mybatis.mapper.common.Mapper<User>{
}
實體類:
@Table(name = "tb_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 用戶名
private String userName;
// 密碼
private String password;
// 姓名
private String name;
// 年齡
private Integer age;
// 性別,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 創建時間
private Date created;
// 更新時間
private Date updated;
// 備註
private String note;
// 。。。省略getters和setters
}
屬性文件,這裏我們採用了yaml語法,而不是properties:
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb01
username: root
password: 123
hikari:
maximum-pool-size: 20
minimum-idle: 10
mybatis:
type-aliases-package: com.leyou.userservice.pojo
項目結構:
2.1.3.啓動並測試:
啓動項目,訪問接口:http://localhost:8081/user/7
2.2.服務調用者
2.2.1.創建工程
與上面類似,這裏不再贅述,需要注意的是,我們調用user-service的功能,因此不需要mybatis相關依賴了。
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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.leyou.demo</groupId>
<artifactId>user-consumer-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>user-consumer-demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 添加OkHttp支持 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2.2.編寫代碼
首先在啓動類中註冊RestTemplate
:
@SpringBootApplication
public class UserConsumerDemoApplication {
@Bean
public RestTemplate restTemplate() {
// 這次我們使用了OkHttp客戶端,只需要注入工廠即可
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
public static void main(String[] args) {
SpringApplication.run(UserConsumerDemoApplication.class, args);
}
}
然後編寫UserDao,注意,這裏不是調用mapper查數據庫,而是通過RestTemplate遠程查詢user-service-demo中的接口:
@Component
public class UserDao {
@Autowired
private RestTemplate restTemplate;
public User queryUserById(Long id){
String url = "http://localhost:8081/user/" + id;
return this.restTemplate.getForObject(url, User.class);
}
}
然後編寫user-service,循環查詢UserDAO信息:
@Service
public class UserService {
@Autowired
private UserDao userDao;
public List<User> querUserByIds(List<Long> ids){
List<User> users = new ArrayList<>();
for (Long id : ids) {
User user = this.userDao.queryUserById(id);
users.add(user);
}
return users;
}
}
編寫controller:
@RestController
@RequestMapping("consume")
public class ConsumerController {
@Autowired
private UserService userService;
@GetMapping
public List<User> consume(@RequestParam("ids") List<Long> ids) {
return this.userService.queryUserByIds(ids);
}
}
2.2.3.啓動測試:
因爲我們沒有配置端口,那麼默認就是8080,我們訪問:http://localhost:8080/consume?ids=6,7,8
一個簡單的遠程服務調用案例就實現了。
2.3.有沒有問題?
簡單回顧一下,剛纔我們寫了什麼:
- use-service-demo:一個提供根據id查詢用戶的微服務
- consumer-demo:一個服務調用者,通過RestTemplate遠程調用user-service-demo
流程如下:
存在什麼問題?
- 在consumer中,我們把url地址硬編碼到了代碼中,不方便後期維護
- consumer需要記憶user-service的地址,如果出現變更,可能得不到通知,地址將失效
- consumer不清楚user-service的狀態,服務宕機也不知道
- user-service只有1臺服務,不具備高可用性
- 即便user-service形成集羣,consumer還需自己實現負載均衡
其實上面說的問題,概括一下就是分佈式服務必然要面臨的問題:
- 服務管理
- 如何自動註冊和發現
- 如何實現狀態監管
- 如何實現動態路由
- 服務如何實現負載均衡
- 服務如何解決容災問題
- 服務如何實現統一配置
以上的問題,我們都將在SpringCloud中得到答案。
3.Eureka註冊中心
3.1.認識Eureka
首先我們來解決第一問題,服務的管理。
問題分析
在剛纔的案例中,user-service對外提供服務,需要對外暴露自己的地址。而consumer(調用者)需要記錄服務提供者的地址。將來地址出現變更,還需要及時更新。這在服務較少的時候並不覺得有什麼,但是在現在日益複雜的互聯網環境,一個項目肯定會拆分出十幾,甚至數十個微服務。此時如果還人爲管理地址,不僅開發困難,將來測試、發佈上線都會非常麻煩,這與DevOps的思想是背道而馳的。
網約車
這就好比是 網約車出現以前,人們出門叫車只能叫出租車。一些私家車想做出租卻沒有資格,被稱爲黑車。而很多人想要約車,但是無奈出租車太少,不方便。私家車很多卻不敢攔,而且滿大街的車,誰知道哪個纔是願意載人的。一個想要,一個願意給,就是缺少引子,缺乏管理啊。
此時滴滴這樣的網約車平臺出現了,所有想載客的私家車全部到滴滴注冊,記錄你的車型(服務類型),身份信息(聯繫方式)。這樣提供服務的私家車,在滴滴那裏都能找到,一目瞭然。
此時要叫車的人,只需要打開APP,輸入你的目的地,選擇車型(服務類型),滴滴自動安排一個符合需求的車到你面前,爲你服務,完美!
Eureka做什麼?
Eureka就好比是滴滴,負責管理、記錄服務提供者的信息。服務調用者無需自己尋找服務,而是把自己的需求告訴Eureka,然後Eureka會把符合你需求的服務告訴你。
同時,服務提供方與Eureka之間通過“心跳”
機制進行監控,當某個服務提供方出現問題,Eureka自然會把它從服務列表中剔除。
這就實現了服務的自動註冊、發現、狀態監控。
3.2.原理圖
基本架構:
- Eureka:就是服務註冊中心(可以是一個集羣),對外暴露自己的地址
- 提供者:啓動後向Eureka註冊自己信息(地址,提供什麼服務)
- 消費者:向Eureka訂閱服務,Eureka會將對應服務的所有提供者地址列表發送給消費者,並且定期更新
- 心跳(續約):提供者定期通過http方式向Eureka刷新自己的狀態
3.3.入門案例
3.3.1.編寫EurekaServer
接下來我們創建一個項目,啓動一個EurekaServer:
依然使用spring提供的快速搭建工具:
選擇依賴:
完整的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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.leyou.demo</groupId>
<artifactId>eureka-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>eureka-demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!-- SpringCloud版本,是最新的F系列 -->
<spring-cloud.version>Finchley.RC1</spring-cloud.version>
</properties>
<dependencies>
<!-- Eureka服務端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- SpringCloud依賴,一定要放到dependencyManagement中,起到管理版本的作用即可 -->
<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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
編寫啓動類:
@SpringBootApplication
@EnableEurekaServer // 聲明這個應用是一個EurekaServer
public class EurekaDemoApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaDemoApplication.class, args);
}
}
編寫配置:
server:
port: 10086 # 端口
spring:
application:
name: eureka-server # 應用名稱,會在Eureka中顯示
eureka:
client:
register-with-eureka: false # 是否註冊自己的信息到EurekaServer,默認是true
fetch-registry: false # 是否拉取其它服務的信息,默認是true
service-url: # EurekaServer的地址,現在是自己的地址,如果是集羣,需要加上其它Server的地址。
defaultZone: http://127.0.0.1:${server.port}/eureka
啓動服務,並訪問:http://127.0.0.1:10086/eureka
3.3.2.將user-service註冊到Eureka
註冊服務,就是在服務上添加Eureka的客戶端依賴,客戶端代碼會自動把服務註冊到EurekaServer中。
我們在user-service-demo中添加Eureka客戶端依賴:
先添加SpringCloud依賴:
<!-- SpringCloud的依賴 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RC1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Spring的倉庫地址 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
然後是Eureka客戶端:
<!-- Eureka客戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在啓動類上開啓Eureka客戶端功能
通過添加@EnableDiscoveryClient
來開啓Eureka客戶端功能
@SpringBootApplication
@EnableDiscoveryClient // 開啓EurekaClient功能
public class UserServiceDemoApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceDemoApplication.class, args);
}
}
編寫配置
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb01
username: root
password: 123
hikari:
maximum-pool-size: 20
minimum-idle: 10
application:
name: user-service # 應用名稱
mybatis:
type-aliases-package: com.leyou.userservice.pojo
eureka:
client:
service-url: # EurekaServer地址
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true # 當調用getHostname獲取實例的hostname時,返回ip而不是host名稱
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的話會自己尋找
注意:
- 這裏我們添加了spring.application.name屬性來指定應用名稱,將來會作爲應用的id使用。
- 不用指定register-with-eureka和fetch-registry,因爲默認是true
重啓項目,訪問Eureka監控頁面查看
我們發現user-service服務已經註冊成功了
3.3.3.消費者從Eureka獲取服務
接下來我們修改consumer-demo,嘗試從EurekaServer獲取服務。
方法與消費者類似,只需要在項目中添加EurekaClient依賴,就可以通過服務名稱來獲取信息了!
1)添加依賴:
先添加SpringCloud依賴:
<!-- SpringCloud的依賴 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RC1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Spring的倉庫地址 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
然後是Eureka客戶端:
<!-- Eureka客戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2)在啓動類開啓Eureka客戶端
@SpringBootApplication
@EnableDiscoveryClient // 開啓Eureka客戶端
public class UserConsumerDemoApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
public static void main(String[] args) {
SpringApplication.run(UserConsumerDemoApplication.class, args);
}
}
3)修改配置:
server:
port: 8080
spring:
application:
name: consumer # 應用名稱
eureka:
client:
service-url: # EurekaServer地址
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true # 當其它服務獲取地址時提供ip而不是hostname
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的話會自己尋找
4)修改代碼,用DiscoveryClient類的方法,根據服務名稱,獲取服務實例:
@Service
public class UserService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;// Eureka客戶端,可以獲取到服務實例信息
public List<User> queryUserByIds(List<Long> ids) {
List<User> users = new ArrayList<>();
// String baseUrl = "http://localhost:8081/user/";
// 根據服務名稱,獲取服務實例
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
// 因爲只有一個UserService,因此我們直接get(0)獲取
ServiceInstance instance = instances.get(0);
// 獲取ip和端口信息
String baseUrl = "http://"+instance.getHost() + ":" + instance.getPort()+"/user/";
ids.forEach(id -> {
// 我們測試多次查詢,
users.add(this.restTemplate.getForObject(baseUrl + id, User.class));
// 每次間隔500毫秒
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
return users;
}
}
5)Debug跟蹤運行:
生成的URL:
訪問結果:
3.4.Eureka詳解
接下來我們詳細講解Eureka的原理及配置。
3.4.1.基礎架構
Eureka架構中的三個核心角色:
-
服務註冊中心
Eureka的服務端應用,提供服務註冊和發現功能,就是剛剛我們建立的eureka-demo
-
服務提供者
提供服務的應用,可以是SpringBoot應用,也可以是其它任意技術實現,只要對外提供的是Rest風格服務即可。本例中就是我們實現的user-service-demo
-
服務消費者
消費應用從註冊中心獲取服務列表,從而得知每個服務方的信息,知道去哪裏調用服務方。本例中就是我們實現的consumer-demo
3.4.2.高可用的Eureka Server
Eureka Server即服務的註冊中心,在剛纔的案例中,我們只有一個EurekaServer,事實上EurekaServer也可以是一個集羣,形成高可用的Eureka中心。
服務同步
多個Eureka Server之間也會互相註冊爲服務,當服務提供者註冊到Eureka Server集羣中的某個節點時,該節點會把服務的信息同步給集羣中的每個節點,從而實現數據同步。因此,無論客戶端訪問到Eureka Server集羣中的任意一個節點,都可以獲取到完整的服務列表信息。
動手搭建高可用的EurekaServer
我們假設要搭建兩條EurekaServer的集羣,端口分別爲:10086和10087
1)我們修改原來的EurekaServer配置:
server:
port: 10086 # 端口
spring:
application:
name: eureka-server # 應用名稱,會在Eureka中顯示
eureka:
client:
service-url: # 配置其他Eureka服務的地址,而不是自己,比如10087
defaultZone: http://127.0.0.1:10087/eureka
所謂的高可用註冊中心,其實就是把EurekaServer自己也作爲一個服務進行註冊,這樣多個EurekaServer之間就能互相發現對方,從而形成集羣。因此我們做了以下修改:
- 刪除了register-with-eureka=false和fetch-registry=false兩個配置。因爲默認值是true,這樣就會吧自己註冊到註冊中心了。
- 把service-url的值改成了另外一臺EurekaServer的地址,而不是自己
2)另外一臺配置恰好相反:
server:
port: 10087 # 端口
spring:
application:
name: eureka-server # 應用名稱,會在Eureka中顯示
eureka:
client:
service-url: # 配置其他Eureka服務的地址,而不是自己,比如10087
defaultZone: http://127.0.0.1:10086/eureka
注意:idea中一個應用不能啓動兩次,我們需要重新配置一個啓動器:
然後啓動即可。
3)啓動測試:
4)客戶端註冊服務到集羣
因爲EurekaServer不止一個,因此註冊服務的時候,service-url參數需要變化:
eureka:
client:
service-url: # EurekaServer地址,多個地址以','隔開
defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
3.4.3.服務提供者
服務提供者要向EurekaServer註冊服務,並且完成服務續約等工作。
服務註冊
服務提供者在啓動時,會檢測配置屬性中的:eureka.client.register-with-erueka=true
參數是否正確,事實上默認就是true。如果值確實爲true,則會向EurekaServer發起一個Rest請求,並攜帶自己的元數據信息,Eureka Server會把這些信息保存到一個雙層Map結構中。第一層Map的Key就是服務名稱,第二層Map的key是服務的實例id。
服務續約
在註冊服務完成以後,服務提供者會維持一個心跳(定時向EurekaServer發起Rest請求),告訴EurekaServer:“我還活着”。這個我們稱爲服務的續約(renew);
有兩個重要參數可以修改服務續約的行爲:
eureka:
instance:
lease-expiration-duration-in-seconds: 90
lease-renewal-interval-in-seconds: 30
- lease-renewal-interval-in-seconds:服務續約(renew)的間隔,默認爲30秒
- lease-expiration-duration-in-seconds:服務失效時間,默認值90秒
也就是說,默認情況下每個30秒服務會向註冊中心發送一次心跳,證明自己還活着。如果超過90秒沒有發送心跳,EurekaServer就會認爲該服務宕機,會從服務列表中移除,這兩個值在生產環境不要修改,默認即可。
但是在開發時,這個值有點太長了,經常我們關掉一個服務,會發現Eureka依然認爲服務在活着。所以我們在開發階段可以適當調小。
eureka:
instance:
lease-expiration-duration-in-seconds: 10 # 10秒即過期
lease-renewal-interval-in-seconds: 5 # 5秒一次心跳
實例id
先來看一下服務狀態信息:
在Eureka監控頁面,查看服務註冊信息:
在status一列中,顯示以下信息:
- UP(1):代表現在是啓動了1個示例,沒有集羣
- DESKTOP-2MVEC12:user-service:8081:是示例的名稱(instance-id),
- 默認格式是:
${hostname} + ${spring.application.name} + ${server.port}
- instance-id是區分同一服務的不同實例的唯一標準,因此不能重複。
- 默認格式是:
我們可以通過instance-id屬性來修改它的構成:
eureka:
instance:
instance-id: ${spring.application.name}:${server.port}
重啓服務再試試看:
3.4.4.服務消費者
獲取服務列表
當服務消費者啓動是,會檢測eureka.client.fetch-registry=true
參數的值,如果爲true,則會從Eureka Server服務的列表只讀備份,然後緩存在本地。並且每隔30秒
會重新獲取並更新數據。我們可以通過下面的參數來修改:
eureka:
client:
registry-fetch-interval-seconds: 5
生產環境中,我們不需要修改這個值。
但是爲了開發環境下,能夠快速得到服務的最新狀態,我們可以將其設置小一點。
3.4.5.失效剔除和自我保護
失效剔除
有些時候,我們的服務提供方並不一定會正常下線,可能因爲內存溢出、網絡故障等原因導致服務無法正常工作。Eureka Server需要將這樣的服務剔除出服務列表。因此它會開啓一個定時任務,每隔60秒對所有失效的服務(超過90秒未響應)進行剔除。
可以通過eureka.server.eviction-interval-timer-in-ms
參數對其進行修改,單位是毫秒,生成環境不要修改。
這個會對我們開發帶來極大的不變,你對服務重啓,隔了60秒Eureka才反應過來。開發階段可以適當調整,比如10S
自我保護
我們關停一個服務,就會在Eureka面板看到一條警告:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3a8MRenF-1576476509238)(assets/1525618396076.png)]
這是觸發了Eureka的自我保護機制。當一個服務未按時進行心跳續約時,Eureka會統計最近15分鐘心跳失敗的服務實例的比例是否超過了85%。在生產環境下,因爲網絡延遲等原因,心跳失敗實例的比例很有可能超標,但是此時就把服務剔除列表並不妥當,因爲服務可能沒有宕機。Eureka就會把當前實例的註冊信息保護起來,不予剔除。生產環境下這很有效,保證了大多數服務依然可用。
但是這給我們的開發帶來了麻煩, 因此開發階段我們都會關閉自我保護模式:
eureka:
server:
enable-self-preservation: false # 關閉自我保護模式(缺省爲打開)
eviction-interval-timer-in-ms: 1000 # 掃描失效服務的間隔時間(缺省爲60*1000ms)