日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

SpringCloud底层原理

發布時間:2024/1/23 javascript 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SpringCloud底层原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

SpringCloud框架


針對這個架構圖我分層介紹一下:

1、是web服務器的選型,這個我選擇的是nginx+keepalived,haproxy也是一個選擇,但是haproxy在反向代理處理跨域訪問的時候問題很多。所以我們nginx有些地方做了keep-alive模式處理,減少了三次握手的次數,提高了連接效率。keepalived做nginx的負載,虛擬一個vip對外,兩個nginx做高可用,nginx本身反向代理zuul集群。

2、api gateway,這里的zuul很多人詬病,說是速度慢推薦直接用nginx,這里我還是推薦使用zuul的,畢竟zuul含有攔截器和反向代理,在權限管理、單點登錄、用戶認證時候還是很有用的,而且zuul自帶ribbon負載均衡,如果你直接用nginx,還需要單獨做一個feign或者ribbon層,用來做業務集群的負載層,畢竟直接把接口暴露給web服務器太危險了。這里zuul帶有ribbon負載均衡和hystrix斷路器,直接反向代理serviceId就可以代理整個集群了。

3、業務集群,這一層我有些項目是分兩層的,就是上面加了一個負載層,下面是從service開始的,底層只是單純的接口,controller是單獨一層由feign實現,然后內部不同業務服務接口互調,直接調用controller層,只能說效果一般,多了一次tcp連接。所以我推薦合并起來,因為做過spring cloud項目的都知道,feign是含有ribbon的,而zuul也含有ribbon,這樣的話zuul調用服務集群,和服務集群間接口的互調都是高可用的,保證了通訊的穩定性。Hystrix還是要有的,沒有斷路器很難實現服務降級,會出現大量請求發送到不可用的節點。當然service是可以改造的,如果改造成rpc方式,那服務之間互調又是另外一種情況了,那就要做成負載池和接口服務池的形式了,負載池調用接口池,接口池互相rpc調用,feign client只是通過實現接口達到了仿rpc的形式,不過速度表現還是不錯的。

4、redis緩存池,這個用來做session共享,分布式系統session共享是一個大問題。同時呢,redis做二級緩存對降低整個服務的響應時間,并且減少數據庫的訪問次數是很有幫助的。當然redis cluster還是redis sentinel自己選擇。

5、eurake注冊中心這個高可用集群,這里有很多細節,比如多久刷新列表一次,多久監測心跳什么的,都很重要。

6、spring admin,這個是很推薦的,這個功能很強大,可以集成turbine斷路器監控器,而且可以定義所有類的log等級,不用單獨去配置,還可以查看本地log日志文件,監控不同服務的機器參數及性能,非常強大。它加上elk動態日志收集系統,對于項目運維非常方便。

7、zipkin,這個有兩種方式,直接用它自己的功能界面查看方式,或者用stream流的方式,由elk動態日志系統收集。但是我必須要說,這個對系統的性能損害非常大,因為鏈路追蹤的時候會造成響應等待,而且等待時間非常長接近1秒,這在生產環境是不能忍受的,所以生產環境最好關掉,有問題調試的時候再打開。

8、消息隊列,這個必須的,分布式系統不可能所有場景都滿足強一致性,這里只能由消息隊列來作為緩沖,這里我用的是Kafka。

9、分布式事物,我認為這是分布式最困難的,因為不同的業務集群都對應自己的數據庫,互相數據庫不是互通的,互相服務調用只能是相互接口,有些甚至是異地的,這樣造成的結果就是網絡延遲造成的請求等待,網絡抖動造成的數據丟失,這些都是很可怕的問題,所以必須要處理分布式事物。我推薦的是利用消息隊列,采取二階段提交協議配合事物補償機制,具體的實現需要結合業務,這里篇幅有限就不展開說了。

10、config配置中心,這是很有必要的,因為服務太多配置文件太多,沒有這個很難運維。這個一般利用消息隊列建立一個spring cloud bus,由git存儲配置文件,利用bus總線動態更新配置文件信息。

11、實時分布式日志系統,logstash收集本地的log文件流,傳輸給elasticsearch,logstash有兩種方式,1、是每一臺機器啟動一個logstash服務,讀取本地的日志文件,生成流傳給elasticsearch。2、logback引入logstash包,然后直接生產json流傳給一個中心的logstash服務器,它再傳給elasticsearch。elasticsearch再將流傳給kibana,動態查看日志,甚至zipkin的流也可以直接傳給elasticsearch。這個配合spring admin,一個查看動態日志,一個查看本地日志,同時還能遠程管理不同類的日志級別,對集成和運維非常有利。

最后要說說,spring cloud的很多東西都比較精確,比如斷路器觸發時間、事物補償時間、http響應時間等,這些都需要好好的設計,而且可以優化的點非常多。比如:http通訊可以使用okhttp,jvm優化,nio模式,數據連接池等等,都可以很大的提高性能。

還有一個docker問題,很多人說不用docker就不算微服務。其實我個人意見,spring cloud本身就是微服務的,只需要jdk環境即可。編寫dockerfile也無非是集成jdk、添加jar包、執行jar而已,或者用docker compose,將多個不同服務的image組合run成容器而已。但是帶來的問題很多,比如通訊問題、服務器性能損耗問題、容器進程崩潰問題,當然如果你有一套成熟的基于k8s的容器管理平臺,這個是沒問題的,如果沒有可能就要斟酌了。而spring cloud本身就是微服務分布式的架構,所以個人還是推薦直接機器部署的,當然好的DevOps工具將會方便很多。

作者github地址:https://github.com/cyc3552637

引言
面試中面試官喜歡問組件的實現原理,尤其是常用技術,我們平時使用了SpringCloud還需要了解它的實現原理,這樣不僅起到舉一反三的作用,還能幫助輕松應對各種問題及有針對的進行擴展。
以下是《Java深入微服務原理改造房產銷售平臺》課程講到的部分原理附圖,現在免費開放給大家,讓大家輕松應對原理面試題。

服務注冊發現組件Eureka工作原理


1、Eureka 簡介:

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

1.基本原理
上圖是來自eureka的官方架構圖,這是基于集群配置的eureka;?
- 處于不同節點的eureka通過Replicate進行數據同步?
- Application Service為服務提供者?
- Application Client為服務消費者?
- Make Remote Call完成一次服務調用

服務啟動后向Eureka注冊,Eureka Server會將注冊信息向其他Eureka Server進行同步,當服務消費者要調用服務提供者,則向服務注冊中心獲取服務提供者地址,然后會將服務提供者地址緩存在本地,下次再調用時,則直接從本地緩存中取,完成一次調用。

當服務注冊中心Eureka Server檢測到服務提供者因為宕機、網絡原因不可用時,則在服務注冊中心將服務置為DOWN狀態,并把當前服務提供者狀態向訂閱者發布,訂閱過的服務消費者更新本地緩存。

服務提供者在啟動后,周期性(默認30秒)向Eureka Server發送心跳,以證明當前服務是可用狀態。Eureka Server在一定的時間(默認90秒)未收到客戶端的心跳,則認為服務宕機,注銷該實例。

2.Eureka的自我保護機制
在默認配置中,Eureka Server在默認90s沒有得到客戶端的心跳,則注銷該實例,但是往往因為微服務跨進程調用,網絡通信往往會面臨著各種問題,比如微服務狀態正常,但是因為網絡分區故障時,Eureka Server注銷服務實例則會讓大部分微服務不可用,這很危險,因為服務明明沒有問題。

為了解決這個問題,Eureka 有自我保護機制,通過在Eureka Server配置如下參數,可啟動保護機制

eureka.server.enable-self-preservation=true

它的原理是,當Eureka Server節點在短時間內丟失過多的客戶端時(可能發送了網絡故障),那么這個節點將進入自我保護模式,不再注銷任何微服務,當網絡故障回復后,該節點會自動退出自我保護模式。

自我保護模式的架構哲學是寧可放過一個,決不可錯殺一千

3. 作為服務注冊中心,Eureka比Zookeeper好在哪里
著名的CAP理論指出,一個分布式系統不可能同時滿足C(一致性)、A(可用性)和P(分區容錯性)。由于分區容錯性在是分布式系統中必須要保證的,因此我們只能在A和C之間進行權衡。在此Zookeeper保證的是CP, 而Eureka則是AP。

3.1 Zookeeper保證CP
當向注冊中心查詢服務列表時,我們可以容忍注冊中心返回的是幾分鐘以前的注冊信息,但不能接受服務直接down掉不可用。也就是說,服務注冊功能對可用性的要求要高于一致性。但是zk會出現這樣一種情況,當master節點因為網絡故障與其他節點失去聯系時,剩余節點會重新進行leader選舉。問題在于,選舉leader的時間太長,30 ~ 120s, 且選舉期間整個zk集群都是不可用的,這就導致在選舉期間注冊服務癱瘓。在云部署的環境下,因網絡問題使得zk集群失去master節點是較大概率會發生的事,雖然服務能夠最終恢復,但是漫長的選舉時間導致的注冊長期不可用是不能容忍的。

3.2 Eureka保證AP
Eureka看明白了這一點,因此在設計時就優先保證可用性。Eureka各個節點都是平等的,幾個節點掛掉不會影響正常節點的工作,剩余的節點依然可以提供注冊和查詢服務。而Eureka的客戶端在向某個Eureka注冊或時如果發現連接失敗,則會自動切換至其它節點,只要有一臺Eureka還在,就能保證注冊服務可用(保證可用性),只不過查到的信息可能不是最新的(不保證強一致性)。除此之外,Eureka還有一種自我保護機制,如果在15分鐘內超過85%的節點都沒有正常的心跳,那么Eureka就認為客戶端與注冊中心出現了網絡故障,此時會出現以下幾種情況:?
1. Eureka不再從注冊列表中移除因為長時間沒收到心跳而應該過期的服務?
2. Eureka仍然能夠接受新服務的注冊和查詢請求,但是不會被同步到其它節點上(即保證當前節點依然可用)?
3. 當網絡穩定時,當前實例新的注冊信息會被同步到其它節點中

因此, Eureka可以很好的應對因網絡故障導致部分節點失去聯系的情況,而不會像zookeeper那樣使整個注冊服務癱瘓。

4. 總結
Eureka作為單純的服務注冊中心來說要比zookeeper更加“專業”,因為注冊服務更重要的是可用性,我們可以接受短期內達不到一致性的狀況。不過Eureka目前1.X版本的實現是基于servlet的Java web應用,它的極限性能肯定會受到影響。期待正在開發之中的2.X版本能夠從servlet中獨立出來成為單獨可部署執行的服務。

服務網關組件Zuul工作原理


一、zuul是什么
zuul 是netflix開源的一個API Gateway 服務器, 本質上是一個web servlet應用。

Zuul 在云平臺上提供動態路由,監控,彈性,安全等邊緣服務的框架。Zuul 相當于是設備和 Netflix 流應用的 Web 網站后端所有請求的前門。

zuul的例子可以參考 netflix 在github上的 simple webapp,可以按照netflix 在github wiki 上文檔說明來進行使用。

二、zuul的工作原理
1、過濾器機制

zuul的核心是一系列的filters, 其作用可以類比Servlet框架的Filter,或者AOP。

zuul把Request route到 用戶處理邏輯 的過程中,這些filter參與一些過濾處理,比如Authentication,Load Shedding等。 ?

Zuul提供了一個框架,可以對過濾器進行動態的加載,編譯,運行。

Zuul的過濾器之間沒有直接的相互通信,他們之間通過一個RequestContext的靜態類來進行數據傳遞的。RequestContext類中有ThreadLocal變量來記錄每個Request所需要傳遞的數據。

Zuul的過濾器是由Groovy寫成,這些過濾器文件被放在Zuul Server上的特定目錄下面,Zuul會定期輪詢這些目錄,修改過的過濾器會動態的加載到Zuul Server中以便過濾請求使用。

下面有幾種標準的過濾器類型:

Zuul大部分功能都是通過過濾器來實現的。Zuul中定義了四種標準過濾器類型,這些過濾器類型對應于請求的典型生命周期。

(1) PRE:這種過濾器在請求被路由之前調用。我們可利用這種過濾器實現身份驗證、在集群中選擇請求的微服務、記錄調試信息等。

(2) ROUTING:這種過濾器將請求路由到微服務。這種過濾器用于構建發送給微服務的請求,并使用Apache HttpClient或Netfilx Ribbon請求微服務。

(3) POST:這種過濾器在路由到微服務以后執行。這種過濾器可用來為響應添加標準的HTTP Header、收集統計信息和指標、將響應從微服務發送給客戶端等。

(4) ERROR:在其他階段發生錯誤時執行該過濾器。

內置的特殊過濾器

zuul還提供了一類特殊的過濾器,分別為:StaticResponseFilter和SurgicalDebugFilter

StaticResponseFilter:StaticResponseFilter允許從Zuul本身生成響應,而不是將請求轉發到源。

SurgicalDebugFilter:SurgicalDebugFilter允許將特定請求路由到分隔的調試集群或主機。

自定義的過濾器

除了默認的過濾器類型,Zuul還允許我們創建自定義的過濾器類型。

例如,我們可以定制一種STATIC類型的過濾器,直接在Zuul中生成響應,而不將請求轉發到后端的微服務。

