当前位置: 首页 > 后端技术 > Java

解决微服务架构下流量有损问题的实践和探索

时间:2023-04-01 19:34:24 Java

微服务架构下解决流量流失问题的实践与探索观察与回滚安全生产三轴,可以最大限度降低应用在发布过程中因自身代码问题对用户造成的影响。但是仍然不能解决高并发大流量情况下的短期流量丢失问题。因此,本文将重点介绍如何解决发布过程中的流量丢失问题,以实现应用发布过程中线上线下无损的效果。作者|程璞来源|阿里巴巴开发者公众号绝大多数的软件应用生产安全事件都发生在应用发布阶段,虽然通过遵守灰度、可观察和回滚的行业惯例,安全生产的三把斧子可以最大程度地减少应用发布阶段对用户的影响。应用在发布过程中自身代码问题。但是仍然不能解决高并发大流量情况下的短期流量丢失问题。因此,本文将重点介绍如何解决发布过程中的流量丢失问题,以实现应用发布过程中线上线下无损的效果。无损下线、下线后台据统计,大部分应用事故发生在应用下线、下线过程中,有时是应用本身的代码问题引起的。但有时我们也发现,虽然代码本身没有问题,但在应用发布过程中,仍然存在短暂的服务调用错误,如Connectionrefused、Noinstancewhencalling。有过相关发布经验的同学可能对相关问题产生的原因有一定的了解,而且大家发现这类问题一般在高峰时段比较明显,而在半夜流量低的时候比较少见,所以许多人选择在半夜发布应用,希望避免在线发布事故。本节将介绍这些问题背后的真正原因以及业界相应的设计解决方案。常见的流量丢失原因包括但不限于以下几种:服务不能及时下线:服务消费者感知注册中心服务列表延迟,导致某个应用实例在一段时间内下线服务消费者仍然调用离线实例,导致请求错误。初始化慢:应用刚开始接收线上流量初始化加载资源。由于流量大,初始化过程缓慢,出现大量请求响应超时、拥塞、资源耗尽等情况,导致应用刚启动就崩溃。注册过早:服务存在异步资源加载问题。服务未初始化时,注册到注册中心,导致请求响应慢,调用超时报错。发布态和运行态不对齐:使用Kubernetes的滚动发布功能来发布应用,因为Kubernetes滚动发布一般关联的就绪检查机制是检查应用的特定端口是否启动作为应用就绪标志来触发下一批实例的释放,但是在微服务应用中,只有应用完成服务注册后,才能对外提供服务调用。因此,在某些情况下,旧的应用实例会在新应用注册到注册中心之前就已经下线,导致服务不可用。下面分别介绍下线和上线具体过程中如何避免流量丢失。无损下线由于微服务应用本身的调用特性,在高并发下,服务提供者的应用实例直接下线会导致服务消费者的应用实例无法感知下游的实时状态实例是实时的,所以请求会继续转发到离线实例。线路实例会报请求错误,流量会丢失。图1SpringCloud应用消费者无法及时感知离线的提供者服务。例如,对于上图1所示的SpringCloud应用,当两个实例A'和A的为了平衡框架的可用性和性能,消费者在30秒内去注册中心拉取最新的服务列表默认。因此无法实时感知实例A的下线。当流量较大时,消费者会不断通过本地缓存调用离线的A实例,造成流量丢失。基于上述背景,业界提出了相应的无损离线(也称优雅离线)技术方案来应对上述问题。本节将介绍一些业界主流的无损离线技术方案。对于此类问题,业界普遍的解决方案是将应用更新流程分为三个步骤:手动提取流量、停止应用、更新重启。通过手动操作防止客户端调用离线实例简单有效,但有很多局限性:不仅需要流量控制能力来实现流量的实时提取,还需要在应用前进行人工判断停止以确保在途请求已完成。完成的。这种需要人工干预的方式,运维复杂度高,只适合小规模应用。无法解决当前云原生架构下自动弹性伸缩、滚动升级等场景下实例下线过程中的流量问题。损坏问题。本节将介绍一些业界应用于云原生场景的无损离线技术方案。1主动通知通用注册中心提供一个主动注销接口供微服务应用在正常关闭时调用,以便下线的实例及时更新自己在注册中心的状态。在一些基于事件感知注册中心服务列表的微服务框架中主动注销,例如Dubbo,可以让上游服务消费者及时感知提供者下线,避免后续调用下线实例。但是对于像SpringCloud这样的微服务框架,服务消费者是通过定时拉取服务列表来感知注册中心实例的变化。离线实例虽然通过注册中心的主动注销接口在注册中心更新自己的应用状态信息,但是由于上游消费者下次拉取注册中心的应用列表时需要知道它,所以会有消费者-意识到注册表实例更改被延迟。在大流量、高并发场景下,实例下线后也无法实现无损流量。由于无法让已有的消费者实例通过注册中心实时感知下游服务提供者的变化,业界提出使用主动通知来解决此类问题。主动通知流程如下图2所示:图2无损下线方案如图2所示。注册中心,服务实例的状态无法实时更新。消费者A感知到,导致调用离线实例的问题。提供者B在收到下线命令下线前,在等待下线阶段收到的请求的返回值上加上一个特殊的标记,让服务消费者收到返回值并识别相关标记,主动拉取一个注册服务实例实时感知B实例的最新状态,让服务消费者实时感知服务提供者的离线状态。2自适应等待在并发不高的场景下,主动通知的方式可以解决大部分应用离线流量有损的问题。但是对于高并发、大流量的应用下线场景,如果主动通知完成,可能还有一些在途的请求需要下线应用处理后才能下线,否则这些流量是无法处理的回复正常。为了解决这种中途请求问题,可以使用自适应等待机制,在下线前处理所有中途请求,实现流量无损。图3自适应等待机制如上图3所示,自适应等待机制通过统计应用中是否还有未处理的中途请求来决定应用下线的时机,使得等待下线的应用在处理完所有剩余请求之前下线。无损在线延迟加载是软件框架设计过程中最常见的策略。比如在SpringCloud框架中,Ribbon组件的拉取服务列表默认会初始化到第一次调用该服务时,如下图4是第一次和第二次调用远程服务的耗时对比通过在SpringCloud应用中调用RestTemplate:图4应用启动资源初始化与正常运行的耗时对比从图4的结果可以看出,由于某些资源初始化,第一次调用比正常运行耗时数倍。因此,当一个新的应用上线直接处理大量流量时,极易造成大量请求响应缓慢、资源阻塞、应用实例宕机等问题。针对上述应用无损上线场景,业界提出了延迟注册、小流量服务预热、就绪检查等一系列解决方案。详细完整的解决方案如下图5所示:图5无损启动整体方案1延迟注册是一个复杂的应用启动过程,需要在初始化过程中异步加载资源。由于注册通常与应用程序初始化过程同步进行,应用程序在完全初始化之前就已经注册到注册中心供外部消费者调用。在这种情况下,直接调用可能会因为资源尚未加载而导致请求错误。通过设置延迟注册,应用在完全初始化后可以向注册中心注册对外提供服务。例如,开源的微服务治理框架Dubbo原生提供了延迟注册功能[1]。2小流量服务的预热在线发布场景下,刚启动的冷系统往往会直接处理大量的请求。可能是由于系统内部资源初始化不完整,甚至会出现大量的请求超时、阻塞、报错。出现用户宕机等线上发布事故。为了避免这类问题,业界针对不同的框架类型和应用本身的特点设计了不同的对策。比如解决类加载慢的问题,写脚本提示JVM预热。)批量发布接口、延迟注册、通过mock脚本为应用模拟请求预热、小流量预热等。本节将介绍应用最广泛的小流量预热方式。与一般场景相比,新发布的微服务应用实例与其他正常实例平分在线总QPS。小流量预热方式帮助新启动的应用在服务消费者端根据每个服务提供者实例的启动时间计算权重,并结合负载均衡算法控制新启动应用的流量逐渐减少随着启动时间增加到正常水平。运行预热,详细QPS随时间变化曲线如图6:图6开源Dubbo利用小流量预热过程QPS曲线实现小流量服务预热过程原理如图7:图图7小流量应用预热过程示意图在向注册中心注册服务的过程中,服务提供者将自己的预热时间WarmupTime和服务启动时间StartTime以元数据的形式向注册中心注册。消费者在注册中心订阅相关服务实例列表,调用过程中根据WarmupTime和StartTime计算各实例的批量调用权重。刚启动时StartTime与调用时间差值较小的实例的权重,以分配较少的流量给新启动的应用,实现小流量预热。开源Dubbo实现的小流量服务预热模型计算如下:模型中应用QPS对应的f(x)随调用时间x线性变化,其中x表示调用时的时间,startTime为应用starttime,warmupTime是用户配置的应用预热时间,k是一个常量,一般表示每个实例的默认权重。图8应用小流量预热权重计算小流量预热方式可以有效解决高并发大流量下大量请求导致资源初始化慢,请求响应慢,请求阻塞,just导致的资源耗尽问题启动应用程序停机事故。3微服务就绪检查在介绍微服务就绪检查之前,先简单介绍一下相关的Kubernetes探针技术作为技术背景,以便更好的理解以下内容:Kubernetes探针技术是在云原生领域,Kubernetes是为了确保应用Pod在对外提供服务之前已经完全启动并就绪,或者在应用Pod长期运行过程中发生意外后,应用Pod能够及时恢复。提供探针技术,动态检测应用程序的运行状态。无损在线和长期健康运行提供了保障。SurvivalProbeKubernetes中提供的livenessprobe,用于检测容器何时重启。例如,活性探测可以捕获死锁(应用程序正在运行但无法继续进行)。在这种情况下重新启动容器有助于使应用程序在有问题的情况下更可用。ReadinessProbeKubernetes中提供的readinessprobe可以知道容器何时准备就绪,可以开始接受请求流量。当一个Pod中的所有容器都准备就绪时,就可以认为这个Pod就绪了。这个信号的一个用途是控制哪个Pod被用作服务的后端。当Pod未就绪时,它将从Service的负载均衡器中移除。StartupProbeKubernetes中提供的startupprobe可以知道应用容器何时启动。如果配置了此类探针,则可以控制容器在启动成功后进行livenessandreadinesscheck,保证这些liveness和readiness探针不会影响应用的启动。这可用于慢启动容器的活性检查,防止它们在开始运行之前被杀死。探针使用总结1.当容器启动后需要进行生存探针或就绪探针检查时,可以通过设置启动探针来实现。2.当容器应用遇到异常或不健康的情况自动崩溃时,不一定需要生存探测。Kubernetes可以根据Pod的restartPolicy策略自动执行预设的操作。3.当检测失败时容器被kill重启,可以指定生存探测,restartPolicy指定为Always或OnFailure。4.当你希望容器只有在探测成功时才开始接收外部请求流量,可以使用就绪探测。更多使用Kubernetes探针技术的例子,请参考[2]。目前容器+Kubernetes的应用运维部署模式已经成为业界事实上的标准。在相关技术为微服务应用的运维部署带来极大便利的同时,也存在一些特殊的应用部署场景。有些问题需要解决。比如利用Kubernetes的滚动发布功能进行应用发布,因为Kubernetes滚动发布一般伴随的就绪检查机制是通过检查应用特定端口是否启动来触发下一批,作为应用就绪的标志。但是,在微服务应用中,只有应用完成服务注册后,才能对外提供服务调用。因此,在某些情况下,旧应用实例会在新应用注册到注册中心之前被设置为离线,从而导致服务不可用。对于此类因微服务应用的发布状态与应用运行状态不兼容而导致的应用启动事故,目前业界已有相关的解决方案进行处理。比如可以使用就绪检查的方式注册关联服务,使用字节码技术在前后嵌入应用服务注册逻辑,然后在应用中打开一个端口检测应用服务是否注册,进行Kubernetes就绪检测。检测应用的就绪状态,然后绑定用户的发布状态和运行状态,实现微服务的就绪检查,避免相关状态不一致导致应用发布和在线流量丢失的问题。参考资料[1]Dubbo的延迟注册:https://dubbo.apache.org/zh/d...[2]使用Kubernetes探测技术的例子:https://kubernetes.io/zh/docs...链接转载本文为阿里云原创内容,未经允许不得转载。