SpringCloud學習筆記(一) 搭建一個SpringCloud

Jollyzhe發表於2019-01-15

簡介

摘自百度百科:
SpringCloud是一系列框架的有序集合。它利用SpringBoot的開發便利性巧妙地簡化了分散式系統基礎設施的開發,如服務發現註冊、配置中心、訊息匯流排、負載均衡、斷路器、資料監控等,都可以用SpringBoot的開發風格做到一鍵啟動和部署。SpringCloud並沒有重複製造輪子,它只是將目前各家公司開發的比較成熟、經得起實際考驗的服務框架組合起來,通過SpringBoot風格進行再封裝遮蔽掉了複雜的配置和實現原理,最終給開發者留出了一套簡單易懂、易部署和易維護的分散式系統開發工具包。

反正SpringCloud是一個用來做微服務的框架,主要是基於SpringBoot來完成的,相關的微服務框架還有比較出名的Dubbo和DubboX。關於微服務是用來做什麼的,有什麼好處自行百度。

SpringCloud是Spring全家桶中的一員,所以SpringCloud對於Spring的其他元件都有著很好的支援。

SpringCloud主要採用的Eureka,Dubbo主要採用的是zookeeper。

上程式碼

provider 服務提供者

直接新建一個SpringBoot專案,由於是為了示例,本次示例中不使用資料庫,只引入了spring-boot-starter-web

專案及其簡單,只暴露了一個介面,用於測試,yml檔案只配置了埠號為7900

這裡寫圖片描述

consumer 服務消費者

同樣新建一個SpringBoot專案,使其呼叫consumer的介面,同樣只引入了spring-boot-starter-web,yml檔案只是修改了埠號為7910。

這裡寫圖片描述

這裡解釋一下RestTemplates這個類,關於這個類,這個老哥https://blog.csdn.net/u012702547/article/details/77917939的解釋我覺得非常完整了,RestTemplates在SpringCloud中,消費者通過這個類訪問生產者,@bean註解是為了例項化這個類,例項化之後通過@AutoWired註解引入,將其交給Spring進行管理。

分別啟動兩個類,分別訪問localhost:7900/provider/demohttp://localhost:7910/consumer/demo應該都能得到ProviderDemo這個結果,說明consumer成功呼叫了provider中的方法。

問題:
這樣的編碼方式是將介面(http://localhost:7900/provider/demo)硬編碼在程式碼中,但是專案釋出之後,ip地址必然是變動的。而且,硬編碼的方式肯定是無法實現負載均衡的,就是說如果同時啟動多個provider服務,這種硬編碼方式是無法根據負載均衡策略去呼叫服務的。

解決方法:
在Dubbo中使用的ZooKeeper作為服務註冊與發現的容器,在Springcloud中使用的是Eureka作為服務註冊與發現的容器。

Eureka

同樣建立一個SPringBoot專案,下面是pom.xml檔案,我是在idea中直接新增spring-cloud-starter-netflix-eureka-server這個依賴的。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.edu.nuaa</groupId>
    <artifactId>eureka-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-demo</name>
    <description>eureka-demo</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </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>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

    然後在Springboot的啟動類上新增@EnableEurekaServer或者@EnableDiscoveryClient,這兩個註解的註解的區別主要是:

    @EnableEurekaServer是基於 spring-cloud-netflix依賴,只能為eureka作用,是專門給Eureka用的
    @EnableDiscoveryClient是基於 spring-cloud-commons依賴,並且在classpath中實現,是給比如zookeeper、consul使用的,
    舊版本的@EnableEurekaServer的原始碼上面也是也是有@EnableDiscoveryClient註解的。
    本示例是使用Eureka的,故推薦使用@EnableEurekaServer

    @SpringBootApplication
    @EnableEurekaServer
    public class EurekaDemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(EurekaDemoApplication.class, args);
        }
    }
    

      然後在yml檔案中配置如下資訊:

      server:
        port: 8761
      eureka:
        client:
          register-with-eureka: false
          fetch-registry: false
          service-url:
            defaultZone: http://localhost:8761/eureka/

        這個埠號官網設定成8761,這裡也設定成8761;

        `register-with-eureka: false這個的預設值為true,設定為true不會對使用不會有很大的影響,但是在啟動的時候會保下面的錯誤:
        was unable to refresh its cache! status = Cannot execute request on any known server
        是因為啟動的時候自己註冊了自己而引起的衝突

        defaultZone配置eureka的地址,這個如果有多個註冊中心,則用逗號隔開。
        為了服務註冊中心的安全考慮,很多時候我們都會為服務註冊中心加入安全認證。通常與SpringSecurity整合,主要是SpringSecurity的內容,這裡不做討論(主要是我自己嘗試的時候失敗了,啊哈哈哈)。

        配置資訊完成之後,啟動eureka專案,訪問localhost:8761便可以得到下面的介面,目前上面兩個寫的provider和consumer還沒有註冊到該註冊中心:
        這裡寫圖片描述

        關於Eureka配置的含義及其預設值主要參考下表:

        服務註冊類配置

        省略了eureka.client字首

        引數名 說明 預設值
        enabled 啟用Eureka客戶端 true
        registryFetchIntervalSeconds 從Eureka服務端獲取註冊資訊的間隔時間,單位為秒 30
        instanceInfoReplicationIntervalSeconds 更新例項資訊的變化到Eureka服務端的間隔時間,單位為秒 30
        initialInstanceInfoReplicationIntervalSeconds 初始化例項資訊到Eureka服務端的間隔時間,單位為秒 40
        eurekaServiceUrlPollIntervalSeconds 輪詢Eureka服務端地址更改的間隔時間,單位為秒。當我們與Spring CLoud Config整合,動態重新整理Eureka的serviceURL地址時需要關注該引數 300
        eurekaServerReadTimeoutSeconds 讀取Eureka Server資訊的超時時間,單位為秒 8
        eurekaServerConnectTimeoutSeconds 連結Eureka Server的超時時間,單位為秒 5
        eurekaServerTotalConnections 從Eureka客戶端到所有Eureka服務端的連線總數 200
        eurekaServerTotalConnectionsPerHost 從Eureka客戶端到每個Eureka服務端主機的連線總數 50
        eurekaConnectionIdleTimeoutSeconds Eureka服務端連線的空閒關閉時間,單位為秒 30
        heartbeatExecutorThreadPoolSize 心跳連線池的初始化執行緒數 2
        heartbeatExecutorExponentialBackOffBound 心跳超時重試延遲時間的最大乘數值 10
        cacheRefreshExecutorThreadPoolSize 快取重新整理執行緒池的初始化執行緒數 2
        cacheRefreshExecutorExponentialBackOffBound 快取重新整理重試延遲時間的最大乘數值 10
        useDnsForFetchingServiceUrls 使用DNS來獲取Eureka服務端的serviceUrl false
        registerWithEureka 是否要將自身的例項資訊註冊到Eureka服務端 true
        preferSameZoneEureka 是否偏好使用處於相同Zone的Eureka服務端 true
        filterOnlyUpInstances 獲取例項時是否過濾,僅保留UP狀態的例項 true
        fetchRegistry 是否從Eureka服務端獲取註冊資訊 true

        服務例項類配置

        eureka.instance.instanceId為例項的id,可以自定義,預設值為

        ${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}

          省略了eureka.instance為字首

          引數名 說明 預設值
          preferIpAddress 是否優先使用IP地址作為主機名的標識 false
          leaseRenewalIntervalInSeconds Eureka客戶端向服務端傳送心跳的時間間隔,單位為秒 30
          leaseExpirationDurationInSeconds Eureka服務端在收到最後一次心跳之後等待的時間上限,單位為秒。超過該時間之後服務端會將該服務例項從服務清單中剔除,從而禁止服務呼叫請求被髮送到該例項上 90
          nonSecurePort 非安全的通訊埠號 80
          securePort 安全的通訊埠號 443
          nonSecurePortEnabled 是否啟用非安全的通訊埠號 true
          securePortEnabled 是否啟用安全的通訊埠號
          appname 服務名,預設取spring.application.name的配置值,如果沒有則為unknown
          hostname 主機名,不配置的時候講根據作業系統的主機名來獲取

          註冊服務到Eureka

          回到之前寫的provider-demo專案中,在專案中新增下面這個依賴,注意version:

          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
              <version>RELEASE</version>
          </dependency>
            /ul>

          在之前的SpringCloud版本中,還有出現過下面的依賴,但是目前最新的版本是上面這個,親測,在本示例中兩個都是可以的。

          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-eureka</artifactId>
              <version>RELEASE</version>
          </dependency>

            然後在專案的啟動類上新增@EnableEurekaClient註解

            @SpringBootApplication
            @EnableEurekaClient
            public class ProviderDemoApplication {
            
                public static void main(String[] args) {
                    SpringApplication.run(ProviderDemoApplication.class, args);
                }
            }

              從依賴的名字spring-cloud-starter-netflix-eureka-client的名字就知道這個依賴是用來做eureka的客戶端,然後註冊服務到eureka上的。然後在專案的啟動類上的新增的註解為@EnableEurekaClient

              回到上文中會發現搭建eureka註冊中心的時候會發現引入的包是spring-cloud-starter-netflix-eureka-server,在專案的啟動類上的新增的註解為@EnableEurekaServer還是很好記憶和理解的。

              最後在yml檔案中新增上eureka註冊中心的地址並可以了:

              server:
                port: 7900
              eureka:
                client:
                  serviceUrl:
                    defaultZone: http://localhost:8761/eureka/

                分別啟動一下eureka和provider專案,然後訪問一下localhost:8761便會得到下面的頁面:
                這裡寫圖片描述

                可以看到provider的服務已經註冊上eureka上了。
                發現application的名稱為UNKNOWN,可以在yml檔案中新增下面內容修改名字:

                spring:
                  application:
                    name: provider-demo

                  重啟一下服務便可以看到名字,這裡就不貼圖片了。

                  有可能會遇到下面的紅色字型:

                  EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

                  這個主要是eureka的自我保護的心跳機制導致的,即如果真實的服務已經Down掉,但在註冊中心介面服務卻一直存在,且顯示為UP狀態。解決方法可以新增下面內容,上面有eureka的配置資訊中解釋含義:

                  eureka:
                    client:
                      serviceUrl:
                        defaultZone: http://localhost:8761/eureka/
                    instance:
                      lease-renewal-interval-in-seconds: 1
                      lease-expiration-duration-in-seconds: 2

                    服務監控

                    對於服務的眾多資訊可能都需要監控和監聽,SpringCloud主要採用的是下面這個依賴對其實現監聽的:

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

                      新增這個依賴之後重啟服務會發現console中多了下圖這幾個端點:

                      這裡寫圖片描述

                      /actuator/health和/actuator/info以及/actuator這三個就是actuator提供的端點,注意以前的版本是沒有/actuator字首的,2.0以後的版本都加了/actuator字首。可以訪問localhost:7900/actuator嘗試看監聽的都是一些什麼內容

                      如果要開啟全部的監聽端點,可以在yml檔案中加入下面的配置資訊:

                      management:
                        endpoints:
                          web:
                            exposure:
                              include: "*"

                        重啟服務便可以看到所有的端點都開啟用了。

                        主要的端點的含義可以參考下面的表格:

                        端點 描述
                        actuator 為其他端點提供“發現頁面”。要求Spring HATEOAS在classpath路徑上。
                        auditevents 陳列當前應用程式的審計事件資訊。
                        autoconfig 展示自動配置資訊並且顯示所有自動配置候選人以及他們“被不被”應用的原因。
                        beans 顯示應用程式中所有Spring bean的完整列表。
                        configprops 顯示所有配置資訊。
                        dump dump所有執行緒。
                        env 陳列所有的環境變數。
                        flyway Shows any Flyway database migrations that have been applied.
                        health 顯示應用程式執行狀況資訊
                        info 顯示應用資訊。
                        loggers 顯示和修改應用程式中的loggers配置。
                        liquibase 顯示已經應用的任何Liquibase資料庫遷移。
                        metrics 顯示當前應用程式的“指標”資訊。
                        mappings 顯示所有@RequestMapping的url整理列表。
                        shutdown 關閉應用(預設情況下不啟用)。
                        trace 顯示跟蹤資訊(預設最後100個HTTP請求)。

                        使用同樣的方法把consumer-demo一起註冊到Eureka上。

                        ribbon

                        上文只是將服務註冊到eureka上,但是consumer還是硬編碼呼叫,前文也有提到這種硬編碼方式肯定是不合理的,一來服務上線之後,IP地址肯定是變動的, 再則,採用硬編碼的方式是無法實現負載均衡的。

                        ribbon便是一個用來做負載均衡的元件。

                        點進spring-cloud-starter-netflix-eureka-client依賴,會發現,這個依賴中已經新增了spring-cloud-starter-netflix-ribbon,故在專案中可以直接使用ribbon,不用重新新增依賴。

                        在載入restTemplates的方法上新增@LoadBalanced註解,使其具有負載均衡的能力,然後將硬編碼的ip地址換成服務提供者的應用名字(application.name屬性的值),修改之後controller便變成了下面的樣子,其他地方不做修改。

                        @RestController
                        public class ConsumerController {
                        
                            @Bean
                            @LoadBalanced
                            public RestTemplate restTemplate(){
                                return new RestTemplate();
                            }
                        
                            @Autowired
                            private RestTemplate restTemplate;
                        
                            @RequestMapping("/consumer/demo")
                            public String ConsumerDemo(){
                                return this.restTemplate.getForObject("http://provider-demo:7900/provider/demo", String.class);
                            }
                        
                        }

                          重啟一下Eureka,provider-demo,consumer-demo,分別訪問localhost:7900/provider/demohttp://localhost:7910/consumer/demo應該都能得到ProviderDemo 這個結果,說明consumer成功呼叫了provider中的方法。便解決了不採用硬編碼的方式,使得consumer-demo呼叫provider-demo介面。

                          ribbon預設的負載均衡策略是輪詢的。測試一下:

                          首先啟動多個provider-demo專案,我這裡分別用的介面是7900和7901兩個介面,可以在Eureka中看一下provider的資訊:

                          這裡寫圖片描述

                          然後在consumer-demo專案的controller中新增下面的內容,程式碼應該很容易看懂就是模擬loadBalancerClient客戶端來訪問名字為provider-demo的服務介面,然後輸出訪問的介面埠號。

                           @Autowired
                           private LoadBalancerClient loadBalancerClient;
                          
                           @RequestMapping("/consumer/getInterfaceInfo")
                           public void getInterfaceInfo(){
                               ServiceInstance choose = loadBalancerClient.choose("provider-demo");
                               System.out.println(choose.getPort());
                           }

                            然後分別訪問一下多次訪問localhost:7910//consumer/getInterfaceInfo這個URL,可以看到控制檯輸出的資訊在下面,很容易看出是輪詢。

                            ribbon有多種修改負載均衡的策略,可以通過程式碼,也可以通過配置檔案,個人覺得配置檔案的方法比較方便,其他的方法可以自行百度:

                            在consumer-demo的application.yml檔案中新增下面的內容,這裡使用的是隨機的策略,該測試就不貼圖了,反正最後結果是隨機的:

                            #服務提供者的名字
                            provider-demo:  
                              ribbon:
                                # 所要採用的策略
                                NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

                              ribbon所有的策略可以參照下表:

                              策略名 策略描述 實現說明
                              BestAvailableRule 選擇一個最小的併發請求的server 逐個考察Server,如果Server被tripped了,則忽略,在選擇其中ActiveRequestsCount最小的server
                              AvailabilityFilteringRule 過濾掉那些因為一直連線失敗的被標記為circuit tripped的後端server,並過濾掉那些高併發的的後端server(active connections 超過配置的閾值) 使用一個AvailabilityPredicate來包含過濾server的邏輯,其實就就是檢查status裡記錄的各個server的執行狀態
                              WeightedResponseTimeRule 根據響應時間分配一個weight,響應時間越長,weight越小,被選中的可能性越低。 一個後臺執行緒定期的從status裡面讀取評價響應時間,為每個server計算一個weight。Weight的計算也比較簡單responsetime 減去每個server自己平均的responsetime是server的權重。當剛開始執行,沒有形成status時,使用roubine策略選擇server。
                              RetryRule 對選定的負載均衡策略機上重試機制。 在一個配置時間段內當選擇server不成功,則一直嘗試使用subRule的方式選擇一個可用的server
                              RoundRobinRule roundRobin方式輪詢選擇server 輪詢index,選擇index對應位置的server
                              RandomRule 隨機選擇一個server 在index上隨機,選擇index對應位置的server
                              ZoneAvoidanceRule 複合判斷server所在區域的效能和server的可用性選擇server 使用ZoneAvoidancePredicate和AvailabilityPredicate來判斷是否選擇某個server,前一個判斷判定一個zone的執行效能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用於過濾掉連線數過多的Server。

                              相關文章