SpringCloud的使用以及五大核心組件


、SpringCloud介紹


1.1 微服務架構

微服務架構的提出者:馬丁福勒

https://martinfowler.com/articles/microservices.html

// 中文版翻譯網址
http://blog.cuicc.com/blog/2015/07/22/microservices/

馬丁.福勒對微服務大概的概述如下:

​ 就目前而言,對於微服務業界並沒有一個統一的、標准的定義 (While there is no precise definition of this architectural style ) 。
​ 但通在其常而言,微服務架構是一種架構模式或者說是一種架構風格,它提倡將單一應用程序划分成一組小的服務,每個服務運行獨立的自己的進程中,服務之間互相協調、互相配合,為用戶提供最終價值。服務之間采用輕量級的通信機制互相溝通(通常是基於 HTTP 的 RESTful API ) 。每個服務都圍繞着具體業務進行構建,並且能夠被獨立地部署到生產環境、類生產環境等。
另外,應盡量避免統一的、集中式的服務管理機制,對具體的一個服務而言,應根據業務上下文,選擇合適的語言、工具對其進行構建,可以有一個非常輕量級的集中式管理來協調這些服務。可以使用不同的語言來編寫服務,也可以使用不同的數據存儲。

簡而言之,微服務架構樣式[1]是一種將單個應用程序開發為一組小服務的方法,每個小服務都在自己的進程中運行並與輕量級機制(通常是HTTP資源API)進行通信。這些服務圍繞業務功能構建,並且可以由全自動部署機制獨立部署。這些服務的集中管理幾乎沒有,它可以用不同的編程語言編寫並使用不同的數據存儲技術。

1、 微服務架構只是一個樣式,一個風格。

2、 將一個完成的項目,拆分成多個模塊去分別開發。

3、 每一個模塊都是單獨的運行在自己的容器中。

4、 每一個模塊都是需要相互通訊的。 Http,RPC,MQ。

5、 每一個模塊之間是沒有依賴關系的,單獨的部署。

6、 可以使用多種語言去開發不同的模塊。

7、每個模塊都是一個獨立的進程

8、中心化的管理

9、 使用MySQL數據庫,Redis,ES去存儲數據,也可以使用多個MySQL數據庫。

總結:將復雜臃腫的單體應用進行細粒度的划分,每個拆分出來的服務各自打包部署。

1.2 SpringCloud介紹

  • SpringCloud是微服務架構落地的一套技術棧。
  • Springcloud提供了一系列微服務開發的一站式解決方案。也是一系列主流框架的集合
  • SpringCloud中的大多數技術都是基於Netflix公司的技術進行二次研發。
  • SpringCloud的中文社區網站:http://springcloud.cn/
  • SpringCloud的中文網:http://springcloud.cc/
  • 八個技術點:
    • Eureka - 服務的注冊與發現
    • Robbin - 服務之間的負載均衡
    • Feign - 服務之間的通訊
    • Hystrix - 服務的線程隔離以及斷路器
    • Zuul - 服務網關
    • Stream - 實現MQ的使用
    • Config - 動態配置
    • Sleuth - 服務追蹤

dubbo和springcloud都是解決微服務的調用

使用springcloud的時候要注意和springboot版本匹配,官方有推薦的版本

springcloud中五大核心組件

  • 注冊中心

  • 服務調用

  • 路由網關

  • 服務的降級

  • 配置文件中心


二、Eureka(注冊中心)

2.1 引言

​ Eureka 是 Netflix 出品的用於實現服務注冊和發現的工具。 Spring Cloud 集成了 Eureka,並提供了開箱即用的支持。其中, Eureka 又可細分為 Eureka Server 和 Eureka Client。

2.2 實現原理

​ Eureka是SpringCloud中的注冊中心,在Eureka管理了所有的微服務,服務的提供者和消費者都要在注冊中心進行注冊,當有消費者需要調用服務的時候,直接去注冊中心進行拉取提供者的地址和端口號,以及服務名稱等消息,然后消費者根據消息內容使用RestTemplate進行請求到對應的提供者,當下次在進項該調用時候,服務器會將上次拉取的消息緩存在服務器內,所以說,當第二次請求的時候,就算注冊中心宕機也還是能正常訪問的。

提供者:要訪問的目標服務器,提供數據

消費者:請求訪問的服務器,獲取數據

1、Eureka服務正常啟動,如果存在集群的話就要互相同步

2、Eureka客戶端啟動的時候,會根據配置的地址,將該服務注冊到Eureka服務中,

3、Eureka客戶端會每隔30s發送一個心跳給Eureka服務

4、Eureka服務在90s之內沒有收到Eureka客戶端的心跳,會認為客戶端出現故障,然后從服務列表中移除,

5、在一段時間內,Eureka服務端統計到有大量的(85%)Eureka客戶端沒有發送心跳Eureka服務會認為此時,自己出現了網絡故障,就會觸發自我保護機制,不會再移除eureka客戶端。當前不會把數據同步給其他的Eureka服務,但是對外還是提供服務的

6、如果網絡恢復正常,自我保護機制關閉,接着將數據同步到其他的Eureka服務器

7、Eureka客戶端要調用其他服務,需要先到Eureka服務器中拉取其他服務的信息,然后再緩存到本地,再根據客戶端的負載均衡策略進行負載均衡

8、Eureka客戶端會在一段時間內從Eureka服務端拉取最新的數據,更新本地的緩存數據。

9、Eureka客戶端關閉后,Eureka就不會再發送心跳,Eureka服務就從自己的列表中移除

上圖是基於集群配置的eureka;

- 處於不同節點的eureka通過Replicate進行數據同步

- Application Service為服務提供者

- Application Client為服務消費者

- Make Remote Call完成一次服務調用

2.3 服務端搭建

1、創建一個springBoot工程,創建一個父工程,並且在父工程中指定SpringCloud的版本,並且將packaing修改為pom,選擇EurekaServer的依賴,繼承springboot的依賴,繼承springcloud的依賴

 <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR7</spring-cloud.version>
    </properties>

    <dependencies>
        <!-- eureka服務端的依賴-->
        <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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <!--springBoot的核心依賴,也是繼承,可插拔機制,只會加載配置的依賴-->
        <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>