2、過濾器的生命周期

Zuul請求的生命周期如圖,該圖詳細描述了各種類型的過濾器的執行順序。

3、過濾器調度過程

?4、動態加載過濾器

三、zuul 能做什么?
Zuul可以通過加載動態過濾機制,從而實現以下各項功能:

驗證與安全保障: 識別面向各類資源的驗證要求并拒絕那些與要求不符的請求。
審查與監控: 在邊緣位置追蹤有意義數據及統計結果,從而為我們帶來準確的生產狀態結論。
動態路由: 以動態方式根據需要將請求路由至不同后端集群處。
壓力測試: 逐漸增加指向集群的負載流量,從而計算性能水平。
負載分配: 為每一種負載類型分配對應容量,并棄用超出限定值的請求。
靜態響應處理: 在邊緣位置直接建立部分響應,從而避免其流入內部集群。
多區域彈性: 跨越AWS區域進行請求路由,旨在實現ELB使用多樣化并保證邊緣位置與使用者盡可能接近。
除此之外,Netflix公司還利用Zuul的功能通過金絲雀版本實現精確路由與壓力測試。

四、zuul 與應用的集成方式
1、ZuulServlet - 處理請求(調度不同階段的filters,處理異常等)?

ZuulServlet類似SpringMvc的DispatcherServlet,所有的Request都要經過ZuulServlet的處理

三個核心的方法preRoute(),route(), postRoute(),zuul對request處理邏輯都在這三個方法里

ZuulServlet交給ZuulRunner去執行。

由于ZuulServlet是單例,因此ZuulRunner也僅有一個實例。

ZuulRunner直接將執行邏輯交由FilterProcessor處理,FilterProcessor也是單例,其功能就是依據filterType執行filter的處理邏輯

FilterProcessor對filter的處理邏輯。

首先根據Type獲取所有輸入該Type的filter,List<ZuulFilter> list。
遍歷該list,執行每個filter的處理邏輯,processZuulFilter(ZuulFilter filter)
RequestContext對每個filter的執行狀況進行記錄,應該留意,此處的執行狀態主要包括其執行時間、以及執行成功或者失敗,如果執行失敗則對異常封裝后拋出。?
到目前為止,zuul框架對每個filter的執行結果都沒有太多的處理,它沒有把上一filter的執行結果交由下一個將要執行的filter,僅僅是記錄執行狀態,如果執行失敗拋出異常并終止執行。


2、ContextLifeCycleFilter - RequestContext 的生命周期管理?

ContextLifecycleFilter的核心功能是為了清除RequestContext; 請求上下文RequestContext通過ThreadLocal存儲,需要在請求完成后刪除該對象。?

RequestContext提供了執行filter Pipeline所需要的Context,因為Servlet是單例多線程,這就要求RequestContext即要線程安全又要Request安全。

context使用ThreadLocal保存,這樣每個worker線程都有一個與其綁定的RequestContext,因為worker僅能同時處理一個Request,這就保證了Request Context 即是線程安全的由是Request安全的。

3、GuiceFilter - GOOLE-IOC(Guice是Google開發的一個輕量級,基于Java5(主要運用泛型與注釋特性)的依賴注入框架(IOC)。Guice非常小而且快。)?

4、StartServer - 初始化 zuul 各個組件 (ioc、插件、filters、數據庫等)

5、FilterScriptManagerServlet - ?uploading/downloading/managing scripts, 實現熱部署

Filter源碼文件放在zuul 服務特定的目錄, zuul server會定期掃描目錄下的文件的變化,動態的讀取\編譯\運行這些filter,

如果有Filter文件更新,源文件會被動態的讀取,編譯加載進入服務,接下來的Request處理就由這些新加入的filter處理。

http://www.cnblogs.com/lexiaofei/p/7080257.html

跨域時序圖


?

Ribbon工作原理
Ribbon 是netflix 公司開源的基于客戶端的負載均衡組件,是Spring Cloud大家庭中非常重要的一個模塊;Ribbon應該也是整個大家庭中相對而言比較復雜的模塊,直接影響到服務調度的質量和性能。全面掌握Ribbon可以幫助我們了解在分布式微服務集群工作模式下,服務調度應該考慮到的每個環節。
本文將詳細地剖析Ribbon的設計原理,幫助大家對Spring Cloud 有一個更好的認知。

一. Spring集成下的Ribbon工作結構
先貼一張總覽圖,說明一下Spring如何集成Ribbon的,如下所示:

image.png

Spring Cloud集成模式下的Ribbon有以下幾個特征:

Ribbon 服務配置方式
每一個服務配置都有一個Spring ApplicationContext上下文,用于加載各自服務的實例。
比如,當前Spring Cloud 系統內,有如下幾個服務:
服務名稱?? ?角色?? ?依賴服務
order?? ?訂單模塊?? ?user
user?? ?用戶模塊?? ?無
mobile-bff?? ?移動端BFF?? ?order,user
mobile-bff服務在實際使用中,會用到order和user模塊,那么在mobile-bff服務的Spring上下文中,會為order 和user 分別創建一個子ApplicationContext,用于加載各自服務模塊的配置。也就是說,各個客戶端的配置相互獨立,彼此不收影響

和Feign的集成模式
在使用Feign作為客戶端時,最終請求會轉發成 http://<服務名稱>/<relative-path-to-service>的格式,通過LoadBalancerFeignClient, 提取出服務標識<服務名稱>,然后根據服務名稱在上下文中查找對應服務的負載均衡器FeignLoadBalancer,負載均衡器負責根據既有的服務實例的統計信息,挑選出最合適的服務實例
二、Spring Cloud模式下和Feign的集成實現方式
和Feign結合的場景下,Feign的調用會被包裝成調用請求LoadBalancerCommand,然后底層通過Rxjava基于事件的編碼風格,發送請求;Spring Cloud框架通過 Feigin 請求的URL,提取出服務名稱,然后在上下文中找到對應服務的的負載均衡器實現FeignLoadBalancer,然后通過負載均衡器中挑選一個合適的Server實例,然后將調用請求轉發到該Server實例上,完成調用,在此過程中,記錄對應Server實例的調用統計信息。

