服務治理的概念
服務治理是主要針對分散式服務框架、微服務,處理服務呼叫之間的關係,服務釋出和發現(誰是提供者,誰是消費者,要註冊到哪裡),出了故障誰呼叫誰,服務的引數都有哪些約束,如何保證服務的質量?如何服務降級和熔斷?怎麼讓服務受到監控,提高機器的利用率?
微服務有哪些問題需要治理?
-
服務註冊與發現: 單體服務拆分為微服務後,如果微服務之間存在呼叫依賴,就需要得到目標服務的服務地址,也就是微服務治理的 服務發現 。要完成服務發現,就需要將服務資訊儲存到某個載體,載體本身即是微服務治理的服務註冊中心,而儲存到載體的動作即是服務註冊。
-
可觀測性: 微服務由於較單體應用有了更多的部署載體,需要對眾多服務間的呼叫關係、狀態有清晰的掌控。可觀測性就包括了呼叫拓撲關係、監控(Metrics)、日誌(Logging)、呼叫追蹤(Trace)等。
-
流量管理: 由於微服務本身存在不同版本,在版本更迭過程中,需要對微服務間呼叫進行控制,以完成微服務版本更迭的平滑。這一過程中需要根據流量的特徵(訪問引數等)、百分比向不同版本服務分發,這也孵化出灰度釋出、藍綠髮布、A/B測試等服務治理的細分主題。
-
服務容錯: 任何服務都不能保證100%不出問題,生產環境複雜多變,服務執行過程中不可避免的發生各種故障(當機、過載等等),工程師能夠做的是在故障發生時儘可能降低影響範圍、儘快恢復正常服務,需要引入「熔斷、隔離、限流和降級、超時機制」等「服務容錯」機制來保證服務持續可用性。
-
安全: 不同微服務承載自身獨有的業務職責,對於業務敏感的微服務,需要對其他服務的訪問進行認證與鑑權,也就是安全問題。
-
控制: 對服務治理能力充分建設後,就需要有足夠的控制能力,能實時進行服務治理策略向微服務分發。
-
服務本身的治理: 確保微服務主機的健康,有能力將不健康節點從微服務叢集中移除。
服務註冊與發現
silky支援服務的自動註冊和發現,支援使用 Zookeeper 、Nacos 、Consul 作為服務註冊中心。服務例項上線、下線智慧感知。
-
當服務例項啟動時,會向服務註冊中心新增或是更新服務後設資料(如果不存在新增服務後設資料、如果存在服務後設資料則更新);同時,更新服務註冊中心該例項的終結點(例項地址資訊)。
-
使用 Zookeeper 或是 Nacos 作為服務註冊中心,會通過 釋出-訂閱 的方式從服務註冊中心獲取最新的服務後設資料和服務例項的終結點(例項地址)資訊,並更新到本地記憶體;
-
如果使用 Consul 作為服務註冊中心,則會通過心跳的方式從服務註冊中心 拉取 最新的服務後設資料和服務例項的終結點(例項地址)資訊。當服務註冊中心的終結點(地址資訊)發生變化,服務例項的記憶體中服務路由表資訊也將得到更新。
-
當在RPC通訊過程中發生IO異常或是通訊異常時,服務例項將會在n(配置屬性為:
Governance:UnHealthAddressTimesAllowedBeforeRemoving
)次後從服務註冊中心移除。(UnHealthAddressTimesAllowedBeforeRemoving
如果的值等於0,則服務例項將會被立即移除)。 -
在RPC通訊過程中,採用長連結, 支援心跳檢測。在服務之間建立連線後,如果
Governance:EnableHeartbeat
配置為true
,那麼會定時(通過配置Governance:HeartbeatWatchIntervalSeconds
)傳送一個心跳包,從而保證會話連結的可靠性。如果心跳檢測到通訊異常,則會根據配置屬性(Governance:UnHealthAddressTimesAllowedBeforeRemoving
)n次後,從服務註冊中心移除。
負載均衡
在RPC通訊過程中,silky框架支援 輪詢(Polling)、 隨機(Random) 、 雜湊一致性(HashAlgorithm) 等負載均衡演算法。負載均衡的預設值為 輪詢(Polling) ,開發者可以通過配置屬性 Governance:ShuntStrategy
來統一指定負載均衡演算法。同時,開發者也可以通過GovernanceAttribute
特性來重置應用服務方法(服務條目)的負載均衡演算法。
例如:
[HttpGet("{name}")]
[Governance(ShuntStrategy = ShuntStrategy.HashAlgorithm)]
Task<TestOut> Get([HashKey]string name);
如果選擇使用 雜湊一致性(HashAlgorithm) 作為負載均衡演算法,則需要使用[HashKey]
對某一個引數進行標識,這樣,相同引數的請求,在RPC通訊過程中,都會被路由到通一個服務例項。
超時
在RPC通訊中,如果在給定的配置時長沒有返回結果,則會丟擲超時異常。一般地,開發者可以通過配置屬性Governance:TimeoutMillSeconds
來統一的配置RPC呼叫超時時長,預設值為5000
ms。同樣地,開發者也可以通過GovernanceAttribute
特性來重置應用服務方法的超時時長。
[HttpGet("{name}")]
[Governance(TimeoutMillSeconds = 1000)]
Task<TestOut> Get([HashKey]string name);
如果將超時時長配置為0
,則表示在RPC呼叫過程中,不會出現超時異常,直到RPC呼叫返回結果或是丟擲RPC呼叫丟擲其他異常。
::: tip 提示
建議在開發環境中, 將配置屬性Governance:TimeoutMillSeconds
設定為0
,方便開發者進行除錯。
:::
故障轉移(失敗重試)
在RPC通訊過程中,如果發生IO異常(IOException
)、通訊異常(CommunicationException
)、或是找不到本地服務條目(服務提供者丟擲NotFindLocalServiceEntryException
異常)、超出服務提供者允許的最大處理併發量(NotFindLocalServiceEntryException
),則服務消費者會根據配置的次數選擇其他服務例項重新呼叫。
-
如果RPC呼叫過程中發生的是IO異常(
IOException
)或是通訊異常(CommunicationException
)或是服務提供者丟擲NotFindLocalServiceEntryException
異常,將會把選擇的服務例項的狀態變更為不可用狀態,在Governance:UnHealthAddressTimesAllowedBeforeRemoving
次標識後,服務例項將會下線(將服務提供者的例項地址從服務註冊中心移除)。 -
如果超出服務提供者例項允許的最大併發量,則會選擇其他服務例項進行呼叫,但不會變更服務例項的狀態。(換句話說,也就是服務提供者觸發了限流保護)
-
其他型別的異常不會導致失敗重試。
開發者通過Governance:RetryTimes
配置項來確定失敗重試的次數,預設值等於3
。同樣地,開發者也可以通過GovernanceAttribute
特性來重置失敗重試次數。如果RetryTimes
被設定為小於等於0
,則不會發生失敗重試。通過Governance:RetryIntervalMillSeconds
可以配置失敗重試的間隔時間。
[HttpGet("{name}")]
[Governance(RetryTimes = 3)]
Task<TestOut> Get([HashKey]string name);
開發者也可以自定義失敗重試策略,只需要繼承InvokeFailoverPolicyProviderBase
基類,通過重寫Create
方法構建失敗策略。
下面的例子演示了定義超時重試的策略:
public class TimeoutFailoverPolicyProvider : InvokeFailoverPolicyProviderBase
{
private readonly IServerManager _serverManager;
public TimeoutFailoverPolicyProvider( IServerManager serverManager)
{
_serverManager = serverManager;
}
public override IAsyncPolicy<object> Create(string serviceEntryId, object[] parameters)
{
IAsyncPolicy<object> policy = null;
var serviceEntryDescriptor = _serverManager.GetServiceEntryDescriptor(serviceEntryId);
if (serviceEntryDescriptor?.GovernanceOptions.RetryTimes > 0)
{
policy = Policy<object>
.Handle<Timeoutxception>()
.Or<SilkyException>(ex => ex.GetExceptionStatusCode() == StatusCode.Timeout)
.WaitAndRetryAsync(serviceEntryDescriptor.GovernanceOptions.RetryIntervalMillSeconds,
retryAttempt =>
TimeSpan.FromMilliseconds(serviceEntryDescriptor.GovernanceOptions.RetryIntervalMillSeconds),
(outcome, timeSpan, retryNumber, context)
=> OnRetry(retryNumber, outcome, context));
}
return policy;
}
}
熔斷保護(斷路器)
RPC通訊過程中,在開啟熔斷保護的情況下,如果在連續發生n次 非業務類異常 (非業務類異常包括友好類異常、鑑權類異常、引數校驗類異常等),則會觸發熔斷保護,在一段時間內,該服務條目將不可用。
開發者通過Governance:EnableCircuitBreaker
配置項來確定是否要開啟熔斷保護,通過Governance:ExceptionsAllowedBeforeBreaking
配置項來確定在熔斷保護觸發前允許的異常次數(這裡的異常為非業務類異常),通過Governance:BreakerSeconds
配置項來確定熔斷的時長(單位為:秒)。同樣地,熔斷保護也可以通過GovernanceAttribute
來進行配置。
[HttpGet("{name}")]
[Governance(EnableCircuitBreaker = true,ExceptionsAllowedBeforeBreaking = 2, BreakerSeconds = 120)]
Task<TestOut> Get([HashKey]string name);
限流
限流的目的是通過對併發訪問/請求進行限速,或者對一個時間視窗內的請求進行限速來保護系統,一旦達到限制速率則可以拒絕服務、排隊或等待、降級等處理。
Silky微服務框架的限流分為兩部分,一部分是服務內部RPC之間的通訊,一部分是對HTTP請求的進行限流。
RPC限流
當服務提供者接收到RPC請求後,如果當前服務例項併發處理量大於配置的Governance:MaxConcurrentHandlingCount
,當前例項無法處理該請求,會丟擲OverflowMaxServerHandleException
異常。服務消費者會根據配置重試該服務的其他例項,可參考故障轉移(失敗重試)節點。
Governance:MaxConcurrentHandlingCount
的配置預設值為50
,如果配置小於等於0
,則表示不對rpc通訊進行限流。這裡的配置針對的是服務例項所有併發處理的能力,並不是針對某個服務條目的併發量配置,所以開發者無法通過GovernanceAttribute
特性來修改併發處理量的配置。
HTTP限流
Silky框架除了支援服務內部之間RPC呼叫的限流之外,還支援通過AspNetCoreRateLimit實現對Http請求的限流。AspNetCoreRateLimit 支援通過Ip或是針對IP進行限流。
下面我們來簡述如何使用 AspNetCoreRateLimit 達到對http請求的限流。
1. 新增配置
閘道器應用新增限流配置檔案 ratelimit.json。在RateLimiting:Client
配置節點配置針對客戶端的限流通用規則,通過RateLimiting:Client:Policies
配置節點重寫針對特定客戶端的限流策略。開發者可以參考ClientRateLimitMiddleware熟悉相關的配置屬性。在RateLimiting:Ip
配置節點配置針對IP的限流通用規則,通過RateLimiting:Ip:Policies
配置節點重寫針對特定IP的限流策略。開發者可以參考IpRateLimitMiddleware熟悉相關的配置屬性。
如果閘道器採用分散式部署,可以通過RateLimiting:RedisConfiguration
屬性配置redis服務作為儲存服務。
例如:
{
"RateLimiting": {
"Client": {
"EnableEndpointRateLimiting": false,
"StackBlockedRequests": false,
"ClientIdHeader": "X-ClientId",
"HttpStatusCode": 429,
"EndpointWhitelist": [
"get:/api/license",
"*:/api/status"
],
"ClientWhitelist": [
"dev-id-1",
"dev-id-2"
],
"GeneralRules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 5
}
],
"QuotaExceededResponse": {
"Content": "{{ \"data\":null,\"errorMessage\": \"Whoa! Calm down, cowboy! Quota exceeded. Maximum allowed: {0} per {1}. Please try again in {2} second(s).\",\"status\":\"514\",\"statusCode\":\"OverflowMaxRequest\" }}",
"ContentType": "application/json",
"StatusCode": 429
},
"Policies": {
"ClientRules": [
]
}
}
},
"RedisConfiguration": "127.0.0.1:6379,defaultDatabase=1"
}
2. 註冊服務
在 Startup
啟動類中, 新增 AspNetCoreRateLimit 的相關服務。
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
var redisOptions = configuration.GetRateLimitRedisOptions();
services.AddClientRateLimit(redisOptions);
services.AddIpRateLimit(redisOptions);
}
當然,如果開發者是通過 AddSilkyHttpServices()
進行服務註冊,在這個過程中,已經同時新增了 AspNetCoreRateLimit 的相關服務
3.啟用 AspNetCoreRateLimit 的 相關中介軟體,實現HTTP限流。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseClientRateLimiting(); // 針對客戶端進行限流
// app.UseIpRateLimiting(); // 針對IP進行限流
}
服務回退(服務降級)
在RPC呼叫過程中,如果執行失敗,我們通過呼叫Fallback方法
,從而達到服務降級操作。
想要實現服務回退的處理,我們需要在定義服務方法的時候通過FallbackAttribute
特性指定的要回退的介面型別。給定的介面需要定義一個與服務方法引數相同的方法。
FallbackAttribute
需要指定回退介面介面的型別,方法名稱(如果預設,則回退介面定義的方法名稱與服務條目方法一致),如果定義了多個回退方法,還要給出權重配置。
屬性 | 配置 | 備註 |
---|---|---|
Type | 回退介面型別 | 必須指定,一般與服務介面定義在一起 |
MethodName | 指定的回退方法 | 如果不配置的話,則與服務條目方法一致,定義的方法的引數必須一致 |
[HttpPatch]
[Fallback(typeof(IUpdatePartFallBack))]
Task<string> UpdatePart(TestInput input);
IUpdatePartFallBack
需要定義一個與UpdatePart
引數相同的方法。
public interface IUpdatePartFallBack
{
Task<string> UpdatePart(TestInput input);
}
定義的回退介面和方法只有在被實現後,才會在RPC呼叫失敗後執行定義的Fallback方法
。服務回退的實現方式有兩種方式,一種是在服務端實現回退,一種是在客戶端實現。
在服務端實現回退方法
如果在服務端實現回退方法,當RPC在服務端執行業務方法失敗,如果服務端有存在定義的回退介面的實現,那麼會降級執行回退方法。
舉一個比較適用的場景,例如: 在一個簡訊收發的業務場景中,如果使用阿里雲作為服務提供商相對更便宜,但是如果欠費後我們希望能夠平滑的切換到騰訊雲提供商。在這樣的業務場景下,如果我們預設選擇使用阿里雲作為服務提供商, 我們就可以通過在服務端實現一個定義的Fallback方法
從而達到平滑的使用備用簡訊服務提供商的作用。
在消費端實現回退方法
當然, 我們也可以在呼叫端(消費端)實現定義的Fallback方法
,如果RPC呼叫失敗,那麼在呼叫端就會執行實現了的Fallback方法
。返回給前端的是降級後的資料,並不會發生異常。
public class TestUpdatePartFallBack : IUpdatePartFallBack, IScopedDependency
{
public async Task<string> UpdatePart(TestInput input)
{
return "this is a fallback method for update part";
}
}
鏈路跟蹤
silky框架使用SkyAPM實現了鏈路跟蹤,開發者通過引入相應的配置和服務,即可實現對http請求、RPC呼叫、TCC分散式事務執行過程以及EFCore資料訪問的呼叫鏈路跟蹤。
開發者可以通過檢視鏈路跟蹤節點熟悉如何進行配置和引入相應的服務以及如何部署 skywalking,並通過 skywalking 檢視呼叫的鏈路。
安全
在silky框架中,非常重視對安全模組的設計。
- 通過
rpc:token
的配置,保證外部無法通過RPC埠直接訪問應用服務。服務內部之間的呼叫均需要對rpc:token
進行校驗,如果rpc:token
不一致,則不允許進行呼叫。 - 在閘道器處統一實現身份認證與授權。開發者可以通過檢視身份認證與授權節點檢視相關文件。
- 開發者可以通過
GovernanceAttribute
特性來禁止外部訪問某個應用服務方法。被外部禁止訪問的應用服務方法只允許服務內部之間通過RPC方式進行通訊。
[Governance(ProhibitExtranet = true)]
Task<string> Delete(string name);
快取攔截
在RPC通訊過程中,通過引入快取攔截,極大的提高了系統效能。
開發者可以通過快取文件,熟悉快取攔截的使用方法。
開源地址
- github: https://github.com/liuhll/silky
- gitee: https://gitee.com/liuhll2/silky