SpringCloud

来源:https://www.cnblogs.com/wzl66/archive/2023/12/17/17909844.html
-Advertisement-
Play Games

SpringCloud 文章推薦:Eureka:Spring Cloud服務註冊與發現組件(非常詳細) (biancheng.net) 概述 Spring Cloud 是一個服務治理平臺,是若幹個框架的集合,提供了全套的分散式系統解決方案。包含了:服務註冊與發現、配置中心、服務網關、智能路由、負載均 ...


SpringCloud

一篇文章搞懂 Spring Cloud 是什麼

文章推薦:Eureka:Spring Cloud服務註冊與發現組件(非常詳細) (biancheng.net)

概述

Spring Cloud 是一個服務治理平臺,是若幹個框架的集合,提供了全套的分散式系統解決方案。包含了:服務註冊與發現、配置中心、服務網關、智能路由、負載均衡、斷路器、監控跟蹤、分散式消息隊列等等。

Spring Cloud 通過 Spring Boot 風格的封裝,屏蔽掉了複雜的配置和實現原理,最終給開發者留出了一套簡單易懂、容易部署的分散式系統開發工具包。開發者可以快速的啟動服務或構建應用、同時能夠快速和雲平臺資源進行對接。微服務是可以獨立部署、水平擴展、獨立訪問(或者有獨立的資料庫)的服務單元,Spring Cloud 就是這些微服務的大管家,採用了微服務這種架構之後,項目的數量會非常多,Spring Cloud 做為大管家需要管理好這些微服務,自然需要很多小弟來幫忙。

SpringCloud常用組件表

服務的註冊和發現(eureka,nacos,consul)

服務的負載均衡(ribbon,dubbo)

服務的相互調用(openFeign,dubbo)

服務的容錯(hystrix,sentinel)

服務網關(gateway,zuul)

服務配置的統一管理(config-server,nacos,apollo)

服務消息匯流排(bus)

服務安全組件(security,Oauth2.0)

服務監控(admin)(jvm)

鏈路追綜(sleuth+zipkin)

SpringCloud Alibaba與SpringCloud Netflix對照

在這裡插入圖片描述

註冊和發現中心

Eureka快速入門

什麼是CAP原則?

Eureka和zookeeper的區別

CAP原則是指一個分散式系統中,一致性,可用性,分區容錯性

一致性:多個節點的數據保持一致。 (consistent)

可用性:當一個節點發生異常不可用之後,其他的節點任然可以提供服務。 available

分區容錯性:由於每個節點存在的機房或者是分區不一樣,存在數據傳輸時間的消耗,所以每個節點上的數據可能會短暫的不一致。 partition

CAP原則指的是,這三個要素最多只能同時存在實現兩個,不可能三者兼顧。但是每一個分散式系統中都會存在P原則,所以通常只會出現CP和AP組合。

Zookeeper:CP 註重的數據的一致性,當節點發生異常不可用時,可能會造成幾分鐘無法訪問

Eureka:AP 註重的時可可用,但是可能用戶訪問的數據存在一定的差異。

Eureka快速入門

搭建一個組測中心

image-20231204150321007

創建Eureka-server

導入相關的依賴,註意SpringCloud有相應對應的版本。不要隨意的搭配boot和Cloud的版本

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.12.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<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-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <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>

書寫application.yml配置文件

server:
  port: 8761 #預設埠

spring:
  application:
    name: eureka-server  #之前我們很少的指定過模塊的名稱,這裡通常指定。

在啟動類上開啟@EnableEurekaServer //開啟Euraka註冊中心的功能

訪問localhost:8761 表示Eureka-server註冊成功

image-20231204160819473

創建Eureka客戶端

導入依賴

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.12.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
    <java.version>17</java.version>
    <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <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-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <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>

改寫配置文件:

server:
  port: 8081

spring:
  application:
    name: eureka-client-a
#客戶端需要將自己的信息註冊(告訴)伺服器(server)
#發送到哪?
eureka:
  client:
    service-url:  #指定註冊地址
      defaultZone: http://localhost:8761/eureka

啟動類上開啟服務@EnableEurekaClient,

image-20231204164010035

如果我們想將同一臺實列註冊多次,只需要修改配置文件中埠就行了。

Eureka配置文件介紹

瞭解配置文件,我們需要知道Eureka-server需要做一些什麼?

作為註冊中心(房東)

  1. 需要有一個服務列表(容器)保存註冊的應用的信息。
  2. 需要知道自己的下麵註冊的應用是否還線上(心跳機制)
  3. 應用A和應用B需要聯繫,不能總是的通過Eureka-serve實現聯繫,需要在應用的本地保存一份註冊列表。這時需要考慮是否可以容忍臟讀。
  4. 如果某段時間內有大量的應用沒有聯繫自己,這時Eureka-server會認為自己出現了問題。體現Ap原則

Eureka-server配置文件實列

# eureka-server的配置文件主要分為三類: server類  client類 instance類
eureka:
  server:
    eviction-interval-timer-in-ms: 10000 #服務間隔多少時間之後進行一個刪除操作,當一個應用在該時間之內沒有與伺服器進行聯繫的話,伺服器會認為應用已經下線
    renewal-percent-threshold: 0.85 #續約百分比,當超過規定範圍的應用沒有聯繫伺服器時,伺服器會認為自己出現了問題。
  instance:
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #實列id
    prefer-ip-address: true #以ip形式顯示具體的服務信息
    lease-renewal-interval-in-seconds: 5 #指示 eureka 客戶端需要多久(以秒為單位)向 eureka 伺服器發送檢測信號,這個時間應該小於eviction-interval-timer-in-ms

Eureka-client配置文件

#客戶端需要將自己的信息註冊(告訴)伺服器(server)
#發送到哪?
eureka:
  client:
    service-url:  #指定註冊地址
      defaultZone: http://localhost:8761/eureka
    register-with-eureka: true #是否註冊到註冊中心上去
    fetch-registry: true #應用是否拉取服務列表到本地
    registry-fetch-interval-seconds: 10 #間隔多少時間拉取列表到本地
  instance:
    lease-renewal-interval-in-seconds: 5
    hostname: localhost
    prefer-ip-address: true
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