2、application.yml文件的配置

server:
  port: 7777 #表示該服務的端口
eureka:
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka # 需要進行消息注冊的請求地址
    register-with-eureka: false #表示當前服務不會注冊到服務中心
    fetch-registry: false	#表示當前服務不會拉取其他服務
  instance:
    hostname: localhost

3、啟動類添加注解@EnableEurekaServer 表示該服務器為一個消息注冊中心的服務端

@SpringBootApplication
@EnableEurekaServer//配置后代表是一個eureka的客戶端
public class Day0101SpringcloudEurekaServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(Day0101SpringcloudEurekaServerApplication.class, args);
    }
}

2.4 客戶端搭建

所有相對於EurekaServer他們都是客戶端,也就是說提供者和消費者都是注冊中心的客戶端

服務器的搭建即將一個服務注冊到注冊中心

1、創建一個springBoot項目,創建的時候勾選Eureka Discovery Client的依賴

<dependencies>
        <!--eureka客戶端的依賴-->
        <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.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>

    <!--springCloud的核心依賴-->
    <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>

2、編寫配置文件application.yml ,注意,注冊的名稱不要使用"_"

server:
  port: 8081 # 該服務的端口號
eureka:
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:7777/eureka #此服務注冊的地址
  instance:
    hostname: localhost
spring:
  application:
    name: 2002-eureka-client-provider #注冊到注冊中心的名稱  注:不要使用“_”

3、啟動類添加注解@EnableEurekaClient 表示該服務器為一個消息注冊中心的客戶端

@SpringBootApplication
@EnableEurekaClient//表示該服務器是是一個Eureka的客戶端
@ComponentScan("com.jn")
public class Day0102SpringcloudEurekaClientProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(Day0102SpringcloudEurekaClientProviderApplication.class, args);
    }
}

2.5 Eureka客戶端從服務端拉取信息

 //操作eurekClient的API,初始化容器后已經存在
    @Autowired
    private EurekaClient eurekaClient;

    /**
     * 從eureka服務器中拉取指定名稱提供者的信息
     */
    @Test
    void contextLoads() {
        //從eureka服務器中拉取提供者的信息  第二個參數false
        InstanceInfo info = eurekaClient.getNextServerFromEureka("2002-EUREKA-CLIENT-CONSUMER", false);

        //獲取提供者的端口號
        int port = info.getPort();
        System.out.println("port:"+port);//port:8082

        //獲取IP地址
        String hostName = info.getHostName();
        System.out.println("hostName:"+hostName);//hostName:localhost

        //獲取提供者的地址
        String homePageUrl = info.getHomePageUrl();
        System.out.println("homePageUrl:"+homePageUrl);//homePageUrl:http://localhost:8082/
    }

2.6 Eureka的安全性

實現Eureka認證

導入依賴

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

