本篇文章為系列文章,未讀第一集的同學請猛戳這裡:
高可用 Eureka 註冊中心
註冊中心 eureka-server
創建項目
在微服務系列之Eureka註冊中心(一)的項目父工程下再創建一個 eureka-server02 註冊中心的項目,如果是多機器部署不用修改端口,通過 IP 區分服務,如果在一臺機器上演示需要修改端口區分服務。
添加依賴
pom.xml
<code>
<project> 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>
<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-web/<artifactid>
/<dependency>
<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>/<code>
配置文件
集群配置下,註冊中心需要相互註冊實現信息的同步。
eureka-server 的 application.yml
<code> server:
port: 8761 # 端口
spring:
application:
name: eureka-server # 應用名稱(集群下相同)
# 配置 Eureka Server 註冊中心
eureka:
instance:
hostname: eureka01 # 主機名,不配置的時候將根據操作系統的主機名來獲取
client:
# 設置服務註冊中心地址,指向另一個註冊中心
service-url: # 註冊中心對外暴露的註冊地址
defaultZone: http://localhost:8762/eureka//<code>
eureka-server02 的 application.yml
<code> server:
port: 8762 # 端口
spring:
application:
name: eureka-server # 應用名稱(集群下相同)
# 配置 Eureka Server 註冊中心
eureka:
instance:
hostname: eureka02 # 主機名,不配置的時候將根據操作系統的主機名來獲取
client:
# 設置服務註冊中心地址,指向另一個註冊中心
service-url: # 註冊中心對外暴露的註冊地址
defaultZone: http://localhost:8761/eureka//<code>
啟動類
啟動類不變,啟動兩個 server。
訪問
訪問:http://localhost:8761/ 或者 http://localhost:8762/ 都出現如下圖說明互相註冊成功。
Status 顯示方式為默認值,如果想要清晰可見每個服務的 IP + 端口需要通過以下配置來實現。
顯示 IP + 端口
一個普通的 Netflix Eureka 實例註冊的 ID 等於其主機名(即,每個主機僅提供一項服務)。 Spring Cloud Eureka 提供了合理的默認值,定義如下:${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}},也就是:主機名:應用名:應用端口。
我們也可以可以自定義進行修改:
<code> eureka:
instance:
prefer-ip-address: true # 是否使用 ip 地址註冊
instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port/<code>
服務提供者 service-provider
創建項目
在剛才的父工程下創建一個 service-provider 服務提供者的項目。
添加依賴
pom.xml
<code>
<project> 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>
<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-web/<artifactid>
/<dependency>
<dependency>
<groupid>org.projectlombok/<groupid>
<artifactid>lombok/<artifactid>
<scope>provided/<scope>
/<dependency>
<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>/<code>
配置文件
application.yml
<code> 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//<code>
實體類
Product.java
<code> 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;
}/<code>
編寫服務
ProductService.java
<code> package com.example.service;
import com.example.pojo.Product;
import java.util.List;
/**
* 商品服務
*/
public interface ProductService {
/**
* 查詢商品列表
*
* @return
*/
List<product> selectProductList();
}/<product>/<code>
ProductServiceImpl.java
<code>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)
);
}
}/<product>/<code>
控制層
ProductController.java
<code>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();
}
}/<product>/<code>
該項目我們可以通過單元測試進行測試,也可以直接通過 url 使用 postman 或者瀏覽器來進行測試。
啟動類
ServiceProviderApplication.java
<code>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);
}
}/<code>
註冊中心
訪問註冊中心,可以看到用戶服務已經註冊至註冊中心。
服務消費者 service-consumer
創建項目
在剛才的父工程下創建一個 service-consumer 服務消費者的項目。
添加依賴
pom.xml
<code>
<project> 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>
<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-web/<artifactid>
/<dependency>
<dependency>
<groupid>org.projectlombok/<groupid>
<artifactid>lombok/<artifactid>
<scope>provided/<scope>
/<dependency>
<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>/<code>
配置文件
application.yml
<code>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//<code>
實體類
Product.java
<code>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;
}/<code>
Order.java
<code>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;
}/<product>/<code>
消費服務
OrderService.java
<code>package com.example.service;
import com.example.pojo.Order;
public interface OrderService {
/**
* 根據主鍵查詢訂單
*
* @param id
* @return
*/
Order selectOrderById(Integer id);
}/<code>
對於服務的消費我們這裡講三種實現方式:
- DiscoveryClient:通過元數據獲取服務信息
- LoadBalancerClient:Ribbon 的負載均衡器
- @LoadBalanced:通過註解開啟 Ribbon 的負載均衡器
DiscoveryClient
Spring Boot 不提供任何自動配置的RestTemplate bean,所以需要在啟動類中注入 RestTemplate。
<code>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);
}
}/<code>
OrderServiceImpl.java
<code>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>> response = restTemplate.exchange(
sb.toString(),
HttpMethod.GET,
null,
new ParameterizedTypeReference<list>>() {});
return response.getBody();
}
}/<list>/<list>/<serviceinstance>/<string>/<product>/<code>
LoadBalancerClient
OrderServiceImpl.java
<code>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>> response = restTemplate.exchange(
sb.toString(),
HttpMethod.GET,
null,
new ParameterizedTypeReference<list>>() {});
return response.getBody();
}
}/<list>/<list>/<product>/<code>
@LoadBalanced
啟動類注入 RestTemplate 時添加 @LoadBalanced 負載均衡註解,表示這個 RestTemplate 在請求時擁有客戶端負載均衡的能力。
<code>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);
}
}/<code>
OrderServiceImpl.java
<code>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>> response = restTemplate.exchange(
"http://service-provider/product/list",
HttpMethod.GET,
null,
new ParameterizedTypeReference<list>>() {});
return response.getBody();
}
}/<list>/<list>/<product>/<code>
控制層
OrderController.java
<code>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);
}
}/<code>
訪問
訪問: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 都能正常通信。
- 微服務的負載均衡策略會自動剔除死亡的微服務節點。
如何關閉自我保護
註冊中心配置自我保護
<code>eureka:
server:
enable-self-preservation: false # true:開啟自我保護模式,false:關閉自我保護模式
eviction-interval-timer-in-ms: 60000 # 清理間隔(單位:毫秒,默認是 60*1000)/<code>
Eureka 優雅停服
配置了優雅停服以後,將不需要 Eureka Server 中配置關閉自我保護。本文使用 actuator 實現。
添加依賴
服務提供者添加 actuator 依賴
<code>
<dependency>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-actuator/<artifactid>
/<dependency>/<code>
配置文件
服務提供者配置度量指標監控與健康檢查
<code># 度量指標監控與健康檢查
management:
endpoints:
web:
exposure:
include: shutdown # 開啟 shutdown 端點訪問
endpoint:
shutdown:
enabled: true # 開啟 shutdown 實現優雅停服/<code>
優雅停服
使用 POST 請求訪問:http://localhost:7070/actuator/shutdown 效果如下
Eureka 安全認證
添加依賴
註冊中心添加 security 依賴
<code>
<dependency>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-security/<artifactid>
/<dependency>/<code>
配置文件
註冊中心配置安全認證
<code>spring:
# 安全認證
security:
user:
name: root
password: 123456/<code>
修改訪問集群節點的 url
核心代碼就是有色部分:http://root:123456@localhost:8762/eureka/
註冊中心的配置文件
<code># 配置 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//<code>
服務提供者的配置文件
<code># 配置 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//<code>
服務消費者的配置文件
<code># 配置 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//<code>
過濾 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/** 的所有請求
<code>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/** 的所有請求
}
}/<code>
方案二
保持密碼驗證的同時禁用 CSRF 防禦機制
<code>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();
}
}/<code>
訪問
使用配置好的用戶名和密碼登錄以後可看到註冊中心界面,啟動服務提供者和服務消費者,功能正常使用,至此 Eureka 註冊中心所有的知識點就講解結束了。
掃碼關注 哈嘍沃德先生「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕鬆噢 ~
閱讀更多 哈嘍沃德先生 的文章