Spring Cloud 微服務實戰


Eureka 服務治理

Maven dependency

  • 與spring boot的版本的對應
    1. Finchley兼容Spring Boot 2.0.x,不兼容Spring Boot 1.5.x
    2. Dalston和Edgware兼容Spring Boot 1.5.x,不兼容Spring Boot 2.0.x
  • Spring Boot 1.5.x
    1. org.springframework.boot:spring-cloud-starter-eureka-server
    2. org.springframework.boot:spring-cloud-starter-eureka
  • Spring Boot 2.0.x
    1. org.springframework.boot:spring-cloud-starter-netflix-eureka-server
    2. org.springframework.boot:spring-cloud-starter-netflix-eureka-client
  • parent
    1. org.springframework.boot:spring-boot-starter-parent
    2. org.springframework.cloud:spring-cloud-dependencies

服務注冊

  • 搭建注冊中心
    1. server.contextPath無法指定
    2. @EnableEurekaServer :注冊為Eureka服務端應用
    3. eureka.client.register-with-eureka=true: 注冊中心不需要注冊自己,但是搭建集群需要
    4. eureka.client.fetch-registry=true: 注冊中心也不需要發現服務,但是搭建集群需要
    5. eureka.instance.hostname :
      1. 注冊中心實例名字,單注冊中心設置為localhost,從而取消默認的registered-replicas
      2. 如果搭建集群,hostname需要和eureka.client.serviceUrl.defaultZone的host相同,否則會出現在unavaiable里
  • 搭建Client,注冊服務
    1. eureka.client.register-with-eureka : 注冊服務,默認true
    2. eureka.instance.instanceId : 實例名
      1. 默認 ${spring.cloud.client.hostname}😒{spring.application.name}😒{spring.application.instance_id:${server.port}}
      2. 隨即port使用${spring.cloud.client.hostname}😒{spring.application.name}😒{random.int[1000,99999]}
    3. eureka.instance.preferIpAddress : 是否優先使用IP作為主機名的標示
    4. eureka.instance.appname: 注冊服務名字,並不是service-id,默認取spring.application.name
    5. eureka.instance.hostname: 主機名,默認取操作系統主機名
    6. eureka.client.serviceUrl.defaultZone=http://localhost:8090/eureka : 服務注冊中心地址
    7. @EnableDiscoveryClient : 注冊為Eureka客戶端應用,spring boot app可以省略
    8. 客戶端除了java,還有很多其他語言的客戶端,也可以開發自己語言的客戶端
      1. eureka-js-client
      2. python-eureka

服務維護

  • 服務下線
    1. 當用spring boot actuator shutdown功能正確關閉spring boot 應用時,會向注冊中心發送下線(Down)請求,剔除服務
    2. 殺死進程等非正確關閉不會觸發
  • 服務剔除
    1. 注冊中心默認每隔1min觸發一次服務剔除任務,剔除失效的服務
    2. eureka.instance.lease-expiration-duration-in-seconds=90 : 服務失效時間,默認90s
  • 服務續約
    1. eureka.instance.lease-renewal-interval-in-seconds=30 : 服務續約心跳頻率,默認30s
    2. 服務在被剔除后還是能通過續約再次注冊
  • 自我保護
    1. 注冊中心紅色字體警告表示已經啟用自我i保護
    2. 15分鍾內心跳成功比例高於85%觸發自我保護,使得服務實例不會過期
      1. eureka.server.renewal-percent-threshold配置比例
    3. Renews threshold : Eureka Server 期望每分鍾收到客戶端心跳的總數。
      1. Renews threshold = Client實例數(60/心跳頻率,默認30)心跳成功率(默認85%)
    4. Renews (last min) : Eureka Server 最后 1 分鍾收到客戶端實例續約的總數。
    5. 如果Renews (last min) < Renews threshold,5分鍾后看到注冊中心紅色字體警告,表示有實例心跳異常,並且已經啟用自我保護
    6. 如果網絡恢復,一段時間后紅色字體警告消失
    7. eureka.server.enable-self-preservation=false : 本地調試建議關閉服務中心自我保護功能
    8. 在生產環境中自我保護功能十分必要

服務消費

  • 發現服務與調用
    1. spring-cloud-starter-eureka
    2. eureka.client.fetch-registry :獲取服務注冊列表,默認true
    3. eureka.clent.registry-fetch-interval-seconds=30 : 獲取注冊列表的時間間隔,默認30s
    4. @EnableDiscoveryClient : 注冊為Eureka客戶端應用
    5. RestTemplate.getForEntity("http://MD-SERVICE/MarkdownNote/menu/get", String.class)

Eureka集群 - High Available : 多個Eureka組成的集群組成了高可用,更健壯的注冊服務

  • Eureka集群需滿足以下條件
    1. 當兩個注冊中心相互設置對方url到eureka.client.serviceUrl.defaultZone,超過兩個的,在每個下配置所有其他服務地址,以","隔開
    2. 每個注冊中心的eureka.instance.hostname必須不同
    3. eureka.client.register-with-eureka和eureka.client.fetch-registry可以設置為false
    4. 每個注冊中心的spring.application.name可以相同也可不同
    5. 當滿足上面條件的時候,registered-replicas中會出現對方,這時候往這個注冊中心注冊服務的時候,請求會被轉發到registered-replicas中所有的注冊中心,同而實現服務同步
    6. 轉發只限一次,無法循環轉發
  • Client使用Eureka集群
    1. Eureka服務器不可用不會影響Client自身的啟動
    2. Client推薦設置所有注冊中心,以","隔開,Client按順序連接到第一個可用的Eureka注冊中心
    3. Client會默認每30s從當前連接的注冊中心獲取注冊列表,並且同時維護心跳並轉發,轉發失敗不會報錯
    4. 一旦Client獲取到某個服務,即使所有的注冊中心都不可用,Client也能使用此服務
    5. 當當前連接的Eureka服務器不可用,獲取列表和維護心跳都會嘗試3次retry,這3次會嘗試連接別的Eureka服務器,三次后還失敗,則等待下一個30s
  • Eureka集群的壓力只與Client的數量有關,與Client之間的請求交互量無關,一般無須做Eureka集群的load balance
    1. prefer-same-zone-eureka=false的時候執行以上規則
    2. prefer-same-zone-eureka=true的時候先通過region取availability-zones內的第一個zone,然后通過這個zone取service-url下的list執行以上規則

Ribbon 負載均衡

RestTemplate

  • 配合Spring MVC使用
    1. @RequestMapping( value= "/subject/{alias}/put", method=RequestMethod.PUT )
    2. @PathVariable String alias
    3. @RequestParam String param
    4. @RequestBody List subjects
  • 滿足兩個條件的RestTemplate會使用Ribbon自動裝載
    1. 是一個spring bean
    2. @LoadBalanced注釋

負載均衡器

BaseLoadBalancer

  • 默認使用RoundRobinRule
  • 啟動間隔為10秒的定時任務,檢查server是否健康

DynamicServerListLoadBalancer extends BaseLoadBalancer

ServerList : 從Eureka獲取實例清單和實例信息

  • DomainExtractingServerList : 默認配置
    1. spring cloud 的實現
    2. 構造函數傳入DiscoveryEnabledNIWSServerList

ServerListUpdater : 服務更新器

  • PollingServerListUpdater 默認配置
    1. ServerListUpdater的默認實現,通過間隔為30s的定時任務更新服務
  • EurekaNotificationServerListUpdater
    1. 利用Eureka的事件監聽器更新服務

