在微服務架構風格的系統中,如果單個微服務垮掉或地址不可訪問,雖然對系統的影響是有限的,但我們也必須采取一定的手段來保證每個微服務盡量可用;並且在大並發的情況下,雖然可以通過EDA消息隊列處理的方式提高吞吐量,但仍然需要WebApi能夠更加高效的偵聽用戶請求,處理消息,即使在某個服務短暫不可用的情況下。本篇文章主要來詳細講一講要保證微服務的高可用性,可以通過哪些手段來實現。
一、保證微服務負載可用
這里的問題指的是當某個微服務或者微服務依賴的持久化存儲出現不可訪問時,會造成此塊服務不可用,我們需要有一定的手段能夠盡量避免這個問題;為了達到這個目標,通常可以從4個方面來解決。
1.數據庫高可用
現代的關系型數據庫系統或NoSql通常是作為微服務的持久化存儲機制的。當數據庫所在的服務器、數據服務或數據庫故障或不可用時,會造成業務中斷;所以我們應該利用數據庫產品本身的高可用機制來解決這個問題,這里以SQL Server 2016關系型數據庫為例。
SQL Server 2016數據庫服務提供了一種SQL AlwaysOn的高可用機制。SQL AlwaysOn是將多台SQL Server組合成一個虛擬的SQL Server,然后通過SQL AlwaysOn的功能將需要能夠自動轉移故障的數據庫同步到多台SQL Server上。當WebApi連接數據庫服務時,連接的是虛擬IP和端口,然后SQL AlwaysOn會自動將數據訪問請求定向到主物理SQL Server上;當主服務器垮掉時,會自動轉移數據服務到一台從數據庫服務器上,從數據庫服務器自動成為新的主數據庫服務器,后續的WebApi連接虛擬IP和端口時,會自動連接到新的主數據庫服務器上,這個階段對WebApi來說是完全透明的。在SQL Server 2016中,AlwaysOn的管理界面大致如下,作為開發人員或架構師,了解即可,通常這是由運維團隊管理的。

2.微服務高可用
通常我們會將某個微服務WebApi部署到物理主機、虛擬機或其他形態的主機(比如docker)的Web Server服務上。這里通常會有兩個方面的原因造成微服務無法訪問,一是微服務所在的Web Server或主機停止響應或關機、二是微服務並發訪問量太大,造成資源大量占用,無法響應用戶請求。
除了前面系列文章講解的軟件架構解決外,我們還需要配合另一個機制能夠盡量保證微服務高可用,這個機制就是NLB(網絡負載均衡)。
如果你的WebApi主機在內網,可以通過F5等硬件設備提供NLB支持,如果你的WebApi部署在雲端,可以使用雲端供應商提供的NLB相關服務提供NLB支持。NLB是將多台Web服務器組合成一個虛擬的Web服務器,當然還要通過端口組織。通過文件復制功能,比如Windows Server自帶的DFS的功能將多台Web服務器承載相同的WebApi保持WebApi內容一致。當前端調用WebApi服務時,連接的是NLB上配置的虛擬IP和端口,然后根據NLB的配置(有根據Web服務器負載情況路由到請求少的主機上;有根據每個請求自動輪詢每個主機;有根據某個會話總是請求到特定主機),將前端的請求路由到合適的WebApi主機上。在阿里雲上,NLB的管理界面大致如下,作為開發人員或架構師,了解即可,通常也是由運維團隊管理的。