分散式系統一致性演算法

在我們分散式系統中,存在多個系統之間實現數據的集群,採用CP一致性演算法保證每個節點數據的一致性的問題。比如Eureka、Zookeeper、Nacos實現集群都必須保證每個節點數據同步性的問題。

Zookeeper基於ZAP協議實現保證每個節點數據同步的問題,中心化思想集群模式。分為領導和跟隨者角色。(主從模式)

Eureka基於AP模式實現註冊中心,去中心化的思想、每個節點都是對等的,採用你中有我,我中有你的形式實現註冊中心。

常見分散式一致性演算法:

  1. ZAP協議(底層就是基於Paxos實現),核心底層基於2PC兩階段提交協議實現。
  2. Nacos中集群保證一致性演算法採ratf協議模式,採用心跳機制實現選舉的。Raft (thesecretlivesofdata.com)
  3. Eureka沒有分散式數據一致性的機制 節點都是相同的

Eureka運行的理解

Eureka服務註冊、下線、續約、剝離都是註冊列表的CRUD

Eureka註冊

Eureka客戶端的register源碼解釋:

boolean register() throws Throwable {
    logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
    EurekaHttpResponse<Void> httpResponse;
    try {
        //註冊的主要實現調用,instanceInfo讀取配置文件配置項(ip,port,hostname)
        httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
    } catch (Exception e) {
        logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);
        throw e;
    }
    if (logger.isInfoEnabled()) {
        logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());
    }
    return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
}

eurekaTransport.registrationClient.register(instanceInfo);調用register解釋

@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
    String urlPath = "apps/" + info.getAppName();
    ClientResponse response = null;
    try {
        Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
        addExtraHeaders(resourceBuilder);
        //發送post請求給urlPath地址,restFul風格post表示增加
        response = resourceBuilder
            .header("Accept-Encoding", "gzip")
            .type(MediaType.APPLICATION_JSON_TYPE)
            .accept(MediaType.APPLICATION_JSON)
            .post(ClientResponse.class, info);
        return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
    } finally {
        if (logger.isDebugEnabled()) {
            logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
                         response == null ? "N/A" : response.getStatus());
        }
        if (response != null) {
            response.close();
        }
    }
}

客服端發送post請求之後,那麼服務端是如何處理保存Client信息的

註冊表結構的第一個Key是應用名稱(全大寫) spring.application.name

Value中的key是應用的實例id Eureka.instance.instance-id

Value中的value是具體的服務節點信息

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
    = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
    read.lock();
    try {
        Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
        REGISTER.increment(isReplication);
        if (gMap == null) {
            final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
            gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
            if (gMap == null) {
                gMap = gNewMap;
            }
        }
        Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
        ……………………………………………………………………………………………………………………………………………………………………
    } finally {
        read.unlock();
    }
}
Eureka續約
@Override
public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
    String urlPath = "apps/" + appName + '/' + id;
    ClientResponse response = null;
    try {
        WebResource webResource = jerseyClient.resource(serviceUrl)
            .path(urlPath)
            .queryParam("status", info.getStatus().toString())
            .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString());
        if (overriddenStatus != null) {
            webResource = webResource.queryParam("overriddenstatus", overriddenStatus.name());
        }
        Builder requestBuilder = webResource.getRequestBuilder();
        addExtraHeaders(requestBuilder);
        //發送put請求給伺服器端
        response = requestBuilder.put(ClientResponse.class);
        EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response));
        if (response.hasEntity() &&
            !HTML.equals(response.getType().getSubtype())) { //don't try and deserialize random html errors from the server
            eurekaResponseBuilder.entity(response.getEntity(InstanceInfo.class));
        }
        return eurekaResponseBuilder.build();
    } finally {
        if (logger.isDebugEnabled()) {
            logger.debug("Jersey HTTP PUT {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
        }
        if (response != null) {
            response.close();
        }
    }
}

Eureka的剝除也是一樣發送相應的delete請求給伺服器端。

服務發現

什麼是服務發現?
通過服務名稱獲取到服務具體實例的過程。

@GetMapping("/test")
public String doDiscovery(String serverName){
    //通過服務名稱獲取相應的服務實例
    //註意返回值是一個list集合類型,因為一個實例名稱可以開啟多個實列,實現一個集群的實現
    List<ServiceInstance> instances = discoveryClient.getInstances(serverName);
    instances.forEach(System.out::println);
    ServiceInstance serviceInstance = instances.get(0);
    String host = serviceInstance.getHost();
    int port = serviceInstance.getPort();
    String url="http://"+host+port;
    //使用restTemplate發送Http請求給url就可以了
    return serviceInstance.toString();
}

返回結果:從中我們可以通過服務名稱獲取到服務的埠和hostName,通過字元串的拼接就可以實現發送Http請求給相應的地址,只需知道相應API的功能就可以了。