/**
? ? ?* Create an {@link Observable} that once subscribed execute network call asynchronously with a server chosen by load balancer.
? ? ?* If there are any errors that are indicated as retriable by the {@link RetryHandler}, they will be consumed internally by the
? ? ?* function and will not be observed by the {@link Observer} subscribed to the returned {@link Observable}. If number of retries has
? ? ?* exceeds the maximal allowed, a final error will be emitted by the returned {@link Observable}. Otherwise, the first successful
? ? ?* result during execution and retries will be emitted.
? ? ?*/
? ? public Observable<T> submit(final ServerOperation<T> operation) {
? ? ? ? final ExecutionInfoContext context = new ExecutionInfoContext();
? ? ? ??
? ? ? ? if (listenerInvoker != null) {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? listenerInvoker.onExecutionStart();
? ? ? ? ? ? } catch (AbortExecutionException e) {
? ? ? ? ? ? ? ? return Observable.error(e);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ??
? ? ? ? // 同一Server最大嘗試次數
? ? ? ? final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
? ? ? ? //下一Server最大嘗試次數
? ? ? ? final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();
?
? ? ? ? // Use the load balancer
? ? ? ? // 使用負載均衡器,挑選出合適的Server,然后執行Server請求,將請求的數據和行為整合到ServerStats中
? ? ? ? Observable<T> o =?
? ? ? ? ? ? ? ? (server == null ? selectServer() : Observable.just(server))
? ? ? ? ? ? ? ? .concatMap(new Func1<Server, Observable<T>>() {
? ? ? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? ? ? // Called for each server being selected
? ? ? ? ? ? ? ? ? ? public Observable<T> call(Server server) {
? ? ? ? ? ? ? ? ? ? ? ? // 獲取Server的統計值
? ? ? ? ? ? ? ? ? ? ? ? context.setServer(server);
? ? ? ? ? ? ? ? ? ? ? ? final ServerStats stats = loadBalancerContext.getServerStats(server);
? ? ? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? ? ? // Called for each attempt and retry 服務調用
? ? ? ? ? ? ? ? ? ? ? ? Observable<T> o = Observable
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .just(server)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .concatMap(new Func1<Server, Observable<T>>() {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? public Observable<T> call(final Server server) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? context.incAttemptCount();//重試計數
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? loadBalancerContext.noteOpenConnection(stats);//鏈接統計
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if (listenerInvoker != null) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? listenerInvoker.onStartWithServer(context.toExecutionInfo());
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? } catch (AbortExecutionException e) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? return Observable.error(e);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //執行監控器,記錄執行時間
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? final Stopwatch tracer = loadBalancerContext.getExecuteTracer().start();
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //找到合適的server后,開始執行請求
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //底層調用有結果后,做消息處理
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? return operation.call(server).doOnEach(new Observer<T>() {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? private T entity;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? public void onCompleted() {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? recordStats(tracer, stats, entity, null);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 記錄統計信息
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? public void onError(Throwable e) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? recordStats(tracer, stats, null, e);//記錄異常信息
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? logger.debug("Got error {} when executed on server {}", e, server);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if (listenerInvoker != null) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? listenerInvoker.onExceptionWithServer(e, context.toExecutionInfo());
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? public void onNext(T entity) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? this.entity = entity;//返回結果值
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if (listenerInvoker != null) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? listenerInvoker.onExecutionSuccess(entity, context.toExecutionInfo());
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? private void recordStats(Stopwatch tracer, ServerStats stats, Object entity, Throwable exception) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? tracer.stop();//結束計時
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //標記請求結束,更新統計信息
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? loadBalancerContext.noteRequestCompletion(stats, entity, exception, tracer.getDuration(TimeUnit.MILLISECONDS), retryHandler);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? });
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? });
? ? ? ? ? ? ? ? ? ? ? ? //如果失敗,根據重試策略觸發重試邏輯
? ? ? ? ? ? ? ? ? ? ? ? // 使用observable 做重試邏輯,根據predicate 做邏輯判斷,這里做
? ? ? ? ? ? ? ? ? ? ? ? if (maxRetrysSame > 0)?
? ? ? ? ? ? ? ? ? ? ? ? ? ? o = o.retry(retryPolicy(maxRetrysSame, true));
? ? ? ? ? ? ? ? ? ? ? ? return o;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? });
? ? ? ? ?// next請求處理,基于重試器操作 ??
? ? ? ? if (maxRetrysNext > 0 && server == null)?
? ? ? ? ? ? o = o.retry(retryPolicy(maxRetrysNext, false));
? ? ? ??
? ? ? ? return o.onErrorResumeNext(new Func1<Throwable, Observable<T>>() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public Observable<T> call(Throwable e) {
? ? ? ? ? ? ? ? if (context.getAttemptCount() > 0) {
? ? ? ? ? ? ? ? ? ? if (maxRetrysNext > 0 && context.getServerAttemptCount() == (maxRetrysNext + 1)) {
? ? ? ? ? ? ? ? ? ? ? ? e = new ClientException(ClientException.ErrorType.NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "Number of retries on next server exceeded max " + maxRetrysNext
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? + " retries, while making a call for: " + context.getServer(), e);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? else if (maxRetrysSame > 0 && context.getAttemptCount() == (maxRetrysSame + 1)) {
? ? ? ? ? ? ? ? ? ? ? ? e = new ClientException(ClientException.ErrorType.NUMBEROF_RETRIES_EXEEDED,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "Number of retries exceeded max " + maxRetrysSame
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? + " retries, while making a call for: " + context.getServer(), e);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (listenerInvoker != null) {
? ? ? ? ? ? ? ? ? ? listenerInvoker.onExecutionFailed(e, context.toFinalExecutionInfo());
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? return Observable.error(e);
? ? ? ? ? ? }
? ? ? ? });
? ? }
從一組ServerList 列表中挑選合適的Server

? ? /**
? ? ?* Compute the final URI from a partial URI in the request. The following steps are performed:
? ? ?* <ul>
? ? ?* <li> ?如果host尚未指定,則從負載均衡器中選定 host/port
? ? ?* <li> ?如果host 尚未指定并且尚未找到負載均衡器,則嘗試從 虛擬地址中確定host/port
? ? ?* <li> 如果指定了HOST,并且URI的授權部分通過虛擬地址設置,并且存在負載均衡器,則通過負載就均衡器中確定host/port(指定的HOST將會被忽略)
? ? ?* <li> 如果host已指定,但是尚未指定負載均衡器和虛擬地址配置,則使用真實地址作為host
? ? ?* <li> if host is missing but none of the above applies, throws ClientException
? ? ?* </ul>
? ? ?*
? ? ?* @param original Original URI passed from caller
? ? ?*/
? ? public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
? ? ? ? String host = null;
? ? ? ? int port = -1;
? ? ? ? if (original != null) {
? ? ? ? ? ? host = original.getHost();
? ? ? ? }
? ? ? ? if (original != null) {
? ? ? ? ? ? Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original); ? ? ? ?
? ? ? ? ? ? port = schemeAndPort.second();
? ? ? ? }
?
? ? ? ? // Various Supported Cases
? ? ? ? // The loadbalancer to use and the instances it has is based on how it was registered
? ? ? ? // In each of these cases, the client might come in using Full Url or Partial URL
? ? ? ? ILoadBalancer lb = getLoadBalancer();
? ? ? ? if (host == null) {
? ? ? ? ? ? // 提供部分URI,缺少HOST情況下
? ? ? ? ? ? // well we have to just get the right instances from lb - or we fall back
? ? ? ? ? ? if (lb != null){
? ? ? ? ? ? ? ? Server svc = lb.chooseServer(loadBalancerKey);// 使用負載均衡器選擇Server
? ? ? ? ? ? ? ? if (svc == null){
? ? ? ? ? ? ? ? ? ? throw new ClientException(ClientException.ErrorType.GENERAL,
? ? ? ? ? ? ? ? ? ? ? ? ? ? "Load balancer does not have available server for client: "
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? + clientName);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? //通過負載均衡器選擇的結果中選擇host
? ? ? ? ? ? ? ? host = svc.getHost();
? ? ? ? ? ? ? ? if (host == null){
? ? ? ? ? ? ? ? ? ? throw new ClientException(ClientException.ErrorType.GENERAL,
? ? ? ? ? ? ? ? ? ? ? ? ? ? "Invalid Server for :" + svc);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? logger.debug("{} using LB returned Server: {} for request {}", new Object[]{clientName, svc, original});
? ? ? ? ? ? ? ? return svc;
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? // No Full URL - and we dont have a LoadBalancer registered to
? ? ? ? ? ? ? ? // obtain a server
? ? ? ? ? ? ? ? // if we have a vipAddress that came with the registration, we
? ? ? ? ? ? ? ? // can use that else we
? ? ? ? ? ? ? ? // bail out
? ? ? ? ? ? ? ? // 通過虛擬地址配置解析出host配置返回
? ? ? ? ? ? ? ? if (vipAddresses != null && vipAddresses.contains(",")) {
? ? ? ? ? ? ? ? ? ? throw new ClientException(
? ? ? ? ? ? ? ? ? ? ? ? ? ? ClientException.ErrorType.GENERAL,
? ? ? ? ? ? ? ? ? ? ? ? ? ? "Method is invoked for client " + clientName + " with partial URI of ("
? ? ? ? ? ? ? ? ? ? ? ? ? ? + original
? ? ? ? ? ? ? ? ? ? ? ? ? ? + ") with no load balancer configured."
? ? ? ? ? ? ? ? ? ? ? ? ? ? + " Also, there are multiple vipAddresses and hence no vip address can be chosen"
? ? ? ? ? ? ? ? ? ? ? ? ? ? + " to complete this partial uri");
? ? ? ? ? ? ? ? } else if (vipAddresses != null) {
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? Pair<String,Integer> hostAndPort = deriveHostAndPortFromVipAddress(vipAddresses);
? ? ? ? ? ? ? ? ? ? ? ? host = hostAndPort.first();
? ? ? ? ? ? ? ? ? ? ? ? port = hostAndPort.second();
? ? ? ? ? ? ? ? ? ? } catch (URISyntaxException e) {
? ? ? ? ? ? ? ? ? ? ? ? throw new ClientException(
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ClientException.ErrorType.GENERAL,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "Method is invoked for client " + clientName + " with partial URI of ("
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? + original
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? + ") with no load balancer configured. "
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? + " Also, the configured/registered vipAddress is unparseable (to determine host and port)");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? throw new ClientException(
? ? ? ? ? ? ? ? ? ? ? ? ? ? ClientException.ErrorType.GENERAL,
? ? ? ? ? ? ? ? ? ? ? ? ? ? this.clientName
? ? ? ? ? ? ? ? ? ? ? ? ? ? + " has no LoadBalancer registered and passed in a partial URL request (with no host:port)."
? ? ? ? ? ? ? ? ? ? ? ? ? ? + " Also has no vipAddress registered");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? } else {
? ? ? ? ? ? // Full URL Case URL中指定了全地址,可能是虛擬地址或者是hostAndPort
? ? ? ? ? ? // This could either be a vipAddress or a hostAndPort or a real DNS
? ? ? ? ? ? // if vipAddress or hostAndPort, we just have to consult the loadbalancer
? ? ? ? ? ? // but if it does not return a server, we should just proceed anyways
? ? ? ? ? ? // and assume its a DNS
? ? ? ? ? ? // For restClients registered using a vipAddress AND executing a request
? ? ? ? ? ? // by passing in the full URL (including host and port), we should only
? ? ? ? ? ? // consult lb IFF the URL passed is registered as vipAddress in Discovery
? ? ? ? ? ? boolean shouldInterpretAsVip = false;
?
? ? ? ? ? ? if (lb != null) {
? ? ? ? ? ? ? ? shouldInterpretAsVip = isVipRecognized(original.getAuthority());
? ? ? ? ? ? }
? ? ? ? ? ? if (shouldInterpretAsVip) {
? ? ? ? ? ? ? ? Server svc = lb.chooseServer(loadBalancerKey);
? ? ? ? ? ? ? ? if (svc != null){
? ? ? ? ? ? ? ? ? ? host = svc.getHost();
? ? ? ? ? ? ? ? ? ? if (host == null){
? ? ? ? ? ? ? ? ? ? ? ? throw new ClientException(ClientException.ErrorType.GENERAL,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "Invalid Server for :" + svc);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? logger.debug("using LB returned Server: {} for request: {}", svc, original);
? ? ? ? ? ? ? ? ? ? return svc;
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? // just fall back as real DNS
? ? ? ? ? ? ? ? ? ? logger.debug("{}:{} assumed to be a valid VIP address or exists in the DNS", host, port);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? // consult LB to obtain vipAddress backed instance given full URL
? ? ? ? ? ? ? ? //Full URL execute request - where url!=vipAddress
? ? ? ? ? ? ? ? logger.debug("Using full URL passed in by caller (not using load balancer): {}", original);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? // end of creating final URL
? ? ? ? if (host == null){
? ? ? ? ? ? throw new ClientException(ClientException.ErrorType.GENERAL,"Request contains no HOST to talk to");
? ? ? ? }
? ? ? ? // just verify that at this point we have a full URL
?
? ? ? ? return new Server(host, port);
? ? }
?
三. LoadBalancer--負載均衡器的核心
LoadBalancer 的職能主要有三個:

維護Sever列表的數量(新增、更新、刪除等)
維護Server列表的狀態(狀態更新)
當請求Server實例時,能否返回最合適的Server實例
本章節將通過詳細闡述著這三個方面。

3.1 負載均衡器的內部基本實現原理

先熟悉一下負載均衡器LoadBalancer的實現原理圖:

Eureka與Ribbon整合工作原理


Eurek進行服務的注冊與發現(請看之前的筆記[Spring Cloud Eureka搭建注冊中心])
ribbon進行RestTemplate負載均衡策略(下期寫ribbon實現負載均衡以及手寫負責均衡)
hystrix 實現熔斷機制以及通過dashboard查看熔斷信息(有時間寫hystrix dashboard詳解)

項目結構如下(不包含Eureka服務注冊與發現),另外部署


image.png

spring-cloud-study-provider 作為服務提供者將服務注冊到Eureka集群
spring-cloud-study-api 作為項目api提供基礎類庫支持
spring-cloud-study-consumer 作為服務消費者從Eureka集群獲取提供者信息,并進行消費,集成了Eureka,ribbon, hystrix, hystrix dashboard

Eureka主要實現服務的注冊與發現(請看之前的筆記[Spring Cloud Eureka搭建注冊中心]),這里不在重復
消費端eureka配置

eureka:
? client:
? ? register-with-eureka: false
? ? fetch-registry: true
? ? service-url:
? ? ? defaultZone: http://eureka-server.com:7001/eureka/,http://eureka-client1.com:7002/eureka/,http://eureka-client2.com:7003/eureka/
?
服務提供方eureka配置
eureka:
? client:
? ? service-url:
? ? ? defaultZone: http://eureka-server.com:7001/eureka/,http://eureka-client1.com:7002/eureka/,http://eureka-client2.com:7003/eureka/
? ? register-with-eureka: true
? ? fetch-registry: false
? instance:
? ? instance-id: spring-cloud-study-provider # 調用服務時需要此名稱(全部大寫)
? ? prefer-ip-address: true
ribbon實現負載均衡,默認采用:輪詢。
引用jar

<dependency>
? ? ? ? ? ? <groupId>org.springframework.cloud</groupId>
? ? ? ? ? ? <artifactId>spring-cloud-starter-ribbon</artifactId>
? ? ? ? ? ? <version>1.3.1.RELEASE</version>
? ? ? ? </dependency>
在RestTemplat加入LoanBalance注釋即可
@Configuration
public class RestConfigBean {
?
? ? @Bean
? ? @LoadBalanced
? ? public RestTemplate getRestTemplate()
? ? {
? ? ? ? return new RestTemplate();
? ? }
}
hystrix 實現熔斷機制以及通過dashboard進行監控
引入jar依賴

? ? ? ? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.springframework.cloud</groupId>
? ? ? ? ? ? <artifactId>spring-cloud-starter-hystrix</artifactId>
? ? ? ? ? ? <version>1.3.1.RELEASE</version>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.springframework.cloud</groupId>
? ? ? ? ? ? <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
? ? ? ? ? ? <version>1.3.1.RELEASE</version>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>com.netflix.hystrix</groupId>
? ? ? ? ? ? <artifactId>hystrix-metrics-event-stream</artifactId>
? ? ? ? ? ? <version>1.5.12</version>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>com.netflix.hystrix</groupId>
? ? ? ? ? ? <artifactId>hystrix-javanica</artifactId>
? ? ? ? ? ? <version>1.5.12</version>
? ? ? ? </dependency>
啟動hystrix有及hystrix dashboard
@SpringBootApplication
@EnableDiscoveryClient #啟用eureak服務發現
@EnableHystrix # 啟用hystrix熔斷
@EnableHystrixDashboard # 啟用hystrix dashboard服務監控
public class ConsumerApplication {
?
? ? public static void main(String[] args) {
? ? ? ? SpringApplication.run(ConsumerApplication.class, args);
? ? }
}
重要步驟
先啟動eureka服務器,這邊啟動三臺,模擬集群, 訪問

http://eureka-server.com:7001/
http://eureka-client1.com:7002/
http://eureka-client2.com:7003/

如果訪問地址出現下圖,表示eureka啟動成功

image.png

啟動服務提供者,將服務注冊到eureka服務器

[http://eureka-server.com:7001/](http://eureka-server.com:7001/)
[http://eureka-client1.com:7002/](http://eureka-client1.com:7002/)
[http://eureka-client2.com:7003/](http://eureka-client2.com:7003/)
訪問以上地址,出現下圖,表示服務提供者注冊服務到eureka集群成功

image.png

啟動服務提供者,從eureka集群獲取服務提供者信息,并進行服務消費,啟動成功后,進行測試

image.png

image.png

http://localhost:9001/dept/get/2 訪問這個地址時,出現RuntimeException異常,將進行熔斷,將返回getIdError方法的內容

image.png

查看熔斷信息(訪問地址:http://localhost:9001/hystrix)


image.png

地址欄輸入:localhost:9001/hystrix.stream
title:隨便輸入
點擊 按鈕提交
訪問:http://localhost:9001/dept/get/2, 服務提供者控制臺將出現異常
查詢hystrix dashboard頁面,刷新

image.png

解決分布式一致性


主要內容包括4部分:

傳統分布式事務不是微服務中一致性的最佳選擇
微服務架構中應滿足數據最終一致性原則
微服務架構實現最終一致性的三種模式
對賬是最后的終極防線。


我們先來看一下第一部分,傳統使用本地事務和分布式事務保證一致性

傳統單機應用一般都會使用一個關系型數據庫,好處是應用可以使用 ACID transactions。為保證一致性我們只需要:開始一個事務,改變(插入,刪除,更新)很多行,然后提交事務(如果有異常時回滾事務)。更進一步,借助開發平臺中的數據訪問技術和框架(如Spring),我們需要做的事情更少,只需要關注數據本身的改變。隨著組織規模不斷擴大,業務量不斷增長,單機應用和數據庫已經不足以支持龐大的業務量和數據量,這個時候需要對應用和數據庫進行拆分,就出現了一個應用需要同時訪問兩個或兩個以上的數據庫情況。開始我們用分布式事務來保證一致性,也就是我們常說的兩階段提交協議(2PC)。

本地事務和分布式事務現在已經非常成熟,相關介紹很豐富,此處不多作討論。我們下面來討論以下為什么分布式事務不適用于微服務架構。

首先,對于微服務架構來說,數據訪問變得更加復雜,這是因為數據都是微服務私有的,唯一可訪問的方式就是通過API。這種打包數據訪問方式使得微服務之間松耦合,并且彼此之間獨立非常容易進行性能擴展。

其次,不同的微服務經常使用不同的數據庫。應用會產生各種不同類型的數據,關系型數據庫并不一定是最佳選擇。例如,某個產生和查詢字符串的應用采用Elasticsearch的字符搜索引擎;某個產生社交圖片數據的應用可以采用圖數據庫,例如,Neo4j;基于微服務的應用一般都使用SQL和NoSQL結合的模式。但是這些非關系型數據大多數并不支持2PC。可見在微服務架構中已經不能選擇分布式事務了。

依據CAP理論,必須在可用性(availability)和一致性(consistency)之間做出選擇。如果選擇提供一致性需要付出在滿足一致性之前阻塞其他并發訪問的代價。這可能持續一個不確定的時間,尤其是在系統已經表現出高延遲時或者網絡故障導致失去連接時。

依據目前的成功經驗,可用性一般是更好的選擇,但是在服務和數據庫之間維護數據一致性是非常根本的需求,微服務架構中應選擇滿足最終一致性。

當然選擇了最終一致性,就要保證到最終的這段時間要在用戶可接受的范圍之內。那么我們怎么實現最終一致性呢?

從一致性的本質來看,是要保證在一個業務邏輯中包含的服務要么都成功,要么都失敗。那我們怎么選擇方向呢?保證成功還是保證失敗呢?我們說業務模式決定了我們的選擇。實現最終一致性有三種模式:可靠事件模式、業務補償模式、TCC模式。

可靠事件模式屬于事件驅動架構,當某件重要事情發生時,例如更新一個業務實體,微服務會向消息代理發布一個事件。消息代理會向訂閱事件的微服務推送事件,當訂閱這些事件的微服務接收此事件時,就可以完成自己的業務,也可能會引發更多的事件發布。1. 如訂單服務創建一個待支付的訂單,發布一個“創建訂單”的事件

支付服務消費“創建訂單”事件,支付完成后發布一個“支付完成”事件

訂單服務消費“支付完成”事件,訂單狀態更新為待出庫。

從而就實現了完成的業務流程。但是這并不是一個完美的流程。

這個過程可能導致出現不一致的地方在于:某個微服務在更新了業務實體后發布事件卻失敗;雖然微服務發布事件成功,但是消息代理未能正確推送事件到訂閱的微服務;接受事件的微服務重復消費了事件。

可靠事件模式在于保證可靠事件投遞和避免重復消費,可靠事件投遞定義為:(a)每個服務原子性的業務操作和發布事件

(b)消息代理確保事件傳遞至少一次。避免重復消費要求服務實現冪等性,如支付服務不能因為重復收到事件而多次支付。

因為現在流行的消息隊列都實現了事件的持久化和at least once的投遞模式,(b)特性(消息代理確保事件投遞至少一次)已經滿足,今天不做展開。

下面分享的內容主要從可靠事件投遞和實現冪等性兩方面來討論,我們先來看可靠事件投遞。首先我們來看一個實現的代碼片段,這是從某生產系統上截取下來的。

根據上述代碼及注釋,初看可能出現3種情況:

操作數據庫成功,向消息代理投遞事件也成功
操作數據庫失敗,不會向消息代理中投遞事件了
操作數據庫成功,但是向消息代理中投遞事件時失敗,向外拋出了異常,剛剛執行的更新數據庫的操作將被回滾從上面分析的幾種情況來看,貌似沒有問題。但是仔細分析不難發現缺陷所在,在上面的處理過程中存在一段隱患時間窗口。


微服務A投遞事件的時候可能消息代理已經處理成功,但是返回響應的時候網絡異常,導致append操作拋出異常。最終結果是事件被投遞,數據庫確被回滾。

2) 在投遞完成后到數據庫commit操作之間如果微服務A宕機也將造成數據庫操作因為連接異常關閉而被回滾。最終結果還是事件被投遞,數據庫卻被回滾。這個實現往往運行很長時間都沒有出過問題,但是一旦出現了將會讓人感覺莫名很難發現問題所在。下面給出兩種可靠事件投遞的實現方式:

一.本地事件表

本地事件表方法將事件和業務數據保存在同一個數據庫中,使用一個額外的“事件恢復”服務來恢復事件,由本地事務保證更新業務和發布事件的原子性。考慮到事件恢復可能會有一定的延時,服務在完成本地事務后可立即向消息代理發布一個事件。

微服務在同一個本地事務中記錄業務數據和事件
微服務實時發布一個事件立即通知關聯的業務服務,如果事件發布成功立即刪除記錄的事件
事件恢復服務定時從事件表中恢復未發布成功的事件,重新發布,重新發布成功才刪除記錄的事件其中第2條的操作主要是為了增加發布事件的實時性,由第三條保證事件一定被發布。本地事件表方式業務系統和事件系統耦合比較緊密,額外的事件數據庫操作也會給數據庫帶來額外的壓力,可能成為瓶頸。
二、外部事件表

外部事件表方法將事件持久化到外部的事件系統,事件系統需提供實時事件服務以接受微服務發布事件,同時事件系統還需要提供事件恢復服務來確認和恢復事件。

業務服務在事務提交前,通過實時事件服務向事件系統請求發送事件,事件系統只記錄事件并不真正發送
業務服務在提交后,通過實時事件服務向事件系統確認發送,事件得到確認后事件系統才真正發布事件到消息代理
業務服務在業務回滾時,通過實時事件向事件系統取消事件
如果業務服務在發送確認或取消之前停止服務了怎么辦呢?事件系統的事件恢復服務會定期找到未確認發送的事件向業務服務查詢狀態,根據業務服務返回的狀態決定事件是要發布還是取消該方式將業務系統和事件系統獨立解耦,都可以獨立伸縮。但是這種方式需要一次額外的發送操作,并且需要發布者提供額外的查詢接口介紹完了可靠事件投遞再來說一說冪等性的實現,有些事件本身是冪等的,有些事件卻不是。


如果事件本身描述的是某個時間點的固定值(如賬戶余額為100),而不是描述一條轉換指令(如余額增加10),那么這個事件是冪等的。我們要意識到事件可能出現的次數和順序是不可預測的,需要保證冪等事件的順序執行,否則結果往往不是我們想要的。如果我們先后收到兩條事件,(1)賬戶余額更新為100,(2)賬戶余額更新為120。

1.微服務收到事件(1)

2.微服務收到事件(2)

3. 微服務再次收到事件1

顯然結果是錯誤的,所以我們需要保證事件(2)一旦執行事件(1)就不能再處理,否則賬戶余額仍不是我們想要的結果。

為保證事件的順序一個簡單的做法是在事件中添加時間戳,微服務記錄每類型的事件最后處理的時間戳,如果收到的事件的時間戳早于我們記錄的,丟棄該事件。如果事件不是在同一個服務器上發出的,那么服務器之間的時間同步是個難題,更穩妥的做法是使用一個全局遞增序列號替換時間戳。

對于本身不具有冪等性的操作,主要思想是為每條事件存儲執行結果,當收到一條事件時我們需要根據事件的id查詢該事件是否已經執行過,如果執行過直接返回上一次的執行結果,否則調度執行事件。

重復處理開銷大事件使用事件存儲過濾重復事件

在這個思想下我們需要考慮重復執行一條事件和查詢存儲結果的開銷。重復處理開銷小的事件重復處理如果重復處理一條事件開銷很小,或者可預見只有非常少的事件會被重復接收,可以選擇重復處理一次事件,在將事件數據持久化時由數據庫拋出唯一性約束異常。

如果重復處理一條事件的開銷相比額外一次查詢的開銷要高很多,使用一個過濾服務來過濾重復的事件,過濾服務使用事件存儲存儲已經處理過的事件和結果。

當收到一條事件時,過濾服務首先查詢事件存儲,確定該條事件是否已經被處理過,如果事件已經被處理過,直接返回存儲的結果;否則調度業務服務執行處理,并將處理完的結果存儲到事件存儲中。

一般情況下上面的方法能夠運行得很好,如果我們的微服務是RPC類的服務我們需要更加小心,可能出現的問題在于,(1)過濾服務在業務處理完成后才將事件結果存儲到事件存儲中,但是在業務處理完成前有可能就已經收到重復事件,由于是RPC服務也不能依賴數據庫的唯一性約束;(2)業務服務的處理結果可能出現位置狀態,一般出現在正常提交請求但是沒有收到響應的時候。

對于問題(1)可以按步驟記錄事件處理過程,比如事件的記錄事件的處理過程為“接收”、“發送請求”、“收到應答”、“處理完成”。好處是過濾服務能及時的發現重復事件,進一步還能根據事件狀態作不同的處理。

對于問題(2)可以通過一次額外的查詢請求來確定事件的實際處理狀態,要注意額外的查詢會帶來更長時間的延時,更進一步可能某些RPC服務根本不提供查詢接口。此時只能選擇接收暫時的不一致,時候采用對賬和人工接入的方式來保證一致性。

補償模式

為了描述方便,這里先定義兩個概念:

業務異常:業務邏輯產生錯誤的情況,比如賬戶余額不足、商品庫存不足等。

技術異常:非業務邏輯產生的異常,如網絡連接異常、網絡超時等。

補償模式使用一個額外的協調服務來協調各個需要保證一致性的微服務,協調服務按順序調用各個微服務,如果某個微服務調用異常(包括業務異常和技術異常)就取消之前所有已經調用成功的微服務。

補償模式建議僅用于不能避免出現業務異常的情況,如果有可能應該優化業務模式,以避免要求補償事務。如賬戶余額不足的業務異常可通過預先凍結金額的方式避免,商品庫存不足可要求商家準備額外的庫存等。

我們通過一個實例來說明補償模式,一家旅行公司提供預訂行程的業務,可以通過公司的網站提前預訂飛機票、火車票、酒店等。

假設一位客戶規劃的行程是,

(1)上海-北京6月19日9點的某某航班,

(2)某某酒店住宿3晚,

(3)北京-上海6月22日17點火車。在客戶提交行程后,旅行公司的預訂行程業務按順序串行的調用航班預訂服務、酒店預訂服務、火車預訂服務。最后的火車預訂服務成功后整個預訂業務才算完成。

如果火車票預訂服務沒有調用成功,那么之前預訂的航班、酒店都得取消。取消之前預訂的酒店、航班即為補償過程。

為了降低開發的復雜性和提高效率,協調服務實現為一個通用的補償框架。補償框架提供服務編排和自動完成補償的能力。

要實現補償過程,我們需要做到兩點:

首先要確定失敗的步驟和狀態,從而確定需要補償的范圍。

在上面的例子中我們不光要知道第3個步驟(預訂火車)失敗,還要知道失敗的原因。如果是因為預訂火車服務返回無票,那么補償過程只需要取消前兩個步驟就可以了;但是如果失敗的原因是因為網絡超時,那么補償過程除前兩個步驟之外還需要包括第3個步驟。

其次要能提供補償操作使用到的業務數據。

比如一個支付微服務的補償操作要求參數包括支付時的業務流水id、賬號和金額。理論上說實際完成補償操作可以根據唯一的業務流水id就可以,但是提供更多的要素有益于微服務的健壯性,微服務在收到補償操作的時候可以做業務的檢查,比如檢查賬戶是否相等,金額是否一致等等。

做到上面兩點的辦法是記錄完整的業務流水,可以通過業務流水的狀態來確定需要補償的步驟,同時業務流水為補償操作提供需要的業務數據。

當客戶的一個預訂請求達到時,協調服務(補償框架)為請求生成一個全局唯一的業務流水號。并在調用各個工作服務的同時記錄完整的狀態。

記錄調用bookFlight的業務流水,調用bookFlight服務,更新業務流水狀態

記錄調用bookHotel的業務流水,調用bookHotel服務,更新業務流水狀態

記錄調用bookTrain的業務流水,調用bookTrain服務,更新業務流水狀態

當調用某個服務出現異常時,比如第3步驟(預訂火車)異常

協調服務(補償框架)同樣會記錄第3步的狀態,同時會另外記錄一條事件,說明業務出現了異常。然后就是執行補償過程了,可以從業務流水的狀態中知道補償的范圍,補償過程中需要的業務數據從記錄的業務流水中獲取。

對于一個通用的補償框架來說,預先知道微服務需要記錄的業務要素是不可能的。那么就需要一種方法來保證業務流水的可擴展性,這里介紹兩種方法:大表和關聯表。

大表顧明思議就是設計時除必須的字段外,還需要預留大量的備用字段,框架可以提供輔助工具來幫助將業務數據映射到備用字段中。

關聯表,分為框架表和業務表,技術表中保存為實現補償操作所需要的技術數據,業務表保存業務數據,通過在技術表中增加業務表名和業務表主鍵來建立和業務數據的關聯。

大表對于框架層實現起來簡單,但是也有一些難點,比如預留多少字段合適,每個字段又需要預留多少長度。另外一個難點是如果向從數據層面來查詢數據,很難看出備用字段的業務含義,維護過程不友好。

關聯表在業務要素上更靈活,能支持不同的業務類型記錄不同的業務要素;但是對于框架實現上難度更高,另外每次查詢都需要復雜的關聯動作,性能方面會受影響。

有了上面的完整的流水記錄,協調服務就可以根據工作服務的狀態在異常時完成補償過程。但是補償由于網絡等原因,補償操作并不一定能保證100%成功,這時候我們還要做更多一點。

通過重試保證補償過程的完整。從而滿足最終一致性。

補償過程作為一個服務調用過程同樣存在調用不成功的情況,這個時候需要通過重試的機制來保證補償的成功率。當然這也就要求補償操作本身具備冪等性。

關于冪等性的實現在前面做過討論。

重試策略

如果只是一味的失敗就立即重試會給工作服務造成不必要的壓力,我們要根據服務執行失敗的原因來選擇不同的重試策略。

如果失敗的原因不是暫時性的,由于業務因素導致(如業務要素檢查失敗)的業務錯誤,這類錯誤是不會重發就能自動恢復的,那么應該立即終止重試。

如果錯誤的原因是一些罕見的異常,比如因為網絡傳輸過程出現數據丟失或者錯誤,應該立即再次重試,因為類似的錯誤一般很少會再次發生。

如果錯誤的原因是系統繁忙(比如http協議返回的500或者另外約定的返回碼)或者超時,這個時候需要等待一些時間再重試。

重試操作一般會指定重試次數上線,如果重試次數達到了上限就不再進行重試了。這個時候應該通過一種手段通知相關人員進行處理。

對于等待重試的策略如果重試時仍然錯誤,可逐漸增加等待的時間,直到達到一個上限后,以上限作為等待時間。

如果某個時刻聚集了大量需要重試的操作,補償框架需要控制請求的流量,以防止對工作服務造成過大的壓力。

另外關于補償模式還有幾點補充說明:

微服務實現補償操作不是簡單的回退到業務發生時的狀態,因為可能還有其他的并發的請求同時更改了狀態。一般都使用逆操作的方式完成補償。

補償過程不需要嚴格按照與業務發生的相反順序執行,可以依據工作服務的重用程度優先執行,甚至是可以并發的執行。

有些服務的補償過程是有依賴關系的,被依賴服務的補償操作沒有成功就要及時終止補償過程。

如果在一個業務中包含的工作服務不是都提供了補償操作,那我們編排服務時應該把提供補償操作的服務放在前面,這樣當后面的工作服務錯誤時還有機會補償。

設計工作服務的補償接口時應該以協調服務請求的業務要素作為條件,不要以工作服務的應答要素作為條件。因為還存在超時需要補償的情況,這時補償框架就沒法提供補償需要的業務要素。

補償模式就介紹到這里,下面介紹第三種模式:TCC模式(Try-Confirm-Cancel)

一個完整的TCC業務由一個主業務服務和若干個從業務服務組成,主業務服務發起并完成整個業務活動,TCC模式要求從服務提供三個接口:Try、Confirm、Cancel。

1) Try:完成所有業務檢查 預留必須業務資源2) Confirm:真正執行業務 不作任何業務檢查 只使用Try階段預留的業務資源 Confirm操作滿足冪等性3) Cancel: 釋放Try階段預留的業務資源 Cancel操作滿足冪等性整個TCC業務分成兩個階段完成。

第一階段:主業務服務分別調用所有從業務的try操作,并在活動管理器中登記所有從業務服務。當所有從業務服務的try操作都調用成功或者某個從業務服務的try操作失敗,進入第二階段。

第二階段:活動管理器根據第一階段的執行結果來執行confirm或cancel操作。如果第一階段所有try操作都成功,則活動管理器調用所有從業務活動的confirm操作。否則調用所有從業務服務的cancel操作。

需要注意的是第二階段confirm或cancel操作本身也是滿足最終一致性的過程,在調用confirm或cancel的時候也可能因為某種原因(比如網絡)導致調用失敗,所以需要活動管理支持重試的能力,同時這也就要求confirm和cancel操作具有冪等性。

在補償模式中一個比較明顯的缺陷是,沒有隔離性。從第一個工作服務步驟開始一直到所有工作服務完成(或者補償過程完成),不一致是對其他服務可見的。另外最終一致性的保證還充分的依賴了協調服務的健壯性,如果協調服務異常,就沒法達到一致性。

TCC模式在一定程度上彌補了上述的缺陷,在TCC模式中直到明確的confirm動作,所有的業務操作都是隔離的(由業務層面保證)。另外工作服務可以通過指定try操作的超時時間,主動的cancel預留的業務資源,從而實現自治的微服務。

TCC模式和補償模式一樣需要需要有協調服務和工作服務,協調服務也可以作為通用服務一般實現為框架。與補償模式不同的是TCC服務框架不需要記錄詳細的業務流水,完成confirm和cancel操作的業務要素由業務服務提供。

在第4步確認預訂之前,訂單只是pending狀態,只有等到明確的confirm之后訂單才生效。

如果3個服務中某個服務try操作失敗,那么可以向TCC服務框架提交cancel,或者什么也不做由工作服務自己超時處理。

TCC模式也不能百分百保證一致性,如果業務服務向TCC服務框架提交confirm后,TCC服務框架向某個工作服務提交confirm失敗(比如網絡故障),那么就會出現不一致,一般稱為heuristic exception。

需要說明的是為保證業務成功率,業務服務向TCC服務框架提交confirm以及TCC服務框架向工作服務提交confirm/cancel時都要支持重試,這也就要confirm/cancel的實現必須具有冪等性。如果業務服務向TCC服務框架提交confirm/cancel失敗,不會導致不一致,因為服務最后都會超時而取消。

另外heuristic exception是不可杜絕的,但是可以通過設置合適的超時時間,以及重試頻率和監控措施使得出現這個異常的可能性降低到很小。如果出現了heuristic exception是可以通過人工的手段補救的。

如果有些業務由于瞬時的網絡故障或調用超時等問題,通過上文所講的3種模式一般都能得到很好的解決。但是在當今云計算環境下,很多服務是依賴于外部系統的可用性情況,在一些重要的業務場景下還需要周期性的對賬來保證真實的一致性。比如支付系統和銀行之間每天日終是都會有對賬過程。

以上就是今天分享的內容,主要介紹的是微服務架構中需要滿足最終一致性原則以及實現最終一致性的3種模式。

級聯故障流程


圖片描述

斷路器組件Hystrix工作原理


1、Netflix Hystrix斷路器是什么?
Netflix Hystrix是SOA/微服務架構中提供服務隔離、熔斷、降級機制的工具/框架。Netflix Hystrix是斷路器的一種實現,用于高微服務架構的可用性,是防止服務出現雪崩的利器。

2、為什么需要斷路器?
在分布式架構中,一個應用依賴多個服務是非常常見的,如果其中一個依賴由于延遲過高發生阻塞,調用該依賴服務的線程就會阻塞,如果相關業務的QPS較高,就可能產生大量阻塞,從而導致該應用/服務由于服務器資源被耗盡而拖垮。

另外,故障也會在應用之間傳遞,如果故障服務的上游依賴較多,可能會引起服務的雪崩效應。就跟數據癱瘓,會引起依賴該數據庫的應用癱瘓是一樣的道理。

當一個應用依賴多個外部服務,一切都正常的情況下,如下圖:

如果其中一個依賴發生延遲,當前請求就會被阻塞

出現這種情況后,如果沒有應對措施,后續的請求也會被持續阻塞

每個請求都占用了系統的CPU、內存、網絡等資源,如果該應用的QPS較高,那么該應用所以的服務資源會被快速消耗完畢,直至應用死掉。如果這個出問題的依賴(Dependency I),不止這一個應用,亦或是受影響的應用上層也有更多的依賴,那就會帶來我們前面所提到的服務雪崩效應。

所以,為了應對以上問題,就需要有支持服務隔離、熔斷等操作的工具

二、Hystrix 簡介
1、Hystrix具備哪些能力/優點?
在通過網絡依賴服務出現高延遲或者失敗時,為系統提供保護和控制
可以進行快速失敗,縮短延遲等待時間和快速恢復:當異常的依賴回復正常后,失敗的請求所占用的線程會被快速清理,不需要額外等待
提供失敗回退(Fallback)和相對優雅的服務降級機制
提供有效的服務容錯監控、報警和運維控制手段
2、Hystrix 如何解決級聯故障/防止服務雪崩?
Hystrix將請求的邏輯進行封裝,相關邏輯會在獨立的線程中執行
Hystrix有自動超時策略,如果外部請求超過閾值,Hystrix會以超時來處理
Hystrix會為每個依賴維護一個線程池,當線程滿載,不會進行線程排隊,會直接終止操作
Hystrix有熔斷機制: 在依賴服務失效比例超過閾值時,手動或者自動地切斷服務一段時間
所以,當引入了Hystrix之后,當出現某個依賴高延遲的時候:

三、Hystrix 工作原理
1、Hystrix工作流


1、創建HystrixCommand 或者 HystrixObservableCommand 對象
2、執行命令execute()、queue()、observe()、toObservable()
3、如果請求結果緩存這個特性被啟用,并且緩存命中,則緩存的回應會立即通過一個Observable對象的形式返回
4、檢查熔斷器狀態,確定請求線路是否是開路,如果請求線路是開路,Hystrix將不會執行這個命令,而是直接執行getFallback
5、如果和當前需要執行的命令相關聯的線程池和請求隊列,Hystrix將不會執行這個命令,而是直接執行getFallback
6、執行HystrixCommand.run()或HystrixObservableCommand.construct(),如果這兩個方法執行超時或者執行失敗,則執行getFallback()
7、Hystrix 會將請求成功,失敗,被拒絕或超時信息報告給熔斷器,熔斷器維護一些用于統計數據用的計數器。
這些計數器產生的統計數據使得熔斷器在特定的時刻,能短路某個依賴服務的后續請求,直到恢復期結束,若恢復期結束根據統計數據熔斷器判定線路仍然未恢復健康,熔斷器會再次關閉線路。

依賴隔離
Hystrix采用艙壁隔離模式隔離相互之間的依賴關系,并限制對其中任何一個的并發訪問。

線程&線程池

客戶端(通常指Web應用)通過網絡請求依賴時,Hystrix會將請求外部依賴的線程與會將App容器(Tomcat/Jetty/…)線程隔離開,以免請求依賴出現延遲時影響請求線程。

Hystrix會為每個依賴維護一個線程池,當線程滿載,不會進行線程排隊,會Return fallback或者拋出異常

可能會有人有疑問,為什么不依賴于HTTP Client去做容錯保護(快速失敗、熔斷等),而是在訪問依賴之外通過線程&線程池隔離的方式做這個斷路器(Hystrix)。

主要是以下幾個方面:

不同的依賴執行的頻率不同,需要分開來對待
不同的依賴可能需要不同的Client的工具/協議來訪問,比如我們可能用HTTP Client,可能用Thrift Client。
Client在執行的過程中也可能會出現非網絡異常,這些都應該被隔離
Client的變化會引起斷路器的變化
所以,Hystrix這樣設計的好處是:

斷路器功能與不同的Client Library隔離
不同依賴之間的訪問互不影響
當發生大量異常時,不會造成App Container的響應線程排隊,并且當異常的依賴恢復正常后,失敗的請求所占用的線程會被快速清理,不需要額外等待
為不支持異步的依賴提供了異步的可能
這樣做的成本是,多了一些線程上的資源消耗(排隊,調度和上下文切換),不過從官方給到的數據上可能,這個消耗完全可以接受。目前Netflix每天有100億+的Hystrix命令執行,平均每個應用實例都有40+個線程池。每個線程池有5-20個線程 依然運行良好(不過這里 ken.io 不得不吐槽下,官方沒有透露單個實例硬件配置)

官方給了一組測試數據,在單個應用實例60QPS,且每秒鐘有350個Hystix子線程(350次Hystrix Command執行)的情況下。Hystrix的線程成本通常為0-3ms,如果CPU使用率超過90%,這個線程成本為有所上升約為9ms。相對于網絡請求的時間消耗,這個成本完全可以接受。

四、備注
本文參考

https://github.com/Netflix/Hystrix/wiki
分布式追蹤Sleuth工作原理


一、概述

在單體應用時代,接口緩慢能夠被迅速定位和發現,而隨著分布式微服務的流行,服務之間的調用關系越來越復雜,錯中復雜的調用關系使得我們想找到某一個接口的效率緩慢變得非常困難,而分布式服務調用跟蹤組件就解決了這個 問題。Sleuth是SprinCloud在分布式系統中提供追蹤解決方案,zipkin是基于Google Dapper的分布式鏈路調用監控系統。先介紹下有關的專業術語,

Span:基本工作單元,例如,在一個新建的span中發送一個RPC等同于發送一個回應請求給RPC,span通過一個64位ID唯一標識,trace以另一個64位ID表示,span還有其他數據信息,比如摘要、時間戳事件、關鍵值注釋(tags)、span的ID、以及進度ID(通常是IP地址)?
Trace:一系列spans組成的一個樹狀結構,例如,如果你正在跑一個分布式服務工程,你可能需要創建一個trace。
Annotation:用來及時記錄一個事件的存在,一些核心annotations用來定義一個請求的開始和結束 ?
? ? ? ? ? ? ? ?cs ? - Client Sent -客戶端發起一個請求,這個annotion描述了這個span的開始

? ? ? ? ? ? ? ?sr ? - Server Received -服務端獲得請求并準備開始處理它,如果將其sr減去cs時間戳便可得到網 ? ? ? ? ? ? ? ? ? ? ? ? ?絡延遲

? ? ? ? ? ? ? ?ss ?- Server Sent -注解表明請求處理的完成(當請求返回客戶端),如果ss減去sr時間戳便可得到服 ? ? ? ? ? ? ? ? ? ? ? ?務端需要的處理請求時間

? ? ? ? ? ? ? cr ?- Client Received -表明span的結束,客戶端成功接收到服務端的回復,如果cr減去cs時間戳 ? ? ? ? ? ? ? ? ? ? 便可得到客戶端從服務端獲取回復的所有所需時間

二、功能開發實現

?1.創建zipkin-server服務

? ? ?zipkin-server主要作用是使用ZipkinServer 的功能,收集調用數據鏈,并提供展示頁面供用戶使用。創建普通的SpringBoot項目zipkin-server,在pom.xml文件中增加如下依賴

<dependencies>
? ?<dependency>
? ? ? <groupId>io.zipkin.java</groupId>
? ? ? <artifactId>zipkin-server</artifactId>
? ?</dependency>
? ?<dependency>
? ? ? <groupId>io.zipkin.java</groupId>
? ? ? <artifactId>zipkin-autoconfigure-ui</artifactId>
? ? ? <scope>runtime</scope>
? ?</dependency>
? ?<dependency>
? ? ? <groupId>org.springframework.boot</groupId>
? ? ? <artifactId>spring-boot-starter-test</artifactId>
? ? ? <scope>test</scope>
? ?</dependency>
</dependencies>
在ZipKinServerApplication主方法上添加@EnableZipkinServer注解,啟用ZipkinServer功能。

@EnableZipkinServer
@SpringBootApplication
public class ZipkinServerApplication {
?
? ?public static void main(String[] args) {
? ? ? SpringApplication.run(ZipkinServerApplication.class, args);
? ?}
}
修改配置文件

spring.application.name=zipkin-server
server.port=9107
啟動服務,可以看到鏈路監控頁面,此時沒有收集到任何鏈路調用記錄。

2.給原先服務增加鏈路追蹤支持

? ?給eureka-provider、eureka-consumer、gateway三個服務增加如下依賴

<dependency>
? ?<groupId>org.springframework.cloud</groupId>
? ?<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
該依賴內部包含了兩個依賴,等于同時引入了spring-cloud-starter-sleuth,spring-cloud-sleuth-zipkin兩個依賴。

修改配置文件

#配置zipkin
#指定zipkin的服務端,用于發送鏈路調用報告
spring.zipkin.base-url=http://10.17.5.50:9107
# 采樣率,值為[0,1]之間的任意實數,這里代表100%采集報告。
spring.sleuth.sampler.percentage=1
重新部署啟動服務,調用zuul接口,觀察zipkin-server頁面,生成調用鏈路

三、小結

? ?本文還只是基本的鏈路分析,如果生產上使用,還需要把監控內容持久化、把監控內容發送從http模式切換到MQ等改造,這些內容下次再詳細介紹。

? ?碼云地址:https://gitee.com/gengkangkang/springcloud.git

? ? github地址:https://github.com/gengkangkang/springcloud.git

SpringBoot自動配置工作原理


Spring Boot的配置文件
初識Spring Boot時我們就知道,Spring Boot有一個全局配置文件:application.properties或application.yml。

我們的各種屬性都可以在這個文件中進行配置,最常配置的比如:server.port、logging.level.* 等等,然而我們實際用到的往往只是很少的一部分,那么這些屬性是否有據可依呢?答案當然是肯定的,這些屬性都可以在官方文檔中查找到:

https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#common-application-properties

(所以,話又說回來,找資料還得是官方文檔,百度出來一大堆,還是稍顯業余了一些)

除了官方文檔為我們提供了大量的屬性解釋,我們也可以使用IDE的相關提示功能,比如IDEA的自動提示,和Eclipse的YEdit插件,都可以很好的對你需要配置的屬性進行提示,下圖是使用Eclipse的YEdit插件的效果,Eclipse的版本是:STS 4。

?以上,是Spring Boot的配置文件的大致使用方法,其實都是些題外話。

那么問題來了:這些配置是如何在Spring Boot項目中生效的呢?那么接下來,就需要聚焦本篇博客的主題:自動配置工作原理或者叫實現方式。

工作原理剖析
Spring Boot關于自動配置的源碼在spring-boot-autoconfigure-x.x.x.x.jar中:

當然,自動配置原理的相關描述,官方文檔貌似是沒有提及。不過我們不難猜出,Spring Boot的啟動類上有一個@SpringBootApplication注解,這個注解是Spring Boot項目必不可少的注解。那么自動配置原理一定和這個注解有著千絲萬縷的聯系!

@EnableAutoConfiguration


?@SpringBootApplication是一個復合注解或派生注解,在@SpringBootApplication中有一個注解@EnableAutoConfiguration,翻譯成人話就是開啟自動配置,其定義如下:

?而這個注解也是一個派生注解,其中的關鍵功能由@Import提供,其導入的AutoConfigurationImportSelector的selectImports()方法通過SpringFactoriesLoader.loadFactoryNames()掃描所有具有META-INF/spring.factories的jar包。spring-boot-autoconfigure-x.x.x.x.jar里就有一個這樣的spring.factories文件。

這個spring.factories文件也是一組一組的key=value的形式,其中一個key是EnableAutoConfiguration類的全類名,而它的value是一個xxxxAutoConfiguration的類名的列表,這些類名以逗號分隔,如下圖所示:

這個@EnableAutoConfiguration注解通過@SpringBootApplication被間接的標記在了Spring Boot的啟動類上。在SpringApplication.run(...)的內部就會執行selectImports()方法,找到所有JavaConfig自動配置類的全限定名對應的class,然后將所有自動配置類加載到Spring容器中。

自動配置生效
每一個XxxxAutoConfiguration自動配置類都是在某些條件之下才會生效的,這些條件的限制在Spring Boot中以注解的形式體現,常見的條件注解有如下幾項:

@ConditionalOnBean:當容器里有指定的bean的條件下。

@ConditionalOnMissingBean:當容器里不存在指定bean的條件下。

@ConditionalOnClass:當類路徑下有指定類的條件下。

@ConditionalOnMissingClass:當類路徑下不存在指定類的條件下。

@ConditionalOnProperty:指定的屬性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表當xxx.xxx為enable時條件的布爾值為true,如果沒有設置的情況下也為true。

以ServletWebServerFactoryAutoConfiguration配置類為例,解釋一下全局配置文件中的屬性如何生效,比如:server.port=8081,是如何生效的(當然不配置也會有默認值,這個默認值來自于org.apache.catalina.startup.Tomcat)。

在ServletWebServerFactoryAutoConfiguration類上,有一個@EnableConfigurationProperties注解:開啟配置屬性,而它后面的參數是一個ServerProperties類,這就是習慣優于配置的最終落地點。

在這個類上,我們看到了一個非常熟悉的注解:@ConfigurationProperties,它的作用就是從配置文件中綁定屬性到對應的bean上,而@EnableConfigurationProperties負責導入這個已經綁定了屬性的bean到spring容器中(見上面截圖)。那么所有其他的和這個類相關的屬性都可以在全局配置文件中定義,也就是說,真正“限制”我們可以在全局配置文件中配置哪些屬性的類就是這些XxxxProperties類,它與配置文件中定義的prefix關鍵字開頭的一組屬性是唯一對應的。

至此,我們大致可以了解。在全局配置的屬性如:server.port等,通過@ConfigurationProperties注解,綁定到對應的XxxxProperties配置實體類上封裝為一個bean,然后再通過@EnableConfigurationProperties注解導入到Spring容器中。

而諸多的XxxxAutoConfiguration自動配置類,就是Spring容器的JavaConfig形式,作用就是為Spring 容器導入bean,而所有導入的bean所需要的屬性都通過xxxxProperties的bean來獲得。

可能到目前為止還是有所疑惑,但面試的時候,其實遠遠不需要回答的這么具體,你只需要這樣回答:

Spring Boot啟動的時候會通過@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自動配置類,并對其進行加載,而這些自動配置類都是以AutoConfiguration結尾來命名的,它實際上就是一個JavaConfig形式的Spring容器配置類,它能通過以Properties結尾命名的類中取得在全局配置文件中配置的屬性如:server.port,而XxxxProperties類是通過@ConfigurationProperties注解與全局配置文件中對應的屬性進行綁定的。

通過一張圖標來理解一下這一繁復的流程:

?圖片來自于王福強老師的博客:https://afoo.me/posts/2015-07-09-how-spring-boot-works.html?

總結
綜上是對自動配置原理的講解。當然,在瀏覽源碼的時候一定要記得不要太過拘泥與代碼的實現,而是應該抓住重點脈絡。

一定要記得XxxxProperties類的含義是:封裝配置文件中相關屬性;XxxxAutoConfiguration類的含義是:自動配置類,目的是給容器中添加組件。

而其他的主方法啟動,則是為了加載這些五花八門的XxxxAutoConfiguration類。
——
原文鏈接:https://blog.csdn.net/u014745069/article/details/83820511
?

總結

以上是生活随笔為你收集整理的SpringCloud底层原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

91丨九色丨丝袜 | 久久久91精品国产 | 中文字幕 国产视频 | 超碰在线成人 | 欧美日韩一区二区三区在线免费观看 | 超碰99在线| 国产精品久久久久久久久久久久久久 | 亚洲男男gⅴgay双龙 | 亚洲综合色婷婷 | 国产精品对白一区二区三区 | 亚洲色图22p | 99在线视频观看 | 国产精品亚洲a | 狠狠插天天干 | 免费黄色a网站 | 国产午夜精品福利视频 | 操天天操 | 午夜视频在线观看一区二区三区 | 成人av网页| www.色的| 四虎永久免费网站 | 欧美黄色成人 | 中文字字幕在线 | 五月激情在线 | 韩国精品福利一区二区三区 | 500部大龄熟乱视频使用方法 | 国产高清一级 | 国产一区免费在线 | 国产黄在线免费观看 | 日韩中文字幕第一页 | 999久久久久久久久 69av视频在线观看 | 亚洲电影一级黄 | 视频国产 | 亚州精品在线视频 | 欧美在线视频一区二区三区 | www.888av| 九九有精品 | 久久久网| 亚洲一区二区91 | 国产美女主播精品一区二区三区 | 亚洲综合日韩在线 | 欧美日韩久久不卡 | 免费黄在线观看 | 国产理论一区二区三区 | 伊在线视频 | 香蕉久久久久久av成人 | 国产精品黄色 | 黄色a在线观看 | 亚洲激情网站免费观看 | 色在线亚洲 | 黄色毛片大全 | 成人网色 | 国产丝袜 | 亚洲精品成人av在线 | 国产精品高清在线观看 | 免费亚洲婷婷 | 久久a热6| 久久久99精品免费观看app | 亚洲,国产成人av | 91精品国产91久久久久 | 国产精品乱码一区二三区 | 欧美精品久久久久久久久老牛影院 | 中文字幕在线观看你懂的 | 日韩欧美在线免费观看 | 国产精品中文字幕av | 国产免费观看高清完整版 | 天天干夜夜操视频 | 欧美一级爽 | 久久精品电影网 | 国产黄色资源 | 免费男女羞羞的视频网站中文字幕 | 日日夜夜精品网站 | 69国产精品视频 | 日日夜夜精品免费观看 | 亚洲精品婷婷 | 国产精品短视频 | 99精品色| 精品久久国产一区 | 免费男女羞羞的视频网站中文字幕 | 国产亚洲精品久久久久久移动网络 | 成年人电影免费在线观看 | free. 性欧美.com| 人人爽人人爽人人片av免 | 激情视频免费在线 | 天天干天天草 | 日韩精品在线免费播放 | 狠狠躁天天躁 | 奇米影音四色 | 午夜视频在线瓜伦 | 亚洲乱码精品久久久久 | 国产视频美女 | 天天草天天 | 日韩av影视 | 国产免费午夜 | 久久久精品视频网站 | 日韩av免费大片 | 成人黄色短片 | 亚洲欧洲精品一区二区 | 天天爱天天色 | 美女黄频在线观看 | 亚洲日本va午夜在线影院 | 欧美日韩国产网站 | 国产日韩欧美在线免费观看 | 日韩在线观看视频在线 | 亚洲成a人片77777kkkk1在线观看 | 草久在线观看视频 | 久久综合九色综合欧美就去吻 | 婷婷精品视频 | 色综合a| 久久久久激情视频 | 亚洲色视频 | 亚洲涩涩涩 | 久99久中文字幕在线 | 高清中文字幕av | 最近日本中文字幕 | 久久久久久伊人 | www操操操| 久久久久免费精品国产小说色大师 | 精品国产乱码久久久久久天美 | 奇米网8888| 天天搞天天干天天色 | 免费在线观看成人小视频 | 国产精品一区二区久久精品 | 九月婷婷人人澡人人添人人爽 | 中文字幕在线看视频 | 亚洲综合在线五月 | 久久精品波多野结衣 | 国产一卡二卡四卡国 | 久久久久久国产精品免费 | 精品国产伦一区二区三区免费 | 国产精品久久久久久久久久99 | 成年人免费在线观看网站 | 久久草在线视频国产 | 99久久精品国产亚洲 | 亚洲综合在线播放 | 精品色999 | 免费久久精品视频 | 天天操天天插 | 久久伊人免费视频 | 日韩啪啪小视频 | 9在线观看免费高清完整版在线观看明 | 久久综合色一综合色88 | 青青草国产精品 | 日韩av在线小说 | 国产中文字幕免费 | 一级黄色在线视频 | 欧美日韩高清不卡 | 97视频资源 | 国产99一区 | 91自拍视频在线观看 | 亚洲精品国产成人 | 麻豆影视网站 | 麻豆精品传媒视频 | av免费线看| 日韩在线中文字幕 | 韩国av电影在线观看 | 亚洲午夜大片 | 免费视频a | 成人片在线播放 | 在线观看视频国产 | 在线观看深夜福利 | 手机在线看片日韩 | 在线视频观看国产 | 国产亚洲精品av | 91麻豆精品国产午夜天堂 | 精品夜夜嗨av一区二区三区 | 爱爱av网站 | 亚洲国产成人精品久久 | 久久精品波多野结衣 | 在线观看国产高清视频 | av在线精品 | 久草久草在线观看 | 91精品国产99久久久久 | 日韩欧美一区视频 | 亚洲天堂激情 | 国产99re| 西西www4444大胆在线 | 免费日p视频 | 国产精品亚洲视频 | 精品在线99 | 欧美日韩在线精品 | 91看片在线免费观看 | 精品欧美小视频在线观看 | 欧美少妇xx | 2023国产精品自产拍在线观看 | 欧美激情精品一区 | 在线免费视频一区 | 欧美午夜a| 日本黄色片一区二区 | 日韩午夜av | 3d黄动漫免费看 | 狠狠操狠狠干2017 | 国产色影院 | 久久99精品国产91久久来源 | 日日摸日日 | 在线观看免费成人 | 91丨九色丨勾搭 | 久久久www | 色天天综合久久久久综合片 | 国产成人无码AⅤ片在线观 日韩av不卡在线 | 久久久久女人精品毛片 | 久久精品99北条麻妃 | 国内外成人在线 | 日韩欧美在线综合网 | 亚洲狠狠| 国产视频在线观看一区 | 亚洲午夜久久久综合37日本 | 国产精品午夜久久 | 91视频91自拍 | 国产资源免费 | 十八岁以下禁止观看的1000个网站 | 国产一级久久久 | 婷婷在线资源 | 亚洲片在线观看 | 波多野结衣精品视频 | 国产一级免费电影 | 精品美女视频 | 天天综合精品 | 精品在线视频观看 | 中文字幕av免费在线观看 | 国产视频97| 国产一级一级国产 | 免费a网址 | 成人久久亚洲 | 婷婷新五月 | 国产一二三四在线观看视频 | 久久噜噜少妇网站 | 日本美女xx | 久久久久久久久久久影院 | 国产美女视频黄a视频免费 久久综合九色欧美综合狠狠 | 欧美日韩高清国产 | 日韩欧美在线视频一区二区三区 | 91视频电影| 欧美另类性 | 奇米网8888 | 天天操天天干天天摸 | av成人免费在线观看 | 国产黄色看片 | 91干干干 | 亚洲国产操| 伊人电影天堂 | 久久色视频 | 99在线播放| 97超碰在线播放 | 激情电影在线观看 | 日日天天av| 天天色天| www.com久久久| 美女黄久久| 中文字幕国语官网在线视频 | 久久精品屋| 亚洲区色| 国内精品久久久久久久影视麻豆 | 免费成人在线观看视频 | 日韩一区二区三区在线看 | 人人爽人人爽人人爽学生一级 | 日韩av有码在线 | 日韩av在线一区二区 | 九九在线免费视频 | 日日成人网 | 色国产精品 | 99久久精品国产一区二区成人 | 少妇性xxx | 911精品美国片911久久久 | 亚洲精品乱码久久久久久蜜桃动漫 | 日本3级在线观看 | 91丨九色丨蝌蚪丰满 | 免费视频网 | 中文av在线播放 | 黄色高清视频在线观看 | 日韩视频三区 | 久草在线费播放视频 | 少妇bbbb | 99精品黄色片免费大全 | 国产原厂视频在线观看 | 亚洲精品xxxx | 国产v视频| 免费在线观看日韩 | 国产一线二线三线性视频 | 美女黄网久久 | 亚洲欧美成人在线 | 国产成a人亚洲精v品在线观看 | 亚洲精品99久久久久中文字幕 | 欧美va天堂va视频va在线 | 国产色一区| 日韩精品三区四区 | 久久久久女人精品毛片 | 操操操av| 91av在线不卡| 日本黄色免费播放 | 在线视频 国产 日韩 | 国产在线视频在线观看 | 欧美激情第八页 | 亚洲精品99久久久久中文字幕 | 婷婷色综合网 | 欧美日韩调教 | 亚洲精品高清视频在线观看 | 色婷婷综合成人av | 日本中文字幕在线一区 | 久久99精品久久只有精品 | 天天爱天天插 | 最近中文字幕大全 | 国产美女精品人人做人人爽 | 国产精品一区二区久久国产 | 五月婷婷久久丁香 | 欧美精品乱码久久久久 | 激情五月婷婷 | 麻豆av一区二区三区在线观看 | 国产精品视频地址 | 国产一区福利在线 | 狠狠色丁香婷婷 | 欧美日韩一区二区视频在线观看 | 久久久免费视频播放 | 欧美日韩精品在线播放 | 99婷婷 | 亚洲狠狠操 | 免费在线观看国产精品 | 一本色道久久综合亚洲二区三区 | 涩涩网站在线播放 | 欧美久久久影院 | 日产乱码一二三区别在线 | 国产中年夫妇高潮精品视频 | 精品三级av | 国产一区二区精品91 | 中文字幕一区二区三区乱码不卡 | 黄色三级网站 | 97国产一区二区 | 一区二区三区免费看 | 精品国产免费看 | 国产又粗又长又硬免费视频 | 亚洲精品99久久久久中文字幕 | 91欧美国产| 91在线视频免费 | 五月婷婷六月丁香激情 | 黄色三级在线观看 | 国产一区在线观看视频 | 日韩免费视频在线观看 | 日韩xxxbbb| 日韩综合视频在线观看 | 国产精品入口传媒 | 在线国产一区二区 | 成人在线免费看视频 | 91日韩在线视频 | 91禁看片| 狠狠狠色丁香婷婷综合久久88 | 亚洲午夜久久久久久久久电影网 | 久久精品视频中文字幕 | 欧美日韩在线视频一区二区 | 亚洲最新av | 激情婷婷在线观看 | 亚洲码国产日韩欧美高潮在线播放 | 久久久久久综合网天天 | 四虎影视久久久 | av视屏在线 | 国产精品国产三级国产aⅴ入口 | 精品欧美一区二区在线观看 | 日韩欧美国产激情在线播放 | 国产成人三级三级三级97 | 激情综合网色播五月 | 激情欧美一区二区三区免费看 | 香蕉视频91 | 免费看的黄色小视频 | 日本性xxxxx 亚洲精品午夜久久久 | 开心激情网五月天 | 国产日韩欧美在线观看 | 日韩在线看片 | 日韩中文三级 | 91精品国产乱码在线观看 | 狠狠色丁香婷婷综合视频 | 国产成人性色生活片 | 色吊丝在线永久观看最新版本 | 国产女人免费看a级丨片 | 久久97久久97精品免视看 | 精选久久| av在线电影网站 | 久久精彩免费视频 | 在线视频福利 | 欧美国产三区 | 国产精品第三页 | 18pao国产成视频永久免费 | 国产成人精品一区二区在线 | 4438全国亚洲精品在线观看视频 | 99国产精品视频免费观看一公开 | 久久涩视频 | 毛片888| 正在播放国产一区 | 黄色av一区二区三区 | 色婷婷免费视频 | 国产夫妻性生活自拍 | 国产高清久久久久 | 国产精品一区二区吃奶在线观看 | 精品a在线| 日韩免费在线视频观看 | 色亚洲激情| 久久国产露脸精品国产 | 久久免费看a级毛毛片 | 亚洲精品国产精品乱码在线观看 | 天天色天天骑天天射 | 97国产一区二区 | 久久久穴 | zzijzzij日本成熟少妇 | 免费成人在线视频网站 | 久久精品视频网址 | 一区二区三区在线免费观看视频 | 久久涩涩网站 | 在线免费高清一区二区三区 | 色综合天天 | 综合婷婷丁香 | 国产精品福利午夜在线观看 | 午夜在线国产 | 国产特级毛片aaaaaa毛片 | 国产亚洲视频系列 | 精品理论片 | 午夜视频久久久 | 亚洲成人999| 99麻豆久久久国产精品免费 | 欧美午夜理伦三级在线观看 | 精品视频久久久久久 | 99久久久国产精品免费观看 | 亚洲精品综合一二三区在线观看 | 亚洲精品中文字幕视频 | 欧美另类性 | 激情偷乱人伦小说视频在线观看 | 九九精品毛片 | 日韩视频一区二区三区在线播放免费观看 | 久久久久成人精品 | 天天天天爽 | 欧美国产日韩在线观看 | 国产午夜精品一区二区三区 | 久久电影网站中文字幕 | 亚洲91精品在线观看 | 四虎在线永久免费观看 | 中文字幕在线影院 | 五月花激情| 99精品国产福利在线观看免费 | 日本韩国欧美在线观看 | 日韩精品久久久久 | 久久a国产 | 亚洲天堂网在线观看视频 | 草免费视频 | 911久久香蕉国产线看观看 | 人人干97| 国产91精品看黄网站 | 日韩电影中文字幕在线观看 | 在线综合 亚洲 欧美在线视频 | 999久久久久 | 一区二区三区电影在线播 | 91成人黄色 | 在线观看视频中文字幕 | 国产精品自拍在线 | 最新日本中文字幕 | 午夜三级在线 | 夜夜高潮夜夜爽国产伦精品 | 色婷婷欧美 | 色偷偷男人的天堂av | 伊人久久精品久久亚洲一区 | 婷婷激情综合 | 99精彩视频在线观看免费 | 在线视频观看国产 | 美女久久久久久久久久久 | 婷婷丁香花 | 成年人在线免费看片 | 黄色成人在线网站 | 成人性生交大片免费看中文网站 | 一级片免费视频 | 欧美日韩视频免费看 | 五月婷婷激情 | 国产1级毛片 | 久久久久9999亚洲精品 | 91福利在线观看 | 成人av片免费看 | 超碰在线官网 | 免费精品视频在线观看 | 欧美精彩视频在线观看 | av一区二区三区在线 | 国产伦精品一区二区三区照片91 | 国产网站色 | 国语对白少妇爽91 | aaa亚洲精品一二三区 | 亚洲精品网站在线 | 97超级碰 | 亚洲毛片在线观看. | 欧美在线一级片 | 亚洲免费永久精品国产 | 欧美激情视频一区二区三区免费 | 97精品国产一二三产区 | 久久综合九色综合欧美就去吻 | 最近中文字幕高清字幕免费mv | 超碰在线人人97 | 在线播放日韩av | 日韩久久精品一区二区 | 91麻豆精品国产91久久久使用方法 | 九九久久影院 | 国产精品视频地址 | 一级片在线 | 国产香蕉97碰碰碰视频在线观看 | 国产一区二区手机在线观看 | 麻豆国产网站入口 | 中国一级片在线播放 | 少妇bbb搡bbbb搡bbbb | 天天天操操操 | 天天操网站 | 不卡的av电影 | 欧美精品乱码久久久久 | 五月婷婷色播 | 毛片在线播放网址 | 91精品一区在线观看 | 精品久久久久国产 | 亚洲黄色一级大片 | 18久久久| 亚洲欧美视频在线播放 | 久久精品综合视频 | 欧美激情xxxx性bbbb | 亚洲国产字幕 | 高清国产午夜精品久久久久久 | 日本中文字幕在线免费观看 | 欧洲av不卡| 欧美黄污视频 | 国产成人99av超碰超爽 | 国产精品v欧美精品 | 日本不卡一区二区三区在线观看 | 日韩av一区二区三区 | 97视频免费播放 | 激情五月综合 | 国产码电影| 久久香蕉国产精品麻豆粉嫩av | 在线观看一级片 | 久久亚洲日本 | 成人性生活大片 | 国产久草在线观看 | 99精品系列 | 久久涩涩网站 | 久久天天躁夜夜躁狠狠85麻豆 | 黄色免费国产 | 免费亚洲电影 | 在线免费观看av网站 | 久久精品久久久精品美女 | 一二三四精品 | 亚洲精选在线观看 | 福利电影一区二区 | 亚洲免费色 | 久久久精品国产免费观看一区二区 | 久久国产电影 | 欧美一级免费高清 | 久久伊人免费视频 | 久久在线看 | 新版资源中文在线观看 | 99久久婷婷国产一区二区三区 | 99热这里只有精品1 av中文字幕日韩 | 最新国产中文字幕 | 91社区国产高清 | 亚洲h在线播放在线观看h | 国产精品一区二区三区免费看 | 精品99在线 | 日韩精品一区不卡 | 一级性视频 | 国产精品视频地址 | 国产精品久久婷婷六月丁香 | 亚洲精品高清在线观看 | 狠狠色丁香婷婷综合久久片 | 国产丝袜 | 国产盗摄精品一区二区 | 99久久婷婷国产综合亚洲 | 狠狠色丁香九九婷婷综合五月 | 狠狠操91 | 91视频在线观看免费 | 六月丁香激情综合色啪小说 | 69xx视频 | 在线观看黄污 | 97色国产| 最近久乱中文字幕 | 丁香婷婷综合激情五月色 | 久久最新网址 | v片在线播放 | 亚洲精品免费看 | 午夜精品久久久99热福利 | 天天骚夜夜操 | 成人黄色av免费在线观看 | 亚洲精品在线免费看 | 黄色毛片一级 | 日韩精品在线免费观看 | 日韩最新理论电影 | 99色免费 | 欧美三级高清 | 国产亚洲视频在线 | 成人在线播放免费观看 | 亚洲高清不卡av | 久久激情小视频 | 中文日韩在线视频 | 成人午夜影院 | 丁香花在线视频观看免费 | 五月天六月色 | 精品国产1区 | 日韩欧美高清视频在线观看 | 手机在线看a | 免费看黄色大全 | 日韩 精品 一区 国产 麻豆 | 欧美日韩国产伦理 | 五月婷婷天堂 | 国产第一页在线播放 | 99re国产 | www夜夜| 欧美在线不卡一区 | 国产精品免费在线播放 | 在线免费黄色av | av天天澡天天爽天天av | 曰本三级在线 | 国产精品igao视频网网址 | 日韩精品一区二区不卡 | 婷婷久久久久 | 免费看亚洲毛片 | 黄色录像av | 81国产精品久久久久久久久久 | 深爱五月网 | 亚洲九九爱 | 婷婷精品国产一区二区三区日韩 | 在线观看久 | 国产不卡在线视频 | 99国产情侣在线播放 | 国产91全国探花系列在线播放 | 在线观看日韩中文字幕 | 亚洲成a人片在线www | 欧美一级免费在线 | 欧美日韩69 | 99中文字幕视频 | 在线观看视频国产 | 久久视频在线观看中文字幕 | 天天曰天天 | 91麻豆精品国产91久久久久久 | 国产一二三区在线观看 | 九色porny真实丨国产18 | 97国产视频 | 三级av黄色 | 免费a视频在线 | 97精品视频在线播放 | 丁香六月在线 | 欧美最猛性xxxxx(亚洲精品) | 黄色国产高清 | 中文av一区二区 | 91av手机在线观看 | 正在播放亚洲精品 | 91麻豆精品国产91久久久无限制版 | 中文字幕日韩高清 | 美女视频黄频大全免费 | 国产成人av网址 | 99麻豆久久久国产精品免费 | 精品视频99| 在线观看免费高清视频大全追剧 | 91久久电影 | 亚洲国内精品视频 | 岛国精品一区二区 | 国产美女视频黄a视频免费 久久综合九色欧美综合狠狠 | 日本高清中文字幕有码在线 | 人人网av| 99久久久久久久 | 超碰人人做 | 亚洲日本激情 | 国产精品久久久久久久久久久久午夜 | 亚洲欧洲精品一区 | 国产一区二区三区免费在线观看 | www.天天干.com | 精品国产理论 | 久草视频免费在线观看 | 日p在线观看 | 亚洲电影院 | 免费久久片 | 久久久福利 | 婷婷丁香色综合狠狠色 | 国产精品 国产精品 | 色噜噜日韩精品一区二区三区视频 | 香蕉久久久久久久 | 久久久久久久久久久久国产精品 | 97成人免费 | 久久99久久99精品免费看小说 | 中文字幕一区二区三区在线播放 | 日韩中文字幕国产精品 | 日韩色av色资源 | 麻豆视频免费在线播放 | 这里有精品在线视频 | 亚洲国产成人在线播放 | zzijzzij亚洲日本少妇熟睡 | 国产精品亚洲片夜色在线 | 草樱av| 天天射天天添 | 欧美一区三区四区 | 精品一区二区免费视频 | 久久久综合精品 | 黄色片免费看 | 国产精品久久片 | 成人毛片100免费观看 | 亚洲欧美精品在线 | 91激情小视频| 色丁香婷婷 | 成人cosplay福利网站 | 麻豆av一区二区三区在线观看 | 激情开心| 男女男视频| 99在线视频精品 | 欧美成人在线免费 | 久久经典视频 | 免费观看一区二区三区视频 | 久久人人爽人人爽人人 | 日韩精品视频第一页 | 久久人人精品 | 97国产精品 | 国产成人三级在线播放 | 国产精品一区二区久久国产 | 九九热免费在线观看 | 国产99久久久欧美黑人 | 国产精品美女免费 | av天天草| 亚洲理论片在线观看 | 日韩在线理论 | 在线免费观看黄色 | av丝袜制服 | 国产中文字幕一区二区 | 国产视频每日更新 | 91精品视频播放 | 色先锋av资源中文字幕 | 国产精品入口麻豆 | 有码中文字幕在线观看 | 成年美女黄网站色大片免费看 | 精品国模一区二区 | 中文字幕在线一区观看 | 91成人黄色 | www.久久色| 美女免费视频网站 | 99国内精品久久久久久久 | 久久精品久久久久电影 | 夜夜骑日日 | 国产精品自在线 | 国产免费成人 | 综合网中文字幕 | 免费观看国产精品 | 亚洲不卡av一区二区三区 | 中文字幕在线中文 | 国产福利精品在线观看 | 99在线免费观看视频 | 亚洲www天堂com | www.91av在线 | 97色在线| 国产一区二区三区四区大秀 | 青青河边草免费直播 | 日本精品视频免费观看 | 日日夜夜天天操 | 在线精品在线 | 一区二区三区视频在线 | 2019天天干天天色 | 91久久人澡人人添人人爽欧美 | 欧美日韩不卡一区二区三区 | 在线观看理论 | 国产亚洲欧美日韩高清 | 久久久久日本精品一区二区三区 | 国产五十路毛片 | 91成人亚洲| 在线视频观看国产 | 最近中文字幕在线播放 | 欧美成人精品三级在线观看播放 | 亚洲深爱激情 | 国产a国产 | 日韩视频免费观看高清 | 一本一道久久a久久综合蜜桃 | 日韩黄色网络 | 精品久久久久久亚洲综合网站 | 黄色大片中国 | 丝袜少妇在线 | 韩日三级av | 亚洲精品在线网站 | 蜜臀av性久久久久蜜臀aⅴ涩爱 | 久久综合给合久久狠狠色 | 成人精品视频久久久久 | 亚洲伊人第一页 | 成 人 黄 色 免费播放 | 国产精品一区二区三区视频免费 | 久热爱 | 国产精品9999 | 黄色在线视频网址 | 日韩一区二区三免费高清在线观看 | 成人av网页 | 有没有在线观看av | 国产精品久久久精品 | 中文av不卡| 婷婷丁香色综合狠狠色 | 欧美一区中文字幕 | 亚洲六月丁香色婷婷综合久久 | 日韩精品一区二区三区电影 | 97在线看| 久久九九精品 | 中文字幕在线观看播放 | 久久久.com| 日韩系列在线观看 | 国语精品视频 | 成人在线视频免费看 | 黄网站免费看 | 天天躁天天操 | 九九在线国产视频 | 中文字幕在线观看完整版 | 久久免费黄色 | 伊人中文在线 | 天堂av在线 | 黄色特一级 | 久草在线视频中文 | 日韩精品中文字幕一区二区 | 国产一区免费在线 | 亚洲欧美日韩一区二区三区在线观看 | 亚洲精品乱码久久久久久蜜桃欧美 | 久久久精品免费观看 | av成人动漫| 日韩av区 | 不卡av电影在线观看 | 九九欧美 | 99久久精品免费 | 五月婷婷伊人网 | 日韩欧美高清不卡 | 天天干,天天射,天天操,天天摸 | 最近乱久中文字幕 | 97看片吧 | www.黄色在线 | 狠狠色丁香久久婷婷综合_中 | 免费看毛片在线 | 久艹视频在线免费观看 | 午夜精品久久久久久 | 亚洲精品欧美专区 | 最新成人av | 美女很黄免费网站 | 欧美一级日韩三级 | 蜜臀aⅴ精品一区二区三区 久久视屏网 | 亚洲精品在线视频播放 | japanese黑人亚洲人4k | 中文字幕在线观看你懂的 | 欧美一区二区三区不卡 | 天天干天天射天天操 | 亚洲综合视频在线观看 | 欧美最新大片在线看 | 九九视频精品免费 | 成人av手机在线 | 一级理论片在线观看 | 久久不射电影院 | 天天干天天操天天搞 | 国内精品视频在线播放 | 日本韩国精品在线 | 精品av网站 | 欧美精品久久久久 | adn—256中文在线观看 | 成人免费在线观看av | 激情喷水 | 日韩欧美视频在线观看免费 | 综合网久久 | 97品白浆高清久久久久久 | 亚洲精品视频在线看 | 超碰在线9 | 美女一二三区 | 日本字幕网 | 日韩a在线播放 | 久久天堂影院 | 91手机电视 | 久爱精品在线 | 免费看三级网站 | 婷婷六月激情 | 国产人免费人成免费视频 | 涩涩网站在线播放 | 天天爱av导航 | av中文字幕在线观看网站 | 91av电影在线观看 | 女人18精品一区二区三区 | 亚洲综合在线五月天 | 欧美激情视频三区 | 岛国av在线免费 | 日韩精品一区二区三区免费视频观看 | 97视频一区 | 精品国产免费一区二区三区五区 | 日韩av中文 | 亚洲精品国内 | 国产精品欧美日韩在线观看 | 日韩三级成人 | av电影一区二区三区 | 欧美精品在线观看 | 精品福利在线观看 | 国产精品第一视频 | 探花视频在线观看 | av千婊在线免费观看 | 国产在线精品福利 | 亚洲国产精品视频 | 99视频国产精品免费观看 | 在线a人片免费观看视频 | 国产日本在线观看 | 蜜桃视频成人在线观看 | 人人看看人人 | 日韩91精品 | 久久国产精品99国产 | 狠狠干天天 | 成人免费大片黄在线播放 | 国产麻豆精品久久一二三 | 菠萝菠萝在线精品视频 | 中文字幕第一页av | 色视频网站在线观看一=区 a视频免费在线观看 | 又黄又刺激视频 | 国产剧情一区 | 91理论电影 | 尤物97国产精品久久精品国产 | 欧美小视频在线观看 | 欧美日韩国产三级 | 狠狠色免费 | 日韩欧美一区二区三区黑寡妇 | 9ⅰ精品久久久久久久久中文字幕 | 黄色软件视频网站 | 成人97人人超碰人人99 | 国产精品一区免费观看 | 欧美日韩国产伦理 | 色网站免费在线看 | 中文字幕人成乱码在线观看 | 色综合天天视频在线观看 | 国产精品一区二区电影 | 亚洲九九九在线观看 | 亚洲欧美日本A∨在线观看 青青河边草观看完整版高清 | 中国黄色一级大片 | 久久久这里有精品 | 国产精品videoxxxx| 波多野结衣视频在线 | 亚洲高清不卡av | 天天射天天搞 | 黄色网址在线播放 | 91女神的呻吟细腰翘臀美女 | 久久精品99精品国产香蕉 | 人人爽人人av | 久久久免费播放 | 综合国产视频 | 91一区啪爱嗯打偷拍欧美 | 欧美日韩破处 | 国产亚洲日本 | 婷婷看片 | 国产午夜精品久久久久久久久久 | 日本在线观看黄色 | 亚洲视频每日更新 | aaa亚洲精品一二三区 | 免费看黄色小说的网站 | 欧美性做爰猛烈叫床潮 | 中文字幕中文字幕在线一区 | 久久精美视频 | a级黄色片视频 | 色国产精品一区在线观看 | 久久精品久久久久久久 | 国产传媒中文字幕 | avlulu久久精品 | 亚洲激情 在线 | 婷婷干五月 | 国产精品永久久久久久久www | 国产午夜精品久久久久久久久久 | 一区二区三区视频在线 | 久久男人影院 | 国产香蕉久久精品综合网 | 国产欧美精品一区二区三区四区 | 国产精品video爽爽爽爽 | 中文一二区 | 操操操人人 | 精品毛片久久久久久 | 色香蕉网 | 国内精品久久久久久久久久久 | 波多野结衣电影一区二区 | 欧美在线久久 | 免费a v网站 | 丁香婷婷激情网 | 久久精品99久久久久久 | 国产精品久久久久aaaa | 日本论理电影 | 97人人人人 | 成人在线网站观看 | 亚洲国产精品女人久久久 | 日日色综合 | 九九九九九九精品 | 在线观看黄网站 | 人人藻人人澡人人爽 | 色婷婷亚洲综合 | 天天综合亚洲 | 国产小视频在线播放 | 国产一线二线三线在线观看 | 日本中文字幕高清 | 激情六月婷婷久久 | 午夜精品久久久久久久99婷婷 | 色a综合 | 久久999精品 | 天天操天天射天天舔 | 久久一二区 | 欧美va日韩va| 久久免费视频2 | 美女黄频| 国产69精品久久99的直播节目 | 国内视频在线观看 | 超碰在线日韩 | 中文av日韩 | 九九热视频在线 | 天天艹天天干天天 | 日日干天天爽 | 97视频网站| 亚洲黄网址| www.亚洲| 99爱在线| 久草免费电影 |