1. 程式人生 > 實用技巧 >SpringCloud的使用以及五大核心元件

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