編寫配置類,要讓spring掃描到這個類

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 忽略掉/eureka/**
        http.csrf().ignoringAntMatchers("/eureka/**");
        super.configure(http);
    }
}

編寫主啟動類,@EnableWebSecurity // 開啟EnableWebSecurity組件

@SpringBootApplication
@EnableEurekaServer//配置后代表是一個eureka的服務端
@EnableWebSecurity // 開啟EnableWebSecurity組件
@ComponentScan("com.jn")
public class Day0101SpringcloudEurekaServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(Day0101SpringcloudEurekaServerApplication.class, args);
    }

}

編寫配置文件

# 指定用戶名和密碼
spring:
  security:
    user:
      name: root
      password: root

其他服務想注冊到Eureka上需要添加用戶名和密碼進行認證

eureka:
  client:
    service-url:
      defaultZone: http://用戶名:密碼@localhost:8761/eureka

2.7 Eureka的集群

如果程序的正在運行,突然Eureka宕機了。

  • 如果調用方訪問過一次被調用方了,Eureka的宕機不會影響到功能。

  • 如果調用方沒有訪問過被調用方,Eureka的宕機就會造成當前功能不可用。

這里將eureka集群3台為例,將單機版的eureka拷貝3份后,配置分別如下

如果程序的正在運行,突然Eureka宕機了。

  • 如果調用方訪問過一次被調用方了,Eureka的宕機不會影響到功能。

  • 如果調用方沒有訪問過被調用方,Eureka的宕機就會造成當前功能不可用。

這里將eureka集群3台為例,將單機版的eureka拷貝3份后,配置分別如下

eureka8001

server:
  port: 8001
spring:
  application:
    name: eureka-server # 服務器域名
eureka:
  client:
    fetch-registry: false
    register-with-eureka: false
    service-url:
		#集群的情況下,服務端之間要互相注冊,指向對方,多個地址用逗號隔開
      defaultZone: http://eureka8002.com:8002/eureka,http://eureka8003.com:8003/eureka
  instance:
    instance-id: eureka8001.com

eureka8002

server:
  port: 8002
spring:
  application:
    name: eureka-server2
eureka:
  client:
    fetch-registry: false
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka8001.com:8001/eureka,http://eureka8003.com:8003/eureka
  instance:
    instance-id: eureka8002.com

eureka8003

server:
  port: 8003
spring:
  application:
    name: eureka-server3 
eureka:
  client:
    fetch-registry: false
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka8001.com:8001/eureka,http://eureka8002.com:8002/eureka
  instance:
    instance-id: eureka8003.com

​ 域名映射

集群后三台Eureka服務的IP都不一樣,所以為了方便在本地測試,可以在hosts文件中做域名映射,把三台服務的ip都映射成127.0.0.1。hosts文件路勁如下:

C:\Windows\System32\drivers\etc

127.0.0.1 eureka8001.com
127.0.0.1 eureka8002.com
127.0.0.1 eureka8003.com

發布的每一個服務都應該注冊到所有的Eureka中,所以每一個服務中都要寫三個Eureka服務地址。

server:
  port: 8082
spring:
  application:
    name: provider-server
eureka:
  client:
    service-url:
		# 這里寫三台Eureka地址
      defaultZone: http://eureka8001.com:8001/eureka,http://eureka8002.com:8002/eureka,http://eureka8003.com:8003/eureka

2.8 Eureka的細節

EurekaClient啟動是,將自己的信息注冊到EurekaServer上,EurekaSever就會存儲上EurekaClient的注冊信息。

當EurekaClient調用服務時,本地沒有注冊信息的緩存時,去EurekaServer中去獲取注冊信息。

EurekaClient會通過心跳的方式去和EurekaServer進行連接。(默認30sEurekaClient會發送一次心跳請求,如果超過了90s還沒有發送心跳信息的話,EurekaServer就認為你宕機了,將當前EurekaClient從注冊表中移除)

eureka:
  instance:
    lease-renewal-interval-in-seconds: 30      #心跳的間隔
    lease-expiration-duration-in-seconds: 90    # 多久沒發送,就認為你宕機了

EurekaClient會每隔30s去EurekaServer中去更新本地的注冊表

eureka:
  client:
    registry-fetch-interval-seconds: 30 # 每隔多久去更新一下本地的注冊表緩存信息

Eureka的自我保護機制,統計15分鍾內,如果一個服務的心跳發送比例低於85%,EurekaServer就會開啟自我保護機制

  • 不會從EurekaServer中去移除長時間沒有收到心跳的服務。
  • EurekaServer還是可以正常提供服務的。
  • 網絡比較穩定時,EurekaServer才會開始將自己的信息被其他節點同步過去
eureka:
  server:
    enable-self-preservation: true  # 開啟自我保護機制

CAP定理,C - 一致性,A-可用性,P-分區容錯性,這三個特性在分布式環境下,只能滿足2個,而且分區容錯性在分布式環境下,是必須要滿足的,只能在AC之間進行權衡。

如果選擇CP,保證了一致性,可能會造成你系統在一定時間內是不可用的,如果你同步數據的時間比較長,造成的損失大。

Eureka就是一個AP的效果,高可用的集群,Eureka集群是無中心,Eureka即便宕機幾個也不會影響系統的使用,不需要重新的去推舉一個master,也會導致一定時間內數據是不一致。

Eureka關閉自我保護機制,默認是開啟的

eureka:
  server:
  	enbale-self-preservation: false

三、遠程服務調用方式

3.1 Ribbon方式

3.1.1 ribbon搭建

根據注冊到注冊中心的服務名調用,底層還是基於RestTemplate實現

1、使用的時候需要引入依賴

 <!--ribbon通訊的核心依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

2、啟動類添加開始負載均衡注解

@SpringBootApplication
@EnableEurekaClient
@ComponentScan("com.jn")
public class Day0110SpringcloudRibbonConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(Day0110SpringcloudRibbonConsumerApplication.class, args);
    }

    @LoadBalanced//表示是負載均衡
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

請求方式

@RequestMapping("/test")
    public String testRibbon(){
        // ribbon調用的方式是根據服務名稱調用   RIBBON-PROVIDER表示是發布者在注冊中心的名稱
        return restTemplate.getForObject("http://RIBBON-PROVIDER/provider/hello?msg={0}",String.class,"ribbon");
    }
3.1.2 負載均衡

負載均衡策略

Ribbon支持的負載均衡策略

RandomRule 隨機策略 隨機選擇server

RoundRobinRule 輪詢策略 按照順序選擇server(ribbon默認策略)

RetryRule 重試策略 在一個配置時間段內,當選擇server不成功,則一直嘗試選擇一個可用的server

BestAvailableRule 最低並發策略 逐個考察server,如果server斷路器打開,則忽略,再選擇其中並發鏈接最低的server

AvailabilityFilteringRule 可用過濾策略 過濾掉一直失敗並被標記為circuit tripped的server,過濾掉那些高並發鏈接的server(active connections超過配置的閾值)

ResponseTimeWeightedRule 響應時間加權重策略 根據server的響應時間分配權重,響應時間越長,權重越低,被選擇到的概率也就越低。響應時間越短,權重越高,被選中的概率越高,這個策略很貼切,綜合了各種因素,比如:網絡,磁盤,io等,都直接影響響應時間

ZoneAvoidanceRule 區域權重策略 綜合判斷server所在區域的性能,和server的可用性,輪詢選擇server並且判斷一個AWS Zone的運行性能是否可用,剔除不可用的Zone中的所有server

使用指定類型的負載均衡,將其注入容器內

@Bean
public IRule robbinRule(){
    return new RandomRule();
}
3.1.3 自定義負載均衡

1、自定義一個繼承AbstractLoadBalancerRule的類,並且交給spring容器管理,內部編寫負載均衡策略

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.RoundRobinRule;
import com.netflix.loadbalancer.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 自定義負載均衡策略,每三次切換一次
 */
public class RibbonMyRule extends AbstractLoadBalancerRule {

    private AtomicInteger nextServerCyclicCounter;
    private static final boolean AVAILABLE_ONLY_SERVERS = true;
    private static final boolean ALL_SERVERS = false;
    private int total =0; // 記錄調用的次數
    private int index = 0; // 記錄調用哪個服務


    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

    public RibbonMyRule() {
        nextServerCyclicCounter = new AtomicInteger(0);
    }

    public RibbonMyRule(ILoadBalancer lb) {
        this();
        setLoadBalancer(lb);
    }

    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }

        Server server = null;
        int count = 0;
        while (server == null && count++ < 10) {
            List<Server> reachableServers = lb.getReachableServers();
            List<Server> allServers = lb.getAllServers();
            int upCount = reachableServers.size();
            int serverCount = allServers.size();


            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }

//            int nextServerIndex = incrementAndGetModulo(serverCount);
//            server = allServers.get(nextServerIndex);

            if(total <=2){
                server = allServers.get(index); // 0 0 0 1 1
                total++; // 1 2 3 0 1
            }else{
                index++;
                total = 0;
                if(index >= serverCount){ // 索引是從0開始的,長度是2
                    index = 0;
                }
            }


            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }

            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }

            // Next.
            server = null;
        }

        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }

    /**
     * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
     *
     * @param modulo The modulo to bound the value of the counter.
     * @return The next value.
     */
    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextServerCyclicCounter.get();
            int next = (current + 1) % modulo;
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