ServerListFilter :更新服務的時候,對服務進行過濾

  • ZoneAffinityServerListFilter : 區域親和,ServerListFilter的抽象實現類
    1. 通過服務提供方和消費方的區域來篩選實例, 剔出不同區域
    2. 當前區域符合以下任何條件,則不執行區域親和,從而保證跨區域的高可用性
      1. 當前區域實例故障百分比 >= 80% : 如果只有1個可用,總量在5個或以上的區域不親和
      2. 實例平均負載 >=60%
      3. 可用實例數(總數-故障) < 2 : 只有1個可用不親和
    3. getFilteredListOfServers方法
  • DefaultNIWSServerListFilter Netflix默認配置
    1. 完全繼承ZoneAffinityServerListFilter,默認的NIWS(Netflix Internal Web Service)過濾器
  • ServerListSubsetFilter : 大規模集群
    1. 繼承ZoneAffinityServerListFilter,適合大規模服務器集群(百個)
    2. 第一,通過區域親和獲取服務清單作為候選清單
    3. 第二,同時從當前消費方的服務清單和候選清單剔除不健康的服務
      1. 實例並發連接數超過客戶端配置的值,默認0
      2. 實例失敗數超過配置,默認0
      3. 如果按照以上規則剔除后,剔除比例不滿10%(默認),則按健康算法排序后繼續剔除末尾的服務,直到滿足比例
      4. 改變默認設置
        1. {clientName}.{nameSpace}.ServerListSubsetFilter.eliminationConnectionThresold
        2. {clientName}.{nameSpace}.ServerListSubsetFilter.eliminationFailureThresold
        3. {clientName}.{nameSpace}.ServerListSubsetFilter.eliminationFailureThresold
    4. 第三, 從剔除后的候選清單隨即選出一批實例子集,默認20個
      1. {clientName}.{nameSpace}.ServerListSubsetFilter.size
  • ZonePreferenceServerListFilter: SpringCloud默認配置
    1. 除了這個是spring cloud新增的過濾器,其他都是Netflix Ribbon源生,繼承ZoneAffinityServerListFilter
    2. 第一,通過區域親和獲取服務清單作為候選清單
    3. 第二,通過Client配置預設的Zone屬性進行過濾,選出務提供方和消費方Zone設置相同的清單,如果為空,則使用原候選清單
    4. 與ZoneAffinityServerListFilter 區別在於永遠執行區域親和,不關心3個條件,不保證跨區域的高可用性,只有當當前區域沒有服務實例的時候才會使用ZoneAffinityServerListFilter 篩選的服務
    5. getFilteredListOfServers方法

ZoneAwareLoadBalancer extends DynamicServerListLoadBalancer

  • SpringCloud默認配置
  • ZoneAwareLoadBalancer會通過ZoneAvoidanceRule選擇合適的區域,合適的服務器
  • ZoneAwareLoadBalancer重寫了chooseServer方法,在服務方的Zone只有一個的時候(也就是DynamicServerListLoadBalancer 的區域親和生效的時候),使用BaseLoadBalancer默認的RoundRobinRule,否則使用ZoneAvoidanceRule
  • DynamicServerListLoadBalancer已經是區域親和,為什么還需要ZoneAwareLoadBalancer
    1. DynamicServerListLoadBalancer 采用BaseLoadBalancer默認的RoundRobinRule
    2. 如果消費方的Zone和服務方所有的Zone都不同,就會得到所有Zone的服務器,區域親和實效

負載均衡策略

RandomRule

  • 隨機選擇

RoundRobinRule

  • 按順序選擇
  • 10次后退出循環

RetryRule

  • 對RoundRobinRule進行重試一定時間

WeightedResponseTimeRule

  • 30s的定時任務對服務實例進行權重計算
  • 權重計算方式
    1. 計算出所有實例的總的平均響應時間,totalResponseTime
    2. 權重區間=totalResponseTime - 實例自己的avgResponseTime
    3. 權重區間寬度越大,被選中概率越大
    4. 比如A,B,C的avgResponseTime為10,20,40,那么 權重區間為(0-60],(60-110],(110-140)
  • 選擇實例
    1. 從0-140產生一個隨機數,比如70,那么就選擇70所在權重區間的B

BestAvailableRule

  • 過濾掉故障實例,選出當前並發數最小的實例,最空閑的那個

AvailabilityFilteringRule

  • 先按RoundRobinRule選擇
  • 然后按以下條件過濾
    1. 是否故障
    2. 並發請求大於閾值,默認232-1
  • 如果滿足任意一個,就重復選擇
  • 重復10此后,還是不滿足過濾條件,直接使用RoundRobinRule

ZoneAvoidanceRule

  • SpringCloud默認配置
  • ZoneAwareLoadBalancer只有Zone的個數>1,才使用ZoneAvoidanceRule ,否則使用RoundRobinRule
  • 先按以下規則剔除符合的Zone區域
    1. Zone的實例為0
    2. Zone實例平均負載<0
    3. 實例故障率大於閾值,默認0.99999,實例故障率=段濾器斷開次數/實例數
    4. 然后剔除平均負載最高的Zone
  • 如果上面規則沒有剔除任何Zone
    1. 如果實例最大平均負載小於閾值,默認20%,則返回所有Zone為可用Zone
      1.如果實例最大平均負載大於閾值,隨機剔除一個高負載的區域
  • 如果0<可用Zone<總Zone,則隨機選擇一個Zone,否則使用所有Zone
  • 從返回的Zone過濾實例
    1. 通過主過濾條件和次過濾條件進行實例過濾
    2. 任何過濾之后,滿足2條件中的任一,停止過濾
  • 過濾后的實例進行RoundRobinRule

Region / Zone

Eureka Server的Zone

  • 通過eureka.client.prefer-same-zone-eureka=true開啟client對Eureka Server的區域親和功能
  • client的availability-zones的第一個zone寫成client自己的zone(eureka.instance.metadata-map.zone的值)
  • 這樣client就會找到availability-zones的第一個zone,找到service-url相應的zone的Eureka Server的服務器list,去注冊服務並且維護心跳

Client的Zone

  • 同一個Zone可以屬於不同的Region,但是這是沒有意義的,spring cloud之根據Zone進行服務器分組,所有原則上一個Zone只屬於一個Region
  • eureka.client.region : 設置region
  • eureka.client.availability-zones.{ragion}=sh,bj,sz : 設置當前region所有的zone
  • eureka.instance.metadataMap.zone=sh : 設置client自己的zone

參數配置

  • 全局參數 : ribbon.ConnectTimeOut=250
  • 客戶端參數 : hello-service.ribbon.ConnectTimeOut=250
    1. 你當前實例調用的hello-service的配置,而不是你當前實例的配置
    2. 客戶端參數覆蓋全局參數
  • 如果沒有Eureka,可以使用以下客戶端參數 配置實現Ribbon負載均衡
    1. hello-service.ribbon.listOfServers=localhost:8080,localhost:8081,localhost:8082
  • 更多參數查看
    1. com.netflix.client.config.CommonClientConfigKey

重試機制

  • spring.cloud.loadbalancer.retry.enabled=true
    1. 開啟重試機制
  • ribbon.OkToRetryOnAllOperations
    1. 對所有操作進行重試
  • ribbon.ConnectTimeout
    1. 請求連接的超時
  • ribbon.ReadTimeout
    1. 請求處理超時時間
  • ribbon.MaxAutoRetries
    1. 對當前實例的重試次數
  • ribbon.MaxAutoRetriesNextServer
    1. 切換實例的重試次數
  • hystrix.command.defalut.execution.isolation.thread.timeoutInMilliseconds > ribbon.ConnectTimeOut
    1. 斷路器的超時要大於ribbon的超時,否則不觸發重試

Hystrix 容錯保護

Getting Start

  • Maven dependency
    • springboot 1.5.x
      1. org.springframework.cloud:spring-cloud-starter-hystrix
    • springboot 2.0.x
      1. org.springframework.cloud:spring-cloud-starter-netflix-hystrix
  • @EnableCircuitBreaker : 開啟斷路器功能
  • @SpringCloudApplication : 包含了斷路器功能
    1. @SpringBootApplication
    2. @EnableDiscoveryClient
    3. @EnableCircuitBreaker
  • @HystixCommand(fallbackMethod="helloFallBack") : 需要容錯保護的方法
  • 定義回調方法helloFallBack
    1. helloFallBack所執行的操作被稱為服務降級
    2. 與@HystixCommand修飾的方法具有相同返回類型和參數
    3. 在參數里添加Throwable e來獲取原操作拋出的異常
  • 會引起服務降級的情況
    1. 斷路器打開
    2. 當前命令的線程池,請求隊列或者信號量占滿
    3. 被保護的操作拋出異常,比如網絡錯誤
    4. 網絡超時,Hystrix默認超時時間為2s
  • 降級操作可以再次使用HystixCommand,形成級聯的降級策略
  • 非annonation的方式支持請求緩存功能

Hystrix的功能與作用

  • 控制被依賴服務的延時和失敗

斷路器原理

  • isOpen() : 斷路器是否打開
    1. 斷路器標識circuitOpen=true,返回true
    2. 判斷度量指標metrics的HealthCounts統計對象
      1. 這個統計對象由一個默認間隔為10s的定時任務維護
      2. 如果滿足以下全部的兩個條件,將circuitOpen設置為true,並返回true
        1. 請求總數QPS超出閾值,默認20,
        2. 錯誤百分比超出閾值,默認50%
    3. 如果斷路器從關閉切換至打開,將當前時間記錄到circuitOpenedOrLastTestedTime中
  • allowRequest(): 判斷請求是否被允許
    1. 判斷是否強制打開或者強制關閉,如果強制關閉,所有請求都允許,但是isOpen還是會執行,用於模擬斷路器打開/關閉
    2. 通過isOpen判斷斷路器狀態,斷路器關閉,則返回true
    3. 如果斷路器打開,則調用allowSingleTest(),通過circuitOpenedOrLastTestedTime判斷斷路器是否在休眠期內,默認5s
      1. 如果是在休眠期,則返回false
      2. 如果不在休眠期,則返回true,並設置當前時間到circuitOpenedOrLastTestedTime,設置斷路器狀態為"半開"
  • 當斷路器"半開",請求會被允許
    1. 請求成功,調用markSuccess(),關閉斷路器
    2. 請求失敗,重新打開斷路器
  • 通過以上原理可以看出,斷路器並不是用於標識網絡錯誤,請求拋錯,網絡超時等信息的,這些信息會直接引起服務降級,斷路器是用於表示網絡壓力和網絡異常比例的
  • 斷路器的開閉策略保證了不會因為某個服務的網絡異常影響整個服務鏈,不會因為某個服務的網絡問題造成服務積壓,影響其他服務性能

依賴隔離

  • 傳統的服務對線程池線程個數的限制是作用於整個服務進程實例的,當線程池占滿之后,進程下的所有服務都需要等待
  • Hystrix對每個當前進程所依賴的服務創建獨立的線程池,好處如下
    1. 某個依賴服務出現延遲也不會影響其他服務
    2. 當依賴服務出現配置問題,能快速的反映出問題,同時可以通過spring cloud config/bus 動態解決問題
  • 每個專有線程池提供內置的並發實現,從而創建異步訪問(就是請求合並?)
  • 這種設計會給系統帶來更多的負載和開銷,但是Netflix通過實驗證明這種開銷是很小的
  • 對於對延遲有極高要求的系統,Hystrix設計了另外的解決方案:信號量
    1. 信號量比線程池的開銷小,但是不能實現異步,不能設置超時,所以要求依賴服務足夠可靠
    2. execution.isolation.strategy=SEMAPHORE,使用信號量代替線程池
    3. 信號量默認值為10,估算方法與線程池並發度的估算類似,標准如下
      1. 訪問內存的請求一般耗時1ms以內,性能可以達到5000rps的,可以設置為1或2?

使用詳解

  • 非Annonation方式
  • @HystixCommand
    1. fallbackMethod="helloFallBack" : 需要容錯保護的方法
    2. ignoreExceptions=HystrixBadRequestException.class : 使得服務降級忽略某個exception,HystrixBadRequestException默認忽略
    3. threadPoolKey=''pool-a' : 線程池分組名稱,默認等於groupKey,建議不用默認
    4. groupKey="group-a" : 命令組名,按組統計命令告警,儀表盤等信息,默認等於類名
    5. commandKey="hello" : 命令名稱,默認方法名
    6. commandProperties={@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="500")} : 屬性配置
  • 請求緩存
    1. @CacheResult(cacheKeyMethod="helloKey")
      1. 必須與@HystixCommand合用
    2. @CacheKey(value="參數同名"):
      1. 定義在參數上,如果沒有使用所有參數作為key
      2. cacheKeyMethod比他優先級高
      3. 支持使用參數的屬性作為key,只要將value=屬性名字
    3. @CacheRemove
      1. 定義在一些寫操作的請求上面,使得緩存能被更新
      2. commandKey:需要被緩存的請求的命令名稱,必須指定
    4. Hystix的緩存局限性很強,只適合一些簡單的緩存需求,復雜需求建議使用緩存框架
  • 請求合並
    1. 將一段時間窗內(默認10ms)的請求合並,進行批處理,服務提供方需要提供批處理接口
    2. @HystrixCollapser
      1. 注解在需要進行合並的操作上面,這里是findUser,返回User對象
      2. batchMethod="findAllUser" : 調用批處理接口的方法,返回一個User的List,需要用@HystixCommand修飾
      3. collapserProperties={@HystrixProperty(name="timerDelayInMilliseconds",value="100")} : 設置批處理的配置屬性,這里設置批處理時間窗為100毫秒
    3. 請求合並能夠減少通訊次數,減少線程池的使用量,但是也會帶來相應的額外開銷
      1. 請求合並會增加請求的延遲,最壞的情況延遲=原請求延遲+時間窗
      2. 原本就高延遲的請求適合使用請求合並
      3. 高並發系統適合使用請求合並,如果一個時間窗里只有寥寥幾個請求,不適合使用
    4. 請求合並功能目前不提供開啟/關閉屬性來控制,這樣使得代碼不夠靈活

屬性配置

  • 配置優先級由低到高
    1. 全局默認值 : 默認值,無需配置
    2. 全局配置屬性: hystrix.command.default,spring cloud config和spring cloud bus實現
    3. 實例默認值 : 默認值,無需配置
    4. 實例配置屬性: hystrix.command.HystrixCommandKey,spring cloud config和spring cloud bus實現
  • 可以使用配置文件,也可以使用注解的commandProperties和collapserProperties等

hystrix.command.default/HystrixCommandKey

execution: 執行相關 控制HystrixCommand.run() 執行

  • 執行的隔離策略: execution.isolation.strategy
    1. 線程池隔離:THREAD(默認) / 信號量隔離:SEMAPHORE
    2. 信號量適用於接口並發量高的情況,如每秒數百次調用的情況,導致的線程開銷過高, 適用於非網絡調用,執行速度快,可靠
  • 超時時間: execution.isolation.thread.timeoutInMilliseconds
    1. 全局默認1000,實例默認2000
  • 是否啟用超時設置: execution.timeout.enabled
    1. 默認true
  • 超時是否中斷正在執行的run : execution.isolation.thread.interruptOnTimeout
    1. 默認true
  • 執行取消動作時,是否中斷正在執行的run: execution.isolation.thread.interruptOnCancel
    1. 默認true
  • 設置最大的信號量: execution.isolation.semaphore.maxConcurrentRequests
    1. 只對於使用信號量[SEMAPHORE]策略的生效, 默認10
    2. 且該值必需小於容器的線程池大小,否則並不起保護作用,因為其實容器線程池的一小部分而已

fallback: 控制HystrixCommand.getFallback() 執行 對於線程池或者信號量執行策略都生效

  • 最大的並發調用getFallback() : fallback.isolation.semaphore.maxConcurrentRequests
    1. 默認10, 如果超出該數,則后續的會被拒絕,並拋出異常
  • 是否啟用降級服務: fallback.enabled
    1. 默認true

circuitBreaker: 斷路器 控制HystrixCircuitBreaker

  • 是否開啟斷路器用於健康監控和短路請求 : circuitBreaker.enabled
    1. 默認true
  • 請求總數QPS的最小值:circuitBreaker.requestVolumeThreshold
    1. 默認20,超出20才有可能開啟斷路器,開啟的條件之一
  • 錯誤百分比最大值:
    1. 默認50%,超出有可能開啟斷路器,開啟的條件之一
  • 斷路器休眠時間:circuitBreaker.sleepWindowInMilliseconds
    1. 默認5000毫秒,如果不在休眠期,則設置斷路器狀態為"半開",允許此次請求
  • 是否強制打開斷路器:circuitBreaker.forceOpen
    1. 默認false,如果打開則會拒絕左右的請求
    2. 優先級比circuitBreaker.forceClosed高
  • 是否強制關閉斷路器: circuitBreaker.forceClosed
    1. 默認false,true則允許所有的請求

requestContext: 請求上下文,控制HystrixRequestContext 被HystrixCommand使用

  • 是否開啟請求緩存: requestCache.enabled
    1. 默認true
  • 是否記錄HystrixCommand執行或者事件的日志到HystrixRequestLog : requestLog.enabled
    1. 默認true

metrics[一般不需要設置] : 度量HystrixCommand 和 HystrixObservableCommand 的執行情況

  • 設置滾動時間窗的長度 :metrics.rollingStats.timeInMilliseconds
    1. 默認10000毫秒,將這10000毫秒分成10個桶,統計每個桶采集健康度等信息,供斷路器使用
    2. 該項不可以動態修改,以防止統計的不正確
  • 設置滾動的統計窗口被分成的桶的數量:metrics.rollingStats.numBuckets
    1. 默認10
    2. metrics.rollingStats.timeInMilliseconds % metrics.rollingStats.numBuckets == 0 這個必須成立(整除),否則會拋異常
  • 是否開啟百分數統計: metrics.rollingPercentile.enabled
    1. 默認tru, 如果為false,則所有概要統計值為-1
  • 設置百分數統計滾動時間窗的長度 :metrics.rollingPercentile.timeInMilliseconds
    1. 默認60000
  • 設置百分數統計的桶的數量:metrics.rollingPercentile.numBuckets
    1. 默認6
  • 設置百分數統計的桶的最大數量,metrics.rollingPercentile.bucketSize
    1. 默認100
  • 斷路器健康快照的采集時間窗的長度:metrics.healthSnapshot.intervalInMilliseconds
    1. 默認500ms

hystrix.collapser.default/HystrixCollapserKey

  • 設置批處理合並在多少時間內的請求 : timerDelayInMilliseconds
    1. 默認10毫秒
  • 一次最多合並的請求數: maxRequestsInBatch
    1. 默認Integer.MAX_VALUE
  • 批處理是否開啟請求緩存 :requestCache.enabled
    1. 默認true

hystrix.threadpool.default/HystrixThreadPollKey

  • 設置依賴服務的命令執行的線程數 : coreSize
    1. 默認10
  • 線程池的最大隊列大小 : maxQueueSize
    1. 默認-1,這時候使用SynchronousQueue,其他則為
    2. LinkedBlockingQueue達到限制時,請求會被拒絕(服務降級?),無法動態改變
  • 設置拒絕請求的隊列最大值
    1. 默認5,如果maxQueueSize=-1時無效
    2. 通過設置此值,即使LinkedBlockingQueue沒有達到限制,也能拒絕請求,解決LinkedBlockingQueue無法動態改變的問題
  • 設置滾動時間窗的長度:metrics.rollingStats.timeInMilliseconds
    1. 統計線程池的度量指標,默認10000毫秒
  • 設置桶數: metrics.rollingStats.numBuckets
    1. 默認10

Hystrix Dashboard

Getting Start

  • Maven dependency

    • springboot 1.5.x
      1. org.springframework.cloud : spring-cloud-starter-hystrix-dashboard
    • springboot 2.0.x
      1. org.springframework.cloud : spring-cloud-starter-netflix-hystrix-dashboard
      2. com.netflix.hystrix : hystrix-metrics-event-stream
  • @EnableHystrixDashboard

  • ServletRegistrationBean

      @Bean
      public ServletRegistrationBean<HystrixMetricsStreamServlet> registration(){
          HystrixMetricsStreamServlet servlet = new HystrixMetricsStreamServlet();
          ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<HystrixMetricsStreamServlet>();
          registrationBean.setServlet(servlet);
          registrationBean.setEnabled(true);//是否啟用該registrationBean
          registrationBean.addUrlMappings("/hystrix.stream");
          return registrationBean;
      }
    
  • 調用帶有@HystrixCommand的服務

儀表盤詳解

Turbine集群監控

  • Maven dependency

    • springboot 1.5.x
      1. org.springframework.cloud : spring-cloud-starter-turbine
    • springboot 2.0.x
      1. org.springframework.cloud : spring-cloud-starter-netflix-turbine
  • @EnableTurbine

  • 不再需要ServletRegistrationBean

  • turbine.app-config

    1. 需要監控的服務名,以逗號隔開,不支持*
  • turbine.cluster-name-expression="default"

    1. 可以自定義當前的集群名字cluster-a,dashboard使用http://host:port/turbine.stream?cluster=cluster-a來監控
    2. 當為default時,dashboard使用http://host:port/turbine.stream來監控
    3. 當app太多的時候,可以啟動多個turbine,但是dashboard只需要一個
  • turbine.aggregator.clusterConfig=cluster-a

    1. 當turbine.cluster-name-expression="cluster-a"時,需要設置此值為cluster-a
    2. 默認default,所以當turbine.cluster-name-expression="default"的時候不需要設置
  • turbine.combine-host-port=true

    1. 默認true,如果為false,會只用host來區分服務,這樣會導致turbine只監控一個服務器上turbine.app-config配置的第一個服務,因為它認為其他服務都是同一個
  • commandKey

    1. 不同服務實例,相同的commandKey會被認為是同一個服務,合並監控,默認等於方法名
    2. 相同服務的不同實例由於commandKey相同,會被合並監控
  • threadPoolKey

    1. 不同服務實例相同的threadPoolKey會被認為是相同thread pool,默認等於類名

與消息代理結合

  • [?]

Feign 服務客戶端

Getting Start

  • Maven Dependency
    1. springboot 1.5.x
      1. org.springframework.cloud : spring-cloud-starter-feign
    2. springboot 2.0.x
      1. org.springframework.cloud : spring-cloud-starter-openfeign
  • @EnableFeignClients
    1. basePackages="com.citi.cv.rates" : 需要掃描的包路徑,如果@FeignClient在其他依賴包里面,需要手動設置
  • 用@FeignClient("hello-service")修飾自定義的hello service 的interface作為client
    1. 可以使用配置來設置service-id: @FeignClient("${rates.ds.appname}")
  • 用@RequestMapping("/hello")修飾interface的方法
    1. interface的方法如果有參數,需要與hello-service的Controller的參數保持一致,包括@PathVariable,@RequestBody等注解
    2. 這里的注解的value值與Controller的默認方式不同,必須要顯示的聲明@PathVariable("name")

Feign 特性

  • Feign 繼承特性
    1. Service提供方可以通過引入FeignClient interface包來讓Controller繼承
      1. 這樣Controller便不再需要重復RequestMapping的操作
      2. 但是參數還是需要@PathVariable等配置,這里可以顯示的聲明@PathVariable("name"),也可以不顯示聲明
      3. Service端的FeignClient interface的@FeignClient的名字並沒有實際意義
    2. 服務消費方也可以通過引入FeignClient interface包來直接調用Serivce提供的服務
    3. FeignClient應該由服務提供方提供,服務提供方在構建和維護FeignClient的時候,需要注意開閉原則和方法等的注釋,以提供友好的服務
  • Feign整合了Ribbon
    1. 一切Ribbon的配置和直接使用Ribbon沒有區別
  • Feign整合Hystrix
    1. Feign默認給所有FeignClient方法封裝Hystrix

    2. 一切Hystrix的配置和直接使用Hystrix沒有區別

    3. 禁用Hystrix

      1. 全部禁用 : feign.hystrix.enable=false

      2. 單個禁用 : FeignClient(name="hello-service",configuration=DisableHystrixConfiguration.class)

         @Configuration
         public class DisableHystrix {
             @Bean
             @Scope("prototype")
             public Feign.Builder feignBuilder() {
                 return Feign.Builder();
             }
         }
        
    4. 服務降級與Hystrix存在差異

      1. 需要實現FeignClient interface,然后使用FeignClient(name="hello-service",fallback=HelloFallBack.class)
      2. 也可以使Feign禁用Hystrix,轉而在本地的服務上面自己使用Hystrix進行服務降級
    5. 使用請求合並

      1. 在本地的服務上面自己使用Hystrix進行請求合並
  • 請求壓縮 : 使用GZIP進行請求和響應的壓縮
    1. feign.compression.request.enable=true
    2. feign.compression.response.enable=true
    3. feign.compression.request.mime-types
      1. 指定壓縮的請求類型
      2. 默認值:text/xml,application/xml,application/json
    4. feign.compression.request.min-request-size
      1. 指定開啟壓縮功能的請求數據大小的下限
      2. 默認2048
  • 日志配置
    1. 默認不會輸出任何日志,因為Feign默認的日志級別是feign.Logger.Level.NONE

    2. 設置全局日志級別

       @Bean
       Logger.Level feignLoggerLevel() {
           return Logger.Level.FULL
       }
      
    3. 單獨設置日志級別 : FeignClient(name="hello-service",configuration=FullLogConfiguration.class)

       @Configuration
       public class FullLogConfiguration{
           @Bean
           Logger.Level feignLoggerLevel() {
               return Logger.Level.FULL
           }
       }
      
    4. 日志級別

      1. NONE : 不記錄任何信息
      2. BASIC : 記錄請求方法,URL,響應狀態碼,執行時間
      3. HEADERS : BASIC, 請求和響應頭信息
      4. FULL : 所有請求響應的明細: 頭信息,請求體,元數據等
    5. feign的日志是DEBUG級別的,通過配置開啟DEBUG

      1. logging.level.com.service.hello.HelloService=DEBUG

Gateway

Gatting Start

Predicate

  • Before / After / Between : 時間斷言
  • Cookie / Header : 匹配name和一定規則的value
  • Host / Method/Path
  • Query : param name和value
  • RemoteAddr : 限定ip

Filters

  • AddRequestHeader / AddResponseHeader / SecureHeaders :添加頭信息
  • SetResponseHeader / RewriteResponseHeader:修改頭信息
  • RemoveRequestHeader / RemoveResponseHeader /RemoveNonProxyHeaders / PreserveHostHeader : 刪除頭信息
  • Hystrix / FallbackHeaders
    1. 需要引入spring-cloud-starter-netflix-hystrix依賴,開啟斷路器功能,實現服務降級
    2. fallbackUri : 支持本地app的path uri。是否直接支持遠程uri?
    3. FallbackHeaders用於添加異常詳細信息
  • RequestRateLimiter : TO-DO
  • SaveSession : TO-DO
  • RewritePath / SetPath / PrefixPath / StripPrefix / RedirectTo : 修改路徑
  • SetStatus
  • RequestSize : 限制request大小
  • Retry :目前不支持帶body的retry

修改RequestBody

@Bean
fun routes(builder: RouteLocatorBuilder): RouteLocator {
    return builder.routes().route(routeId) { predicateSpec ->
        predicateSpec.path(predicatePath).filters { gatewayFilterSpec ->
            gatewayFilterSpec.modifyRequestBody(String::class.java, String::class.java, MediaType.APPLICATION_JSON_VALUE) { _, body ->
                Mono.just( body.toUpCase() )
            }
        }.uri(routeUri)
    }.build()
}

Global Filter

  • To-Do

Actuator API

  • TO-DO

其他

  • Reactor Netty Access Logs?
  • CORS Configuration?

Zuul 網關服務

路由詳解

  • Maven dependency
    1. org.springframework.cloud : spring-cloud-starter-netflix-zuul
  • @EnableZuulProxy
  • 單實例配置
    1. zuul.routes.route-name.path=/user-service/**
    2. zuul.routes.route-name.url=http://host:port/
  • 多實例配置
    1. zuul.routes.route-name.path=/user-service/**
    2. zuul.routes.route-name.serviceId=user-service
    3. ribbon.eureka.enabled=false
      1. Spring cloud Zuul默認與eureka集成實現serviceId,這里沒有eureka,所以關閉此集成功能
    4. user-service.ribbon.listOfServers=http://host1:port1/,http://host2:port2/
  • 集成eureka
    1. 路由配置
      1. zuul.routes.route-name.path=/user-service/???
      2. zuul.routes.route-name.serviceId=user-service
    2. 簡易寫法
      1. zuul.routes.user-service=/user-service/???
    3. 默認規則
      1. zuul.routes.route-name.path=/user-service/???這種寫法,默認會將user-service作為真實url的前綴
      2. 所以使用zuul的spring could application建議不要使用content path,從而省略不必要的前綴,否則前綴過多
      3. spring cloud zuul默認將所有eureka的服務配置了路由,前綴默認等於eureka的服務名字
      4. zuul.ignored-services : 忽略某些service的路由,*代表全部忽略,然后用簡易寫法開啟某些
  • 自定義路由規則
    1. PatternServiceRouteMapper : 通過正則表達式自定義服務名與路由的規則
    2. 只需要用@Bean new一個PatternServiceRouteMapper 的實例
  • 通配符
    1. 符號? 匹配任意單個字符
    2. 符號* 匹配任意多個字符
    3. 符號** 支持多級目錄結構
  • 路由規則沖突
    1. 先配置路由規則的優先
    2. properties配置文件是不保證順序的,但是yaml是保證的
  • 忽略表達式
    1. zuul.ignored-patterns=/xx/hello/xx
    2. 忽略所以路由的hello,注意是所有,建議配置忽略表達式的時候帶上service name,以免將其他服務的錯誤忽略了
  • forward 本地跳轉
    1. 不知道有什么作用?
  • Cookie與頭信息
    1. Zuul默認忽略請求中的敏感信息
    2. 通過zuul.sensitiveHeaders配置,默認包括Cookie,Set-Cookie,Authorization三個屬性
    3. 這樣做的原因
      1. 微服務價格,無狀態的RESTful API占大多數
      2. 網關本事就可以處理安全方面的過濾
      3. 有狀態的可以放在網關之外
    4. 關閉忽略的方法
      1. zuul.sensitiveHeaders=Cookie : 用覆蓋的方式關閉除了Cookie之外的其他頭信息的忽略,設為空關閉全部忽略,不推薦
      2. zuul.routes.[route].customSensitiveHeaders=true : 對指定路由開啟自定義敏感頭
      3. zuul.routes.[route].sensitiveHeaders= :設置指定路由的敏感頭
    5. 重定向問題
      1. 請求后的重定向跳轉,會直接跳轉到路由后的具體實例的host:port
      2. zuul.addHostHeader=true : 將當前網關服務的host信息加到轉發的host斗信息里,解決重定向問題
  • Ribbon和Hystrix
    1. 當用serviceId進行配置時,會啟用Ribbon和Hystrix,配置參數與單獨使用Ribbon和Hystrix時一樣
    2. zuul實現了服務降級,返回特定的JSON信息
    3. 如果Ribbon timeout小於Hystrix timeout,zuul默認配置了重試功能
      1. zuul.retryalbe=true
      2. zuul.routes.[route].retryalbe=true
      3. 添加Maven dependency
        1. org.springframework.retry : spring-retry
  • 動態路由
    1. 利用spring cloud config 動態刷新機制實現動態路由功能

    2. @RefreshScope修飾zuul配置類

       @RefreshScope
       @ConfigurationProperties("zuul")
       public ZuulProperties zuulProperties() {
           return new ZuulProperties();
       }
      

過濾器詳解

  • ZuulFilter
    1. 繼承ZuulFilter並且注冊為spring bean的filter默認會被啟用
  • pre : 請求被路由之前調用
    1. [-3] ServletDetectionFilter :
      1. 檢查請求是否通過Spring Dispatcher。通過isDispatcherServletRequest設置
      2. 通過RequestUtils.isDispatcherServletRequest()獲取
    2. [-2] Servlet30WrapperFilter : 包裝HttpServletRequest
    3. [-1] FormBodyWrapperFilter : 解析表單數據,並為請求重新編碼
    4. [1] DebugFilter :
      1. 當這個過濾器被開啟的時候,會設置RequestContext.setDebugRouting() ,RequestContext.setDebugRequest()為true
      2. 所以filter都能拿到這兩個值,可以判斷該值做一些debug操作,比如debug日志,更多返回信息等
      3. 開啟方式
        1. zuul.debug.request=true
        2. 在請求上加參數debug=true
    5. [5] PreDecorationFilter : 對請求進行路由前的預處理
  • routing : 路由請求時被調用
    1. [10] RibbonRoutingFilter : 對serviceId配置生效,通過Ribbon和Hystrix進行路由
    2. [100] SimpleHostRoutingFilter : 對url配置生效,不使用Ribbon和Hystrix,只用http client進行路由
    3. [500] SendForwardFilter : 處理forward本地跳轉
  • post : routing后被調用, pre,routing之后的error之后也會再調用post
    1. [0] SendErrorFilter
      1. 在有error.status_code參數時生效
      2. 處理有錯誤的請求
    2. [1000] SendResponseFilter : 處理響應信息
  • error: 異常處理,在pre,routing, post 拋出異常之后被調用
    1. pre,routing階段的filter的異常被error處理之后,會再調用post。post階段的filter的異常被error處理之后不會再調用post
    2. 沒有默認的error階段的過濾器
    3. 自定義的Filter[pre,routing]拋出異常的時候,如果不設置error.status_code參數,不會被post的SendErrorFilter處理
    4. 可以通過RequestContext.set設置error.status_code,error.message,error.exception。但是這種方式需要在所有filter處理
    5. 自定義error filter設置error.status_code,error.message,error.exception,這樣就能被SendErrorFilter處理了
    6. 但是post階段的filter的異常被error filter處理之后不會再調用post,所以還是不能被SendErrorFilter處理
    7. 自定義另一個error filter,繼承SendErrorFilter,優先級必須大於第一個自定義的error filter,並且只處理post之后的異常
      1. 只處理post之后的異常可以通過自定義的post階段的filter設置自定義屬性值請求上下文RequestContext里
      2. 也可以通過繼承FilterProcessor方式
    8. 自定義error處理
      1. 通過自定義post過濾器,並且禁用SendErrorFilter
      2. 通過修改spring boot的/error端點
  • 禁用過濾器
    1. zuul.SendErrorFilter.post.disable=true
    2. 由於使用的是SimpleClassName,所以需要保證過濾器名字不能重復

Config 配置中心

Getting Start

  • 搭建配置中心

    1. Maven dependency
      1. org.springframework.cloud : spring-cloud-config-server
    2. @EnableConfigServer
    3. spring.cloud.config.server.git.uri=ssh://git@cedt-icg-bitbucketcli.nam.nsroot.net:7999/cvp/rates-cloud.git
    4. spring.cloud.config.server.git.searchPaths=rates-config/src/main/resources
    5. spring.cloud.config.server.git.username=username
    6. spring.cloud.config.server.git.password=password
  • 配置規則

    1. 通過host:port/配置規則 訪問配置中心的配置文件:applicationname-profile.properties/yml
      1. /applicationname/profile/lable
      2. /lable/applicationname-profile.properties/yml
      3. applicationname-profile.properties/yml
    2. 說明
      1. lable代表git branch
      2. lable不寫默認master branch
      3. 只支持properties/yml 文件
      4. 支持將properties/yml文件互相轉換
      5. 以下配置文件會被映射
        1. applicationname-profile.yml
        2. applicationname-profile.properties
        3. applicationname.yml 里的[--- profiles]
        4. applicationname.properties
        5. application.yml 里的[--- profiles]
        6. application.properties
        7. application-profile.yml 里的[--- profiles2]
        8. application-profile.properties
      6. 如果用/lable/applicationname-profile.properties/yml,會合並屬性,后加載的覆蓋先加載的
      7. 支持通配符,比如*,但是如果以通配符開始的,要用雙引號引起來
    3. 使用技巧
      1. application.yml: 存放所有項目的公用屬性
      2. lable: ${spring.profiles.active}:
        1. 通過不同的branch分不同的環境
        2. 但是每個環境也可以保持所有的環境profiles,這樣可以在不同環境的branch進行merge
        3. 如果想保持每個環境的屬性的整潔性,則無法進行merge,需要手動提交所有的branch,這樣難免會有遺漏,可以注釋所有不同環境的屬性,方便對比branch,避免遺漏
        4. local可以使用本地文件系統
      3. profile: module-name: application-module-name.yml:存放同一個module下的不同服務的mudule公用屬性
      4. app name: appname.yml : 存放一個project或者instance的獨有屬性
      5. 一個project如果使用不同的appname部署了多個instance,如何將它們的公用屬性集合存放
        1. 使用第二個profile: project,以逗號隔開。application-project.yml
        2. 多個profile,排在后面的后加載,也就是后面的擁有更高的優先等級,會覆蓋前面的,applicationname.yml優先級更高
        3. application-profile.yml里寫第二個profile,第一個profile要排在第二個profile前面,否則加載不到
  • 搭建client

    1. Maven dependency
      1. org.springframework.cloud : spring-cloud-starter-config
    2. bootstrap.yml
      1. spring.application.name=test
      2. spring.cloud.config.profile=local
      3. spring.cloud.config.label=master
      4. spring.cloud.config.uri=http://localhost:9601

配置中心詳解

  • 使用Git作為默認倉庫的好處
    1. Git的版本控制功能,可以滿足一些根據不同版本啟動不同配置的服務的特殊要求
    2. Git的Hook功能可以有效的監控配置內容的修改
  • Git 配置多個倉庫
    1. uri占位符
      1. {application},{profile},{label}除了用於配置規則之外,還用於git uri和searchPaths的占位符
      2. ssh://git@cedt-icg-bitbucketcli.nam.nsroot.net:7999/cvp/{application}.git可以實現根據application name動態配置多個git倉庫
    2. 配置repos
      1. 以spring.cloud.config.server.git.uri/searchPaths方式配置一個主倉庫,主倉庫會在啟動的時候clone到本地,其他的只在請求的時候clone
      2. spring.cloud.config.server.git.repos.applicationname.uri/searchPaths/pattern
      3. pattern=applicationname/profile/label,支持通配符,比如applicationname/prod*,多個pattern以逗號隔開
    3. 利用配置repos和searchPaths占位符{application}的配合使用,可以實現一個git倉庫擁有多個子項目的情況下,獲取各自git倉庫的配置文件
  • Git 倉庫訪問權限
    1. http方式需要使用username/password
    2. ssh方式需要生成ssh key,配置在git倉庫
      1. spring.cloud.config.server.git.ignoreLocalSshSettings=true: 關閉本地ssh默認設置,使用配置文件設置
      2. spring.cloud.config.server.git.privateKey=| + key,注意格式
      3. spring.cloud.config.server.git.passphrase : 私鑰密碼
      4. hostKeyAlgorithm和hostKey不設置
      5. 多個倉庫的所有repos都要設置ignoreLocalSshSettings和privateKey,這時候可以使用配置引用
      6. 生成ssh key,可以利用eclipse的ssh功能privateKey是id_rsa,publicKey使用Load Existing Key,選擇id_rsa文件
  • Git使用本地文件
    1. spring.cloud.config.server.git.uri=file://path/config-repo
    2. windows使用file:///
    3. 本地倉庫多用於開發調試
  • Git/SVN 本地倉庫
    1. 默認配置在/tmp/config-repo-隨機數里
    2. spring.cloud.config.server.git/svn.basedir : 指定默認路徑
  • 不使用Git,使用項目本地配置
    1. spring.profiles.active=native
  • 屬性覆蓋
    1. spring.cloud.config.server.overrides.xxx
  • 安全保護
    1. 利用spring security實現安全保護
  • 加密解密
    1. [?]

客戶端詳解

  • 通過指定的文件名加載配置文件

    1. spring.cloud.config.name=core,default
  • 整合Eureka服務化的配置中心

    1. spring.cloud.config.discovery.enabled=true : 開啟通過服務訪問配置中心
    2. spring.cloud.config.discovery.serviceId=rates-config
    3. bootstrap.yml
      1. 可以使用---spring: profiles:的方式設置不同環境的eureka url
      2. 也可以使用bootstrap-{profile}.yml的方式設置不同環境的eureka url
      3. spring.cloud.config.profile可以等於${spring.profiles.active},從而減少一個shell配置
  • 客戶端啟動健壯性

    1. 如何由於config service網絡問題造成拿不到配置文件,有可能會導致客戶端啟動失敗的情況,這對release是一種災難
    2. 方案1:失敗快速響應 + 重試
      1. spring.cloud.config.failFast=true : 一旦config service宕機,會快速失敗,停止啟動應用
      2. 如果只是網絡延遲等問題造成的,那么可以加上重試功能,只需要加上Maven dependency
        1. org.springframework.retry : spring-retry
        2. org.springframework.boot : org-boot-starter-aop
      3. 默認重試6次,配置參數:
        1. spring.cloud.config.retry.multiplier: 第一次重試的間隔
        2. spring.cloud.config.retry.initial-interval: 下一次重試的間隔的倍數,默認1.1
        3. spring.cloud.config.retry.max-interval: 最大的重試間隔
        4. spring.cloud.config.retry.max-attempts: 最大重試次數,默認6次
    3. 方案2:不使用失敗快速響應,當 config service出問題的時候,使用本地配置文件進行啟動
      1. spring.cloud.config.failFast=false
      2. 本地需要有一份配置,做好的做法是將本項目Git倉庫作為配置中心的Git倉庫
      3. 啟動后,當config service恢復正常,動態刷新機制還能使用
      4. 方案2完全隔離了config service對客戶端的影響,但是相比於方案1,無法實現不同項目共用相同配置文件的目的
  • 動態刷新

    1. 對config server的每次請求,config server會去git pull最新版本
    2. @RefreshScope :
      1. 修飾@Value相應的類,加在啟動類沒用
      2. 配合@ConfigurationProperties使用
    3. 通過actuator的refresh功能刷新客戶端
    4. 可以與git Web Hook配合實現提交自動刷新
    5. 這種方式需要refresh所以相應服務,很麻煩,可以用Spring Cloud Bus解決

Bus 消息總線

  • Kafka整合spring cloud bus
    1. Maven dependency
      1. org.springframework.cloud : spring-cloud-starter-bus-kafka
    2. kafka broker host配置
      1. spring.cloud.stream.kafka.binder.brokers=localhost
    3. 啟動應用,POST訪問/actuator/bus-refresh
      1. bus自動創建名為springCloudBus的Topic
      2. refresh會刷新所以接入此Kafka總線的應用的配置
    4. refresh提供參數來支持刷新指定目的地的應用,並且支持通配符
      1. /actuator/bus-refresh?destination=serviceId:port
    5. spring bus與kafka的交互實際上是由spring cloud stream完成的,bus只是stream的一種應用
  • 配置bus
    1. spring.cloud.bus.trace.enable=true
      1. bus整合了kafka的消費者控制台功能:kafka-console-consumer
      2. 先設置為true開啟,然后訪問 /trace
      3. 除了trace默認關閉,Env,Refresh,Ack默認開啟,Env用於刷新環境變量
    2. spring.cloud.bus.enable
      1. 設置設否啟動消息總線,默認true
    3. spring.cloud.bus.destination
      1. 定義Topic名字,默認springCloudBus

Stream 消息驅動

Maven dependency

  • org.springframework.cloud : spring-cloud-stream-binder-kafka

核心概念

  • 發布-訂閱模式
    1. 消息驅動的模式,使用了發布-訂閱設計模式
  • 綁定器 Binder
    1. 綁定器負責實現應用於消息中間件的交互
    2. 通過綁定器的概念實現應用於消息中間件的隔離
    3. 切換不同的消息中間件之需要切換綁定器的實現
  • 消費組
    1. 實現一個消息只被同組的一個消費者獨占消費
    2. 解決分布式架構相同服務不需要重復處理事件的問題
    3. spring.cloud.stream.bindings.[channelName].group : 指定消費者所屬的組
  • 消息分區
    1. 擁有相同特征的消息會被同一個消費者消費
    2. spring cloud 不關心消息中間件是否實現了消息分區,都會在其上層實現分區

使用詳解

  • 綁定消息通道
    1. @EnableBinding
      1. 綁定指定消息通道,發送和接收消息前都要先綁定
      2. value是消息通道接口的class,會自動實現接口,並創建相應的spring bean,可以直接注入
    2. @Input("ChannelName")
      1. value是消息通道的名稱,默認對應Kafka的Topic,可以設置對應的Topic
      2. 修飾消息接收通道接口的方法,返回SubscribableChannel
    3. @Output("ChannelName")
      1. value是消息通道的名稱,默認對應Kafka的Topic,可以設置對應的Topic
      2. 修飾消息發送通道接口的方法,返回MessageChannel
    4. spring.cloud.stream.bindings.[channelName].destination=xxx
      1. 指定消息通道的目的地,比如Kafka的Topic
      2. 如果是個@Input通道,可以指定多個目的地,用逗號隔開
      3. 如果沒有設置,默認等於消息通道的Value
      4. 在一個應用里避免使用同一個Topic的@Input通道和@Output,因為你沒理由監聽自己發送的消息
      5. 通過這個屬性,可以實現修改配置文件來啟動不同的實例監聽不同的Topic,Nice!!!
  • 發送消息
    1. 注入消息通道MidCurveChannel
      1. MidCurveChannel.getChannel().send(MessageBuilder.withPayload("message").build());
    2. 注入MessageChannel
      1. MessageChannel.send(MessageBuilder.withPayload("message").build());
      2. 如果有多個MessageChannel的bean,可以用@Qualifier("ChannelName"),或者屬性名字定義為ChannelName,這時候要注意ChannelName的大小寫
    3. 使用Spring Integration的原生支持
      1. @InboundChannelAdapter(value=RatesOutputChannel.TEST,poller=@Poller(fixedDelay="3000"))
      2. 支持返回自定義對象作為消息發送的類型
      3. 不支持傳入參數
    4. 大部分情況下, 消息以JSON等形式傳輸
  • 接收消息
    1. @StreamListener("ChannelName")
      1. StreamListener支持對JSON的自定轉換
    2. 如果發送的是Message[T] message,那么可以用Message[T]進行接收,也可以用T進行接收
  • 消息反饋
    1. @SendTo("ChannelName")
    2. 修飾在接收消息的@StreamListener的方法上
    3. 將方法的返回值發送給ChannelName通道,返回值可以不是Message,可以是任何類型
    4. @StreamListener的方法有返回值必須用@SendTo修飾
    5. 消息反饋要發送到與監聽通道不同的Topic里,不要反饋到原來的Topic,所以如果需要反饋消息,需要創建一個對應的反饋Topic

響應式編程

  • [?]

消費組與消息分區

  • 消費組
    1. spring.cloud.stream.bindings.[channelName].group : 指定消費者所屬的組
    2. 指定消費組后,消費者會根據組名來獲取當前的offset,從當前offset開始獲取kafka中的消息,如果不指定,offset=HW
    3. 同一個應用只能為一個Topic啟動一個消費者,也就是說一個消費組里的兩個消費者肯定是兩個進程
  • 生產者創建分區
    1. spring.cloud.stream.kafka.binder.brokers
      1. kafka集群,只寫其中一個,也能使用集群
      2. 如果寫了多個,但是不屬於一個集群,那么后面的回覆蓋前面的
    2. spring.cloud.stream.kafka.binder.autoAddPartitions=true
      1. 開啟自動添加分區
    3. spring.cloud.stream.kafka.binder.replicationFactor=2
      1. 設置自動每個分區的副本數量,默認1即只有Leader副本
      2. spring cloud會自動均分副本broker,盡量保證leader與副本不在同一個broker
    4. spring.cloud.stream.bindings.[channelName].producer.partitionCount=2
      1. 為Topic開啟多個分區
      2. pring cloud會自動均分partition,盡量保證partition不在同一個broker
    5. spring.cloud.stream.bindings.[channelName].producer.partitionKeyExpression=payload.name
      1. 為分區設置key,相同key的消息會分到一個分區
      2. payload表示消息本身
      3. payload.name表示以消息對象的name屬性作為key
      4. 當相同key的消息必須在同一個消費者處理的時候使用此特性
  • Kafka開啟消費者分區
    1. spring.cloud.stream.bindings.[channelName].consumer.partitioned=true
    2. spring.cloud.stream.instanceCount :
      1. 准備啟動多少個實例數量,默認情況下Kafka分區會平均分配給這些實例
    3. spring.cloud.stream.bindings.[channelName].consumer.concurrency=1
      1. 為當前實例的輸入通道啟動多少個消費者進行並發處理, 當前實例分配到的分區會平均分配給這些消費者
    4. spring.cloud.stream.instanceIndex : 實例索引,使用Kafka需要設置,從0開始

綁定器

  • 自動配置選擇classpath綁定器,在pom中引入綁定器
  • 設置默認綁定器
    1. spring.cloud.stream.defaultBinder=rabbit
  • 為消息通道單獨設置綁定器
    1. spring.cloud.stream.bindings.[channelName].binder=kafka1
    2. spring.cloud.stream.binders.kafka1.type=kafka
    3. spring.cloud.stream.binders.kafka1.environment : 設置綁定器屬性

配置詳解

  • Kafka綁定器配置 : spring.cloud.stream.kafka.binder
    1. brokers : Kafka的host:port,以逗號隔開,也可以不設置port
    2. defaultBrokerPort: 默認的Kafka的port
    3. zkNodes : Zookeeper的host:port,以逗號隔開,也可以不設置port,2.0之后版本不再需要
    4. defaultBrokerPort: 默認的Zookeeper的port
    5. header : 設置自定義頭信息
    6. socketBufferSize: kafka的socket,默認2097152,2m
    7. autoCreateTopics
      1. 自定創建新主題,默認true,false如果主題不存在,啟動會失敗
    8. autoAddPartitions
      1. 自動創建新的分區,默認false,當已有topic的分區數量少於需求數量時,會啟動失敗
      2. 雖然默認false,但是在第一次創建Topic的時候,會根據生產者的partitionCount創建分區
    9. minPartitionCount
      1. 只有當autoCreateTopics,autoAddPartitions 為true時生效,表示最少需要的分區數量
      2. 如果這個值小於partitionCount或者instanceCount * concurrency,則會被他們覆蓋,也就是會根據實際需要設置
    10. replicationFactor : 自動創建主題的副本數量
  • Kafka消費者配置 : spring.cloud.stream.kafka.bindings.[channelName].consumer
    1. maxAttempts : 重試次數,默認3
    2. backOffInitialInterval : 重試起始間隔時間,默認1000
    3. backOffMaxInterval : 重試最大間隔時間,默認10000
    4. backOffMultiplier :: 重試間隔時間乘數,默認2
    5. autoCommitOffset
      1. 是否自動提交offset,默認true,false會加入ask頭信息實現延遲確認
    6. autoCommitOnError
      1. autoCommitOffset為true才生效
      2. 發生錯誤時是否自動提交offset,默認等於enableDlq,enableDlq默認false
    7. enableDlq
      1. 是否啟動Dlq行為
      2. Dlq會將引起錯誤的消息發送到topic為error.destination.group的topic,destination是原topic的名字
    8. recoveryInterval
      1. 嘗試恢復連接的時間間隔,默認5000
    9. resetOffsets
      1. 是否用startOffset的值來重置消費者的offset,默認false
    10. startOffset
      1. 見上一條
  • kafka生產者配置 :spring.cloud.stream.kafka.bindings.[channelName].producer
    1. partitionCount=2
      1. 為Topic開啟多個分區
    2. partitionKeyExpression=payload.name
      1. 為分區設置key,相同key的消息會分到一個分區
      2. payload表示消息本身
      3. payload.name表示以消息對象的name屬性作為key
      4. 當相同key的消息必須在同一個消費者處理的時候使用此特性
    3. sync
      1. 是否同步發送,默認false,即允許批量發送
    4. batchTimeout
      1. 批量發送等待時間,默認0,0不代表不批量發送,而是說只批量發送前一次發送時產生的消息
      2. 如果消息生產快速,可以用這個值來以延遲的代價增加系統吞吐量
    5. bufferSize
      1. Kafka批量發送的緩存數據上限,默認16384,16kb

Sleuth 服務跟蹤

Getting Start

  • Maven dependency
    1. org.springframework.cloud : spring-cloud-starter-sleuth
  • 添加任意日志,Sleuth會為以下通訊方式添加跟蹤信息
    1. 瀏覽器發起的請求
    2. 通過RestTemplate發起的請求
    3. 通過Zuul路由的請求
    4. 通過Stream綁定的消息中間件傳遞的請求
  • 跟蹤信息
    1. 服務的ServiceId
    2. TraceId : 表示一次請求鏈路的Id,等於第一個工作單元的SpanId
    3. SpanId : 表示一個工作單元的Id,比如一次Http請求代表一個工作單元
      1. 通過記錄第一次SpanId和最后一次SpanId可以計算工作單元的時間消耗
    4. 是否輸出到Zipkin等服務,默認false
    5. Http頭信息里包含這些跟蹤信息
      1. X-B3-TraceId
      2. X-B3-SpanId
      3. X-B3-ParentSpanId : 上一個工作單元的SpanId
      4. X-B3-Sampled : 是否被抽樣輸出
      5. X-Span-Name : 工作單元名稱
    6. spring mvc請求分發日志設置為DEBUG,可以獲取更多跟蹤信息
      1. logging.level.org.springframework.web.servlet.DispatcherServlet=DEBUG
  • 抽樣收集
    1. 通過實現Sampler接口實現抽樣策略
    2. 默認使用PercentageBasedSampler實現的抽樣策略,百分比策略
      1. spring.sleuth.sampler.percentage=0.1
    3. 在不對系統造成性能印象的前提下,在一定時間窗內充分利用存儲空間來實現抽樣

整合ELK

  • [?]

整合Zipkin


免責聲明!

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



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