服務治理的概念
服務治理是主要針對分布式服務框架、微服務,處理服務調用之間的關系,服務發布和發現(誰是提供者,誰是消費者,要注冊到哪里),出了故障誰調用誰,服務的參數都有哪些約束,如何保證服務的質量?如何服務降級和熔斷?怎么讓服務受到監控,提高機器的利用率?
微服務有哪些問題需要治理?
-
服務注冊與發現: 單體服務拆分為微服務后,如果微服務之間存在調用依賴,就需要得到目標服務的服務地址,也就是微服務治理的 服務發現 。要完成服務發現,就需要將服務信息存儲到某個載體,載體本身即是微服務治理的服務注冊中心,而存儲到載體的動作即是服務注冊。
-
可觀測性: 微服務由於較單體應用有了更多的部署載體,需要對眾多服務間的調用關系、狀態有清晰的掌控。可觀測性就包括了調用拓撲關系、監控(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