微服务治理热门技术揭秘:无损上线

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 微服务引擎MSE面向业界主流开源微服务项目, 提供注册配置中心和分布式协调(原生支持Nacos/ZooKeeper/Eureka)、云原生网关(原生支持Ingress/Envoy)、微服务治理(原生支持Spring Cloud/Dubbo/Sentinel,遵循 OpenSergo 服务治理规范)能力。

为什么有了无损下线,还需要无损上线?无损上线可以解决哪些问题?本篇文章将一一回答这些问题。

无损上线功能不得不说是一个客户打磨出来的功能

我们将从一次发布问题的排查与解决的过程说起。

背景

阿里云内部某应用中心服务在发布过程中出现了大量的5xx超时异常。初步怀疑是无损下线问题,于是很快便接入了 MSE 提供的无损下线功能。但是接入无损下线功能后继续发布,应用的发布过程依然存在大量的超时报错。根据业务方同学的反馈,大概应用在启动后 5秒左右,会有大量超时请求。

无损下线功能未生效?

于是拉了相关的同学开始排查。应用的调用情况如下: gateway - > A -> C 。image.png

发布的应用为 C 应用,发布过程中出现大量超时报错。我们通过相关日志与应用的启动情况,整理如下线索

【服务端视角】:找了一台 C 应用的机器 xxx.xxx.xxx.60 观察

第一阶段 xxx.xxx.xxx.60 (C应用)下线阶段

  • 20:27:51 开始重启,执行重启脚本
  • 同时观察到执行了 sendReadOnlyEvent 动作,表明服务端发送只读事件,客户端不会再请求该服务端
  • 在 sendReadOnlyEvent 后,开始陆续执行注销服务动作
  • 20:27:54 注销所有 provider seivce 完成
  • 20:28:15  应用收到 kill -15 信号

第二阶段 xxx.xxx.xxx.60 (C应用)上线阶段

  • 20:28:34 服务端重新启动
  • 20:29:19 在Dubbo注册中心控制台观察到 xxx.xxx.xxx.60 注册完毕
  • 20:29:19,257 日志中看到 start NettyServer


【客户端视角】:找了一台 A 应用的机器 XXX.XXX.XXX.142 观察

  • 20:27:51 received readOnly event,收到服务端发送的只读事件,此时该客户端不会请求至 XXX.XXX.XXX.60 机器
  • 20:27:56 close [xxx.xxx.xxx.142:0 -> /XXX.XXX.XXX.60:20880] ,关闭channel连接

业务日志报错信息

同时搜C应用的机器 XXX.XXX.XXX.60的报错相关的日志,共237条日志

其中最早的time:  2020-07-30 20:29:26,524 其中最晚的 time:  2020-07-30 20:29:59,788

结论

观察这些迹象可以初步得出结论

  • 无损下线过程均符合预期,并且下线过程中并没有出现任何报错
  • 报错期间处于服务端应用成功启动后且注册成功后,与业务方观察的现象一致

这时候怀疑是上线期间的问题,同时排查服务端相关日志,发在报错期间,服务端线程被打满

image.png问题定位为上线过程中的问题,与无损下线无关

无损上线实践

我们帮助用户解决问题的思路:帮助用户发现问题的本质、找到问题的通用性、解决问题、将解决通用问题的能力产品化。

发现用户dubbo版本比较低,缺少自动打线程堆栈的能力

  • 通过MSE 增加Dubbo线程池满自动 JStack 能力

这是每次发布必现的问题,通过观察线程池满时的 JStack 日志,有助于我们定位问题。

阻塞在异步连接等资源准备上

初步观察 JStack 日志,发现不少线程阻塞在 taril/druid 等异步连接资源准备上

image.png

同时我们云上也有有客户遇到过,应用启动后一段时间内 Dubbo 线程池满的问题,后经过排查由于 Redis 连接池中的连接未提前建立,流量进来后大量线程阻塞在 Redis 连接建立上。

连接池通过异步线程保持连接数量,默认在应用启动后 30 秒建立最小连接数的连接。

解决思路

  • 提前建立连接
  • 使用服务延迟发布特性

预建连接

以 JedisPool 预建连接为例,提前建立Redis等连接池连接,而不是等流量进来后开始建立连接导致大量业务线程等待连接建立。

org.apache.commons.pool2.impl.GenericObjectPool#startEvictor
protected synchronized void startEvictor(long delay) {
    if(null != _evictor) {
        EvictionTimer.cancel(_evictor);
        _evictor = null;
    }
    if(delay > 0) {
        _evictor = new Evictor();
        EvictionTimer.schedule(_evictor, delay, delay);
    }
}

JedisPool 通过定时任务去异步保证最小连接数的建立,但这会导致应用启动时,Redis连接并未建立完成。

主动预建连接方式:在使用连接之前使用 GenericObjectPool#preparePool 方法去手动去准备连接。

在微服务上线过程中,在初始化Redis的过程中提前去创建 min-idle 个 redis 连接,确保连接建立完成后再开始发布服务。

JedisPool warm-internal-pool

同样有类似问题,预建数据库连接等异步建连逻辑,保证在业务流量进来之前,异步连接资源一切就绪。

延迟发布

延迟发布为了一些需要异步加载的前置资源如提前准备缓存资源,异步下载资源等,需要控制服务注册时机,即控制流量进入的时机保证服务所需的前置资源准备完成该服务才可以进行发布,延迟发布有两种方式

  • 通过 delay 配置方式

image.png

通过指定 delay 大小例如 300 s,Dubbo/Spring Cloud 服务将会在 Spring 容器初始化完成后进行后等待 5 分钟,再执行服务注册逻辑。

  • online 命令上线

通过打开默认不注册服务配置项,再配合发布脚本等方式执行 curl 127.0.0.1:54199/online 地址触发主动注册。我们可以在前置资源准备完成后,通过 online 命令去注册服务。

也可以在 MSE 实例详情通过服务上线去注册服务。

image.png

阻塞在 ASMClassLoader 类加载器上

image.png

大量线程阻塞在 fastjson 的 ASMClassLoader 类加载器加载类的过程中,翻看 ClassLoader 加载类的代码其默认是同步类加载。在高并发场景下会导致大量线程阻塞在类加载上,从而影响服务端性能,造成线程池满等问题。

private ClassLoader(Void unused, ClassLoader parent) {
    this.parent = parent;
    // 开启并行类加载
    if (ParallelLoaders.isRegistered(this.getClass())) {
        parallelLockMap = new ConcurrentHashMap<>();
        package2certs = new ConcurrentHashMap<>();
        domains =
            Collections.synchronizedSet(new HashSet<ProtectionDomain>());
        assertionLock = new Object();
    } else {
        // no finer-grained lock; lock on the classloader instance
        parallelLockMap = null;
        package2certs = new Hashtable<>();
        domains = new HashSet<>();
        assertionLock = this;
    }
}
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            ......
            return c;
        }
    }
protected Object getClassLoadingLock(String className) {
    Object lock = this;
    //如果开启类加载器并行类加载,则锁在所加载的类上,而不是类加载器上
    if (parallelLockMap != null) {
        Object newLock = new Object();
        lock = parallelLockMap.putIfAbsent(className, newLock);
        if (lock == null) {
            lock = newLock;
        }
    }
    return lock;
}

解决思路

  • 开启类加载器并行加载

类加载器开启并行类加载

JDK7上,如果调用Classloader.registerAsParallelCapable方法,则会开启并行类加载功能,把锁的级别从ClassLoader对象本身,降低为要加载的类名这个级别。换句话说只要多线程加载的不是同一个类的话,loadClass方法都不会锁住。

我们可以看 Classloader.registerAsParallelCapable 方法的介绍

protected static boolean registerAsParallelCapable()

Registers the caller as parallel capable.

The registration succeeds if and only if all of the following conditions are met:

1. no instance of the caller has been created

2. all of the super classes (except class Object) of the caller are registered as parallel capable

它要求注册该方法时,其注册的类加载器无实例并且该类加载器的继承链路上所有类加载器都调用过registerAsParallelCapable,对于低版本的Tomcat/Jetty webAppClassLoader 以及fastjson的ASMClassLoader都未开启类加载,如果应用里面有多个线程在同时调用loadClass方法进行类加载的话,那么锁的竞争将会非常激烈。


MSE Agent 通过无侵入方式在类加载器被加载前开启其并行类加载的能力,无需用户升级Tomcat/Jetty,同时支持通过配置动态开启类加载并行类加载能力。

http://140.205.61.252/2016/01/29/3721/

其他一些问题

  • JVM JIT编译问题引起cpu飙高
  • 日志同步打印导致线程阻塞
  • Jetty 低版本类加载类同步加载
  • K8s场景下,微服务与K8s Service 生命周期未对齐

解决思路

  • 服务预热
  • 客户端负载均衡
  • 服务端服务分层发布
  • 业务日志异步化
  • 提供微服务 Readiness 接口

业务日志异步化

同步进行日志打印,由于日志打印使用的是业务线程,由于日志打印过程中存在序列化、类加载等逻辑,在高并发的场景下会导致业务线程hang住,导致服务框架线程池满等问题。MSE Agent支持动态使用异步日志打印能力,将日志打印任务与业务线程分开,提高业务线程吞吐量。


小流量预热

  • 应用启动后,大量请求进入,导致应用存在许多问题,所以需要微服务的一些能力来解决服务预热问题
  • JVM JIT编译线程占用CPU过高,CPU/load短期内飙高,Dubbo 处理请求性能下降
  • 瞬时请求量过大,导致线程阻塞在类加载、缓存等,从而导致 Dubbo 服务线程池满


小流量预热,MSE 服务治理通过 OneAgent 无侵入提供了以下几种能力

客户端负载均衡

通过增强客户端负载均衡能力,对于刚上线的需要预热的节点进行流量权重的调整,做到刚上线的应用按照用户所配置的规则进行小流量预热,用户只需指定预热规则即可按照预期对刚上线的节点进行小流量预热

image.png

  • 业务方的一台服务端实例使用服务预热后的效果

服务预热开启后,待预热的应用将在预热周期内通过小流量实现应用启动过程的预热初始化。下图预热时长为120秒,预热曲线为2次的预热效果图:

image.png说明 该测试Demo是定时伸缩模拟应用启动,因此除了预热过程,还包含应用下线的过程。下图预热时长为120秒,预热曲线为5次的预热效果图:

image.png

如上图所示,相比于2次预热过程,5次预热过程刚启动的这段时间(即17:41:01~17:42:01),QPS一直保持在一个较低值,以满足需要较长时间进行预热的复杂应用的预热需求。

服务端分层发布

通过修改服务注册的逻辑,增加对应用load等指标的监控,对服务进行分批注册已经回滚注册等逻辑,保证服务注册过程中,流量分服务进入,系统load始终低于阈值,并且需要在指定时长内将服务注册上去。


缺点:在应用的服务流量平均,不存在超热点接口的情况下,分层发布可以很好地解决服务预热问题。但是如果应用存在一些超热服务,可能这个服务几乎占所有流量90%以上,那服务端分层发布效果并不会很明显。

注意:对于一些存在依赖的服务接口,服务分层发布可能需要业务梳理服务分批发布的顺序


打通K8s与微服务生命周期

K8S提供两种健康检查机制:

  • livenessProbe,用于探测不健康的Pod,探测失败将会重启Pod。
  • readinessProbe,用于探测一个Pod是否就绪接受流量,探测失败将会在Service路由上摘取该节点。


如果不配置 readinessProbe ,默认只检查容器内进程是否启动运行,而对于进程的运行情况很难考量,Mse Agent 通过对外提供 readiness 接口,只有 Spring Bean 初始化完成以及异步资源准备就绪并且开始服务注册时, readiness 才返回 200。将微服务侧的服务暴露与 K8s Service 体系打通,使K8s管控能感知到进程内部的服务就绪时机,从而进行正确地服务上线。

我们需要在MSE无损上线页面开启无损滚动发布的配置

image.png

同时给应用配置K8s的就绪检查接口,如果您的应用在阿里云容器服务ACK上,可以在阿里云容器ACK服务对应应用配置的中健康检查区域,选中就绪检查右侧的开启,配置如下参数,然后单击更新。

image.png

该应用在下次重启时,该配置即可生效。

服务并行订阅与注册

通过并行的服务注册与订阅,可以大幅提升应用启动的速度,解决服务启动慢的问题。

以并行服务订阅为例:image.png

如上图所示,通过 Java Agent 将服务框架 refer 的流程从 SpringBean 的初始化流程中剥离出来并且通过异步线程来实现服务的并行订阅与注册。


总结

通过不断地观察业务情况,然后进行不断地问题分析思考与解决的尝试,直到开启了服务小流量预热能力后,彻底解决了业务团队应用在上线期间线程池满导致请求有损的问题。

  • 发布期间 Exception 总量与发布日期(包含无损上线功能陆续上线的节点)的情况如下图

image.png

9月15号发布了服务小流量预热能力后,发布期间相关 Exception 下降至 2。(经业务方确认不是因为发布引起的,可以忽略


上线了无损上线功能后,业务团队的应用中心持续多个月的发布报错问题总算告一段落,但是无损上线功能远不止于此。还解决许多云上客户上线有损的情况,功能的能力与场景也在不断地解决问题中逐渐完善与丰富。


MSE 无损上线


MSE 服务治理一个特点是通过 Agent 无侵入地支持市面上近五年来Dubbo、Spring Cloud所有版本,所以无损上线这个功能也会是如此,下面会以Dubbo为例子无损上线的功能,当然所有能力我们都是无缝支持 Dubbo、Spring Cloud 的。


下面开始系统地介绍一下 MSE 服务治理的无损上线,我们可以先从开源的一个 Dubbo 应用上线的流程开始分析


  • 应用初始化,Spring Bean容器初始化
  • 收到 ContextRefreshedEvent后,Dubbo 会去拉取 Dubbo应用所需的配置、元数据等
  • exportServices 注册服务


开源 Dubbo 上线流程还是非常完善与严谨,但是依旧存在一些场景会导致服务上线存在问题

  • 当服务信息注册到注册中心后,在消费者看来该服务就是可以被调用的。然而,此时可能存在一些数据库、缓存资源等一些异步资源尚未加载完毕的场景,这取决于你的系统有没有对应的组件,它们何时加载完毕,也完全取决于你的业务。
  • 如果在大流量的场景下,服务在注册到注册中心后,马上有大流量进入,存在一系列问题,导致线程阻塞,对业务流量造成损失
  • 比如Redis的JedisPool连接池创建后并不会立即建立连接,会在流量进来后开始建立连接,如果一开始涌进的是大流量,则导致大量线程阻塞在连接池重的连接的建立上
  • FastJson 以及 Jetty/tomcat等低版本中,并未开启类加载器并行类加载能力,导致大量线程阻塞在类加载器加载类上
  • JVM JIT 编译问题引起cpu飙高
  • 线程阻塞在业务日志上
  • 云原生场景下,微服务与K8s的生命周期未对齐的情况
  • 滚动发布,重启的pod还未注册至注册中心,但是readiness检查以及通过。导致第一个pod还未注册至注册中心,最后一个pod以及下线,导致短时间内的客户端NoProvider异常

针对如上问题,MSE 服务治理不仅提供了完整的解决方案,还提供了白屏化开箱即用的能力,动态配置实时生效。

image.png

同时 MSE 服务治理针对无损上下线的场景还提供了完整的可观测能力。

image.png

无损上线功能可以总结为以下这张图

image.png

不只是无损上下线

无损上下线能力是微服务流量治理中的重要的一环,当然除了无损下线,MSE还提供了全链路灰度、流控降级与容错、数据库治理等一系列的微服务治理能力。服务治理是微服务改造深入到一定阶段之后的必经之路,在这个过程中我们不断有新的问题出现。

  • 除了无损上下线,服务治理还有没其他能力?
  • 服务治理能力有没一个标准的定义,服务治理能力包含哪些?
  • 多语言场景下,有无全链路的最佳实践或者标准?
  • 异构微服务如何可以统一治理?

当我们在探索服务治理的过程中,我们在对接其他微服务的时候,我们发现治理体系不同造成的困扰是巨大的,打通两套甚者是多套治理体系的成本也是巨大的。为此我们提出了 OpenSergo 项目。OpenSergo 要解决的是不同框架、不同语言在微服务治理上的概念碎片化、无法互通的问题。

image.png

OpenSergo 社区也在联合各个社区进行进一步的合作,社区来一起讨论与定义统一的服务治理标准。当前社区也在联合 bilibili、字节跳动等企业一起共建标准,也欢迎感兴趣的开发者、社区与企业一起加入到 OpenSergo 服务治理标准共建中。欢迎大家加入 OpenSergo 社区交流群(钉钉群)进行讨论:34826335


相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务&nbsp;ACK 容器服务&nbsp;Kubernetes&nbsp;版(简称&nbsp;ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情:&nbsp;https://www.aliyun.com/product/kubernetes
相关文章
|
8天前
|
负载均衡 Java 开发者
细解微服务架构实践:如何使用Spring Cloud进行Java微服务治理
【4月更文挑战第17天】Spring Cloud是Java微服务治理的首选框架,整合了Eureka(服务发现)、Ribbon(客户端负载均衡)、Hystrix(熔断器)、Zuul(API网关)和Config Server(配置中心)。通过Eureka实现服务注册与发现,Ribbon提供负载均衡,Hystrix实现熔断保护,Zuul作为API网关,Config Server集中管理配置。理解并运用Spring Cloud进行微服务治理是现代Java开发者的关键技能。
|
11天前
|
Kubernetes 监控 Cloud Native
构建高效云原生应用:基于Kubernetes的微服务治理实践
【4月更文挑战第13天】 在当今数字化转型的浪潮中,企业纷纷将目光投向了云原生技术以支持其业务敏捷性和可扩展性。本文深入探讨了利用Kubernetes作为容器编排平台,实现微服务架构的有效治理,旨在为开发者和运维团队提供一套优化策略,以确保云原生应用的高性能和稳定性。通过分析微服务设计原则、Kubernetes的核心组件以及实际案例,本文揭示了在多变的业务需求下,如何确保系统的高可用性、弹性和安全性。
16 4
|
1月前
|
监控 Cloud Native 云计算
构建未来:云原生架构下的微服务治理
【2月更文挑战第30天】随着云计算的不断演进,云原生技术逐渐占据了软件开发与运维的核心地位。本文深入探讨了在云原生生态系统中,如何有效管理和治理微服务,确保系统的高可用性、可扩展性和安全性。通过对容器化技术、服务网格、以及微服务框架的剖析,我们展示了在云平台上构建和管理微服务的先进策略和实践。
|
1月前
|
负载均衡 算法 微服务
常见的微服务流量治理策略
常见的微服务流量治理策略
43 3
|
2月前
|
设计模式 人工智能 负载均衡
《后端架构设计中的微服务治理与容错机制》
【2月更文挑战第8天】在高并发、大规模应用中,微服务架构已经成为一种常见的设计模式。然而,随着微服务数量的增加,服务之间的依赖关系变得复杂,微服务治理和容错机制成为了关键问题。本文将介绍在后端架构设计中,如何进行微服务治理以及如何实现有效的容错机制,以应对复杂的微服务环境。
|
2月前
|
存储 运维 监控
|
2月前
|
缓存 负载均衡 算法
|
5月前
|
Kubernetes Java 双11
抗住双十一!实战Alibaba笔记,深度解析阿里微服务亿级流量治理
随着微服务的发展及DDD领域驱动设计的兴起,越来越多的企业开始使用微服务架构。为了应对微服务化带来的难题,一批微服务组件与应用涌现出来,如辅助问题排查得分布式调用链追踪探针、简化部署运维的Kubernetes,以及本书介绍的熔断器组件等。
|
6月前
|
运维 Java BI
千亿流量并发治理!Alibaba实战Sentinel笔记,为微服务保驾护航
随着微服务的发展及DDD领域驱动设计的兴起,越来越多的企业开始使用微服务架构。无论是项目重构,还是新项目的开发,即使项目初期没有多大的流量,但从长远考虑,企业也基本会优先使用微服务架构。但“鱼和熊掌不可兼得”,项目微服务化在提升开发效率及降低后期维护成本的同时,也加大了服务部署运维及问题排查的难度,并且容易导致服务崩溃出现级联效应,也就是“服务雪崩”。
|
6月前
|
存储 缓存 监控
架构师进阶,微服务设计与治理的16条常用原则
架构师进阶,微服务设计与治理的16条常用原则
227 0