共计 8391 个字符,预计需要花费 21 分钟才能阅读完成。
作者:屿山、十眠
微服务体系架构中,服务之间的依赖关系盘根错节,咱们往往会应用负载平衡组件配合注册核心来实现服务间的感知。而这种感知行为须要调用方、负载平衡组件、注册核心、被调用方互相配合才可能实现,在呈现问题时咱们又可能很难确定是哪一部分的问题,在惯例场景中,注册核心会有对应的控制台能够查看,而调用方、负载平衡组件、被调用方处则须要咱们手动减少日志打印语句并重启利用能力失去相干的信息,而有些组件又难以找到适合的地位增加咱们日志代码,使得这类问题的排查效率低下。
负载平衡原理分析
咱们以 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/ali…
以 Spring Cloud 罕用的客户端负载平衡组件 Ribbon 作为示例,其工作原理如下图所示。
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)
因为在这个场景下不须要过滤条件,流量过滤条件局部放弃默认敞开即可。在打印内容局部,因为咱们所关注的内容是该办法的入参,因而勾选通用分类中的申请参数,其余选项能够依据需要勾选。
指标实例依据理论须要抉择全副或是指定实例,最初开启规定并点击确定。
察看服务发现后果
在实现上述配置后,咱们能够在对应的 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 个实例。
一键解决全链路灰度流量逃逸问题
有时某个性能发版依赖多个服务同时降级上线。咱们心愿能够对这些服务的新版本同时进行小流量灰度验证,这就是微服务架构中特有的全链路灰度场景,通过构建从网关到整个后端服务的环境隔离来对多个不同版本的服务进行灰度验证。在公布过程中,咱们只需部署服务的灰度版本,流量在调用链路上流转时,由流经的网关、各个中间件以及各个微服务来辨认灰度流量,并动静转发至对应服务的灰度版本。如下图:
上图能够很好展现这种计划的成果,咱们用不同的色彩来示意不同版本的灰度流量,能够看出无论是微服务网关还是微服务自身都须要辨认流量,依据治理规定做出动静决策。当服务版本发生变化时,这个调用链路的转发也会实时扭转。相比于利用机器搭建的灰度环境,这种计划不仅能够节俭大量的机器老本和运维人力,而且能够帮忙开发者实时疾速的对线上流量进行精细化的全链路管制。
在咱们生产环境应用全链路灰度的过程中,咱们经常会遇到一些问题:
- 咱们配置全链路灰度的流量流向是否合乎预期,咱们的流量是否依照咱们配置的灰度规定进行匹配。
- 咱们灰度的流量呈现了大量的慢调用、异样,我该如何确定是咱们新版本代码的业务问题还是因为咱们在流量灰度过程中思考不全导致的零碎问题,如何疾速定位问题,从而实现高效的迭代。
- 在咱们设计灰度零碎的过程中,咱们须要思考如何对咱们的灰度流量进行打标,有些时候在入口利用、微服务接口处可能难以找到适合的流量特色(参数、headers 等携带的具备业务语义的标识),在这样的场景下咱们如何快捷地对咱们的流量进行打标。
基于以上一些列的问题,也是咱们在反对云上客户落地全链路灰度的过程中一直碰到的问题。微服务洞察能力也就是咱们在这个过程中形象设计进去的一个能力。针对上诉的问题,咱们的微服务洞察能力都可能很好地解决。
洞察灰度流量,流量逃逸问题无所遁形
对于灰度流量,咱们在应用全链路灰度中往往会关注以下三个问题:
- 咱们配置全链路灰度的流量流向是否合乎预期,有没有流量打到了非灰度利用
- 合乎灰度规定的流量是否被打上了对应的灰度标签
- 不合乎灰度规定的流量是否存在被误打上灰度标签的状况
因为如果产生灰度流量没能依照预期调度,或者非灰度流量被谬误地调度到灰度利用上的状况,不仅会影响灰度性能的测试,甚至会影响非灰度利用的失常运行。
为了答复上述的三个问题,咱们须要观测灰度流量在零碎中实在的标签和门路的能力。为此咱们能够减少自定义流量规定,在配置规定的流量过滤条件局部选中对应的全链路灰度标签,并在打印内容中对应咱们配置的灰度规定勾选申请参数、Headers 等信息,所有具备该灰度标签的流量的日志会被主动采集并且打印。
通过在控制台察看采集的日志中的参数、Headers 等信息能够判断流量匹配是否正确,不合乎灰度规定的流量是否存在被误打上灰度标签的状况。而通过观察灰度流量日志中的 appName 信息能够判断其所通过的利用是否全部都是灰度版本,从而判断全链路灰度流量的门路是否合乎预期。
对于是否有匹配的流量没有被打上标签这个问题,咱们能够去除标签的流量过滤条件,从而采集全副的流量,并且通过在控制台对参数等信息的筛选,察看是否有符合条件的流量没有被打上灰度标签。
定位灰度流量的问题
在全链路灰度中,因为整体零碎中运行着灰度利用和非灰度利用,相较于日常场景更加简单。在灰度流量呈现慢调用或者异样时,疾速定位的难度也会更大,而微服务洞察的动静打印日志能力可能减速这一过程。
在灰度开始前,如果咱们对可能会呈现的问题没有很好的预期,能够后行在入口利用处配置较粗粒度的日志规定,不便咱们察看到问题的呈现。
创立规定时选中入口利用,在指标接口列表中,咱们能够增加全副的 web、rpc 接口,也能够只增加咱们所关注的。随后在流量过滤条件中选中对应的灰度标签、开启慢调用并输出慢调用阈值(在此处慢调用能够是绝对原版本较慢的调用而非相对意义上的慢)或是开启异样,随后在打印内容中选中定位所需的信息,比方申请参数、错误信息、调用堆栈等。
因为该规定利用于入口利用,咱们须要关上后续链路日志的开关,以打印该流量门路上的所有日志。
在开启规定后,咱们能够点击该规定对应的大盘,来观察所采集的日志。
合乎过滤条件的申请和它后续链路的日志都会被采集,咱们能够在申请列表中抉择查看某一申请的链路详情,从而发现呈现问题的具体位置。对于某些问题也能够看到其异样堆栈,从而确定产生异样的办法调用和导致该异样的可能的外部办法,随后咱们能够通过配置自定义流量规定,在指标接口中选中自定义埋点,并输出这些嫌疑办法。
在打印内容中能够抉择申请参数,返回值等信息辅助判断。在开启规定后便能够打印这些办法的日志,从而判断问题产生的起因。
总结
本文基于常见的服务调用场景,以 Ribbon 负载平衡组件为例,展现了微服务洞察能力可能在要害的地位为咱们还原与记录丰盛的现场信息,使得原有的黑盒场景可能便捷直观地被观测到,在微服务架构下,相似的不便观测的重要场景还有十分多,都能够借助微服务洞察能力来监测或是在异样时辅助排查。同时,全链路灰度是微服务治理中比拟重要的一个场景,咱们在落地全链路灰度的过程中最让人头大的两个问题就是流量路由不失效以及流量逃逸,咱们借助于微服务洞察能力能够疾速定位与解决全链路灰度相干的问题。
MSE 微服务洞察能力咱们还在继续地打磨与欠缺,旨在帮忙咱们更好地治理咱们的微服务利用,助力于云上帮忙企业构建残缺的微服务体系。欢送大家尝鲜与体验~