在主啟動類自定義負載均衡

 /**
     * 設置使用負載均衡的策略
     * @return
     */
    @Bean
    public IRule rule(){
        return new RibbonMyRule();//表示使用自定義的負載均衡策略
    }

3.2 Feign方式

Feign 是一個聲明web服務客戶端,這便得編寫web服務客戶端更容易,使用Feign 創建一個接口並對它進行注解,它具有可插拔的注解支持包括Feign注解與JAX-RS注解,Feign還支持可插拔的編碼器與解碼器,Spring Cloud 增加了對 Spring MVC的注解,Spring Web 默認使用了HttpMessageConverters, Spring Cloud 集成 Ribbon 和 Eureka 提供的負載均衡的HTTP客戶端 Feign.

提供者的對外暴露接口

package com.jn.controller;

import com.jn.entity.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

/**
 * @param
 * @return
 */
@RestController
@RequestMapping("/provider")
public class HelloController {

    @Value("${server.port}")
    private Integer port;

    @RequestMapping("/hello")
    public String  testHello(String msg){

        System.out.println( "訪問的端口號為+"+port+",傳遞的參數為"+msg);

        return "訪問的端口號為+"+port+",傳遞的參數為"+msg;
    }
    @RequestMapping("/test1")
    public String test1(){
        return "hello test1"+port;
    }
    //方法接收多個參數
    @RequestMapping("/login")
    public String login(String username,String password){
        return "login:username="+username+",password="+password;
    }
    //方法接收對象
    @RequestMapping("/addUserFrom")
    public String addUserFrom(User user){
        return "addUserFrom"+user.toString();
    }
    //方法接收JSON數據
    @RequestMapping("/addUserJson")
    public User addUserJson(@RequestBody User user){
        return user;
    }
    //restful風格
    @RequestMapping("/getUserById/{id}")
    public String getUserByID(@PathVariable Integer id){
        return "getUserById:id="+id;
    }
}

因為feign底層是使用了ribbon作為負載均衡的客戶端,而ribbon的負載均衡也是依賴於eureka 獲得各個服務的地址,所以要引入eureka-client

 <!--eureka的核心依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!--feign的核心依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

調用服務編寫映射接口 並且添加@FeignClient注解,表示為映射接口,一級路徑可以在類上使用@RequestMapping注解,也可以在方法直接寫全路徑

/**
 * 這個接口映射調用的模塊
 * 使用@FeignClient會自動去eureka拉取該服務的信息
 */
@FeignClient("RIBBON-PROVIDER")
@RequestMapping(value = "/provider")
public interface IProviderClient {
    @RequestMapping("/test1")
    String test1();
//
//    //方法接收多個參數
    @RequestMapping("/login")
    String login(@RequestParam("username") String username, @RequestParam("password") String password);
//

//    //方法接收JSON數據  響應如果是json格式數據,會自動根據返回值進行封裝,如果該方法返回值是String類型,將會返回json格式的數據
    @RequestMapping("/addUserJson")
    User addUserJson(@RequestBody User user);
//
//    //restful風格
    @RequestMapping("/getUserById/{id}")
    String getUserByID(@PathVariable("id") Integer id);
}

測試,直接調用接口即可

@SpringBootTest
class Day0111SpringcloudFeignConsumerApplicationTests {
    @Autowired
    IProviderClient client;
    @Test//測試傳遞字符串格式
    public void testLogin(){
        String result = client.login("kobe", "123");
        System.out.println("result:"+result);
    }

    @Test//測試傳遞json數據(常用)
    public void addUserJson(){
        User user = new User();
        user.setId(10);
        user.setUsername("admin");
        user.setPassword("123");
        User returnUser = client.addUserJson(user);
        System.out.println(returnUser);
    }

    @Test//測試restful請求方式
    void testGetUserById() {
        String userById = client.getUserByID(100);
        System.out.println(userById);
    }
}

四、Hystrix(保護機制)

4.1 服務雪崩

​ 多個微服務之間調用的時候,假設微服務A調用微服務B和微服務C,微服務B和微服務C又調用其他的微服務,
這就是所謂的"扇出”、如果扇出的鏈路上某個微服務的調用響應時間過長或者不可用,對微服務A的調用就會占用越來越多的系統資源,進而引起系統崩潰,所謂的“雪崩效應”。
​ 對於高流量的應用來說,單- -的后端依賴可能會導致所有服務器上的所有資源都在幾秒中內飽和。比失敗更糟的是,這些應用程序還可能導致服務之間的延遲增加,備份隊列,線程和其他系統資源緊張,導致整個系統發生更多的級聯故障,這些都表示需要對故障和延遲進行隔離和管理,以便單個依賴關系的失敗,不能取消整個應用程序或系統。
我們需要:棄車保帥:

4.2 Hystrix概念

​ Hystrix是一個用於處理分布式系統的延遲和容錯的開源庫, 在分布式系統里,許多依賴不可避免的會調用 失敗,比如超時,異常等,Hystrix能夠保證在一 個依賴出問題的情況下, 不會導致整體服務失敗,避免級聯故障,以提高分布式系統的彈性。

4.3 Hystrix的斷路機制

4.3.1 線程池隔離(默認)

服務的調用者(消費者)會從Hystrix線程池中申請一個線程幫助自己訪問服務,服務調用者的線程會阻塞住,等着線程池中的線程反饋結果,當服務器宕機,線程池的線程效應超時時,該線程池的線程就被全部被申請完,此時就拒絕訪問該服務器

4.3.2 信號量隔離

每個前線程去訪問服務會有標記,這個服務每訪問一次會加1,服務訪問成功會減一,如果這個值超過10就不讓訪問這個服務,請求就被攔截下來。

4.3.3 降級服務

當需要調用的微服務出現問題時,默認會去調用一個本地降級方法,降級方法會返回錯誤信息或者一個合理的默認值,從而繼續后面的業務,線程不會阻塞在哪里。

4.3.4 熔斷器

在微服務的訪問過程中,如果大量的請求訪問超時或者失敗,則熔斷器就會自動打開,如果熔斷器打開之后,后續所有訪問這個微服務的請求,會立刻被通知失敗。熔斷器開啟后,會每隔一段時間進入半開狀態,半開狀態下,會釋放一個服務嘗試請求資源,如果請求成功,則熔斷器就會 關閉,反之又會回到半開的狀態。

4.4 Ribbon整合Hystrix

1、導入hystrix核心依賴,還要導入eureka、ribbon的依賴

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

2、編寫本地降級方法,接口使用@HystrixComman並指定對應方法

@RestController
public class HystrixTestController {
    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "helloHystrix")//表示服務降級調用本地的helloHystrix方法
    @RequestMapping("/hello")
    public String hello(){
        String forObject = restTemplate.getForObject("http://RIBBON-PROVIDER/provider/hello?msg=123", String.class);
        return forObject;
    }

    //本地降級方法
    public String helloHystrix(){
        return "【hystrix】:調用的服務器異常......";
    }
}

3、主啟動類添加@EnableHystrix開啟服務降級注解

@SpringBootApplication
@ComponentScan("com.jn")
@EnableEurekaClient
@EnableHystrix//表示開啟服務降級功能
public class Day0213SpringcloudHystrixRibbonApplication {

    public static void main(String[] args) {
        SpringApplication.run(Day0213SpringcloudHystrixRibbonApplication.class, args);
    }
    @Bean
    @LoadBalanced//開啟負載均衡
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

4、將訪問資源服務器關閉后,訪問的結果

4.5 Hystirx整合feign

4.5.1 基於fallback方式

2、在配置文件中開啟hystrix

#開啟hystrix
feign:
  hystrix:
    enabled: true

3、提供一個映射接口的實現類。並且交給spring容器管理

@Component
public class ProviderClientImpl implements IProviderClient {
    @Override
    public String testHello(String msg) {
        return "服務器出現異常,msg="+msg;
    }

    @Override
    public String test1() {
        return "請求服務器出現異常";
    }

    @Override
    public String login(String username, String password) {
        return null;
    }

    @Override
    public User addUserJson(User user) {
        return null;
    }

    @Override
    public String getUserByID(Integer id) {
        return null;
    }
   
}

4、在映射接口IproviderClient的@FeignClient注解中指定降級調用方法,

細節:開啟服務降級后,Controller上不能再使用@RequestMapping指定一級路徑,而應該在方法上指定

/**
 * 這個接口映射調用的模塊
 * 使用@FeignClient會自動去eureka拉取該服務的信息
 */
@FeignClient(value = "RIBBON-PROVIDER",
        fallback = ProviderClientImpl.class
)
//@RequestMapping(value = "/provider")//開啟了hystrix后這個路徑要寫到下面的方法中
public interface IProviderClient {

    @RequestMapping("/provider/hello")
    String  testHello(String msg);

    @RequestMapping("/provider/test1")
    String test1();
//
//    //方法接收多個參數
    @RequestMapping("/provider/login")
    String login(@RequestParam("username") String username, @RequestParam("password") String password);
//

//    //方法接收JSON數據  響應如果是json格式數據,會自動根據返回值進行封裝,如果該方法返回值是String類型,將會返回json格式的數據
    @RequestMapping("/provider/addUserJson")
    User addUserJson(@RequestBody User user);
//
//    //restful風格
    @RequestMapping("/provider/getUserById/{id}")
    String getUserByID(@PathVariable("id") Integer id);

}

調用方無法知道具體的錯誤信息是什么,我們可以使用FallBackFactory方式

4.5.2 基於FallBackFactory方式

1、通過FallBackFactory的方式去實現這個功能,FallBackFactory基於Fallback,創建一個POJO類,實現FallBackFactory ,在服務請求超時時,會調用該類的creat方法,其中cause就是異常信息

細節:要交給spring容器管理,

​ 返回值一定要是映射接口的實現類

/**
 * 服務降級調用降級方法前如果是服務器出現異常會調用該類的create方法,返回值為映射接口,
 * @param
 * @return
 */
@Component
public class ProviderClientFactory implements FallbackFactory<IProviderClient> {
    @Autowired
    ProviderClientImpl client;  //注:這里一定要指定的是映射接口實現類

    @Override
    public IProviderClient create(Throwable cause) {
        System.out.println("調用的服務器出現異常......");
        cause.printStackTrace();
        return client;
    }
}

2、修改Client接口中的屬性,指定該POJO類

@FeignClient(value = "RIBBON-PROVIDER",
//        fallback = ProviderClientImpl.class
        fallbackFactory = ProviderClientFactory.class
)

4.5 斷路器

在配置文件中添加配置即可

hystrix:
  command:
    default:
      circuitBreaker:
        enabled: true
        requestVolumeThreshold: 2 
        sleepWindowInMilliseconds: 10000 

4.6 整合hystrix-dashboard(儀表盤)

導入依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>

在啟動類中添加注解

@EnableHystrixDashboard

配置一個Servlet路徑,指定上Hystrix的Servlet

@WebServlet("/hystrix.stream")
public class HystrixServlet extends HystrixMetricsStreamServlet {
}

//------------------------------------------------------------
// 在啟動類上,添加掃描Servlet的注解
@ServletComponentScan("com.jn.servlet")

高版本的SpringCloud中需要添加

hystrix:
  dashboard:
    proxy-stream-allow-list: "*"

測試直接訪問http://host:port/hystrix

在當前位置輸入映射好的servlet路徑


五、zuul路由網關

5.1 zuul的簡介

​ 網關是系統的唯一對外的入口,介於客戶端和服務器端之間的中間層,處理非業務功能 提供路由請求、鑒權、監控、緩存、限流等功能。它將"1對N"問題轉換成了"1對1”問題。

​ 通過服務路由的功能,可以在對外提供服務時,只暴露網關中配置的調用地址,而調用方就不需要了解后端具體的微服務主機。

5.2 zull的優點

路由網關優點

微服務網關介於服務端與客戶端的中間層,所有外部服務請求都會先經過微服務網關,客戶只能跟微服務網關進行交互,無需調用特定微服務接口,使得開發得到簡化。

服務網關 = 路由轉發 + 過濾器+負載均衡

(1)路由轉發:接收一切外界請求,轉發到后端的微服務上去。

(2)過濾器:在服務網關中可以完成一系列的橫切功能,例如權限校驗、限流以及監控等,這些都可以通過過濾器完成。

​ (3) 調用者只需要維護網關的地址,對外只暴露網關的地址,其他地址用服務名稱映射

(4)可以支持負載均衡

(5)里面也是支持服務降級

5.3 zull的快速入門

1、導入依賴

<!--注冊中心客戶端依賴-->
<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--路由網關的核心依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

2、主啟動類添加@EnableZuulProxy注解,開啟路由網關

@SpringBootApplication
@EnableZuulProxy//表示開啟路由網關
@EnableEurekaClient//表示是Eureka注冊中心的客戶端
public class Day0216SpringcloudZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(Day0216SpringcloudZuulApplication.class, args);
    }
}

