1.Dubbo调用超时那些事儿
2.Java教程:dubbo源码解析-网络通信
3.干货 | Dubbo 接口测试技术,调调用测试开发进阶必备(附源码)
4.Dubbo源码解析:网络通信
5.我找到了Dubbo源码的用源源码BUG,同事纷纷说我有点东西
Dubbo调用超时那些事儿
其实之前很早就看过Dubbo源码中关于超时这部分的解析处理逻辑,但是调调用没有记录下来,最近在某脉上看到有人问了这个问题,用源源码想着再回顾一下。解析企业官网介绍源码开始从dubbo的调调用请求开始,看看dubbo(2.6.6)在超时这块是用源源码怎么处理的:
com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeChannel#request(java.lang.Object, int)@Overridepublic ResponseFuture request(Object request, int timeout) throws RemotingException { if (closed) { throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");}// create request.Request req = new Request();req.setVersion(Version.getProtocolVersion());req.setTwoWay(true);req.setData(request);DefaultFuture future = new DefaultFuture(channel, req, timeout);try { channel.send(req);} catch (RemotingException e) { future.cancel();throw e;}return future;}DefaultFuture从返回值ResponseFuture类型可以看出,这是解析一个异步方法(不等同于Dubbo的异步调用)。那么调用超时的调调用关键可以从ResponseFuture来看:
public interface ResponseFuture { Object get() throws RemotingException;Object get(int timeoutInMillis) throws RemotingException;void setCallback(ResponseCallback callback);boolean isDone();}可以看到这是一个接口,从request方法可以得知实现类是用源源码DefaultFuture,从构造函数入手:
public DefaultFuture(Channel channel,解析 Request request, int timeout) { this.channel = channel;this.request = request;this.id = request.getId();this.timeout = timeout > 0 ? timeout : channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);// put into waiting map.FUTURES.put(id, this);CHANNELS.put(id, channel);}可以得知每一个DefaultFuture都有一个id,并且等于requestId,调调用timeout是用源源码从url中获取的配置,没有时默认ms。解析
从代码的注释可以看到FUTURES这个map应该就是关键,是一个waiting map。
DefaultFuture中还有一个方法:
public static void received(Channel channel, Response response) { try { DefaultFuture future = FUTURES.remove(response.getId());if (future != null) { future.doReceived(response);} else { logger.warn("The timeout response finally returned at "+ (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()))+ ", response " + response+ (channel == null ? "" : ", channel: " + channel.getLocalAddress()+ " -> " + channel.getRemoteAddress()));}} finally { CHANNELS.remove(response.getId());}}可以看到调用的地方为:
com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler#received
@Overridepublic void received(Channel channel, Object message) throws RemotingException { //省略一些代码} else if (message instanceof Response) { handleResponse(channel, (Response) message);//省略一些代码}}com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler#handleResponse
static void handleResponse(Channel channel, Response response) throws RemotingException { if (response != null && !response.isHeartbeat()) { DefaultFuture.received(channel, response);}}回到DefaultFuture.received,可以看到通过Response id从FUTURES中拿了一个DefaultFuture出来,然后调用了doReceived方法,也就是说Response id和Request id 相同。结下来看看doReceived做了什么:
private void doReceived(Response res) { lock.lock();try { response = res;if (done != null) { done.signal();}} finally { lock.unlock();}if (callback != null) { invokeCallback(callback);}}首先是加锁,然后通过唤醒了阻塞在Condition上的线程。看看什么地方会阻塞在done这个条件上:
@Overridepublic Object get(int timeout) throws RemotingException { if (timeout <= 0) { timeout = Constants.DEFAULT_TIMEOUT;}if (!isDone()) { long start = System.currentTimeMillis();lock.lock();try { while (!isDone()) { done.await(timeout, TimeUnit.MILLISECONDS);if (isDone() || System.currentTimeMillis() - start > timeout) { break;}}} catch (InterruptedException e) { throw new RuntimeException(e);} finally { lock.unlock();}if (!isDone()) { throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));}}return returnFromResponse();}是get方法,get方法确实在request请求后被调用:
(Result) currentClient.request(inv, timeout).get()可以看到get方法的大致逻辑为,先获取锁,然后循环判断isDone,并阻塞等到条件,当条件超时,微课堂源码带课程如果任务完成,或者超过timeout结束循环,接着判断isDone,如果超时抛出TimeoutException。并且通过sent(request请求时间)是否>0()来判断是clientSide还是serverSide超时。
isDone逻辑如下:
@Overridepublic boolean isDone() { return response != null;}如果是正常Response,也有可能是超时的现象,可以看到get方法最后调用了一个函数:
public interface ResponseFuture { Object get() throws RemotingException;Object get(int timeoutInMillis) throws RemotingException;void setCallback(ResponseCallback callback);boolean isDone();}0TIMEOUT SIDESERVER_TIMEOUT(服务端超时): 这个就是正常的我们消费端请求一个RPC接口,服务端由于性能等一些原因处理时间超过了timeout配置时间。
CLIENT_TIMEOUT:我们可以看到是通过sent(上面有说sent>0)这个来判断是否clientTimeout,那么这个sent什么时候改变呢?就在发送请求的地方:
public interface ResponseFuture { Object get() throws RemotingException;Object get(int timeoutInMillis) throws RemotingException;void setCallback(ResponseCallback callback);boolean isDone();}1也就是说handler.sent一旦调用成功返回,那么就不算clientSide Timeout了。那么CLIENT_TIMEOUT大概率就是由于client端网络,系统等原因超时。
原文:/post/Java教程:dubbo源码解析-网络通信
在之前的内容中,我们探讨了消费者端服务发现与提供者端服务暴露的相关内容,同时了解到消费者端通过内置的负载均衡算法获取合适的调用invoker进行远程调用。接下来,我们聚焦于远程调用过程,即网络通信的细节。
网络通信位于Remoting模块中,支持多种通信协议,包括但不限于:dubbo协议、rmi协议、hessian协议、ty进行网络通讯,NettyClient.doOpen()方法中可以看到Netty的相关类。序列化接口包括但不限于:Serialization接口、竞价模板源码怎么设置Hessian2Serialization接口、Kryo接口、FST接口等。
序列化方式如Kryo和FST,性能往往优于hessian2,能够显著提高序列化性能。这些高效Java序列化方式的引入,可以优化Dubbo的序列化过程。
在配置Dubbo RPC时,引入Kryo和FST非常简单,只需在RPC的XML配置中添加相应的属性即可。
关于服务消费方发送请求,Dubbo框架定义了私有的RPC协议,消息头和消息体分别用于存储元信息和具体调用消息。消息头包括魔数、数据包类型、消息体长度等。消息体包含调用消息,如方法名称、参数列表等。请求编码和解码过程涉及编解码器的使用,编码过程包括消息头的写入、序列化数据的存储以及长度的写入。解码过程则涉及消息头的读取、序列化数据的解析以及调用方法名、参数等信息的提取。
提供方接收请求后,服务调用过程包含请求解码、织梦网站源码风水调用服务以及返回结果。解码过程在NettyHandler中完成,通过ChannelEventRunnable和DecodeHandler进一步处理请求。服务调用完成后,通过Invoker的invoke方法调用服务逻辑。响应数据的编码与请求数据编码过程类似,涉及数据包的构造与发送。
服务消费方接收调用结果后,首先进行响应数据解码,获得Response对象,并传递给下一个处理器NettyHandler。处理后,响应数据被派发到线程池中,此过程与服务提供方接收请求的过程类似。
在异步通信场景中,Dubbo在通信层面为异步操作,通信线程不会等待结果返回。默认情况下,RPC调用被视为同步操作。Dubbo通过CompletableFuture实现了异步转同步操作,通过设置异步返回结果并使用CompletableFuture的get()方法等待完成。
对于异步多线程数据一致性问题,Dubbo使用编号将响应对象与Future对象关联,确保每个响应对象被正确传递到相应的Future对象。通过在创建Future时传入Request对象,可以获取调用编号并建立映射关系。线程池中的线程根据Response对象中的调用编号找到对应的Future对象,将响应结果设置到Future对象中,热血江湖pc源码大全供用户线程获取。
为了检测Client端与Server端的连通性,Dubbo采用双向心跳机制。HeaderExchangeClient初始化时,开启两个定时任务:发送心跳请求和处理重连与断连。心跳检测定时任务HeartbeatTimerTask确保连接空闲时向对端发送心跳包,而ReconnectTimerTask则负责检测连接状态,当判定为超时后,客户端选择重连,服务端采取断开连接的措施。
干货 | Dubbo 接口测试技术,测试开发进阶必备(附源码)
Dubbo接口测试是霍格沃兹测试学院的特色课程,全网深度领先。Dubbo是一个由阿里巴巴开源的RPC解决方案,因其理念与微服务高度契合,近年来受到广泛关注,用户包括京东、当当、去哪儿等大公司。 Dubbo支持RPC场景,其架构设计简洁明了。官方提供了一个易于使用的Demo来展示Dubbo协议的使用,操作简单且功能强大。 下面介绍几种常用的Dubbo接口测试方法: 1. 基于telnet的简单调试接口:Dubbo服务支持简单的telnet交互,可用于快速验证接口的可用性。 2. 传统的基于XML配置的测试方法:通过创建XML配置文件,并将其放置在resources目录下,可以进行测试。 3. 基于API的测试方法:除了XML配置,官方还提供了一种直接通过API进行配置的方式,这种测试方法更加灵活。 4. 泛化调用:在没有API接口或模型类元的情况下,泛化接口调用方式非常有用。这通常用于集成框架,如创建通用服务测试框架,可通过GenericService调用所有服务实现。尽管泛化方法可能需要依赖研发提供的Dubbo接口的jar包,但它也存在一些缺点,如仍然需要jar包或文档来分析接口调用参数信息。 5. 使用泛化方法时,可能只需借助如asm之类的字节码分析框架,即可自动生成接口测试用例模板,减少依赖。 注意以下技术关注点:一线名企大厂内推通道
史上最全软件测试资料文档下载
如何从一个只会点鼠标的手工测试变成测试开发
请关注霍格沃兹测试学院官方机构号:测吧 @测吧 推荐阅读:测试开发是什么?为什么现在那么多公司都要招聘测试开发?
软件测试的岗位会越来越少吗?
软件测试真的干到就干不动了吗?
Dubbo源码解析:网络通信
在之前的章节中,我们探讨了消费者如何通过内置的负载均衡找到服务提供者以及服务暴露的原理。本节重点关注的是消费者如何通过网络与提供者进行远程调用的详细过程,涉及Dubbo框架的网络通信机制。
网络通信主要在Dubbo的Remoting模块中实现,Dubbo支持多种协议,包括自定义的Dubbo协议、RMI、Hessian、HTTP、WebService、Thrift、REST、gRPC、Memcached和Redis等,每种协议有其特点。例如,Dubbo协议利用NIO异步通信,适合处理大量并发小数据量的场景,而RMI采用阻塞式短连接,适合Java RMI应用。
序列化在通信中起着至关重要的作用,Dubbo支持多种序列化方式,如Hessian2、Java、Fastjson等,其中Hessian2是默认选择。近年来,高效序列化技术如Kryo和FST不断涌现,它们的性能优于Hessian2,可通过配置引入以优化性能。
数据在网络传输中需要解决粘包拆包问题,Dubbo通过定义私有RPC协议,消息头包含魔数、类型和长度等信息,以确保数据的正确接收。在消费者发送请求时,首先会生成一个封装了方法和参数的Request对象,经过编码后通过Netty发送。提供方则通过Netty接收请求,解码后执行服务逻辑并返回Response对象。
双向通信中,服务提供方和消费方都通过心跳机制来检查连接状态,客户端和服务端都设有定时任务,确保数据的及时交互。在异步调用中,Dubbo通过CompletableFuture实现从异步到同步的转换,并处理并发调用时的数据一致性问题。
我找到了Dubbo源码的BUG,同事纷纷说我有点东西
某天,运营反馈称,执行一次保存操作后,后台出现3条数据,我立刻怀疑可能存在代码问题。为了确保不会误判,我要求暂停操作,保留现场,以便我进行排查。
查看新增代码,发现是同事三歪进行的改动,他将原有的dubbo XML配置方式改为了注解方式。我询问其改动详情,得知他是更改了模块的配置方式。于是,我决定深入研究,找出问题所在。
dubbo配置方式多样,最常见的为XML配置与注解配置。我已初步推测原因,接下来将进行详细的调试过程。
我使用dubbo版本2.6.2进行调试。首先,针对采用@Reference注解条件下的重试次数配置,我发现调用接口时,会跳转到InvokerInvocationHandler的invoke方法。继续跟踪,最终定位到FailoverClusterInvoker的doInvoke方法。在该方法中,我关注到获取配置的retries值,发现其默认值为null,导致最终计算出的重试次数为3。
采用dubbo:reference标签配置重试次数时,同样在获取属性值后,发现其默认值为0,与注解配置一致,最终计算出的重试次数为1。对比两种配置方式,我总结了以下原因:
在@Reference注解形式下,dubbo会在注入代理对象时,通过自定义驱动器ReferenceAnnotationBeanPostProcessor来注入属性。在标签形式下,虽然也使用了Autowired注解,但dubbo会使用自定义名称空间解析器DubboNamespaceHandler进行解析。
在注解形式下,当配置retries为0时,属性值在注入过程中并未被解析为null,但进入buildReferenceBean时,因nullSafeEquals方法的处理,导致默认值和实际值不一致,最终未保存到map中。而标签形式下,解析器能够正确解析出retries的值为0,避免了后续的问题。
总结发现,采用@Reference注解配置重试次数时,dubbo在注入属性过程中存在逻辑处理上的问题,导致默认值与实际值不一致。此为dubbo的一个逻辑bug。建议在不需要重试时,设置retries为-1,以确保接口的幂等性。需要重试时,设置为1或更大值。
问题解决后,我优化了文件操作,将其改为异步处理,从而缩短了主流程的时间。最终,数据出现3条的状况得以解决。
此问题已得到解决,并在后续dubbo版本2.7.3中修复,确保了在注解配置方式下,nullSafeEquals方法能够正确处理默认值与实际值一致的情况。