3.重試策略
無論是數據庫還是WebApi,因為網絡或服務等原因,可能會出現瞬間故障,也就是在很短的時間內,臨時不可訪問。如果出現這種情況,我們就應該有重試機制,無論是數據庫連接的重試,還是調用WebApi的重試。
a.數據連接的重試
在一些第三方的數據訪問庫或ORM框架中,通常都提供了數據連接重試的功能,這些功能通常都能實現如果數據訪問不可用,要重試連接幾次,每次重試的間隔是多長。示例代碼如下:
protected override void OnConfiguring(DbContextOptionsBuilder optionBuilder) { optionBuilder.UseSqlServer("Server=localhost;Database=DDD1OrderDB;User ID=DDD1user;Password=password", sqlServerOptionsAction:p=> { p.EnableRetryOnFailure( maxRetryCount:5, maxRetryDelay:TimeSpan.FromSeconds(1), errorNumbersToAdd:null ); }); }
b.調用WebApi的重試
無論是前端框架還是后端框架,通常都提供了一些庫和方法可以使用http的方式調用WebApi。我們可以按照需求擴展這些庫,能夠在調用WebApi不可用時,重試幾次。后端代碼調用WebApi重試代碼:
public interface IHttpClient { Task<HttpResponseMessage> GetAsync(string requesturi); Task<HttpResponseMessage> PostAsync(string requesturi, HttpContent content); } public class ReHttpClient : IHttpClient { private HttpClient client; private PolicyWrap policywrap; public ReHttpClient(Policy[] policies) { client = new HttpClient(); policywrap = Policy.WrapAsync(policies); } private Task<T> HttpInvoker<T>(Func<Task<T>> action) { return policywrap.ExecuteAsync(() => action()); } public Task<HttpResponseMessage> GetAsync(string requesturi) { return HttpInvoker(async () => { return await client.GetAsync(requesturi); }); } public Task<HttpResponseMessage> PostAsync(string requesturi, HttpContent content) { return HttpInvoker(async () => { return await client.PostAsync(requesturi, content); }); } }
public class ReHttpClientFactory { public ReHttpClient CreateReHttpClient() => new ReHttpClient(CreatePolicies()); private Policy[] CreatePolicies() => new Policy[] { Policy.Handle<HttpRequestException>() .WaitAndRetryAsync(6,retry=>TimeSpan.FromSeconds(1)+TimeSpan.FromMilliseconds(new Random().Next(0,100))), Policy.Handle<Exception>() .WaitAndRetryAsync(6,retry=>TimeSpan.FromSeconds(1)), Policy.Handle<HttpRequestException>(). }; }
4.斷路器模式
當某些故障是非瞬間故障時,一直重試通常是無意義的,而且也消耗資源。當重試到達一定的次數時,可以判斷為非瞬間故障,斷路器被觸發,則不再重試;斷路器恢復后,則可以重試。
CircuitBreakerAsync(5,TimeSpan.FromMinutes(1))
二、保證微服務地址可用
前端通常通過域名或IP地址作為前綴來訪問特定的微服務WebApi的接口。在IT運維調整的情況下,微服務所在的域名或IP地址可能會發生變化,這樣前端用戶在拿到新的域名或IP地址前,將無法正常調用服務。
為了解決這個問題,我們就需要將微服務通過一個API網關組織起來。API網關會手工或自動配置它所管理的微服務的具體地址,當前端直接調用的API網關的服務時,API網關會根據配置來正確路由請求到特定域名或IP地址的服務。
1.API網關手工配置所路由的WebApi
這種情況需要在API網關手工添加某個服務請求應該路由到哪個特定的域名或IP地址的WebApi接口。手工配置的Json配置文件內容如下:

這里的Upstream指的就是前端調用API網關的特定服務時,Downstream指的就是路由到哪個特定的WebApi。有了配置文件后,就可以使用相關的API網關庫加載配置文件到API網關的WebApi中。
2.WebApi自動注冊地址信息
如果總是通過手工配置映射信息,還是比較麻煩。我們可以讓WebApi自己將信息注冊到一個服務中心中,然后API網關利用這個服務中心的信息實現請求的自動路由。
a.服務中心提供注冊功能
public static class AppBuilderExtension { public static IApplicationBuilder RegisterConsul(this IApplicationBuilder app, IApplicationLifetime lifetime,ServiceEntity serviceEntity) { var consulClient = new ConsulClient(x => x.Address = new Uri($"http://{serviceEntity.ConsulIP}:{serviceEntity.ConsulPort}")); var httpCheck = new AgentServiceCheck() { DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5), Interval = TimeSpan.FromSeconds(10), HTTP = $"http://{serviceEntity.IP}:{serviceEntity.Port}/api/health", Timeout = TimeSpan.FromSeconds(5) }; var registration = new AgentServiceRegistration() { Checks = new[] { httpCheck }, ID = Guid.NewGuid().ToString(), Name = serviceEntity.ServiceName, Address = serviceEntity.IP, Port = serviceEntity.Port, Tags = new[] { $"urlprefix-/{serviceEntity.ServiceName}" } }; consulClient.Agent.ServiceRegister(registration).Wait(); lifetime.ApplicationStopped.Register(() => { consulClient.Agent.ServiceDeregister(registration.ID).Wait(); }); return app; } }
b.WebApi注冊到服務中心
ServiceEntity serviceEntity = new ServiceEntity { IP = Configuration["Service:Address"], Port = Convert.ToInt32(Configuration["Service:Port"]), ServiceName=Configuration["Service:Name"], ConsulIP = Configuration["Consul:IP"], ConsulPort = Convert.ToInt32(Configuration["Consul:Port"]) }; app.RegisterConsul(lifetime, serviceEntity);
c.API網關利用服務中心信息自動路由

好了,本篇文章關於微服務的高可用性就介紹到這里。
QQ討論群:309287205
微服務實戰視頻請關注微信公眾號:
