本篇文章爲系列文章,未讀第一集的同學請猛戳這裏:Spring Cloud 系列之 Netflix Eureka 註冊中心(一)
本篇文章講解 Eureka 集羣、架構原理、自我保護、優雅停服、安全認證等功能實現。
高可用 Eureka 註冊中心
點擊鏈接觀看:高可用 Eureka 註冊中心視頻(獲取更多請關注公衆號「哈嘍沃德先生」)
註冊中心 eureka-server
創建項目
在 Spring Cloud 系列之 Netflix Eureka 註冊中心(一)的項目父工程下再創建一個 eureka-server02
註冊中心的項目,如果是多機器部署不用修改端口,通過 IP 區分服務,如果在一臺機器上演示需要修改端口區分服務。
添加依賴
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>
<groupId>com.example</groupId>
<artifactId>eureka-server02</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 繼承父依賴 -->
<parent>
<groupId>com.example</groupId>
<artifactId>eureka-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<!-- 項目依賴 -->
<dependencies>
<!-- netflix eureka server 依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- spring boot web 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring boot test 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
配置文件
集羣配置下,註冊中心需要相互註冊實現信息的同步。
eureka-server 的 application.yml
server:
port: 8761 # 端口
spring:
application:
name: eureka-server # 應用名稱(集羣下相同)
# 配置 Eureka Server 註冊中心
eureka:
instance:
hostname: eureka01 # 主機名,不配置的時候將根據操作系統的主機名來獲取
client:
# 設置服務註冊中心地址,指向另一個註冊中心
service-url: # 註冊中心對外暴露的註冊地址
defaultZone: http://localhost:8762/eureka/
eureka-server02 的 application.yml
server:
port: 8762 # 端口
spring:
application:
name: eureka-server # 應用名稱(集羣下相同)
# 配置 Eureka Server 註冊中心
eureka:
instance:
hostname: eureka02 # 主機名,不配置的時候將根據操作系統的主機名來獲取
client:
# 設置服務註冊中心地址,指向另一個註冊中心
service-url: # 註冊中心對外暴露的註冊地址
defaultZone: http://localhost:8761/eureka/
啓動類
啓動類不變,啓動兩個 server。
訪問
訪問:http://localhost:8761/ 或者 http://localhost:8762/ 都出現如下圖說明互相註冊成功。
Status
顯示方式爲默認值,如果想要清晰可見每個服務的 IP + 端口需要通過以下配置來實現。
顯示 IP + 端口
一個普通的 Netflix Eureka 實例註冊的 ID 等於其主機名(即,每個主機僅提供一項服務)。 Spring Cloud Eureka 提供了合理的默認值,定義如下:{spring.application.name}:KaTeX parse error: Expected '}', got 'EOF' at end of input: …on.instance_id:{server.port}}},也就是:主機名:應用名:應用端口。
我們也可以可以自定義進行修改:
eureka:
instance:
prefer-ip-address: true # 是否使用 ip 地址註冊
instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
服務提供者 service-provider
點擊鏈接觀看:服務提供者 service-provider 視頻(獲取更多請關注公衆號「哈嘍沃德先生」)
創建項目
在剛纔的父工程下創建一個 service-provider
服務提供者的項目。
添加依賴
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>
<groupId>com.example</groupId>
<artifactId>service-provider</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 繼承父依賴 -->
<parent>
<groupId>com.example</groupId>
<artifactId>eureka-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<!-- 項目依賴 -->
<dependencies>
<!-- netflix eureka client 依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- spring boot web 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok 依賴 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- spring boot test 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
配置文件
application.yml
server:
port: 7070 # 端口
spring:
application:
name: service-provider # 應用名稱(集羣下相同)
# 配置 Eureka Server 註冊中心
eureka:
instance:
prefer-ip-address: true # 是否使用 ip 地址註冊
instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
client:
service-url: # 設置服務註冊中心地址
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
實體類
Product.java
package com.example.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {
private Integer id;
private String productName;
private Integer productNum;
private Double productPrice;
}
編寫服務
ProductService.java
package com.example.service;
import com.example.pojo.Product;
import java.util.List;
/**
* 商品服務
*/
public interface ProductService {
/**
* 查詢商品列表
*
* @return
*/
List<Product> selectProductList();
}
ProductServiceImpl.java
package com.example.service.impl;
import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
/**
* 商品服務
*/
@Service
public class ProductServiceImpl implements ProductService {
/**
* 查詢商品列表
*
* @return
*/
@Override
public List<Product> selectProductList() {
return Arrays.asList(
new Product(1, "華爲手機", 2, 5888D),
new Product(2, "聯想筆記本", 1, 6888D),
new Product(3, "小米平板", 5, 2666D)
);
}
}
控制層
ProductController.java
package com.example.controller;
import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
/**
* 查詢商品列表
*
* @return
*/
@GetMapping("/list")
public List<Product> selectProductList() {
return productService.selectProductList();
}
}
該項目我們可以通過單元測試進行測試,也可以直接通過 url 使用 postman 或者瀏覽器來進行測試。
啓動類
ServiceProviderApplication.java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
// 開啓 EurekaClient 註解,目前版本如果配置了 Eureka 註冊中心,默認會開啓該註解
//@EnableEurekaClient
public class ServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceProviderApplication.class, args);
}
}
註冊中心
訪問註冊中心,可以看到用戶服務已經註冊至註冊中心。
服務消費者 service-consumer
創建項目
在剛纔的父工程下創建一個 service-consumer 服務消費者的項目。
添加依賴
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>
<groupId>com.example</groupId>
<artifactId>service-consumer</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 繼承父依賴 -->
<parent>
<groupId>com.example</groupId>
<artifactId>eureka-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<!-- 項目依賴 -->
<dependencies>
<!-- netflix eureka client 依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- spring boot web 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok 依賴 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- spring boot test 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
配置文件
application.yml
server:
port: 9090 # 端口
spring:
application:
name: service-consumer # 應用名稱
# 配置 Eureka Server 註冊中心
eureka:
client:
register-with-eureka: false # 是否將自己註冊到註冊中心,默認爲 true
registry-fetch-interval-seconds: 10 # 表示 Eureka Client 間隔多久去服務器拉取註冊信息,默認爲 30 秒
service-url: # 設置服務註冊中心地址
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
實體類
Product.java
package com.example.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {
private Integer id;
private String productName;
private Integer productNum;
private Double productPrice;
}
Order.java
package com.example.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order implements Serializable {
private Integer id;
private String orderNo;
private String orderAddress;
private Double totalPrice;
private List<Product> productList;
}
消費服務
OrderService.java
package com.example.service;
import com.example.pojo.Order;
public interface OrderService {
/**
* 根據主鍵查詢訂單
*
* @param id
* @return
*/
Order selectOrderById(Integer id);
}
對於服務的消費我們這裏講三種實現方式:
- DiscoveryClient:通過元數據獲取服務信息
- LoadBalancerClient:Ribbon 的負載均衡器
- @LoadBalanced:通過註解開啓 Ribbon 的負載均衡器
DiscoveryClient
點擊鏈接觀看:DiscoveryClient 視頻(獲取更多請關注公衆號「哈嘍沃德先生」)
Spring Boot 不提供任何自動配置的RestTemplate
bean,所以需要在啓動類中注入 RestTemplate
。
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
// 開啓 Eureka Client 註解,目前版本如果配置了 Eureka 註冊中心,默認會開啓該註解
//@EnableEurekaClient
public class ServiceConsumerApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ServiceConsumerApplication.class, args);
}
}
OrderServiceImpl.java
package com.example.service.impl;
import com.example.pojo.Order;
import com.example.pojo.Product;
import com.example.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
/**
* 根據主鍵查詢訂單
*
* @param id
* @return
*/
@Override
public Order selectOrderById(Integer id) {
return new Order(id, "order-001", "中國", 31994D,
selectProductListByDiscoveryClient());
}
private List<Product> selectProductListByDiscoveryClient() {
StringBuffer sb = null;
// 獲取服務列表
List<String> serviceIds = discoveryClient.getServices();
if (CollectionUtils.isEmpty(serviceIds))
return null;
// 根據服務名稱獲取服務
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("service-provider");
if (CollectionUtils.isEmpty(serviceInstances))
return null;
ServiceInstance si = serviceInstances.get(0);
sb = new StringBuffer();
sb.append("http://" + si.getHost() + ":" + si.getPort() + "/product/list");
// ResponseEntity: 封裝了返回數據
ResponseEntity<List<Product>> response = restTemplate.exchange(
sb.toString(),
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Product>>() {});
return response.getBody();
}
}
LoadBalancerClient
點擊鏈接觀看:LoadBalancerClient 視頻(獲取更多請關注公衆號「哈嘍沃德先生」)
OrderServiceImpl.java
package com.example.service.impl;
import com.example.pojo.Order;
import com.example.pojo.Product;
import com.example.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient; // Ribbon 負載均衡器
/**
* 根據主鍵查詢訂單
*
* @param id
* @return
*/
@Override
public Order selectOrderById(Integer id) {
return new Order(id, "order-001", "中國", 31994D,
selectProductListByLoadBalancerClient());
}
private List<Product> selectProductListByLoadBalancerClient() {
StringBuffer sb = null;
// 根據服務名稱獲取服務
ServiceInstance si = loadBalancerClient.choose("service-provider");
if (null == si)
return null;
sb = new StringBuffer();
sb.append("http://" + si.getHost() + ":" + si.getPort() + "/product/list");
// ResponseEntity: 封裝了返回數據
ResponseEntity<List<Product>> response = restTemplate.exchange(
sb.toString(),
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Product>>() {});
return response.getBody();
}
}
@LoadBalanced
點擊鏈接觀看:@LoadBalanced 視頻(獲取更多請關注公衆號「哈嘍沃德先生」)
啓動類注入 RestTemplate
時添加 @LoadBalanced
負載均衡註解,表示這個 RestTemplate
在請求時擁有客戶端負載均衡的能力。
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
// 開啓 Eureka Client 註解,目前版本如果配置了 Eureka 註冊中心,默認會開啓該註解
//@EnableEurekaClient
public class ServiceConsumerApplication {
@Bean
@LoadBalanced // 負載均衡註解
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ServiceConsumerApplication.class, args);
}
}
OrderServiceImpl.java
package com.example.service.impl;
import com.example.pojo.Order;
import com.example.pojo.Product;
import com.example.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private RestTemplate restTemplate;
/**
* 根據主鍵查詢訂單
*
* @param id
* @return
*/
@Override
public Order selectOrderById(Integer id) {
return new Order(id, "order-001", "中國", 31994D,
selectProductListByLoadBalancerAnnotation());
}
private List<Product> selectProductListByLoadBalancerAnnotation() {
// ResponseEntity: 封裝了返回數據
ResponseEntity<List<Product>> response = restTemplate.exchange(
"http://service-provider/product/list",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Product>>() {});
return response.getBody();
}
}
控制層
OrderController.java
package com.example.controller;
import com.example.pojo.Order;
import com.example.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 根據主鍵查詢訂單
*
* @param id
* @return
*/
@GetMapping("/{id}")
public Order selectOrderById(@PathVariable("id") Integer id) {
return orderService.selectOrderById(id);
}
}
訪問
訪問:http://localhost:9090/order/1
Eureka 架構原理
- Register(服務註冊):把自己的 IP 和端口註冊給 Eureka。
- Renew(服務續約):發送心跳包,每 30 秒發送一次,告訴 Eureka 自己還活着。如果 90 秒還未發送心跳,宕機。
- Cancel(服務下線):當 Provider 關閉時會向 Eureka 發送消息,把自己從服務列表中刪除。防止 Consumer 調用到不存在的服務。
- Get Registry(獲取服務註冊列表):獲取其他服務列表。
- Replicate(集羣中數據同步):Eureka 集羣中的數據複製與同步。
- Make Remote Call(遠程調用):完成服務的遠程調用。
CAP 原則
CAP 原則又稱 CAP 定理,指的是在一個分佈式系統中具有以下其中兩個特性:
- Consistency (一致性)
- Availability (可用性)
- Partition tolerance(分區容錯性)
CAP 由 Eric Brewer 在 2000 年 PODC 會議上提出。該猜想在提出兩年後被證明成立,成爲我們熟知的 CAP 定理。CAP 三者不可兼得。
特性 | 定理 |
---|---|
Consistency | 也叫做數據原子性,系統在執行某項操作後仍然處於一致的狀態。在分佈式系統中,更新操作執行成功後所有的用戶都應該讀到最新的值,這樣的系統被認爲是具有強一致性的。等同於所有節點訪問同一份最新的數據副本。 |
Availability | 每一個操作總是能夠在一定的時間內返回結果,這裏需要注意的是"一定時間內"和"返回結果"。一定時間內指的是,在可以容忍的範圍內返回結果,結果可以是成功或者是失敗。 |
Partition tolerance | 在網絡分區的情況下,被分隔的節點仍能正常對外提供服務(分佈式集羣,數據被分佈存儲在不同的服務器上,無論什麼情況,服務器都能正常被訪問)。 |
取捨策略
CAP 三個特性只能滿足其中兩個,那麼取捨的策略就共有三種:
- CA without P:如果不要求P(不允許分區),則C(強一致性)和A(可用性)是可以保證的。但放棄 P 的同時也就意味着放棄了系統的擴展性,也就是分佈式節點受限,沒辦法部署子節點,這是違背分佈式系統設計的初衷的。
- CP without A:如果不要求A(可用),相當於每個請求都需要在服務器之間保持強一致,而P(分區)會導致同步時間無限延長(也就是等待數據同步完才能正常訪問服務),一旦發生網絡故障或者消息丟失等情況,就要犧牲用戶的體驗,等待所有數據全部一致了之後再讓用戶訪問系統。設計成 CP 的系統其實不少,最典型的就是分佈式數據庫,如 Redis、HBase 等。對於這些分佈式數據庫來說,數據的一致性是最基本的要求,因爲如果連這個標準都達不到,那麼直接採用關係型數據庫就好,沒必要再浪費資源來部署分佈式數據庫。
- AP without C:要高可用並允許分區,則需放棄一致性。一旦分區發生,節點之間可能會失去聯繫,爲了高可用,每個節點只能用本地數據提供服務,而這樣會導致全局數據的不一致性。典型的應用就如某米的搶購手機場景,可能前幾秒你瀏覽商品的時候頁面提示是有庫存的,當你選擇完商品準備下單的時候,系統提示你下單失敗,商品已售完。這其實就是先在 A(可用性)方面保證系統可以正常的服務,然後在數據的一致性方面做了些犧牲,雖然多少會影響一些用戶體驗,但也不至於造成用戶購物流程的嚴重阻塞。
總結
現如今,對於多數大型互聯網應用的場景,主機衆多、部署分散,而且現在的集羣規模越來越大,節點只會越來越多,所以節點故障、網絡故障是常態,因此分區容錯性也就成爲了一個分佈式系統必然要面對的問題。那麼就只能在 C 和 A 之間進行取捨。但對於傳統的項目就可能有所不同,拿銀行的轉賬系統來說,涉及到金錢的對於數據一致性不能做出一絲的讓步,C 必須保證,出現網絡故障的話,寧可停止服務,可以在 A 和 P 之間做取捨。
總而言之,沒有最好的策略,好的系統應該是根據業務場景來進行架構設計的,只有適合的纔是最好的。
Eureka 自我保護
啓動自我保護條件
一般情況下,服務在 Eureka 上註冊後,會每 30 秒發送心跳包,Eureka 通過心跳來判斷服務是否健康,同時會定期刪除超過 90 秒沒有發送心跳的服務。
有兩種情況會導致 Eureka Server 收不到微服務的心跳
- 微服務自身的原因
- 微服務與 Eureka 之間的網絡故障
自我保護模式
Eureka Server 在運行期間會去統計心跳失敗比例在 15 分鐘之內是否低於 85%,如果低於 85%,Eureka Server 會將這些實例保護起來,讓這些實例不會過期,同時提示一個警告。這種算法叫做 Eureka Server 的自我保護模式。
爲什麼要啓動自我保護
- 因爲同時保留"好數據"與"壞數據"總比丟掉任何數據要更好,當網絡故障恢復後,這個 Eureka 節點會退出"自我保護模式"。
- Eureka 還有客戶端緩存功能(也就是微服務的緩存功能)。即使 Eureka 集羣中所有節點都宕機失效,微服務的 Provider 和 Consumer 都能正常通信。
- 微服務的負載均衡策略會自動剔除死亡的微服務節點。
如何關閉自我保護
註冊中心配置自我保護
eureka:
server:
enable-self-preservation: false # true:開啓自我保護模式,false:關閉自我保護模式
eviction-interval-timer-in-ms: 60000 # 清理間隔(單位:毫秒,默認是 60*1000)
Eureka 優雅停服
配置了優雅停服以後,將不需要 Eureka Server 中配置關閉自我保護。本文使用 actuator 實現。
添加依賴
服務提供者添加 actuator 依賴
<!-- spring boot actuator 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置文件
服務提供者配置度量指標監控與健康檢查
# 度量指標監控與健康檢查
management:
endpoints:
web:
exposure:
include: shutdown # 開啓 shutdown 端點訪問
endpoint:
shutdown:
enabled: true # 開啓 shutdown 實現優雅停服
優雅停服
使用 POST 請求訪問:http://localhost:7070/actuator/shutdown 效果如下
Eureka 安全認證
添加依賴
註冊中心添加 security 依賴
<!-- spring boot security 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
配置文件
註冊中心配置安全認證
spring:
# 安全認證
security:
user:
name: root
password: 123456
修改訪問集羣節點的 url
核心代碼就是有色部分:http://root:123456@
localhost:8762/eureka/
註冊中心的配置文件
# 配置 Eureka Server 註冊中心
eureka:
instance:
hostname: eureka01 # 主機名,不配置的時候將根據操作系統的主機名來獲取
prefer-ip-address: true # 是否使用 ip 地址註冊
instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
client:
# 設置服務註冊中心地址,指向另一個註冊中心
service-url: # 註冊中心對外暴露的註冊地址
defaultZone: http://root:123456@localhost:8762/eureka/
服務提供者的配置文件
# 配置 Eureka Server 註冊中心
eureka:
instance:
prefer-ip-address: true # 是否使用 ip 地址註冊
instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
client:
service-url: # 設置服務註冊中心地址
defaultZone: http://root:123456@localhost:8761/eureka/,http://root:123456@localhost:8762/eureka/
服務消費者的配置文件
# 配置 Eureka Server 註冊中心
eureka:
client:
register-with-eureka: false # 是否將自己註冊到註冊中心,默認爲 true
registry-fetch-interval-seconds: 10 # 表示 Eureka Client 間隔多久去服務器拉取註冊信息,默認爲 30 秒
service-url: # 設置服務註冊中心地址
defaultZone: http://root:123456@localhost:8761/eureka/,http://root:123456@localhost:8762/eureka/
過濾 CSRF
Eureka 會自動化配置 CSRF 防禦機制,Spring Security 認爲 POST, PUT, and DELETE http methods 都是有風險的,如果這些 method 發送過程中沒有帶上 CSRF token 的話,會被直接攔截並返回 403 forbidden。
官方給出瞭解決的方法,具體可以參考 spring cloud issue 2754,裏面有大量的討論,這裏提供兩種解決方案。
首先註冊中心配置一個 @EnableWebSecurity
配置類,繼承 org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
,然後重寫 configure
方法。
方案一
使 CSRF 忽略 /eureka/**
的所有請求
package com.example.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* 安全認證配置類
*/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http); // 加這句是爲了訪問 eureka 控制檯和 /actuator 時能做安全控制
http.csrf().ignoringAntMatchers("/eureka/**"); // 忽略 /eureka/** 的所有請求
}
}
方案二
保持密碼驗證的同時禁用 CSRF 防禦機制
package com.example.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* 安全認證配置類
*/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 注意,如果直接 disable 的話會把安全驗證也禁用掉
http.csrf().disable().authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
}
訪問
使用配置好的用戶名和密碼登錄以後可看到註冊中心界面,啓動服務提供者和服務消費者,功能正常使用,至此 Eureka 註冊中心所有的知識點就講解結束了。
本文采用 知識共享「署名-非商業性使用-禁止演繹 4.0 國際」許可協議
。
大家可以通過 分類
查看更多關於 Spring Cloud
的文章。
🤗 您的點贊
和轉發
是對我最大的支持。
📢 掃碼關注 哈嘍沃德先生
「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕鬆噢 ~