在微服务架构风格的系统中,若是单个微服务垮掉或地址不可访问,虽然对系统的影响是有限的,但咱们也必须采起必定的手段来保证每一个微服务尽可能可用;而且在大并发的状况下,虽然能够经过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