3、編寫配置文件

server:
  port: 80
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7777/eureka  #服務注冊地址
spring:
  application:
    name: zuul

4、測試

http://網關IP:網關端口/服務名稱/服務地址

注:注冊名一定要轉為小寫

5.4 Zuul常用配置信息

5.4.1 Zuul的監控界面

導入依賴

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

編寫配置文件

# 查看zuul的監控界面(開發時,配置為*,上線,不要配置)
management:
  endpoints:
    web:
      exposure:
        include: "*"

直接訪問地址http://localhost/actuator/routes

5.4.2 忽略服務配置
# zuul的配置
zuul:
  # 基於服務名忽略服務,無法查看 ,如果要忽略全部的服務  "*",默認配置的全部路徑都會被忽略掉(自定義服務的配置,無法忽略的)
  ignored-services: eureka
  # 監控界面依然可以查看,在訪問的時候,404
  ignored-patterns: /**/search/**
5.4.3 自定義服務配置

配置文件,兩種方式

# zuul的配置
zuul:
  # 指定自定義服務(方式一 , key(服務名):value(路徑))
#  routes:
#	feign-hystrix-dashboad: /dashboad/** # kye:服務名稱,value:映射路徑
  # 指定自定義服務(方式二)
  routes:
    kehu:   # 自定義名稱
      path: /ccc/**     # 映射的路徑
      serviceId: customer   # 服務名稱
5.4. 灰度發布

​ 灰度發布(又名金絲雀發布)是指在黑與白之間,能夠平滑過渡的一種發布方式。在其上可以進行A/B testing,即讓一部分用戶繼續用產品特性A,一部分用戶開始用產品特性B,如果用戶對B沒有什么反對意見,那么逐步擴大范圍,把所有用戶都遷移到B上面來。灰度發布可以保證整體系統的穩定,在初始灰度的時候就可以發現、調整問題,以保證其影響度。

​ 灰度發布可以根據規定服務名稱,生成固定的服務映射地址,

如服務名稱server-v1可以映射為v1/server

使用步驟

1、在zuul服務器中添加一個配置類

@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
    return new PatternServiceRouteMapper(
        "(?<name>^.+)-(?<version>v.+$)",
        "${version}/${name}");
}

2、准備一個服務,提供兩個版本

#v1版本
version: v1
server:
  port: 8088
spring:
  application:
    name: ribbon-provider-${version}
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7777/eureka
# v2版本
version: v2
server:
  port: 8088
spring:
  application:
    name: ribbon-provider-${version}
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7777/eureka

3、測試

5.5 zuul的過濾器

5.5.1 zuul過濾器的執行流程

zuul的過濾器都做一些權限判斷等操作

客戶端請求發送到zuul網關服務器上,首先調用PreFileter過慮鏈中,如果正常放行,會把請求轉發到RoutingFilter過慮鏈,再轉發到指定的服務,指定服務返回一個結果后,會再次經過一個PostFilter過慮鏈,最終在將響應信息交給客戶端,這其中,如果出現錯誤,會轉發到Error過慮鏈中,然后在發送到PostFilter過慮鏈中

5.5.2 過濾器的搭建

1、在zuul的基礎上搭建環境

2、編寫過濾器,繼承ZuulFilter,並且交給spring容器管理

/**
 * 路由網關的過濾器
 */
@Component
public class TestPreFilter extends ZuulFilter {

    /**
     * 設置filter類型
     * @return
     */
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    /**
     * filter的優先級,數值越小,優先級越高
     * @return
     */
    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER -1;
    }

    @Override
    public boolean shouldFilter() {
        return true; // 使用這個filter
    }

    @Override
    public Object run() throws ZuulException {

        // 1校驗
        // 獲取HttpServletRequest
        RequestContext requestContext = RequestContext.getCurrentContext();

        HttpServletRequest request = requestContext.getRequest();

        String token = request.getParameter("token");

        if(StringUtils.isEmpty(token) || !"admin".equals(token)){
            System.out.println("校驗失敗");
            requestContext.setSendZuulResponse(false); // 不在往下執行了

            // 在這里做出響應,這里設置的是枚舉,表示沒有權限
            requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }
        return null;
    }
}

執行的順順序:可以在過濾器的filterOrder( )方法中指定

測試:驗證失敗

測試驗證通過

5.6 zuul的服務降級

路由網關的服務降級指的是通過旅游網關進行其他服務的遠程調用時,如果調用的服務響應超時時,將會調用本地的方法進行響應

使用步驟

1、創建一個POJO類,並且實現FallbackProvider接口,復寫內部方法

/**
 *
 * 測試服務的降級
 * @param
 * @return
 */
@Component
public class ZuulFallbackTest implements FallbackProvider {

    //指定進行服務降級策略的服務名,*表示所有的服務
    @Override
    public String getRoute() {
        return "*";
    }


    //服務降級后調用的本地方法
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            //表示返回的狀態
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.INTERNAL_SERVER_ERROR;//表示服務異常
            }

            //返回的狀態碼
            @Override
            public int getRawStatusCode() throws IOException {
                return HttpStatus.INTERNAL_SERVER_ERROR.value();//500
            }

            //返回對應返回的文本信息
            @Override
            public String getStatusText() throws IOException {
                return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();//返回的是狀態碼的文本信息
            }

            //當服務異常時,如果有資源需要關閉,就可以在這個方法中進行關閉
            @Override
            public void close() {

            }

            //對應響應的數據
            @Override
            public InputStream getBody() throws IOException {
                String errorMsg = "服務異常......進行服務降級......";
                return new ByteArrayInputStream(errorMsg.getBytes("utf-8"));
            }

            //返回數據的格式  如json,xml
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                //指定返回數據格式為json格式
                httpHeaders.setContentType(MediaType.APPLICATION_JSON);
                return httpHeaders;
            }
        };
    }
}

注:如果服務間的調用超過1s任然未響應,那么路由網關就會自動調用服務降級,可以在application.yml文件中進行配置

ribbon: 
  ReadTimeout: 6000 # 設置zuul超時時間
  ConnectTimeout: 6000 # 設置連接的超時時間

5.7 動態路由

​ 在我們之前使用自定義的服務配置后,我們都需要重啟路由網關從而重新加載配置文件,在使用動態路由后就不在需要了,而是動態進行服務配置,本質上是使用過濾器來實現的

配置動態路由,是在路由網關的基礎上實現的

添加一個過濾器,在過濾器的中配置訪問服務的名稱和訪問的uri

/**
 * 測試動態路由
 * @param
 * @return
 */
@Component
public class DynamicRoutingTest extends ZuulFilter {

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER+1;//
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        //1、獲取request
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();

        //2、獲取參數
        String routing = request.getParameter("routing");
        System.out.println(routing+"______________");

        //3、進行判斷
        if(routing!=null && routing.equals("login")){
            //指定訪問服務的名稱
            currentContext.put(FilterConstants.SERVICE_ID_KEY,"ribbon-provider-v2");
            //指定跳轉的路徑
            currentContext.put(FilterConstants.REQUEST_URI_KEY,"/provider/login");
        }

        if(routing!=null && routing.equals("hello")){
            //指定訪問服務的名稱
            currentContext.put(FilterConstants.SERVICE_ID_KEY,"ribbon-provider-v2");
            //指定跳轉的路徑
            currentContext.put(FilterConstants.REQUEST_URI_KEY,"/provider/hello");
        }
        return null;
    }
}
	

六、多語言的支持

6.1 引言

​ 在springcloud的項目中,我們需要允許使用不同語言去實現微服務,但是非java語言是無法介入eureka,hystrix,fegin,ribbon等相關組件。有一種思路就是啟動一個代理的微服務,由該代理微服務同非jvm服務交流,並接入eureka等相關組件。Sidecar就是該思路的實現,總結的說就是作為一個代理的服務來間接性的讓其他語言可以使用Eureka等相關組件。

6.2 原理圖

6.3 Sidecar的實現

1、導入依賴

 <!--sidecar的核心依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix-sidecar</artifactId>
</dependency>
<!--eureka客戶端的依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、編寫配置文件,使用sidecar指定第三方服務的地址和端口號

server:
  port: 8080
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7777/eureka
spring:
  application:
    name: baidu
# 配置第三方的服務信息
sidecar:
  hostname: 14.215.177.38
  port: 80

3、啟動類中添加@EnableSidecar,以及eureka客戶端的依賴

@SpringBootApplication
@EnableSidecar//表示開啟sidecar
@ComponentScan("com.jn")
@EnableEurekaClient
public class Day0322SpringcloudSidecarApplication {

    public static void main(String[] args) {
        SpringApplication.run(Day0322SpringcloudSidecarApplication.class, args);
    }

}

4、測試,開啟該服務器以及路由網關服務器和注冊中心,直接輸入我們配置文件指定的地址http://lcoalhost/baidu進行測試


七、服務間消息傳遞-Stream


7.1 引言

Stream就是在消息隊列的基礎上,對其進行封裝,讓咱們更方便的去操作MQ消息隊列。

7.2 Stream快速入門

啟動RabbitMQ

消費者-導入依賴

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

消費者-配置文件

spring:
  # 連接RabbitMQ
  rabbitmq:
    host: 192.168.199.109
    port: 5672
    username: test
    password: test
    virtual-host: /test

消費者-監聽的隊列

public interface StreamClient {
    @Input("myMessage")
    SubscribableChannel input();
}
//-------------------------------------------------
@Component
@EnableBinding(StreamClient.class)
public class StreamReceiver {
    @StreamListener("myMessage")
    public void msg(Object msg){
        System.out.println("接收到消息: " + msg);
    }
}

生產者-導入依賴

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

生產者-配置文件

spring:
  # 連接RabbitMQ
  rabbitmq:
    host: 192.168.199.109
    port: 5672
    username: test
    password: test
    virtual-host: /test

生產者-發布消息

public interface StreamClient {
    @Output("myMessage")
    MessageChannel output();
}
//----------------------------------------------  在啟動類中添加注解 @EnableBinding(StreamClient.class)
@Autowired
private StreamClient streamClient;

@GetMapping("/send")
public String send(){
    streamClient.output().send(MessageBuilder.withPayload("Hello Stream!!").build());
    return "消息發送成功!!";
}

7.3 Stream重復消費問題

只需要添加一個配置,指定消費者組

spring:
  cloud:
    stream:
      bindings:
        myMessage:				# 隊列名稱
          group: customer      # 消費者組

7.4 Stream的消費者手動ack

編寫配置

spring:
  cloud:
    stream:
      # 實現手動ACK
      rabbit:
        bindings:
          myMessage:
            consumer:
              acknowledgeMode: MANUAL

修改消費端方法

@StreamListener("myMessage")
public void msg(Object msg,
                @Header(name = AmqpHeaders.CHANNEL) Channel channel,
                @Header(name = AmqpHeaders.DELIVERY_TAG) Long deliveryTag) throws IOException {
    System.out.println("接收到消息: " + msg);
    channel.basicAck(deliveryTag,false);
}

八、文件配置服務

8.1 概念

​ 我們一直都是將配置文件配置在各個服務器中,當我需要修改時,不方便維護,比如修改eureka服務端的端口號,那么所有的eureka客戶端的配置文件都需要發生改變,而且修改完后服務器還需要進行重啟,使用這些步驟變得很繁瑣

