从负载均衡到路由,微服务应用现场一键到位

本文涉及的产品
Serverless 应用引擎 SAE,800核*时 1600GiB*时
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 本文基于常见的服务调用场景,以 Ribbon 负载均衡组件为例,展示了微服务洞察能力能够在关键的位置为我们还原与记录丰富的现场信息,使得原有的黑盒场景能够便捷直观地被观测到,在微服务架构下,类似的不便观测的重要场景还有非常多,都可以借助微服务洞察能力来监测或是在异常时辅助排查。

作者:屿山、十眠


微服务体系架构中,服务之间的依赖关系错综复杂,我们往往会使用负载均衡组件配合注册中心来实现服务间的感知。而这种感知行为需要调用方、负载均衡组件、注册中心、被调用方互相配合才能够实现,在出现问题时我们又可能很难确定是哪一部分的问题,在常规场景中,注册中心会有对应的控制台可以查看,而调用方、负载均衡组件、被调用方处则需要我们手动增加日志打印语句并重启应用才能得到相关的信息,而有些组件又难以找到合适的位置添加我们日志代码,使得这类问题的排查效率低下。


负载均衡原理剖析


我们以 Spring Cloud 应用为例分析一下,微服务负载均衡到底是怎么一回事?


本文的 demo 包含 log-demo-spring-cloud-zuul、log-demo-spring-cloud-a、log-demo-spring-cloud-b、log-demo-spring-cloud-c 四个应用,采用最简单的 Spring Cloud 标准用法依次调用,可以直接在项目上查看源码:


https://github.com/aliyun/alibabacloud-microservice-demo/tree/master/mse-simple-demo  


以 Spring Cloud  常用的客户端负载均衡组件 Ribbon 作为示例,其工作原理如下图所示。


1.png


Ribbon 位于客户端一侧,通过服务注册中心(本文中为 Nacos)获取到一份服务端提供的可用服务列表。随后,在客户端发送请求时通过负载均衡算法选择一个服务端实例再进行访问,以达到负载均衡的目的。在这个过程中为了感知服务注册中心的可用服务列表的变化,Ribbon 会在构造 com.netflix.loadbalancer.DynamicServerListLoadBalancer 时,启动一个定时线程去循环调用 com.netflix.loadbalancer.DynamicServerListLoadBalancer#updateListOfServers 方法更新自己持有的可用服务列表。


  @VisibleForTesting
    public void updateListOfServers() {
        List<T> servers = new ArrayList<T>();
        if (serverListImpl != null) {
      //从注册中心获取可用服务列表
            servers = serverListImpl.getUpdatedListOfServers();
            LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
                    getIdentifier(), servers);
            if (filter != null) {
        //根据加载的过滤器过滤地址
                servers = filter.getFilteredListOfServers(servers);
                LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
                        getIdentifier(), servers);
            }
        }
    //更新可用服务列表
        updateAllServerList(servers);
    }


通过代码可以发现,updateAllServerList(servers)方法的参数 servers 就是更新后可用服务列表,不过为了确保获得真实的现场,我们随着调用链继续往下。


  protected void updateAllServerList(List<T> ls) {
        // other threads might be doing this - in which case, we pass
        if (serverListUpdateInProgress.compareAndSet(false, true)) {
            try {
                for (T s : ls) {
                    s.setAlive(true); // set so that clients can start using these
                                      // servers right away instead
                                      // of having to wait out the ping cycle.
                }
                setServersList(ls);
                super.forceQuickPing();
            } finally {
                serverListUpdateInProgress.set(false);
            }
        }
    }


可以看到只有一个线程能够调用 setServersList(ls)方法去更新可用服务列表,之后的调用链还有一些处理逻辑。


com.netflix.loadbalancer.DynamicServerListLoadBalancer#updateAllServerList
  -> com.netflix.loadbalancer.DynamicServerListLoadBalancer#setServersList
  -> com.netflix.loadbalancer.DynamicServerListLoadBalancer#setServerListForZones
  -> com.netflix.loadbalancer.LoadBalancerStats#updateZoneServerMapping


其中 updateZoneServerMapping 方法的参数 Map<String, List<Server>> map 基本等同于本次更新动作最后所更新的可用服务列表。也就是说,只要能打印出这个方法的参数,我们就能够知道每次更新可用服务列表的结果,这就能够帮助我们了解调用方以及负载均衡组件在这个场景下的真实现场。


无侵入的微服务洞察能力


我们是否可以提供一种能力,我们能够动态的在任意代码的关键位置动态地打印需要的日志,观测到任何一部分当下的真实现场,从而辅助我们排查问题。考虑到分布式微服务应用的复杂度,这种能力需要以下一些特点:


  • 分布式特性:满足在分布式场景下,即使是复杂微服务体系架构下,该能力需要打通微服务链路、日志调整、流量条件匹配,上下游联动等一系列分布式场景下的能力。 


  • 无侵入特性:无需重启应用,动态增强与卸载,可以动态增强整个应用或者是任意节点。 


  • 完整的现场保留能力:可以将抓取到的现场上下文等信息,自动保留至远端的日志系统中。 


  • 灵活的规则配:可以灵活匹配任意流量,增强任意方法点位,可以灵活控制所需的保留上下文内容。 


基于以上思考,我们提供了无侵入的微服务洞察能力,可以迅速帮助我们解决微服务场景下的复杂问题的定位与诊断,可以更好地为我们的治理提供思路与帮助,助力于企业构建完整的微服务治理体系。

洞察 loadbalancer 还原服务发现第一现场


下面来看看如何解决微服务负载均衡能力?


将借助微服务洞察能力,在我们寻找到的位置上保存打印目标方法包含入参的现场。我们首先选择 log-demo-spring-cloud-a 应用,在接口列表处选择自定义埋点,并且填入我们所确定的目标类和目标方法。


目标类:
com.netflix.loadbalancer.LoadBalancerStats

目标方法:
updateZoneServerMapping(java.util.Map)


2.png


由于在这个场景下不需要过滤条件,流量过滤条件部分保持默认关闭即可。在打印内容部分,由于我们所关注的内容是该方法的入参,因此勾选通用分类中的请求参数,其余选项可以根据需求勾选。


3.png


目标实例根据实际需要选择全部或是指定实例,最后开启规则并点击确定。


观察服务发现结果


在完成上述配置后,我们可以在对应的 SLS 的 LogStore 中查看收集的日志,其结构大致如下所示。


appName:log-demo-spring-cloud-a
destinationEndpoint:
end:1662541729796
endpoint:10.0.0.24
hostname:log-demo-spring-cloud-a-58b8b7ccc9-gnmsv
interface:com.netflix.loadbalancer.LoadBalancerStats:updateZoneServerMapping(java.util.Map)
ip:10.0.0.24
parameters:[{"unknown":[{"alive":true,"host":"10.0.0.125","hostPort":"10.0.0.125:20002","id":"10.0.0.125:20002","instance":{"clusterName":"DEFAULT","enabled":true,"ephemeral":true,"healthy":true,"instanceHeartBeatInterval":5000,"instanceHeartBeatTimeOut":15000,"instanceId":"10.0.0.125#20002#DEFAULT#DEFAULT_GROUP@@sc-B","ip":"10.0.0.125","ipDeleteTimeout":30000,"metadata":{"__micro.service.app.id__":"hkhon1po62@622bd5a9ab6ab48","preserved.register.source":"SPRING_CLOUD"},"port":20002,"serviceName":"DEFAULT_GROUP@@sc-B","weight":1.0},"metaInfo":{"appName":"DEFAULT_GROUP@@sc-B","instanceId":"10.0.0.125#20002#DEFAULT#DEFAULT_GROUP@@sc-B"},"metadata":{"$ref":"$[0].unknown[0].instance.metadata"},"port":20002,"readyToServe":true,"zone":"UNKNOWN"},{"alive":true,"host":"10.0.0.52","hostPort":"10.0.0.52:20002","id":"10.0.0.52:20002","instance":{"clusterName":"DEFAULT","enabled":true,"ephemeral":true,"healthy":true,"instanceHeartBeatInterval":5000,"instanceHeartBeatTimeOut":15000,"instanceId":"10.0.0.52#200...展开
parentSpanID:-1
ruleName:[237]
serviceType:DYNAMIC
spanID:4096
start:1662541729795
success:true
tag:_base
traceID:ea1a00001816625413997651001d0001
userId:1784327288677274


parameters 部分是被包装成 JSON 格式的入参,将其格式化后可以看到这便是我们想要获取的可用服务列表。


[
    {
        "unknown": [
            {
                "alive": true,
                "host": "10.0.0.125",
                "hostPort": "10.0.0.125:20002",
                "id": "10.0.0.125:20002",
                "instance": {
                    "clusterName": "DEFAULT",
                    "enabled": true,
                    "ephemeral": true,
                    "healthy": true,
                    "instanceHeartBeatInterval": 5000,
                    "instanceHeartBeatTimeOut": 15000,
                    "instanceId": "10.0.0.125#20002#DEFAULT#DEFAULT_GROUP@@sc-B",
                    "ip": "10.0.0.125",
                    "ipDeleteTimeout": 30000,
                    "metadata": {
                        "__micro.service.app.id__": "hkhon1po62@622bd5a9ab6ab48",
                        "preserved.register.source": "SPRING_CLOUD"
                    },
                    "port": 20002,
                    "serviceName": "DEFAULT_GROUP@@sc-B",
                    "weight": 1.0
                },
                "metaInfo": {
                    "appName": "DEFAULT_GROUP@@sc-B",
                    "instanceId": "10.0.0.125#20002#DEFAULT#DEFAULT_GROUP@@sc-B"
                },
                "metadata": {
                    "$ref": "$[0].unknown[0].instance.metadata"
                },
                "port": 20002,
                "readyToServe": true,
                "zone": "UNKNOWN"
            },
            {
                "alive": true,
                "host": "10.0.0.52",
                "hostPort": "10.0.0.52:20002",
                "id": "10.0.0.52:20002",
                "instance": {
                    "clusterName": "DEFAULT",
                    "enabled": true,
                    "ephemeral": true,
                    "healthy": true,
                    "instanceHeartBeatInterval": 5000,
                    "instanceHeartBeatTimeOut": 15000,
                    "instanceId": "10.0.0.52#20002#DEFAULT#DEFAULT_GROUP@@sc-B",
                    "ip": "10.0.0.52",
                    "ipDeleteTimeout": 30000,
                    "metadata": {
                        "__micro.service.app.id__": "hkhon1po62@622bd5a9ab6ab48",
                        "preserved.register.source": "SPRING_CLOUD",
                        "__micro.service.env__": "[{\"desc\":\"k8s-pod-label\",\"priority\":100,\"tag\":\"gray\",\"type\":\"tag\"}]"
                    },
                    "port": 20002,
                    "serviceName": "DEFAULT_GROUP@@sc-B",
                    "weight": 1.0
                },
                "metaInfo": {
                    "appName": "DEFAULT_GROUP@@sc-B",
                    "instanceId": "10.0.0.52#20002#DEFAULT#DEFAULT_GROUP@@sc-B"
                },
                "metadata": {
                    "$ref": "$[0].unknown[1].instance.metadata"
                },
                "port": 20002,
                "readyToServe": true,
                "zone": "UNKNOWN"
            }
        ]
    }
]

为了证明所获取的是真实的现场,我们通过容器控制台,将该应用所调用的被调用方,伸缩至 3 个节点。在完成扩容后,查看日志发现,可用服务列表按照预期由原来的 2 个实例变更为 3 个实例。


一键解决全链路灰度流量逃逸问题


时某个功能发版依赖多个服务同时升级上线。我们希望可以对这些服务的新版本同时进行小流量灰度验证,这就是微服务架构中特有的全链路灰度场景,通过构建从网关到整个后端服务的环境隔离来对多个不同版本的服务进行灰度验证。在发布过程中,我们只需部署服务的灰度版本,流量在调用链路上流转时,由流经的网关、各个中间件以及各个微服务来识别灰度流量,并动态转发至对应服务的灰度版本。如下图:


4.png


上图可以很好展示这种方案的效果,我们用不同的颜色来表示不同版本的灰度流量,可以看出无论是微服务网关还是微服务本身都需要识别流量,根据治理规则做出动态决策。当服务版本发生变化时,这个调用链路的转发也会实时改变。相比于利用机器搭建的灰度环境,这种方案不仅可以节省大量的机器成本和运维人力,而且可以帮助开发者实时快速的对线上流量进行精细化的全链路控制。


在我们生产环境使用全链路灰度的过程中,我们常常会遇到一些问题:


  • 我们配置全链路灰度的流量流向是否符合预期,我们的流量是否按照我们配置的灰度规则进行匹配。 


  • 我们灰度的流量出现了大量的慢调用、异常,我该如何确定是我们新版本代码的业务问题还是因为我们在流量灰度过程中考虑不全导致的系统问题,如何快速定位问题,从而实现高效的迭代。 


  • 在我们设计灰度系统的过程中,我们需要考虑如何对我们的灰度流量进行打标,有些时候在入口应用、微服务接口处可能难以找到合适的流量特征(参数、headers 等携带的具备业务语义的标识),在这样的场景下我们如何快捷地对我们的流量进行打标。 


基于以上一些列的问题,也是我们在支持云上客户落地全链路灰度的过程中不断碰到的问题。微服务洞察能力也就是我们在这个过程中抽象设计出来的一个能力。针对上诉的问题,我们的微服务洞察能力都能够很好地解决。


洞察灰度流量,流量逃逸问题无所遁形


关于灰度流量,我们在使用全链路灰度中往往会关注以下三个问题:


  • 我们配置全链路灰度的流量流向是否符合预期,有没有流量打到了非灰度应用
  • 符合灰度规则的流量是否被打上了对应的灰度标签
  • 不符合灰度规则的流量是否存在被误打上灰度标签的情况 


因为如果发生灰度流量没能按照预期调度,或者非灰度流量被错误地调度到灰度应用上的情况,不仅会影响灰度功能的测试,甚至会影响非灰度应用的正常运行。


为了回答上述的三个问题,我们需要观测灰度流量在系统中真实的标签和路径的能力。为此我们可以增加自定义流量规则,在配置规则的流量过滤条件部分选中对应的全链路灰度标签,并在打印内容中对应我们配置的灰度规则勾选请求参数、Headers 等信息,所有具有该灰度标签的流量的日志会被自动采集并且打印。


5.png


通过在控制台观察采集的日志中的参数、Headers 等信息可以判断流量匹配是否正确,不符合灰度规则的流量是否存在被误打上灰度标签的情况。而通过观察灰度流量日志中的 appName 信息可以判断其所经过的应用是否全部都是灰度版本,从而判断全链路灰度流量的路径是否符合预期。


对于是否有匹配的流量没有被打上标签这个问题,我们可以去除标签的流量过滤条件,从而采集全部的流量,并且通过在控制台对参数等信息的筛选,观察是否有符合条件的流量没有被打上灰度标签。


定位灰度流量的问题


在全链路灰度中,由于整体系统中运行着灰度应用和非灰度应用,相较于日常场景更加复杂。在灰度流量出现慢调用或者异常时,快速定位的难度也会更大,而微服务洞察的动态打印日志能力能够加速这一过程。


在灰度开始前,如果我们对可能会出现的问题没有很好的预期,可以先行在入口应用处配置较粗粒度的日志规则,方便我们观察到问题的出现。


创建规则时选中入口应用,在目标接口列表中,我们可以添加全部的 web、rpc 接口,也可以只添加我们所关注的。随后在流量过滤条件中选中对应的灰度标签、开启慢调用并输入慢调用阈值(在此处慢调用可以是相对原版本较慢的调用而非绝对意义上的慢)或是开启异常,随后在打印内容中选中定位所需的信息,比如请求参数、错误信息、调用堆栈等。


6.png


由于该规则应用于入口应用,我们需要打开后续链路日志的开关,以打印该流量路径上的所有日志。


7.png


在开启规则后,我们可以点击该规则对应的大盘,来观察所采集的日志。


8.png


符合过滤条件的请求和它后续链路的日志都会被采集,我们可以在请求列表中选择查看某一请求的链路详情,从而发现出现问题的具体位置。对于某些问题也可以看到其异常堆栈,从而确定发生异常的方法调用和导致该异常的可能的内部方法,随后我们可以通过配置自定义流量规则,在目标接口中选中自定义埋点,并输入这些嫌疑方法。


9.png


在打印内容中可以选择请求参数,返回值等信息辅助判断。在开启规则后便可以打印这些方法的日志,从而判断问题产生的原因。


总结


本文基于常见的服务调用场景,以 Ribbon 负载均衡组件为例,展示了微服务洞察能力能够在关键的位置为我们还原与记录丰富的现场信息,使得原有的黑盒场景能够便捷直观地被观测到,在微服务架构下,类似的不便观测的重要场景还有非常多,都可以借助微服务洞察能力来监测或是在异常时辅助排查。同时,全链路灰度是微服务治理中比较重要的一个场景,我们在落地全链路灰度的过程中最让人头大的两个问题就是流量路由不生效以及流量逃逸,我们借助于微服务洞察能力可以快速定位与解决全链路灰度相关的问题。


MSE 微服务洞察能力我们还在持续地打磨与完善,旨在帮助我们更好地治理我们的微服务应用,助力于云上帮助企业构建完整的微服务体系。欢迎大家尝鲜与体验~

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
2月前
|
Cloud Native Go 开发工具
不改一行代码轻松玩转 Go 应用微服务治理
为了更好的进行 Go 应用微服务治理,提高研发效率和系统稳定性,本文将介绍 MSE 微服务治理方案,无需修改业务代码,实现治理能力。
19770 4
|
2月前
|
Prometheus 监控 Kubernetes
Prometheus 在微服务架构中的应用
【8月更文第29天】随着微服务架构的普及,监控和跟踪各个服务的状态变得尤为重要。Prometheus 是一个开源的监控系统和时间序列数据库,非常适合用于微服务架构中的监控。本文将详细介绍 Prometheus 如何支持微服务架构下的监控需求,包括服务发现、服务间的监控指标收集以及如何配置 Prometheus 来适应这些需求。
62 0
|
2月前
|
监控 JavaScript 测试技术
从单体应用迁移到微服务的最佳实践
【8月更文第29天】随着软件架构的发展,越来越多的企业开始考虑从传统的单体应用迁移到微服务架构。虽然迁移可以带来诸如更好的可扩展性、更高的灵活性等优势,但这一过程也可能充满挑战。本文将详细介绍如何顺利地进行这一转变,并提供一些实用的步骤和示例代码。
74 0
|
25天前
|
存储 搜索推荐 数据库
MarkLogic在微服务架构中的应用:提供服务间通信和数据共享的机制
随着微服务架构的发展,服务间通信和数据共享成为关键挑战。本文介绍MarkLogic数据库在微服务架构中的应用,阐述其多模型支持、索引搜索、事务处理及高可用性等优势,以及如何利用MarkLogic实现数据共享、服务间通信、事件驱动架构和数据分析,提升系统的可伸缩性和可靠性。
24 5
|
25天前
|
运维 Cloud Native Devops
云原生架构的崛起与实践云原生架构是一种通过容器化、微服务和DevOps等技术手段,帮助应用系统实现敏捷部署、弹性扩展和高效运维的技术理念。本文将探讨云原生的概念、核心技术以及其在企业中的应用实践,揭示云原生如何成为现代软件开发和运营的主流方式。##
云原生架构是现代IT领域的一场革命,它依托于容器化、微服务和DevOps等核心技术,旨在解决传统架构在应对复杂业务需求时的不足。通过采用云原生方法,企业可以实现敏捷部署、弹性扩展和高效运维,从而大幅提升开发效率和系统可靠性。本文详细阐述了云原生的核心概念、主要技术和实际应用案例,并探讨了企业在实施云原生过程中的挑战与解决方案。无论是正在转型的传统企业,还是寻求创新的互联网企业,云原生都提供了一条实现高效能、高灵活性和高可靠性的技术路径。 ##
36 3
|
1月前
|
Cloud Native 持续交付 云计算
云原生之旅:从传统应用到容器化微服务
随着数字化转型的浪潮不断推进,企业对IT系统的要求日益提高。本文将引导你了解如何将传统应用转变为云原生架构,重点介绍容器化和微服务的概念、优势以及实施步骤,旨在帮助读者掌握将应用迁移到云平台的关键技巧,确保在云计算时代保持竞争力。
23 5
|
2月前
|
负载均衡 算法 应用服务中间件
负载均衡技术在Web服务器集群中的应用
【8月更文第28天】随着互联网的发展和用户对Web服务需求的增长,单台服务器很难满足大规模访问的需求。为了提高系统的稳定性和扩展性,通常会采用Web服务器集群的方式。在这种架构中,负载均衡器扮演着至关重要的角色,它能够合理地分配客户端请求到不同的后端服务器上,从而实现资源的最优利用。
75 2
|
21天前
|
缓存 负载均衡 数据管理
深入探索微服务架构的核心要素与实践策略在当今软件开发领域,微服务架构以其独特的优势和灵活性,已成为众多企业和开发者的首选。本文将深入探讨微服务架构的核心要素,包括服务拆分、通信机制、数据管理等,并结合实际案例分析其在不同场景下的应用策略,旨在为读者提供一套全面、深入的微服务架构实践指南。**
**微服务架构作为软件开发领域的热门话题,正引领着一场技术革新。本文从微服务架构的核心要素出发,详细阐述了服务拆分的原则与方法、通信机制的选择与优化、数据管理的策略与挑战等内容。同时,结合具体案例,分析了微服务架构在不同场景下的应用策略,为读者提供了实用的指导和建议。
|
2月前
|
JSON Nacos 开发工具
微服务通过nacos实现动态路由
微服务通过nacos实现动态路由
58 7
|
2月前
|
Java Docker 微服务
微服务架构已成为Java Web开发的新趋势,它通过将应用分解为独立、可部署的服务单元,提升了系统的灵活性与可维护性。
微服务架构已成为Java Web开发的新趋势,它通过将应用分解为独立、可部署的服务单元,提升了系统的灵活性与可维护性。每个服务负责特定功能,通过轻量通信机制协作。利用Spring Boot与Spring Cloud等框架可简化开发流程,支持模块化设计、独立部署、技术多样性和容错性,适应快速迭代的需求。
67 1
下一篇
无影云桌面