欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 资讯 > 【八股消消乐】发送请求有遇到服务不可用吗?如何解决?

【八股消消乐】发送请求有遇到服务不可用吗?如何解决?

2025/4/28 12:38:46 来源:https://blog.csdn.net/m0_51517236/article/details/147568757  浏览:    关键词:【八股消消乐】发送请求有遇到服务不可用吗?如何解决?

在这里插入图片描述

😊你好,我是小航,一个正在变秃、变强的文艺倾年。
🔔本专栏《八股消消乐》旨在记录个人所背的八股文,包括Java/Go开发、Vue开发、系统架构、大模型开发、机器学习、深度学习、力扣算法等相关知识点,期待与你一同探索、学习、进步,一起卷起来叭!

目录

  • 题目
  • 答案

题目

💬技术栈:Dubbo

🔍简历内容:独立定制Cluster扩展解决同机房请求无法连通。

🚩面试问:某公司有一个多机房部署的系统,线上运行一直比较稳定,但最近,部分流量请求先出现超时异常,紧接着出现无提供者异常,最后部分功能不可用。怎么办?

在这里插入图片描述
(1)超时异常日志:

Caused by: org.apache.dubbo.remoting.TimeoutException: Waiting server-side response timeout by scan timer. start time: 2022-10-25 20:14:16.718, end time: 2022-10-25 20:14:16.747, client elapsed: 1 ms, server elapsed: 28 ms, timeout: 5 ms, request: Request [id=2, version=2.0.2, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=sayHello, parameterTypes=[class java.lang.String], arguments=[Geek], attachments={path=com.hmilyylimh.cloud.facade.demo.DemoFacade, remote.application=dubbo-04-api-boot-consumer, interface=com.hmilyylimh.cloud.facade.demo.DemoFacade, version=0.0.0, timeout=5}]], channel: /192.168.100.183:62231 -> /192.168.100.183:28043at org.apache.dubbo.remoting.exchange.support.DefaultFuture.doReceived(DefaultFuture.java:212)at org.apache.dubbo.remoting.exchange.support.DefaultFuture.received(DefaultFuture.java:176)at org.apache.dubbo.remoting.exchange.support.DefaultFuture$TimeoutCheckTask.notifyTimeout(DefaultFuture.java:295)at org.apache.dubbo.remoting.exchange.support.DefaultFuture$TimeoutCheckTask.lambda$run$0(DefaultFuture.java:282)at org.apache.dubbo.common.threadpool.ThreadlessExecutor$RunnableWrapper.run(ThreadlessExecutor.java:184)at org.apache.dubbo.common.threadpool.ThreadlessExecutor.waitAndDrain(ThreadlessExecutor.java:103)at org.apache.dubbo.rpc.AsyncRpcResult.get(AsyncRpcResult.java:193)... 29 more

(2)无提供者异常和原因

org.apache.dubbo.rpc.RpcException: Failed to invoke the method sayHello in the service com.hmilyylimh.cloud.facade.demo.DemoFacade. No provider available for the service com.hmilyylimh.cloud.facade.demo.DemoFacade from registry 127.0.0.1:2181 on the consumer 192.168.100.183 using the dubbo version 3.0.7. Please check if the providers have been started and registered.at org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.checkInvokers(AbstractClusterInvoker.java:366)at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:73)at org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:340)at org.apache.dubbo.rpc.cluster.router.RouterSnapshotFilter.invoke(RouterSnapshotFilter.java:46)at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:321)at org.apache.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:99)... 48 more

在这里插入图片描述

💡建议暂停思考10s,你有答案了嘛?如果你有不同题解,欢迎评论区留言、打卡。

答案

1.从第一条关键异常信息中看到有一个 Caused by 引发的 TimeoutException 异常类,认为发生了超时异常,但单从消费方无法定位真实原因。于是开始求证目标IP的健康状况

Caused by: org.apache.dubbo.remoting.TimeoutException: Waiting server-side response...

2.小航先在 CAT 上发现了目标 IP 在出问题期间几乎没有任何流量进来在 Prometheus 上发现在最近一段时间内 TCP 的连接耗时特别大,基本上都是有 SYN 请求握手包,但是没有 SYN ACK 响应包。然后又找网络人员帮忙实时 tcpdump 抓包测试,结果仍然发现没有 SYN ACK 响应包。好了,可以确认目标 IP 处于不可连通的状态,接着小航去开始确认目标 IP服务是宕机了还是流量被拦截。
3.继续查看日志:
org.apache.dubbo.rpc.RpcException: Failed to invoke the method sayHello…No provider available for the service … from registry 127.0.0.1:2181…
从第二条关键异常信息中,No provider available,显然找不到服务提供者,继续定位堆栈信息:

at org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.checkInvokers(AbstractClusterInvoker.java:366)
at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:73)

对应代码:

protected void checkInvokers(List<Invoker<T>> invokers, Invocation invocation) {// 检查传入的 invokers 服务提供者列表,若集合为空,则会抛出无提供者异常if (CollectionUtils.isEmpty(invokers)) {// 抛出的 RpcException 异常信息中,会有 No provider available 明显的关键字throw new RpcException(RpcException.NO_INVOKER_AVAILABLE_AFTER_FILTER, "Failed to invoke the method "+ invocation.getMethodName() + " in the service " + getInterface().getName()+ ". No provider available for the service " + getDirectory().getConsumerUrl().getServiceKey()+ " from registry " + getDirectory().getUrl().getAddress()+ " on the consumer " + NetUtils.getLocalHost()+ " using the dubbo version " + Version.getVersion()+ ". Please check if the providers have been started and registered.");}
}

定位代码后推断:消费方在内存中找不到对应的提供者,才会提示无提供者异常

继续分析checkInvokers[invokers 列表] => FailoverClusterInvoker => FailoverCluster => cluster = “failover” => 消费方订阅 => 消费方启动或注册中心节点变更会更新invokers列表源数据 => 源数据为什么更新没了 => 消费方已经处于运行状态,只是在调用的时候发生了无提供者异常 => 注册中心的节点发生了变更。

貌似找到了问题所在,小航重新正向复盘:节点宕机,造成提供方节点与注册中心断开心跳连接 => 注册中心会删掉提供方的IP节点 => 消费方感知到注册中心的节点发生变更 => 更新消费方本地的源数据信息 => 消费方在 checkInvokers 中发现 invokers 为空。

4.Ip一些正常一些不正常,首先排除人为恶搞因素,小航思索良久,和公司网工小李探讨了一番,得知它们全都是同一个机房的,最终问题定位到:机房A的某些提供者宕机了,且机房A的消费者状态正常,但把机房A的消费者拉下水导致各种功能无法正常运转

5.核心问题定位后,小航同志开始制作解决方案:机房之间防火墙彻底隔离,同机房已经调用不通,防止消费方硬着头皮一直从宕机的服务拿到响应内容。现在的应用大多数都是数十秒才启动成功,显然同机房请求死路一条,那么消费者的请求发给谁才能通呢?显然:中间商。

在这里插入图片描述

于是小航同志开始设计技术方案:消费方遇到无提供者异常后 => 调用转发服务 => 转发服务找可用机房的可用提供者并发起调用并拿到结果。貌似可行。不过为了避免这段代码,需要给每个消费者写,小航提炼了一个公共插件,既做到了代码公用只维护一份代码,又做到了对应用的非侵入特性,一举两得。

6.结果新的问题又来了,该在调用的哪个环节进行转发服务的处理呢?小航同志琢磨了一下,哪里出错,就从哪里开始,要调用转发服务,就需要先捕获到无提供者异常,翻阅源码:FailoverClusterInvoker#doInvoke => checkInvokers被 protected 修饰 => 可以被子类重写 => dubbo提供了一个检测 invokers 是否可用的机制。小航同志于是一顿操作:定义TransferClusterInvoker 类 => 继承 FailoverClusterInvoker => 重写 checkInvokers

7.CR很快就驳回来了,checkInvokers 只是意在让你检测,与设计意图不符。

8.小航又重新更换思路:FailoverClusterInvoker => doInvoke被 public 修饰 => 在子类 TransferClusterInvoker 中调用父类 FailoverClusterInvoker 的 doInvoke 方法 => 进行 try…catch… 捕获精准异常 => 既不会破坏设计者的意图,还能精准处理无提供者异常后转发调用。

public class TransferClusterInvoker<T> extends FailoverClusterInvoker<T> {// 按照父类 FailoverClusterInvoker 要求创建的构造方法public TransferClusterInvoker(Directory<T> directory) {super(directory);}// 重写父类 doInvoke 发起远程调用的接口@Overridepublic Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {try {// 先完全按照父类的业务逻辑调用处理,无异常则直接将结果返回return super.doInvoke(invocation, invokers, loadbalance);} catch (RpcException e) {// 这里就进入了 RpcException 处理逻辑// 当调用发现无提供者异常描述信息时则向转发服务发起调用if (e.getMessage().toLowerCase().contains("no provider available")){// TODO 从 invocation 中拿到所有的参数,然后再处理调用转发服务的逻辑return doTransferInvoke(invocation);}// 如果不是无提供者异常,则不做任何处理,异常该怎么抛就怎么抛throw e;}}
}

9.核心逻辑已实现,接下来就是代码中触发TransferClusterInvoker,参考 FailoverCluster的编码 => 定义TransferCluster类 => 实现Cluster接口 => 创建 TransferClusterInvoker处理调用至转发服务器 => Dubbo SPI配置。

public class TransferCluster implements Cluster {// 返回自定义的 Invoker 调用器@Overridepublic <T> Invoker<T> join(Directory<T> directory, boolean buildFilterChain) throws RpcException {return new TransferClusterInvoker<T>(directory);}
}

10.下班


🔨总结梳理:Cluster作为路由层,封装多个提供方的路由及负载均衡,并桥接注册中心以 Invoker 为中心发起调用,哪些应用场景可以考虑集群扩展呢?

(1)同机房请求无法连通时,可以考虑转发HTTP请求至可用提供者。

(2)内网本机访问测试环境无法连通时,可以转发请求至HTTP协议的接口,然后在接口中泛化调用各种Dubbo服务。

在这里插入图片描述

(3)如果针对接口的多个提供者需要做适应当前公司业务的筛选、剔除、负载均衡之类的诉求时,也是可以考虑集群扩展的。

总之,不管是无提供者问题,还是公司特殊定制化筛选负载问题,核心都是在针对接口的所有提供者做逻辑处理,提供者为空做转发兼容处理,提供者不为空做特殊筛选负载处理,目的都是在调用时做一种容错兼容处理,让应用程序更加健壮稳定。

📌 [ 笔者 ]   文艺倾年
📃 [ 更新 ]   2025.4.27
❌ [ 勘误 ]   /* 暂无 */
📜 [ 声明 ]   由于作者水平有限,本文有错误和不准确之处在所难免,本人也很想知道这些错误,恳望读者批评指正!

在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词