微服务实战(八):落地微服务架构到直销系统(服务高可用性)

在微服务架构风格的系统中,如果单个微服务垮掉或地址不可访问,虽然对系统的影响是有限的,但我们也必须采取一定的手段来保证每个微服务尽量可用;并且在大并发的情况下,虽然可以通过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网关利用服务中心信息自动路由

好了,本篇文章关于微服务的高可用性就介绍到这里。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏木子昭的博客

精析Python3实现动态web服务(附服务端源码)如果我们提供一个动态网站服务,至少应考虑以下四点:一个优秀的动态web框架应该是这样的:关于WSGI标准WIGS模型的要点:实现源码小结:

实现一个简单的静态web网站,只需将写好的html页面上传到特定的web服务器软件即可,但静态网页其实和图片没什么区别,每次更新网站内容,都需要重新制作htm...

38112
来自专栏zingpLiu

Django快速入门

Django 是用 Python 写的一个自由和开放源码 web 应用程序框架。 web框架是一套组件,能帮助你更快、更容易地开发web站点。当你开始构建一个w...

1153
来自专栏博客园迁移

redis见解

http://blog.csdn.net/zhiguozhu/article/details/50517527 Redis 原生session与redis中的s...

1921
来自专栏Esofar 开发日记

[译]RabbitMQ教程C#版 - 远程过程调用(RPC)

但是如果我们想要运行一个在远程计算机上的函数并等待其结果呢?这将是另外一回事了。这种模式通常被称为 远程过程调用 或 RPC 。

1092
来自专栏Java技术栈

分布式 | Dubbo 架构设计详解

Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度...

1632
来自专栏Java3y

从零单排学Redis【黄金】

好的,今天我们要上黄金段位了,如果还没经历过青铜和白银阶段的,可以先去蹭蹭经验再回来:

1272
来自专栏腾讯IVWEB团队的专栏

使用 Node.js 实现一个简单的 ZooKeeper 客户端

Zookeeper 是一个分布式的、开源的协调服务,用在分布式应用程序中。它提出了一组简单的原语,分布式应用程序可以基于这些原语之上构建更高层的分布式服务用于实...

1.5K0
来自专栏Java帮帮-微信公众号-技术文章全总结

POI导入导出【面试+工作】

POI导入导出【面试+工作】 1.场景一 近期项目中的excel导入导出功能需求频繁的出现,趁此机会,今天笔者对POI的Excel数据的导入导出做一...

4214
来自专栏阿杜的世界

【转】Dubbo架构设计详解总体架构核心要点参考资料

Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度...

1155
来自专栏小狼的世界

在Codeigniter框架中使用NuSOAP

NuSOAP 是一组功能强大的PHP类,这个工具的发布让使用和创建SOAP消息变得相当简单。 NuSOAP有Dirtrich Ayala编写,可以无缝的与许多最...

871

扫码关注云+社区

领取腾讯云代金券