在微服務架構風格的系統中,若是單個微服務垮掉或地址不可訪問,雖然對系統的影響是有限的,但咱們也必須採起必定的手段來保證每一個微服務儘可能可用;而且在大併發的狀況下,雖然能夠經過EDA消息隊列處理的方式提升吞吐量,但仍然須要WebApi可以更加高效的偵聽用戶請求,處理消息,即便在某個服務短暫不可用的狀況下。本篇文章主要來詳細講一講要保證微服務的高可用性,能夠經過哪些手段來實現。前端
這裏的問題指的是當某個微服務或者微服務依賴的持久化存儲出現不可訪問時,會形成此塊服務不可用,咱們須要有必定的手段可以儘可能避免這個問題;爲了達到這個目標,一般能夠從4個方面來解決。sql
現代的關係型數據庫系統或NoSql一般是做爲微服務的持久化存儲機制的。當數據庫所在的服務器、數據服務或數據庫故障或不可用時,會形成業務中斷;因此咱們應該利用數據庫產品自己的高可用機制來解決這個問題,這裏以SQL Server 2016關係型數據庫爲例。docker
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的管理界面大體以下,做爲開發人員或架構師,瞭解便可,一般這是由運維團隊管理的。數據庫
一般咱們會將某個微服務WebApi部署到物理主機、虛擬機或其餘形態的主機(好比docker)的Web Server服務上。這裏一般會有兩個方面的緣由形成微服務沒法訪問,一是微服務所在的Web Server或主機中止響應或關機、二是微服務併發訪問量太大,形成資源大量佔用,沒法響應用戶請求。後端
除了前面系列文章講解的軟件架構解決外,咱們還須要配合另外一個機制可以儘可能保證微服務高可用,這個機制就是NLB(網絡負載均衡)。api
若是你的WebApi主機在內網,能夠經過F5等硬件設備提供NLB支持,若是你的WebApi部署在雲端,可使用雲端供應商提供的NLB相關服務提供NLB支持。NLB是將多臺Web服務器組合成一個虛擬的Web服務器,固然還要經過端口組織。經過文件複製功能,好比Windows Server自帶的DFS的功能將多臺Web服務器承載相同的WebApi保持WebApi內容一致。當前端調用WebApi服務時,鏈接的是NLB上配置的虛擬IP和端口,而後根據NLB的配置(有根據Web服務器負載狀況路由到請求少的主機上;有根據每一個請求自動輪詢每一個主機;有根據某個會話老是請求到特定主機),將前端的請求路由到合適的WebApi主機上。在阿里雲上,NLB的管理界面大體以下,做爲開發人員或架構師,瞭解便可,一般也是由運維團隊管理的。前端框架
不管是數據庫仍是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>(). }; }
當某些故障是非瞬間故障時,一直重試一般是無心義的,並且也消耗資源。當重試到達必定的次數時,能夠判斷爲非瞬間故障,斷路器被觸發,則再也不重試;斷路器恢復後,則能夠重試。
CircuitBreakerAsync(5,TimeSpan.FromMinutes(1))
前端一般經過域名或IP地址做爲前綴來訪問特定的微服務WebApi的接口。在IT運維調整的狀況下,微服務所在的域名或IP地址可能會發生變化,這樣前端用戶在拿到新的域名或IP地址前,將沒法正常調用服務。
爲了解決這個問題,咱們就須要將微服務經過一個API網關組織起來。API網關會手工或自動配置它所管理的微服務的具體地址,當前端直接調用的API網關的服務時,API網關會根據配置來正確路由請求到特定域名或IP地址的服務。
這種狀況須要在API網關手工添加某個服務請求應該路由到哪一個特定的域名或IP地址的WebApi接口。手工配置的Json配置文件內容以下:
這裏的Upstream指的就是前端調用API網關的特定服務時,Downstream指的就是路由到哪一個特定的WebApi。有了配置文件後,就可使用相關的API網關庫加載配置文件到API網關的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網關利用服務中心信息自動路由
png](/img/bVbhXnj)
好了,本篇文章關於微服務的高可用性就介紹到這裏。
QQ討論羣:309287205 微服務實戰視頻請關注微信公衆號:msshcj