[EurekaDiscoveryClient.EurekaServiceInstance@3aa089fe instance = InstanceInfo [instanceId = localhost:eureka-client-b:8082, appName = EUREKA-CLIENT-B, hostName = 192.168.117.1, status = UP, ipAddr = 192.168.117.1, port = 8082, securePort = 443, dataCenterInfo = com.netflix.appinfo.MyDataCenterInfo@276b4a2]

RestTemplate

RestTemplateApi介紹

void RestTemplateApi(){
    String url="";
    String data="";
    //Entity  將會返回消息的完整消息(包括狀態碼,相應的結果)
    //Object  只會返回相應的結果
    restTemplate.getForEntity(url,String.class);
    restTemplate.getForObject(url,String.class);
    restTemplate.postForEntity(url,data,String.class);
    restTemplate.postForObject(url,data,String.class);
    restTemplate.put(url,String.class);
    restTemplate.delete(url);
}

ribbon

負載均衡

在任何一個系統中,負載均衡都是一個十分重要且不得不去實施的內容,它是系統處理高併發、緩解網路壓力和服務端擴容的重要手段之一。

負載均衡(Load Balance) ,簡單點說就是將用戶的請求平攤分配到多個伺服器上運行,以達到擴展伺服器帶寬、增強數據處理能力、增加吞吐量、提高網路的可用性和靈活性的目的。

常見的負載均衡演算法:

  1. 輪詢分發
  2. 固定ip
  3. 隨機分發
  4. 權重分發
  5. Hash

常見的負載均衡方式有兩種:

  • 服務端負載均衡
  • 客戶端負載均衡

服務端負載均衡

服務端負載均衡是最常見的負載均衡方式,其工作原理如下圖。

服務端負載均衡工作原理

客戶端負載均衡

相較於服務端負載均衡,客戶端服務在均衡則是一個比較小眾的概念。

客戶端負載均衡的工作原理如下圖。

客戶端負載均衡原理

客戶端負載均衡是將負載均衡邏輯以代碼的形式封裝到客戶端上,即負載均衡器位於客戶端。客戶端通過服務註冊中心(例如 Eureka Server)獲取到一份服務端提供的可用服務清單。有了服務清單後,負載均衡器會在客戶端發送請求前通過負載均衡演算法選擇一個服務端實例再進行訪問,以達到負載均衡的目的

客戶端負載均衡具有以下特點:

  • 負載均衡器位於客戶端,不需要單獨搭建一個負載均衡伺服器。
  • 負載均衡是在客戶端發送請求前進行的,因此客戶端清楚地知道是哪個服務端提供的服務。
  • 客戶端都維護了一份可用服務清單,而這份清單都是從服務註冊中心獲取的。

Ribbon入門

<!--Spring Cloud Ribbon 依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

使用Ribbon+RestTemplate

@Bean //將 RestTemplate 註入到容器中
@LoadBalanced //在客戶端使用 RestTemplate 請求服務端時,開啟負載均衡(Ribbon)
public RestTemplate restTemplate() {
    return new RestTemplate();
}

使用LoadBalancerClient

1.註入LoadBalancerClient

@Autowired
private LoadBalancerClient loadBalancerClient;

2.調用loadBalancerClient或者加了 @LoadBalanced註解的RestTemplate

@GetMapping("/ribbon")
public URI ribbon(){
    // 通過地址自動負載均衡
    ServiceInstance choose = loadBalancerClient.choose("provider");
    return choose.getUri();
}

在consumer發送發送請求給provider時只需要將原來的URL中的IP使用應用名稱來代替。

Ribbon為我們所做的事情

  • 將我們的請求攔截。獲取當中的服務名稱。
  • 通過服務名稱結合Eureka的服務發現,獲取到服務列表
  • 通過服務列表使用負載均衡演算法,獲取相應的ip和port
  • 重構URL
  • 發送Http請求

Ribbon 實現負載均衡

Ribbon 是一個客戶端的負載均衡器,它可以與 Eureka 配合使用輕鬆地實現客戶端的負載均衡。Ribbon 會先從 Eureka Server(服務註冊中心)去獲取服務端列表,然後通過負載均衡策略將請求分攤給多個服務端,從而達到負載均衡的目的。

Spring Cloud Ribbon 提供了一個 IRule 介面,該介面主要用來定義負載均衡策略,它有 7 個預設實現類,每一個實現類都是一種負載均衡策略。

序號 實現類 負載均衡策略
1 RoundRobinRule 按照線性輪詢策略,即按照一定的順序依次選取服務實例
2 RandomRule 隨機選取一個服務實例
3 RetryRule 按照 RoundRobinRule(輪詢)的策略來獲取服務,如果獲取的服務實例為 null 或已經失效,則在指定的時間之內不斷地進行重試(重試時獲取服務的策略還是 RoundRobinRule 中定義的策略),如果超過指定時間依然沒獲取到服務實例則返回 null 。
4 WeightedResponseTimeRule WeightedResponseTimeRule 是 RoundRobinRule 的一個子類,它對 RoundRobinRule 的功能進行了擴展。 根據平均響應時間,來計算所有服務實例的權重,響應時間越短的服務實例權重越高,被選中的概率越大。剛啟動時,如果統計信息不足,則使用線性輪詢策略,等信息足夠時,再切換到 WeightedResponseTimeRule。
5 BestAvailableRule 繼承自 ClientConfigEnabledRoundRobinRule。先過濾點故障或失效的服務實例,然後再選擇併發量最小的服務實例。
6 AvailabilityFilteringRule 先過濾掉故障或失效的服務實例,然後再選擇併發量較小的服務實例。
7 ZoneAvoidanceRule 預設的負載均衡策略,綜合判斷服務所在區域(zone)的性能和服務(server)的可用性,來選擇服務實例。在沒有區域的環境下,該策略與輪詢(RandomRule)策略類似。

探究一些ZoneAvoidanceRule,通過調用父類PredicateBasedRule的Choose函數,服務數量進行一個取模運算。

為了保持線程安全,Ribbon使用CAS,和原子類保證了線程的安全。

private int incrementAndGetModulo(int modulo) {
    for (;;) {
        int current = nextIndex.get();
        int next = (current + 1) % modulo;
        if (nextIndex.compareAndSet(current, next) && current < modulo)
            return current;
    }
}

更改負載均衡演算法

指定不同的服務使用不同的負載均衡演算法
#訪問不同的服務可以使用不同的演算法規則
provider:  #先寫服務提供者的應用名稱
  rabbion:
     NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule 
更改全局的負載均衡演算法
@Bean
public IRule myRule(){
    return new RandomRule();
}

由於IRule是一個介面,所以我可以通過實現這個介面,自定義自己的負載均衡演算法。

OpenFeign:Spring Cloud聲明式服務調用組件

OpenFeign是一個Web聲明式的Http客戶端調用工具,提供介面和註解形式調用

OpenFeign是一個聲明式RESTful網路請求客戶端。OpenFeign會根據帶有註解的函數信息構建出網路請求的模板,在發送網路請求之前,OpenFeign會將函數的參數值設置到這些請求模板中。雖然OpenFeign只能支持基於文本的網路請求,但是它可以極大簡化網路請求的實現,方便編程人員快速構建自己的網路請求應用

OpenFeign 常用註解

使用 OpenFegin 進行遠程服務調用時,常用註解如下表。

註解 說明
@FeignClient 該註解用於通知 OpenFeign 組件對 @RequestMapping 註解下的介面進行解析,並通過動態代理的方式產生實現類,實現負載均衡和服務調用。
@EnableFeignClients 該註解用於開啟 OpenFeign 功能,當 Spring Cloud 應用啟動時,OpenFeign 會掃描標有 @FeignClient 註解的介面,生成代理並註冊到 Spring 容器中。
@RequestMapping Spring MVC 註解,在 Spring MVC 中使用該註解映射請求,通過它來指定控制器(Controller)可以處理哪些 URL 請求,相當於 Servlet 中 web.xml 的配置。
@GetMapping Spring MVC 註解,用來映射 GET 請求,它是一個組合註解,相當於 @RequestMapping(method = RequestMethod.GET) 。
@PostMapping Spring MVC 註解,用來映射 POST 請求,它是一個組合註解,相當於 @RequestMapping(method = RequestMethod.POST) 。

OpenFeign的入門

<!--添加 OpenFeign 依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

開啟OpenFeign服務@EnableFeignClients

簡單應用

使用OpenFeign,UserServer遠程調用OrderServer中DoOrder介面

OrderServer

@RestController
public class OrderController {
    @GetMapping("/DoOrder")
    public String DoOrder(){
        return  "用戶下了訂單";
    }
}

在UserServer中需要定義一個介面

@FeignClient(value = "eureka-client-a") //服務名稱
@Component
public interface UserOrderFeign {
    //需要遠程調用服務方法的方法簽名
    @GetMapping("/DoOrder")
    public String DoOrder();
}
@RestController
public class UserDoOrder {
    @Autowired
    private UserOrderFeign userOrderFeign;
    @GetMapping("/UserDoOrder")
    public String UserDoOrder(){
        return userOrderFeign.DoOrder();
    }
}

註意點:如果在我們請求訂單模塊時,訂單模塊需要對資料庫進行操作,可能會比較的消耗時間。那麼我們的UserOrder會不會出現超時異常,通過實驗,結果是UserOrder會拋出一個超時異常。

修改OpenFeign遠程調用的超時時間

由於OpenFeign的原理是將Ribbon進行一個封裝,所以如果我們希望修改OpenFeign的超時時間的話,其實質是修改Ribbon的配置。預設超時時間預設是1S

ribbon:
  ReadTimeout: 3000 #訪問超時時間
  ConnectTimeout: 3000 #連接超時時間

OpenFeign核心探索

OPenFeign使用一個註解就可以實現遠程調用是如何做到

可以猜測這個介面一個會議代理對象,我們知道只有兩種代理的方式(JDK動態代理,cglib動態代理)

JDK動態代理為介面創建代理實例

CGLIB通過繼承方式實現代理

所以OPenFeign一定是採用的是JDK動態代理生成代理對象的

   @Test
void contextLoads() {
    UserOrderFeign o = (UserOrderFeign)Proxy.newProxyInstance(EurekaClientBApplication.class.getClassLoader(), new Class[]{UserOrderFeign.class}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            /*
                * 通過代理獲取到方法註解上的API名稱
                * 通過獲取介面上方的FeignClient()中的Value,應用的名稱,結合上Eureka的方法發現
                * 通過拼接URL,使用Ribbon向URL介面發送請求就可以實現遠程調用
                * */
            //獲取API
            GetMapping annotation = method.getAnnotation(GetMapping.class);
            String[] Api = annotation.value();
            String API = Api[0];
            //獲取應用名稱
            Class<?> aClass = method.getDeclaringClass();
            FeignClient feignClient = aClass.getAnnotation(FeignClient.class);
            String appName = feignClient.value();
            //拼接URL
            String url="http://"+appName+"/"+API;
            //使用Ribbon發送請求實現負載均衡
            String forObject = restTemplate.getForObject(url, String.class);
            return forObject;
        }
    });
    //使用JDK動態代理所以一定會調用invoke方法,類似與AOP機制
    String s = o.DoOrder();
    System.out.println(s);
}

OpenFeign 日誌增強

Logger.Level 的具體級別如下:

  • NONE:不記錄任何信息。
  • BASIC:僅記錄請求方法、URL 以及響應狀態碼和執行時間。
  • HEADERS:除了記錄 BASIC 級別的信息外,還會記錄請求和響應的頭信息。
  • FULL:記錄所有請求與響應的明細,包括頭信息、請求體、元數據等等。
  /**
   * Controls the level of logging.
   */
  public enum Level {
    /**
     * No logging.
     */
    NONE,
    /**
     * Log only the request method and URL and the response status code and execution time.
     */
    BASIC,
    /**
     * Log the basic information along with request and response headers.
     */
    HEADERS,
    /**
     * Log the headers, body, and metadata for both requests and responses.
     */
    FULL
  }

在配置文件中開啟介面的日誌級別

logging:
  level:
    #feign 日誌以什麼樣的級別監控該介面
    net.biancheng.c.service.DeptFeignService: debug
@Bean
public Logger.Level level(){
    return Logger.Level.FULL;
}

Hystrix:Spring Cloud服務熔斷與降級組件

服務雪崩

在一個微服務系統中,我們的一個服務可能是一個鏈式調用的,A->B->C如果C發生了宕機的話,那麼A,B中的線程只會在等待超時之後才會將線程回收,在併發量很大的時候,就會出現服務線程無法及時的回收,導致整個伺服器出現崩潰。

image-20231212123227726

解決方案:

  • 調整我們的超時時間,將超時時間縮短就可以做到及時的回收我們的線程。缺點如果伺服器中本身就存在服務耗時比較長,這種方式就會影響到我們正常的服務了。
  • 如果我們上層的服務知道下層的服務已經發生了宕機的話,那麼我之後的請求就可以直接的返回,不需要等待超時時間。及時回收線程。

這種方式並不是保證服務的正常服務的,知道保證當存在一個服務宕機時,緩解伺服器壓力。

熔斷器

熔斷器(Circuit Breaker)一詞來源物理學中的電路知識,它的作用是當線路出現故障時,迅速切斷電源以保護電路的安全。

與物理學中的熔斷器作用相似,微服務架構中的熔斷器能夠在某個服務發生故障後,向服務調用方返回一個符合預期的、可處理的降級響應(FallBack),而不是長時間的等待或者拋出調用方無法處理的異常。這樣就保證了服務調用方的線程不會被長時間、不必要地占用,避免故障在微服務系統中的蔓延,防止系統雪崩效應的發生

Hystrix的基本使用

導入依賴

<!--hystrix 依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

書寫一個Hystrix實現類作為超時失敗後的備選方案

@Component
public class UserOrderHystrix implements UserOrderFeign {
    @Override
    public String DoOrder() {
        return "我是服務超時時的備選方案";
    }
}

當然只有經過了遠程調用的方法才會需要一個熔斷所以需要在Feign介面上面指定失敗的回調類是什麼

@FeignClient(value = "eureka-client-a",fallback = UserOrderHystrix.class) //服務名稱
public interface UserOrderFeign {
    @GetMapping("/DoOrder")
    public String DoOrder();
}

還需要開啟Hystrix服務

@EnableHystrix

feign:
  hystrix:
    enabled: true #在SpringCloud F版本之前是預設開啟的。

hystrix的配置文件

隔離級別預設是使用thread

thread消費者會為每個提供者分配好線程(預設是10個),

優點: 每個線程都有自己的線程組,高度隔離,互不影響

缺點:存線上程的切換,效率比較低

場景:併發量比較大的場景

Semaphore:

優點:不會存線上程的切換,效率比較高。

缺點:但是提供者之間存在影響。

併發量比較小,內部調用。

hystrix: #hystrix的全局控制
  command:
    default: #default是全局控制,也可以換成的單個方法控制,把default換成方法名
      circuitBreaker:
        enabled: true #開啟短路器
        requestVolumeThreshold: 3 #失敗次數(閾值) 10次
        sleepWindowInMilliseconds: 20000 #視窗時間
        errorThresholdPercentage: 60 #失敗率
      execution:
        isolation:
          Strategy: thread #隔離方式thread線程隔離集合和semaphore信號量隔離
          thread:
            timeoutInMilliseconds: 3000 #調用超時時長
      fallback:
        isolation:
          semaphore:
            maxConcurrentRequests: 1000 #信號量隔離級別最大併發數

上面是全部配置。

摸一個方法配置,在方法使用

@HystrixCommand(fallbackMethod = "deptCircuitBreaker_fallback", commandProperties = {
        //以下參數在 HystrixCommandProperties 類中有預設配置
        @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), //是否開啟熔斷器
    @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "1000"), //統計時間窗
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), //統計時間窗內請求次數
        @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), //休眠時間視窗期
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), //在統計時間視窗期以內,請求失敗率達到 60% 時進入熔斷狀態
})

Hystrix 服務熔斷

熔斷機制是為了應對雪崩效應而出現的一種微服務鏈路保護機制。

當微服務系統中的某個微服務不可用或響應時間太長時,為了保護系統的整體可用性,熔斷器會暫時切斷請求對該服務的調用,並快速返回一個友好的錯誤響應。這種熔斷狀態不是永久的,在經歷了一定的時間後,熔斷器會再次檢測該微服務是否恢復正常,若服務恢復正常則恢復其調用鏈路。

熔斷狀態

在熔斷機制中涉及了三種熔斷狀態:

  • 熔斷關閉狀態(Closed):當服務訪問正常時,熔斷器處於關閉狀態,服務調用方可以正常地對服務進行調用。
  • 熔斷開啟狀態(Open):預設情況下,在固定時間內介面調用出錯比率達到一個閾值(例如 50%),熔斷器會進入熔斷開啟狀態。進入熔斷狀態後,後續對該服務的調用都會被切斷,熔斷器會執行本地的降級(FallBack)方法。
  • 半熔斷狀態(Half-Open): 在熔斷開啟一段時間之後,熔斷器會進入半熔斷狀態。在半熔斷狀態下,熔斷器會嘗試恢復服務調用方對服務的調用,允許部分請求調用該服務,並監控其調用成功率。如果成功率達到預期,則說明服務已恢復正常,熔斷器進入關閉狀態;如果成功率仍舊很低,則重新進入熔斷開啟狀態。

三種熔斷狀態之間的轉化關係如下圖:

熔斷狀態轉換

Hystrix 實現熔斷機制

在 Spring Cloud 中,熔斷機制是通過 Hystrix 實現的。Hystrix 會監控微服務間調用的狀況,當失敗調用到一定比例時(例如 5 秒內失敗 20 次),就會啟動熔斷機制。

Hystrix 實現服務熔斷的步驟如下:

  1. 當服務的調用出錯率達到或超過 Hystix 規定的比率(預設為 50%)後,熔斷器進入熔斷開啟狀態。
  2. 熔斷器進入熔斷開啟狀態後,Hystrix 會啟動一個休眠時間窗,在這個時間窗內,該服務的降級邏輯會臨時充當業務主邏輯,而原來的業務主邏輯不可用。
  3. 當有請求再次調用該服務時,會直接調用降級邏輯快速地返回失敗響應,以避免系統雪崩。
  4. 當休眠時間窗到期後,Hystrix 會進入半熔斷轉態,允許部分請求對服務原來的主業務邏輯進行調用,並監控其調用成功率。
  5. 如果調用成功率達到預期,則說明服務已恢復正常,Hystrix 進入熔斷關閉狀態,服務原來的主業務邏輯恢復;否則 Hystrix 重新進入熔斷開啟狀態,休眠時間視窗重新計時,繼續重覆第 2 到第 5 步。

第一個微服務架構

image-20231212192629124

image-20231212192642964

image-20231212193207679

sleuth:鏈路追蹤

什麼是鏈路追蹤

單純的理解鏈路追蹤,就是指一次任務的開始到結束,期間調用的所有系統及耗時(時間跨度)都可以完整記錄下來。

zipkin

Zipkin是Twitter開源的調用鏈分析工具,目前基於springcloud sleuth得到了廣泛的使用,特點是輕量,使用部署簡單。用於展示鏈路情況。

基本使用

導入sleuth依賴:因為sleth需要記錄沒有一次的調用情況,所以所有的consumer-server和provider-server基本需要導入依賴。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

zipkin下載

Spring Cloud Edgware 版本之後,改為強制採用官方提供的 Jar 包的形式啟動。
下載地址:https://repo1.maven.org/maven2/io/zipkin/zipkin-server/

zipkin啟動

image-20231213164006133

添加配置

spring:
  application:
    name: user-service
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      probability: 1 #配置採樣率 預設的採樣比例為:0.1,即10%,所設置的值介於0 到 1,1表示會全部採集
      rate: 10 #為了使用速率限制採樣器,選擇每秒間隔接受trace量,最小數字為0

將所有的項目啟動之後訪問http://127.0.0.1:9411/

Admin監控

功能

Spring Boot Admin提供了很多服務治理方面的功能,利用它能節省我們很多在治理服務方面的時間和精力Spring Boot Admin提供瞭如下功能(包括但不限於):

  • 顯示健康狀態及詳細信息,如JVM和記憶體指標、數據源指標、緩存指標
  • 跟蹤並下載日誌文件
  • 查看jvm系統-和環境屬性
  • 查看Spring啟動配置屬性方便loglevel管理
  • 查看線程轉儲視圖http-traces
  • 查看http端點查看計劃任務
  • 查看和刪除活動會話(使用spring-session)
  • 狀態更改通知(通過電子郵件、Slack、Hipchat…)
  • 狀態變化的事件日誌(非持久性)
  • 下載 heapdump
  • 查看 Spring Boot 配置屬性
  • 支持 Spring Cloud 的環境端點和刷新端點
  • 支持 K8s
  • 易用的日誌級別管理
  • 與JMX-beans交互
  • 查看線程轉儲
  • 查看http跟蹤
  • 查看auditevents
  • 查看http-endpoints
  • 查看計劃任務
  • 查看和刪除活動會話(使用 Spring Session )
  • 查看Flyway/Liquibase資料庫遷移
  • 狀態變更通知(通過電子郵件,Slack,Hipchat等,支持釘釘)
  • 狀態更改的事件日誌(非持久化)

用於管理和監視您的Spring Boot®應用程式。這些應用程式在我們的Spring Boot Admin Client中註冊(通過HTTP),或者是通過Spring Cloud®(例如Eureka,Consul)發現的。 UI只是Spring Boot Actuator端點之上的Vue.js應用程式。

使用

admin存在連個端:一個service端一個是client端,在springBoot項目中使用 Spring Boot Admin,賊好使! - 掘金 (juejin.cn)

在SpringCloud項目中使用可以結合Eureka獲取到所有服務的信息

image-20231213174837421

配置開放所有監控項

# 開啟監控所有項
management:
  endpoints:
         web:
           exposure:
                includem: "*"

註意這個配置並不是springBoot自帶的,而是actuator依賴帶的,我們需要在相應的server服務導入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

最終效果

image-20231213185557060

Gateway:Spring Cloud API網關組件

Gateway:Spring Cloud API網關組件(非常詳細) (biancheng.net)

在微服務架構中,一個系統往往由多個微服務組成,而這些服務可能部署在不同機房、不同地區、不同功能變數名稱下。這種情況下,客戶端(例如瀏覽器、手機、軟體工具等)想要直接請求這些服務,就需要知道它們具體的地址信息,例如 IP 地址、埠號等。

API 網關是一個搭建在客戶端和微服務之間的服務,我們可以在 API 網關中處理一些非業務功能的邏輯,例如許可權驗證、監控、緩存、請求路由等。

API 網關就像整個微服務系統的門面一樣,是系統對外的唯一入口。有了它,客戶端會先將請求發送到 API 網關,然後由 API 網關根據請求的標識信息將請求轉發到微服務實例。

img

所以可以猜測一下網關的功能應該需要有哪些

  1. 路由轉發,所有的請求全部需要在網關這通過相應的方式找到具體的API,轉發到具體的服務上。
  2. 安全方面,不需要直接將微服務裡面的服務埠暴露出去。
  3. 負載均衡。一個服務如果有多台機器運行,那麼我們需要負載均衡一下,緩解伺服器壓力。

作用:就是可以實現用戶的驗證登陸、解決跨域、日誌攔截、許可權控制、限流熔斷、負載均衡、黑名單和白名單機制等。

Zuul與GateWay有那些區別

Zuul網關屬於NetFix公司開源框架,屬於第一代微服務網關

GateWay屬於SpringCloud自己研發的網關框架,屬於第二代微服務網關。相比來說GateWay比Zuul網關的性能要好很多。

Zuul 1.0網關底層基於Servlet實現,阻塞式(BIO)api,不支持長連接

Zuul2.0 NIO

SpringBoot-WebSpringCloudGateWay基於Spring5構建,能夠實現響應式非阻塞式(NIO)api,支持長連接,能夠更好的支持Spring體系產品,依賴SpringBoot-WebFux

springCloud沒有集成和支持Zuul2.0

SpringCloudGateway是基於webFlux框架實現的,而webFlux框架底層則使用了高性能的Reactor模式通信框架的Netty

網關服務的埠號一般多少:80或者443

Spring Cloud Gateway 核心概念

Spring Cloud GateWay 最主要的功能就是路由轉發,而在定義轉發規則時主要涉及了以下三個核心概念,如下表。

核心概念 描述
Route(路由) 網關最基本的模塊。它由一個 ID、一個目標 URI、一組斷言(Predicate)和一組過濾器(Filter)組成。
Predicate(斷言) 路由轉發的判斷條件,我們可以通過 Predicate 對 HTTP 請求進行匹配,例如請求方式、請求路徑、請求頭、參數等,如果請求與斷言匹配成功,則將請求轉發到相應的服務。
Filter(過濾器) 過濾器,我們可以使用它對請求進行攔截和修改,還可以使用它對上文的響應進行再處理。

Gateway 的工作流程

Spring Cloud Gateway 工作流程如下圖。

Spring Cloud Gateway 工作流程

Spring Cloud Gateway 工作流程說明如下:

  1. 客戶端將請求發送到 Spring Cloud Gateway 上。
  2. Spring Cloud Gateway 通過 Gateway Handler Mapping 找到與請求相匹配的路由,將其發送給 Gateway Web Handler。
  3. Gateway Web Handler 通過指定的過濾器鏈(Filter Chain),將請求轉發到實際的服務節點中,執行業務邏輯返迴響應結果。
  4. 過濾器之間用虛線分開是因為過濾器可能會在轉發請求之前(pre)或之後(post)執行業務邏輯。
  5. 過濾器(Filter)可以在請求被轉發到服務端前,對請求進行攔截和修改,例如參數校驗、許可權校驗、流量監控、日誌輸出以及協議轉換等。
  6. 過濾器可以在響應返回客戶端之前,對響應進行攔截和再處理,例如修改響應內容或響應頭、日誌輸出、流量監控等。
  7. 響應原路返回給客戶端。

Nginx和Gateway區別

image-20231214125132137

相同點:

都是可以實現api的攔截,負載均衡、反向代理、請求過濾,可以完全和網關實現一樣的效果。

不同點:

Nginx性能好,併發量在30000到50000,使用C+lua編寫。

GateWay,性能較差,併發量在1000,使用java編寫

GateWay入門

導入依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

註意的當導入了GateWay的依賴之後,就不能導入spring-boot-starter-web依賴了,因為web預設的伺服器是tomcat,而GataWay的伺服器是Netty。

GateWay配置文件

server:
  port: 80
spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      enabled: true #只要添加了依賴預設開啟
      routes:
        - id: user-server-route
          uri: http://localhost:88
          predicates:
            - Path=/UserDoOrder

通過代碼的方式實現路由

package com.zl.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GateWayConfig {
    /*
    * 代碼實現和yml實現可以一起使用
    * */
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder){
        return builder.routes()
                .route("dance-id",r->r.path("/v/dance").uri("https://www.bilibili.com"))
                .build();
    }
}

Spring Cloud Gateway 動態路由

預設情況下,Spring Cloud Gateway 會根據服務註冊中心(例如 Eureka Server)中維護的服務列表,以服務名(spring.application.name)作為路徑創建動態路由進行轉發,從而實現動態路由功能。

我們可以在配置文件中,將 Route 的 uri 地址修改為以下形式。

lb://service-name

以上配置說明如下:

  • lb:uri 的協議,表示開啟 Spring Cloud Gateway 的負載均衡功能。
  • service-name:服務名,Spring Cloud Gateway 會根據它獲取到具體的微服務地址。
server:
  port: 81
spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      enabled: true #只要添加了依賴預設開啟
      routes:
        - id: user-server-route
          uri: lb://user-service  #使用lb:將會實現一個負載均衡的效果。
          predicates:
            - Path=/UserDoOrder
      discovery:
        locator:
          enabled: true #開啟動態路由,但是在訪問API實現,需要在前面添加/應用名稱/APi
          lower-case-service-id: true #開啟服務名稱小寫
# 需要將GateWay服務註冊到Eureka服務上面去,因為GateWay需要通過拉取服務列表,結合服務發現實現動態路由的效果。
eureka:
  client:
    service-url:  #指定註冊地址
      defaultZone: http://localhost:8761/eureka
    register-with-eureka: true #是否註冊到註冊中心上去
    fetch-registry: true #應用是否拉取服務列表到本地
    registry-fetch-interval-seconds: 3 #間隔多少時間拉取列表到本地
  instance:
    lease-renewal-interval-in-seconds: 5
    hostname: localhost
    prefer-ip-address: true
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

斷言工廠predicate

Spring Cloud Gateway 中文文檔 (springdoc.cn)

image-20231214150244126

常見的 Predicate 斷言如下表(假設轉發的 URI 為 http://localhost:8001)。簡單來說predicate就是給請求路由添加了一定限制條件。

斷言 示例 說明
Path - Path=/dept/list/** 當請求路徑與 /dept/list/** 匹配時,該請求才能被轉發到 http://localhost:8001 上。
Before - Before=2021-10-20T11:47:34.255+08:00[Asia/Shanghai] 在 2021 年 10 月 20 日 11 時 47 分 34.255 秒之前的請求,才會被轉發到 http://localhost:8001 上。
After - After=2021-10-20T11:47:34.255+08:00[Asia/Shanghai] 在 2021 年 10 月 20 日 11 時 47 分 34.255 秒之後的請求,才會被轉發到 http://localhost:8001 上。
Between - Between=2021-10-20T15:18:33.226+08:00[Asia/Shanghai],2021-10-20T15:23:33.226+08:00[Asia/Shanghai] 在 2021 年 10 月 20 日 15 時 18 分 33.226 秒 到 2021 年 10 月 20 日 15 時 23 分 33.226 秒之間的請求,才會被轉發到 http://localhost:8001 伺服器上。
Cookie - Cookie=name,c.biancheng.net 攜帶 Cookie 且 Cookie 的內容為 name=c.biancheng.net 的請求,才會被轉發到 http://localhost:8001 上。
Header - Header=X-Request-Id,\d+ 請求頭上攜帶屬性 X-Request-Id 且屬性值為整數的請求,才會被轉發到 http://localhost:8001 上。
Method - Method=GET 只有 GET 請求才會被轉發到 http://localhost:8001 上。

Gateway過濾器

Filter 的分類

Spring Cloud Gateway 提供了以下兩種類型的過濾器,可以對請求和響應進行精細化控制。

過濾器類型 說明
Pre 類型 這種過濾器在請求被轉發到微服務之前可以對請求進行攔截和修改,例如參數校驗、許可權校驗、流量監控、日誌輸出以及協議轉換等操作
Post 類型 這種過濾器在微服務對請求做出響應後可以對響應進行攔截和再處理,例如修改響應內容或響應頭、日誌輸出、流量監控等

按照作用範圍劃分,Spring Cloud gateway 的 Filter 可以分為 2 類:

  • GatewayFilter:應用在單個路由或者一組路由上的過濾器。
  • GlobalFilter:應用在所有的路由上的過濾器。
自定義GlobalFilter過濾器
package com.zl.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.HashMap;

@Component

public class myGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //可以通過exchange獲取到相應的Request和Response
        //通過Request獲取到請求的一下參數
        ServerHttpRequest request = exchange.getRequest();
        URI uri = request.getURI();
        RequestPath path = request.getPath();
        //當請求失敗之後可以使用response發送相應的JSON數據給前端 forExample code:403 message: "你沒有許可權"
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().set("content-type","application/json:charset=utf-8");
        HashMap<String, Object> map = new HashMap<>();
        map.put("code",200);
        map.put("message","你沒有許可權");
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            byte[] bytes = objectMapper.writeValueAsBytes(map);
            DataBuffer wrap = response.bufferFactory().wrap(bytes);
            return response.writeWith(Mono.just(wrap));
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
//        return chain.filter(exchange);
    }
   //配置當前過濾器的位置 數字越小越先執行
    @Override
    public int getOrder() {
        return 0;
    }
}
IP攔截
package com.zl.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@Component
public class IpCheckFilter implements GlobalFilter, Ordered {
    public static final String blockList= "0:0:0:0:0:0:0:1";
    /*
    * 一般使用一個資料庫來存儲黑名單的IP,獲取有的地方使用白名單。
    * 兩種的區別
    * 黑名單表示在該名單中的IP是不可以進行訪問的
    * 百名單表示只有這個名單中的IP是可以進行訪問的。
    * 在網關中我不要做一下比較耗時的操作,比如查詢資料庫,因為網關併發量比較大。通常會將名單存儲到redis中。
    * 如果是名單IP比較少時,直接將IP保存到記憶體中
    * */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        //獲取到訪問這個的IP 通常IPV6,
        String hostString = request.getRemoteAddress().getAddress().getHostName();
        System.out.println(hostString);
        //查詢黑名單中是否存在該IP地址
        if(!blockList.equals(hostString)){
            return chain.filter(exchange);
        }
        ServerHttpResponse response = exchange.getResponse();
        HashMap<Object, Object> map = new HashMap<>();
        response.getHeaders().set("content-type","application/json;charset=utf-8");
        map.put("code",438);
        map.put("message","你是黑名單");
        ObjectMapper objectMapper = new ObjectMapper();
        byte[] bytes = new byte[0];
        try {
            bytes = objectMapper.writeValueAsBytes(map);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        DataBuffer wrap = response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(wrap));
    }

    @Override
    public int getOrder() {
        return -5;
    }
}
自定義token攔截

image-20231217120712581

@Component
/*
* 1、獲取到url,判斷該URL是否不需要驗證token
* 2、獲取請求頭Authorization
* 3、判斷是否為null
* 4、判斷token是否存在與redis中
* 5、進行放行與攔截
* */
public class tokenFilter implements GlobalFilter, Ordered {
    private static List whiterList= Arrays.asList("/doLogin");
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        RequestPath url = exchange.getRequest().getPath();
        if(whiterList.contains(url)){
            return chain.filter(exchange);
        }
        HttpHeaders headers = exchange.getRequest().getHeaders();
        List<String> authorization = headers.get("Authorization");
        if(!CollectionUtils.isEmpty(authorization)){
            String token = authorization.get(0);
            if(StringUtils.hasText(token)){
                //判斷token中是否存在token
                 if(redisTemplate.hasKey(token)){
                     return chain.filter(exchange);
                 }
            }
        }
        //對請求進行攔截
        ServerHttpResponse response = exchange.getResponse();
        Map<String,Object> map=new HashMap<>();
        map.put("code",401);
        map.put("msg","未授權");
        ObjectMapper objectMapper = new ObjectMapper();
        byte[] bytes = new byte[0];
        try {
       

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、垂直分庫場景 場景:在業務系統中,涉及一下表結構,但是由於用戶與訂單每天都會產生大量的數據,單台伺服器的數據存儲以及處理能力是有限的,可以對資料庫表進行拆分,原有資料庫如下 說明1:整個業務系統中的表,大致分為四個,商品信息類的表,訂單相關的表,用戶相關表及省市區相關的表,這裡暫時將省市區的表和 ...
  • 一、Storm集群構建 編寫storm 與 zookeeper的yml文件 storm yml文件的編寫 具體如下: version: '2' services: zookeeper1: image: registry.aliyuncs.com/denverdino/zookeeper:3.4.8 ...
  • 1、JetPack Compose、組合函數與註解和文本修改 1、JetPack Compose:Jetpack Compose 是由 Google 推出的用於構建 Android 用戶界面的現代化工具包。它是一個聲明式的 UI 工具包,用於簡化 Android 應用程式的用戶界面設計和開發。Jet ...
  • Chrome 擴展項目使用前端 html,css,js 基礎技術開發,一大痛點就是改動代碼後的擴展更新問題。仔細想想想,目前前端項目開發已經有 HMR 熱重載技術,在開發 Web 頁面時可以實時查看效果,極大的提升了開發體驗。那麼,能否把這種極致的體驗帶到 Chrome 擴展開發中來呢?經過多番折騰... ...
  • 三、python數據、變數、註釋 1.數據 什麼是數據?在python中像數字,漢字,英文,圖片,音頻都是數據。目前瞭解就可以了 打開命令視窗,win+r 輸入cmd 打開python 2.變數和標識符 什麼是變數? 當我們編寫代碼的時候,我們會找一塊地方存放數據,而數據存放一個的時候還特別好找,當 ...
  • 流暢的orm讓我發現我抵觸的是mybatis而不是java 背景介紹 開發.net 也快10年了,到第三年的時候我已經漸漸瓶頸了,於是我在網上找各種資料但是大部分c#資料全是皮毛資料,稍微深一點點就再講表達式expression,感覺完全沒有那個深度,但是同時期的java講解的都是基本原理,和框架思 ...
  • 2.1、創建Maven工程 2.1.1、創建空項目 2.1.2、設置項目名稱和路徑 2.1.3、設置項目sdk 2.1.4、項目初始狀態 註意:需要關閉項目再重新打開,才能看到SpringBoot-Part文件夾 2.1.5、配置maven 2.1.6、創建module 右擊SpringBoot-P ...
  • 相信大家這幾天被董宇輝事件刷屏了,透徹的說就是幾個關鍵詞: 功高蓋主 去董宇輝 卸磨殺驢 飛鳥盡良弓藏 大家都知道新東方的轉型成功(打著助農的旗號),董宇輝發揮了巨大作用,很多人也是通過董宇輝才認識了東方甄選,讓東方甄選一躍成為抖音前三名的帶貨直播間。就在直播間蒸蒸日上的時候,金主擔心“東方甄選== ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...