Eureka註冊中心
1、什麼是註冊中心
打個比方,註冊中心就好比手機中的通訊錄,所有的聯繫人的聯繫方式就在這個通訊錄中儲存。當需要打電話的時候,只需要查詢通訊錄就可以獲取某個聯繫人的聯繫方式。
註冊中心類似於通信錄,只不過註冊中心儲存的不是聯繫人的聯繫方式,而是每個服務的信息,從註冊中心獲取服務就好比通訊錄的查詢聯繫人,向註冊中心註冊服務,就好比通訊錄的保存聯繫人,先有註冊,才能查找。
注:註冊中心只不過是用來註冊和獲取服務,並不會用來調取服務,具體的調取服務有獲取服務方實現。就好比通訊錄只負責儲存和查詢聯繫人,查到聯繫人後使用撥號軟件撥打查詢到的聯繫人電話。
2、CAP原則和BASE理論
2.1、CAP原則
名稱 | 描述 |
---|---|
Consistency | 一致性。也叫做原子性。系統在執行某些操作後數據仍然處於一致的狀態。在分佈式系統中,更新操作執行成功後所有的用戶都應該讀到最新的值,這樣的系統被認爲是具有強一致性的。等同於所有節點訪問同一份最新的數據副本。 |
Availability | 可用性。每一個操作總是能夠在一定的時間內返回結果,這裏需要注意的是"一定時間內"和"返回結果”。一定時間內指的是在可以容忍的範圍內返回結果,結果可以是成功或者是失敗,且不保證獲取的數據爲最新數據。 |
Partition tolerance | 分區容錯性。分佈式系統在遇到任何網絡分區故障的時候,仍然能夠對外提供滿足一致性和可用性的服務,除非整個網絡環境都發生了故障。 |
CAP原則又稱CAP定理。值得是在系統中,Consistency
、Availability
和Partition tolerance
三者不可兼得。最多隻能同時滿足其中兩者。
CAP 由 Eric Brewer 在 2000 年 PODC 會議上提出。該猜想在提出兩年後被證明成立,成爲我們熟知的 CAP
定理。CAP
三者不可兼得。
- CA:一致性和可用性。如果同時要滿足一致性和可用性,那麼只有單體應用可以實現。因爲如果是分佈式應用的話,如果要保證多個系統的一致性,可定要消耗時間。那麼可用性肯定就達不到要求。只有單體應用,可以滿足
CA
。 - CP:一致性和分區容錯性。這種典型的案例就是涉及到金錢的系統。涉及到金錢的系統肯定要保證數據一致,在分佈式中多臺服務器要保證數據一致,肯定不能再一定的時間內返回數據。好比在進行金錢轉賬的時候,寧願犧牲可用性,也要保證數據一致性。
- AP:可用性和分區容容錯性。這種的經典案例就是搶購性的系統。例如春運的火車票,有時候明明看到還有票,但是在下單的時候卻被告知沒票了,這就是因爲需要滿足可用性,在一定的時間內返回數據,從而放棄了數據的一致性。但是這種情況在最後會有一個最終一致性。好比購票,顯示的是還有車票,但是下單時卻顯示失敗,就是因爲最終一致性。在最後的時候會進行一致性判斷。
如今,對於大多數互聯網應用場景,主機衆多,部署分散,並且規模也越來越大,節點只會越來越多,所以節點故障、網絡故障時常態,分區容錯性也就成爲了一個分佈式系統必然要面對的問題。那麼就只能在C和A之前進行取捨。但是對於設計金錢的系統卻不同,涉及到金錢的是非常重要的,寧願犧牲A,也要保證C。如果出現機器故障的話,寧願停止服務。
沒有最好的策略,只有最符合當前系統的策略。
2.2、BASE理論
CAP 理論已經提出好多年了,難道真的沒有辦法解決這個問題嗎?也許可以做些改變。比如 C 不必使用那麼強的一致性,可以先將數據存起來,稍後再更新,實現所謂的 “最終一致性”。這個思路又是一個龐大的問題,同時也引出了第二個理論 BASE 理論。
BASE全稱爲 Basically Available(基本可用)、Soft state(軟狀態)和Eventually consistent(最終一致性)三個短語句的縮寫,來自 ebay 的架構師提出。
BASE 理論是對 CAP 中一致性和可用性權衡的結果,其來源於對大型互聯網分佈式實踐的總結,是基於 CAP 定理逐步演化而來的。其核心思想是:既然無法做到強一致性(Strong consistency),但每個應用都可以根據自身的業務特點,採用適當的方式來使系統達到最終一致性(Eventual consistency)。
2.2.1、Basically Available(基本可用)
基本可用是指分佈式系統在出現故障時,允許損失部分可用性(例如響應時間、功能上的可用性)。需要注意的是,基本可用絕不等價於系統不可用。
- 響應時間上的丟失:例如原先查詢數據只需要0.5秒,但是現在由於機器故障可以運行返回響應時間變爲1~2秒。
- 功能上的可用性:例如雙十一購物的時候,爲了滿足非常龐大的流量衝擊,爲了保證系統的大方向的穩定,允許對一些服務降級處理(縮減服務器集羣,給別的訪問量大的使用)或者停止一些非必要的服務。
2.2.2、Soft state(軟狀態)
相對於原子性而言,要求多個節點的數據副本都是一致的,這是一種 “硬狀態”。
軟狀態是指允許系統存在中間狀態,而該中間狀態不會影響系統整體可用性。分佈式存儲中一般一份數據會有多個副本,允許不同副本數據同步的延時就是軟狀態的體現。
2.2.3、Eventually consistent(最終一致性)
系統不可能一直是軟狀態,必須有個時間期限。在期限過後,應當保證所有副本保持數據一致性。從而達到數據的最終一致性。這個時間期限取決於網絡延時,系統負載,數據複製方案設計等等因素。
實際上,不只是分佈式系統使用最終一致性,關係型數據庫在某個功能上,也是使用最終一致性的,比如備份,數據庫的複製都是需要時間的,這個複製過程中,業務讀取到的值就是舊值。當然,最終還是達成了數據一致性。這也算是一個最終一致性的經典案例。
3、爲什麼需要註冊中心
在分佈式系統中,不僅僅是需要在註冊中心找到服務和服務地址的映射關係那麼簡單,還需要考慮很多的複雜問題:
- 服務註冊後,如何被及時的發現
- 服務宕機後,如何及時處理下線
- 服務如何有效的水平擴展
- 服務發現時,如何進行路由
- 服務異常時,如何進行降級
- 註冊溪紅心如果實現自身的高可用
這些問題的解決都依賴於註冊中心。簡單看,註冊中心的功能有點類似於 DNS 服務器或者負載均衡器,而實際上,註冊中心作爲微服務的基礎組件,可能要更加複雜,也需要更多的靈活性和時效性。當然上面的問題,單單使用註冊中心是無法完成的,還需要使用SpringCloud的其他組件共同完成。
註冊中心解決了一下問題:
- 服務管理
- 服務之間的自動發現
- 服務的依賴關係管理
4、Eureka
4.1、Eureka介紹
Eureka是Netflix開發的服務發現框架,本身是一個基於REST的服務,主要用於定位運行在AWS域中的中間層服務,以達到負載均衡和中間層服務故障轉移的目的。SpringCloud將它集成在其子項目spring-cloud-netflix中,以實現SpringCloud的服務發現功能。
4.2、Eureka註冊中心的各個角色
- **Eureka Server:**通過 Register、Get、Renew 等接口提供服務的註冊和發現。
- **Service Provider(Eureka Client):**服務提供方,把自身的服務實例註冊到 Eureka Server 中。
- **Service Consumer(Eureka Client):**服務調用方,通過 Eureka Server 獲取服務列表,消費服務。
4.3、Eureka入門案例
4.3.1、創建Mave聚合項目
- server01:註冊中心
- server02:註冊中心(後期搭建集羣版準備)
- provider:服務提供者
- consumer:服務消費者
4.3.2、添加依賴,配置文件
4.3.2.1、父項目
pom.xml
<!-- 繼承 spring-boot-starter-parent 依賴 -->
<!-- 使用繼承方式,實現複用,符合繼承的都可以被使用 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
</parent>
<!--
集中定義依賴組件版本號,但不引入,
在子工程中用到聲明的依賴時,可以不加依賴的版本號,
這樣可以統一管理工程中用到的依賴版本
-->
<properties>
<!-- Spring Cloud Hoxton.SR1 依賴 -->
<spring-cloud.version>Hoxton.SR5</spring-cloud.version>
</properties>
<!-- 項目依賴管理 父項目只是聲明依賴,子項目需要寫明需要的依賴(可以省略版本信息) -->
<dependencyManagement>
<dependencies>
<!-- spring cloud 依賴 -->
<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>
4.3.2.2、server02
pom.xml
<!-- 項目依賴 -->
<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>
application.yml
server:
port: 8761 # 端口
spring:
application:
name: eureka-server # 應用名稱
# 配置 Eureka Server 註冊中心
eureka:
instance:
hostname: localhost # 主機名,不配置的時候將根據操作系統的主機名來獲取
client:
register-with-eureka: false # 是否將自己註冊到註冊中心,默認爲 true
fetch-registry: false # 是否從註冊中心獲取服務註冊信息,默認爲 true
service-url: # 註冊中心對外暴露的註冊地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
如果是單節點需要把
register-with-eureka
和fetch-registry
設置爲false,否則會報錯
4.3.3、單節點啓動
package cn.yanghuisen;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @author admin
* @version 1.0
* @date 2020/6/15 15:16
* @Description TODO
*/
@SpringBootApplication
@EnableEurekaServer // 開啓EurekaServer註解
public class EurekaServer01Application {
public static void main(String[] args) {
SpringApplication.run(EurekaServer01Application.class);
}
}
訪問:http://localhost:8761/
4.3.4、高可用(集羣)
server02
pom.xml
<!-- 項目依賴 -->
<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>
<dependency>
<groupId>cn.yanghuisen</groupId>
<artifactId>eureka-server01</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
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/
server01:application.yml
server:
port: 8761 # 端口
spring:
application:
name: eureka-server # 應用名稱(集羣下相同)
# 配置 Eureka Server 註冊中心
eureka:
instance:
hostname: eureka01 # 主機名,不配置的時候將根據操作系統的主機名來獲取
client:
# 設置服務註冊中心地址,指向另一個註冊中心
service-url: # 註冊中心對外暴露的註冊地址
defaultZone: http://localhost:8762/eureka/
server02:啓動類
package cn.yanghuisen;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @author admin
* @version 1.0
* @date 2020/6/15 15:16
* @Description TODO
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer02Application {
public static void main(String[] args) {
SpringApplication.run(EurekaServer01Application.class);
}
}
訪問:http://localhost:8761/ 或者 http://localhost:8762/
4.3.5、顯示IP+端口
(server01和server02):application.yml
eureka:
instance:
prefer-ip-address: true # 是否使用 ip 地址註冊
instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
4.3.6、provider
pom.xml
<!-- 項目依賴 -->
<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>
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/
實體類
package cn.yanghuisen.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;
}
控制層
package cn.yanghuisen.controller;
import cn.yanghuisen.pojo.Product;
import cn.yanghuisen.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();
}
}
服務類:接口
package cn.yanghuisen.service;
import cn.yanghuisen.pojo.Product;
import java.util.List;
/**
* 商品服務
*/
public interface ProductService {
/**
* 查詢商品列表
*
* @return
*/
List<Product> selectProductList();
}
服務類:實現類
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)
);
}
}
啓動類
package cn.yanghuisen;
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);
}
}
訪問:http://localhost:8761/ 或者 http://localhost:8762/。查看服務提供者是否已經註冊到註冊中心
4.3.7、consumer
pom.xml
<!-- 項目依賴 -->
<dependencies>
<!-- netflix eureka client 依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</exclusion>
</exclusions>
</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>
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/
實體類
package cn.yanghuisen.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;
}
package cn.yanghuisen.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;
}
消費服務:接口
package cn.yanghuisen.service;
import cn.yanghuisen.pojo.Order;
public interface OrderService {
/**
* 根據主鍵查詢訂單
*
* @param id
* @return
*/
Order selectOrderById(Integer id);
}
控制層
package cn.yanghuisen.controller;
import cn.yanghuisen.pojo.Order;
import cn.yanghuisen.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);
}
}
消費服務:方式一
package cn.yanghuisen.service.impl;
import cn.yanghuisen.pojo.Order;
import cn.yanghuisen.pojo.Product;
import cn.yanghuisen.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();
}
}
消費服務:方式二
package cn.yanghuisen.service.impl;
import cn.yanghuisen.pojo.Order;
import cn.yanghuisen.pojo.Product;
import cn.yanghuisen.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.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();
}
}
消費服務:方式三
啓動類
@Bean
@LoadBalanced // 負載均衡註解
public RestTemplate restTemplate() {
return new RestTemplate();
}
package cn.yanghuisen.service.impl;
import cn.yanghuisen.pojo.Order;
import cn.yanghuisen.pojo.Product;
import cn.yanghuisen.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
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;
/**
* 根據主鍵查詢訂單
*
* @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();
}
}
訪問:http://localhost:9090/order/1
4.3.8、自我保護
一般情況下,服務在 Eureka 上註冊後,會每 30 秒發送心跳包,Eureka 通過心跳來判斷服務是否健康,同時會定期刪除超過 90 秒沒有發送心跳的服務。
有兩種情況會導致 Eureka Server 收不到微服務的心跳
- 微服務自身的原因
- 微服務與 Eureka 之間的網絡故障
自我保護模式
Eureka Server 在運行期間會去統計心跳失敗比例在 15 分鐘之內是否低於 85%,如果低於 85%,Eureka Server 會將這些實例保護起來,讓這些實例不會過期,同時提示一個警告。這種算法叫做 Eureka Server 的自我保護模式。
爲什麼要啓動自我保護
- 因爲同時保留"好數據"與"壞數據"總比丟掉任何數據要更好,當網絡故障恢復後,這個 Eureka 節點會退出"自我保護模式”。
- Eureka 還有客戶端緩存功能(也就是微服務的緩存功能)。即使 Eureka 集羣中所有節點都宕機失效,微服務的 Provider 和 Consumer 都能正常通信。
- 微服務的負載均衡策略會自動剔除死亡的微服務節點。
關閉自我保護
server01和server02:application.yml
eureka:
server:
enable-self-preservation: false # true:開啓自我保護模式,false:關閉自我保護模式
eviction-interval-timer-in-ms: 60000 # 清理間隔(單位:毫秒,默認是 60*1000)
4.3.9、優雅停服
配置了優雅停服以後,將不需要 Eureka Server 中配置關閉自我保護。本文使用 actuator 實現。
1、添加依賴
provider:pom.xml
<!-- spring boot actuator 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、配置文件
provider:application.yml
# 度量指標監控與健康檢查
management:
endpoints:
web:
exposure:
include: shutdown # 開啓 shutdown 端點訪問
endpoint:
shutdown:
enabled: true # 開啓 shutdown 實現優雅停服
使用 POST 請求訪問:http://localhost:7070/actuator/shutdown
4.3.10、Eureka安全認證
1、添加依賴
server01和server02:pom.xml
<!-- spring boot security 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、配置文件
server01和server02:application.yml
spring:
# 安全認證
security:
user:
name: root
password: 123456
3、修改訪問集羣節點的 url
server01和server02:application.yml
# 配置 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/
provider:application.yml
# 配置 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/
consumer:application.yml
# 配置 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/
4.3.11、過濾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
方法。
方案一:忽略/eureka/的所有請求
server01和server02:配置類
package cn.yanghuisen.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 cn.yanghuisen.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();
}
}