微服務系列之 Eureka 註冊中心(二)

本篇文章為系列文章,未讀第一集的同學請猛戳這裡:

高可用 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/ 都出現如下圖說明互相註冊成功。

微服務系列之 Eureka 註冊中心(二)

  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>
微服務系列之 Eureka 註冊中心(二)

  

服務提供者 service-provider

  

創建項目

  

  在剛才的父工程下創建一個 service-provider 服務提供者的項目。

微服務系列之 Eureka 註冊中心(二)

微服務系列之 Eureka 註冊中心(二)

微服務系列之 Eureka 註冊中心(二)

微服務系列之 Eureka 註冊中心(二)

微服務系列之 Eureka 註冊中心(二)

  

添加依賴

  

  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>

  

註冊中心

  

  訪問註冊中心,可以看到用戶服務已經註冊至註冊中心。

微服務系列之 Eureka 註冊中心(二)

  

服務消費者 service-consumer

  

創建項目

  

  在剛才的父工程下創建一個 service-consumer 服務消費者的項目。

微服務系列之 Eureka 註冊中心(二)

微服務系列之 Eureka 註冊中心(二)

微服務系列之 Eureka 註冊中心(二)

微服務系列之 Eureka 註冊中心(二)

微服務系列之 Eureka 註冊中心(二)

  

添加依賴

  

  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 註冊中心(二)

  

Eureka 架構原理

  

微服務系列之 Eureka 註冊中心(二)

  

  • Register(服務註冊):把自己的 IP 和端口註冊給 Eureka。
  • Renew(服務續約):發送心跳包,每 30 秒發送一次,告訴 Eureka 自己還活著。如果 90 秒還未發送心跳,宕機。
  • Cancel(服務下線):當 Provider 關閉時會向 Eureka 發送消息,把自己從服務列表中刪除。防止 Consumer 調用到不存在的服務。
  • Get Registry(獲取服務註冊列表):獲取其他服務列表。
  • Replicate(集群中數據同步):Eureka 集群中的數據複製與同步。
  • Make Remote Call(遠程調用):完成服務的遠程調用。

  

微服務系列之 Eureka 註冊中心(二)

  

CAP 原則

  

微服務系列之 Eureka 註冊中心(二)

  

  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 註冊中心(二)

  

有兩種情況會導致 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 註冊中心(二)

  

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 註冊中心(二)

  

  使用配置好的用戶名和密碼登錄以後可看到註冊中心界面,啟動服務提供者和服務消費者,功能正常使用,至此 Eureka 註冊中心所有的知識點就講解結束了。

微服務系列之 Eureka 註冊中心(二)

   掃碼關注 哈嘍沃德先生「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕鬆噢 ~

微服務系列之 Eureka 註冊中心(二)


分享到:


相關文章: