資深架構師解析springcloud分佈式微服務的實現

分佈式系統

微服務就是原來臃腫的項目拆分爲多個模塊互不關聯。如:按照子服務拆分、數據庫、接口,依次往下就更加細粒度,固然運維也就愈來愈難受了。java

分佈式則是偏向與機器將諾大的系統劃分爲多個模塊部署在不一樣服務器上。web

微服務和分佈式就是做用的「目標不同」。算法

微服務與Cloud

微服務是一種概念,spring-cloud是微服務的實現。spring

微服務也不必定必須使用cloud來實現,只是微服務中有許多問題,如:負載均衡、服務註冊與發現、路由等等。數據庫

而cloud則是將這些處理問題的技術整合了。api

Spring-Cloud 組件

Eureka緩存

Eureka是Netifix的子模塊之一,Eureka有2個組件,一個EurekaServer 實現中間層服務器的負載均衡和故障轉移,一個EurekaClient它使得與server交互變得簡單。服務器

Spring-Cloud封裝了Netifix公司開發的Eureka模塊來實現服務註冊和發現。網絡

經過Eureka的客戶端 Eureka Server維持心跳鏈接,維護能夠更方便監控各個微服務的運行。架構

角色關係圖

Eureka使用

客戶端

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
server:
port: 4001
eureka:
client:
serviceUrl:
defaultZone: http://localhost:3000/eureka/ #eureka服務端提供的註冊地址 參考服務端配置的這個路徑
instance:
instance-id: admin-1 #此實例註冊到eureka服務端的惟一的實例ID
prefer-ip-address: true #是否顯示IP地址
leaseRenewalIntervalInSeconds: 10 #eureka客戶須要多長時間發送心跳給eureka服務器,代表它仍然活着,默認爲30 秒 (與下面配置的單位都是秒)
leaseExpirationDurationInSeconds: 30 #Eureka服務器在接收到實例的最後一次發出的心跳後,須要等待多久才能夠將此實例刪除,默認爲90秒

spring:
application:
name: server-admin #此實例註冊到eureka服務端的name

服務端

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-
netflix-eureka-server</artifactId>
</dependency>
yml文件聲明 
server:
port: 3000
eureka:
server:
enable-self-preservation: false #關閉自我保護機制
eviction-interval-timer-in-ms: 4000 #設置清理間隔
(單位:毫秒 默認是60*1000)
instance:
hostname: localhost 
client:
registerWithEureka: false #不把本身做爲一個客戶端註冊到本身身上
fetchRegistry: false #不須要從服務端獲取註冊信息
(由於在這裏本身就是服務端,並且已經禁用本身註冊了)
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
在SpringBoot 啓動項目中加入註解:@EnableEurekaServer 
就能夠啓動項目了,訪問對應地址就能夠看到界面。

Eureka 集羣

服務啓動後Eureka Server會向其餘服務server 同步,當消費者要調用服務提供者,則向服務註冊中心獲取服務提供者的地址,而後將提供者的地址緩存到本地,下次調用時候直接從本地緩存中獲取

yml 服務端

server:
port: 3000
eureka:
server:
enable-self-preservation: false #關閉自我保護機制
eviction-interval-timer-in-ms: 4000 #設置清理間隔
(單位:毫秒 默認是60*1000)
instance:
hostname: eureka3000.com 
client:
registerWithEureka: false #不把本身做爲一個客戶端
註冊到本身身上
fetchRegistry: false #不須要從服務端獲取註冊信息
(由於在這裏本身就是服務端,並且已經禁用本身註冊了)
serviceUrl:
defaultZone: http://eureka3001.com:3001/eureka,
http://eureka3002.com:3002/eureka
(這裏不註冊本身,註冊到其餘服務上面覺得會同步。)

yml 客戶端

server:
port: 4001
eureka:
client:
serviceUrl:
defaultZone:http://localhost:3000/eureka/,http://
eureka3001.com:3001/eureka,http://eureka3002.com:3 002
/eureka #eureka服務端提供的註冊地址 參考服務端配置的這個路徑
instance:
instance-id: admin-1 #此實例註冊到eureka服務端的惟一的實例ID
prefer-ip-address: true #是否顯示IP地址
leaseRenewalIntervalInSeconds: 10 #eureka客戶須要多長時間發
送心跳給eureka服務器,代表它仍然活着,默認爲30 秒 (與下面配置的單位都是秒)
leaseExpirationDurationInSeconds: 30 #Eureka服務器在
接收到實例的最後一次發出的心跳後,須要等待多久才能夠將此實例刪除,默認爲90秒

spring:
application:
name: server-admin #此實例註冊到eureka服務端的name

CAP定理

C:Consistency 一致性
A:Availability 可用性
P:Partition tolerance 分區容錯性
這三個指標不能同時達到

Partition tolerance

分區容錯性,大多數分佈式系統都部署在多個子網絡。每個網絡是一個區。區間的通訊是可能失敗的如一個在本地,一個在外地,他們之間是沒法通訊的。分佈式系統在設計的時候必需要考慮這種狀況。

Consistency

一致性,寫操做後的讀取,必須返回該值。如:服務器A1和服務器A2,如今發起操做將A1中V0改成V1,用戶去讀取的時候讀到服務器A1獲得V1,若是讀到A2服務器可是服務器

仍是V0,讀到的數據就不對,這就不知足一致性。

因此讓A2返回的數據也對,的讓A1給A2發送一條消息,把A2的V0變爲V1,這時候無論從哪裏讀取都是修改後的數據。

Availability

可用性就是用戶只要給出請求就必須迴應,不論是本地服務器仍是外地服務器只要接收到就必須作出迴應,無論數據是不是最新必須作出迴應,負責就不是可用性。

C與A矛盾

一致性和可用性不能同時成立,存在分區容錯性,通訊可能失敗。

若是保證一致性,A1在寫操做時,A2的讀寫只能被鎖定,只有等數據同步了才能讀寫,在鎖按期間是不能讀寫的就不符合可用性。

若是保持可用性,那麼A2就不會被鎖定,因此一致性就不能成立。

綜上 沒法作到一致性和可用性,因此係統在設計的時候就只能選其一。

Eureka與Zookeeper

Zookeeper遵循的是CP原則保持了一致性,因此在master節點由於網絡故障與剩餘「跟隨者」接點失去聯繫時會從新選舉「領導者」,選取「領導者」大概會持續30-120s的時間,且選舉的時候整個zookeeper是不可用的。致使在選舉的時候註冊服務癱瘓。

Eureka在設計的時候遵循AP可用性。Eureka各個接點是公平的,沒有主從之分,down掉幾個幾點也沒問題,其餘接點依然能夠支持註冊,只要有一臺Eureka在,註冊就能夠用,只不過查詢到的數據可能不是最新的。Eureka有自我保護機制,若是15分鐘以內超過85%接點都沒有正常心跳,那麼Eureka認爲客戶端與註冊中心出現故障,此時狀況多是

Eureka不在從註冊列表移除由於長時間沒有瘦到心跳而過時的服務。

Eureka仍然可以接收註冊和查詢,但不會同步到其餘接點。

當網絡穩定後,當前的 實例註冊信息會更新到其餘接點。

Ribbon

rebbon主要提供客戶端的負載均衡,提供了一套完善的客戶端的配置。Rebbin會自動幫助你基於某種規則(如:簡單的輪詢,隨機連接等)。

服務端的負載均衡是一個url經過一個代理服務器,而後經過代理服務器(策略:輪詢,隨機 ,權重等等),反向代理到你的服務器。

客戶端負載均衡是經過一個請求在客戶端已經聲明瞭要調用那個服務,而後經過具體算法來完成負載均衡。

Ribbon使用

引入依賴,Eureka以及把Ribbon集成在裏面。

使用Ribbon只有在RestTemplate上面加入@LoadBalanced註解。

Feign負載均衡

feign是一個聲明式的webService客戶端,使用feign會讓編寫webService更簡單,就是定義一個接口加上註解。

feign是爲了編寫java http客戶端更加簡單,在Ribbon+RestTemplate此基礎上進一步封裝,簡化了使用Spring Cloud Ribbon時,自動封裝服務調用客戶端的開發量。

Feign使用

引入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在啓動類上加@EnableFeignClients
而後在接口上加@FeignClient("SERVER-POWER")註解其中參數就是服務的名字。

Feign集成Ribbon

利用Ribbon維護服務列表信息,融合了Ribbon的負載均衡配置,與Ribbon不一樣的是Feign只須要定義服務綁定接口以聲明的方式,實現簡答的服務調用。

hystrix斷路器

是一種用於處理分佈式系統延遲和容錯的開源庫。在分佈式系統中許多依賴不可避免的會調用失敗,好比超時、異常等,斷路器保證出錯不會致使總體服務失敗,避免級聯故障。

斷路器其實就是一種開關設置,相似保險絲,像調用方返回一個符合預期的、可處理的備選響應,而不是長時間等待或者拋出沒法處理的異常,保證服務調用方線程不會被長時間 沒必要要佔用,從而避免了在分佈式系統中蔓延,乃至雪崩。

微服務中 client->微服務A->微服務B->微服務C->微服務D,其中微服務B異常了,全部請求微服務A的請求都會卡在B這裏,就會致使線程一直累積在這裏,那麼其餘微服務就沒有可用線程,致使整個服務器雪崩。

針對這方案有 服務限流、超時監控、服務熔斷、服務降級

降級 超時

降級就是服務響應過長 ,或者不可用了,就是服務調用不了了,咱們不能把錯誤信息返回出來,或者長時間卡在哪裏,因此要準備一個策略當發生這種問題咱們直接調用這個方法快速返回這個請求,不讓他一直卡在那。

要在調用方作降級(要否則那個微服務都down掉了在作降級就沒有意義)。

引入hystrix依賴

<dependency> 
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId> 
</dependency>
在啓動類上加入@EnableHystrix 或者@EnableCircuitBreaker。
@RequestMapping("/feignPower.do") 
@HystrixCommand(fallbackMethod = "fallbackMethod") 
public Object feignPower(String name){ 
return powerServiceClient.power(); 
} 
fallbackMethod:
public Object fallbackMethod(String name){ 
System.out.println(name); 
return R.error("降級信息"); 
}
這裏的降級信息具體內容根據業務需求來,好比返回一個默認的查詢信息等等。
hystrix有超時監聽,當你請求超過1秒 就會超時,這個是能夠配置的

這裏的降級信息具體內容根據業務需求來,好比返回一個默認的查詢信息等等。

hystrix有超時監聽,當你請求超過1秒 就會超時,這個是能夠配置的

降級什麼用

第一他能夠監聽服務有沒有超時。第二報錯了他這裏直接截斷了沒有讓請求一直卡在這個。

其實降級,當你係統迎來高併發的時候,這時候發現系統立刻承載不了這個大的併發 ,能夠先關閉一些不重要 的微服務(就是在降級方法返回一個比較友好的信息)把資源讓出來給主服務,其實就是總體資源不夠用了,忍痛關閉某些服務,待過渡後再打開。

熔斷限流

熔斷就像生活中的跳閘,好比電路故障了,爲了防止事故擴大,這裏切斷你的電源以避免意外發生。當一個微服務調用屢次,hystrix就會採起熔斷 機制,不在繼續調用你的方法,會默認短路,5秒後試探性的先關閉熔斷機制,若是在這時候失敗一次會直接調用降級方法,必定程度避免雪崩,

限流,限制某個微服務使用量,若是線程佔用超過了,超過的就會直接降級該次調用。

Feign整合hystrix

feign默認支持hystrix,須要在yml配置中打開。
feign: 
hystrix: 
enabled: true

降級方法
@FeignClient(value = "SERVER-POWER", fallback = PowerServiceFallBack.class)
public interface PowerServiceClient {

@RequestMapping("/power.do")
public Object power(@RequestParam("name") String name);
}

在feign客戶端的註解上 有個屬性叫fallback 而後指向一個類 PowerServiceClient 
@Component
public class PowerServiceFallBack implements PowerServiceClient {
@Override
public Object power(String name) {
return R.error("測試降級");
}
}

Zuul 網關

zuul包含了對請求的路由和過濾兩個主要功能

路由是將外部請求轉發到具體的微服務實例上。是實現統一入口基礎而過濾器功能負責對請求的處理過程干預,是實現請求校驗等功能。

Zuul與Eureka進行整合,將zuul註冊在Eureka服務治理下,同時從Eureka獲取其餘服務信息。(zuul分服務最終仍是註冊在Eureka上)

路由

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
最後要註冊在Eureka上因此須要引入eureka依賴
YML
server:
port: 9000
eureka:
client:
serviceUrl:
defaultZone: http://localhost:3000/eureka/ #eureka服務端提供的註冊地址 參考服務端配置的這個路徑
instance:
instance-id: zuul-0 #此實例註冊到eureka服務端的惟一的實例ID
prefer-ip-address: true #是否顯示IP地址
leaseRenewalIntervalInSeconds: 10 #eureka客戶須要多長時間發送心跳給eureka服務器,代表它仍然活着,默認爲30 秒 (與下面配置的單位都是秒)
leaseExpirationDurationInSeconds: 30 #Eureka服務器在接收到實例的最後一次發出的心跳後,須要等待多久才能夠將此實例刪除,默認爲90秒

spring:
application:
name: zuul #此實例註冊到eureka服務端的name 
啓動類 @EnableZuulProxy

在實際開發當中咱們確定不會/server-power這樣經過微服務調用,
可能只要一個/power就行了 
zuul: 
routes:
mypower: 
serviceId: server-power 
path: /power/** 
myorder: 
serviceId: server-order 
path: /order/**
注意/**表明是全部層級 /* 是表明一層。
通常咱們會禁用服務名調用
ignored-services:server-order 這樣就不能經過此服務名調用,
不過這個配置若是一個一個通微服務名字設置太複雜
通常禁用服務名 ignored-services:「*」
有時候要考慮到接口調用須要必定的規範,好比調用微服務URL須要前綴/api,能夠加上一個prefix
prefix:/api 在加上strip-prefix: false /api前綴是不會出如今路由中
zuul:
prefix: /api
ignored-services: "*"
stripPrefix: false
routes:
product:
serviceId: server-product
path: /product/**
order:
serviceId: server-order
path: /order/**

過濾器

過濾器(filter)是zuul的核心組件,zuul大部分功能是經過過濾器實現的,zuul中定義了4種標準過濾器類型,這些過濾器類型對應與請求的生命週期,

PRE:這種過濾器在請求路由前被調用,可利用過濾器進行身份驗證,記錄請求微服務的調試信息等。

ROUTING:這種過濾器將請求路由到微服務,這種過濾器用於構建發送給微服務請求,並使用 Apache HttpClient或Netfix Ribbon請求微服務。

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

ERROR:在其餘階段發送錯誤時執行過濾器

繼承ZuulFilter

@Component
public class LogFilter extends ZuulFilter { 
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}

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

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

@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
//被代理到的微服務
String proxy = (String)ctx.get("proxy");
//請求的地址
String requestURI = (String)ctx.get("requestURI");
//zuul路由後的url
System.out.println(proxy+"/"+requestURI);
HttpServletRequest request = ctx.getRequest();
String loginCookie = CookieUtil.getLoginCookie(request);
ctx.addZuulRequestHeader("login_key",loginCookie);
return null;
}
}

由此可知道自定義zuul Filter要實現如下幾個方法。

filterType:返回過濾器類型,有pre、route、post、erro等幾種取值

filterOrder:返回一個int值指定過濾器的順序,不一樣過濾器容許返回相同數字。

shouldFilter:返回一個boolean判斷過濾器是否執行,true執行,false不執行。

run:過濾器的具體實現。

Spting-Cloud默認爲zuul編寫並開啓一些過濾器。若是要禁用部分過濾器,只需在application.yml裏設置zuul…disable=true,例如zuul.LogFilter.pre.disable=true

zuul也整合了了hystrix和ribbon的, 提供降級回退,繼承FallbackProvider 類 而後重寫裏面的方法。

 

 

私信我免費領取更多java架構資料、源碼、筆記

相關文章
相關標籤/搜索