​ 那么我們可以准備一台專門配置文件信息的服務器,這台服務器配置了所有的公共配置信息,當其他服務器需要啟動時,就去配置文件服務器中去拉取,然后再啟動

​ 配置文件的服務器也可以配置到git,數據庫,RabbitMQ,redis等持久化存儲中,將來配置文件服務器只需要進行對文件配置數據進行推送和拉取即可。

​ 所以,文件配置服務也分服務端和客戶端,所有依賴文件配置服務拉取配置的服務都是文件配置服務的客戶端

8.2 原理圖

8.3 搭建配置文件服務Config-Server

1、創建工程,導入依賴

<!--文件配置服務器  config-server的核心依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>

2、在主啟動類上添加@EnableConfigServer注解,表示該服務為一個配置文件服務器

/**
 * 搭建文件配置服務器
 */
@EnableConfigServer//表示該服務為一個配置文件服務器
@SpringBootApplication
public class Day0220SpringcloudConfigServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(Day0220SpringcloudConfigServerApplication.class, args);
    }
}

3、在resources目錄下編寫其他服務器的配置文件

如:application-eureka-server.yml

server:
  port: 8888
eureka:
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
    register-with-eureka: false
    fetch-registry: false
  instance:
    hostname: localhost

再來一個用來后面測試的配置文件application-test.yml

str1: 111
str2: 222

4、配置服務器的配置文件

spring:
  cloud:
    config:
      server:
        native:
          search-locations: classpath:config #  指定其他服務器的配置文件目錄
server:
  port: 9999

8.4 搭建配置文件客戶端

1、創建工程,並且導入依賴

<!--配置文件客戶端的依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-client</artifactId>
</dependency>
<!--為了后期試驗,這里添加了eureka的依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--為了后期試驗,這里添加了web的依賴-->
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2、編寫配置文件bootstrap.yml配置文件使用bootstrap.yml配置文件比使用application.yml配置文件的優先級要高:

spring:
  cloud:
    config:
      uri: http://localhost:9999 # 配置服務的地址
      name: application  # 指的是讀取文件類型為application類型的配置文件
      profile: eureka-server,test # 指的是讀取eureka-serverh和test的application配置文件,即讀取配置文件服務器的eureka-server-application.yml和application-test.yml配置文件

3、主啟動類

@SpringBootApplication
@EnableEurekaServer//表示是eureka的客戶端
@ComponentScan("com.jn")
public class Day0221SpringcloudConfigClientEurekaApplication {

    public static void main(String[] args) {
        SpringApplication.run(Day0221SpringcloudConfigClientEurekaApplication.class, args);
    }

}

4、啟動文件配置服務器和該服務,訪問eureka服務器配置的路徑http://localhost:8888/,發現文件配置客戶端已經從文件配置服務器中拉取了對應的配置文件

5、繼續編寫一個controller進行測試

/**
 * 測試配置文件客戶端讀取配置文件服務器的配置文件
 * @param
 * @return
 */
@RestController
public class ConfigClientTestController {

    @Value("${server.port}")
    private int port;

    @Value("${str1}")
    private String str1;

    @RequestMapping("/test")
    public String test(){
        return "port:"+port+",str1:"+str1;
    }
}

6、重啟服務器訪問http://localhost:8888/test,說明可以同時從文件配置服務器中拉取多個配置文件


九、服務的追蹤技術-Sleuth

9.1 引言

​ 在整個微服務架構中,微服務很多,一個請求可能需要調用很多的服務,最終才能完成一個功能,如果說整個功能出現了問題,在那么多的服務中,如何去定位問題的所在點,出現問題的原因是什么,我們就可以使用Sleuth技術

  • Sleuth可以獲得整個微服務的鏈路信息
  • Zipkin通過圖形化界面獲取鏈路信息,即可以提供服務間調用流程的視圖
  • Sleuth將日志信息存儲到數據庫中

9.2 Sleuth的使用

1、導入sleuth插件的依賴

<!--引入sleuth插件的依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

2、配置文件中設置日志級別

#編寫sleuth的配置文件
logging:
  level:
    org.springframework.web.servlet.DispathcherServlet: debug

3、測試,當添加該插件的服務去遠程調用其他服務時,會輸出如下日志

SEARCH(字段1):服務名稱
e9c(字段2):總鏈路id
f07(字段3):當前服務的鏈路id
false:不會將當前的日志信息,輸出其他系統中

9.3 Zipkin的使用

日志可讀性不強,Zipkin是提供服務鏈路的可視化頁面

zipkin的安裝

虛擬機使用docker創建zipkin服務,為了提高效率,整合了RabbitMQ,采用異步的方式,同時為了保證數據的持久化,還整合了elasticSearc用於持久化

version: "3.1"
services:
  zipkin:
   image: daocloud.io/daocloud/zipkin:latest
   restart: always
   container_name: zipkin
   ports:
     - 9411:9411
   environment:
     - RABBIT_ADDRESSES=192.168.199.109:5672  #指定了rabbitMQ消息隊列的地址
     - RABBIT_USER=guest # rabbitMQ認證的用戶名
     - RABBIT_PASSWORD=guest # rabbitMQ認證的密碼
     - RABBIT_VIRTUAL_HOST=/ # rabbitMQ的內置虛擬機
     - STORAGE_TYPE=elasticsearch # 持久化的類型
     - ES_HOSTS=http://192.168.40.100:9200 #持久化的地址 使用es會自動為我們生成索引和數據到es服務中

2、導入依賴

 <!--zipkin核心依賴,里面包含了sleuth組件的依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

3、編寫配置文件

#指定服務的名稱
spring:
  sleuth:
    sampler:
      probability: 1   # 百分之多少的sleuth信息需要輸出到zipkin中
  zipkin:
    base-url: http://192.168.199.109:9411/  # 指定zipkin的地址
     sender:
        type: rabbit #  指定發送到rabbitMQ中
#編寫sleuth的配置文件
logging:
  level:
    org.springframework.web.servlet.DispathcherServlet: debug
    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM