26

阿里面试题:使用dubbo过程中遇到过哪些坑?

 4 years ago
source link: https://www.tuicool.com/articles/j6faayU
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Dubbo[   |ˈdʌbəʊ| ,发音为`打波 `] 稳如狗,哪有坑?

77FzQji.jpg!web

如果你用过Dubbo,但是没碰到过什么坑,那只能说明你还没有深交Dubbo,看看笔者那些年使用Dubbo踩过的坑!

父子类有相同属性时值丢失

假设Provider提供的服务中某个服务的参数是WordDTO,并且WordDTO继承自BaseDTO,两个类的定义如下:

@Data
public class BaseDTO implements Serializable {
    private Long id;
}

@Data
public class WordDTO extends BaseDTO {
    private Long id;
    private String uuid;
    private Long timestamp;
    private String word;
}

问题描述:在Consumer侧给WordDTO赋的值,其id属性的值无法在Provider侧获取到。假设Consumer传的值是:{"id":68,"timestamp":1570928394380,"uuid":"f774f99f-987c-4506-8ab8-366cd619bb15","word":"hello world"},在Provider拿到的却是:{"timestamp":1570928394380,"uuid":"f774f99f-987c-4506-8ab8-366cd619bb15","word":"hello world"}。

原因分析:dubbo默认采用的是hessian序列化&反序列化方式,JavaDeserializer在获取fileds时,采用了Map去重。但是在读取值时,根据serializer的顺序,对于同名字段,子类的该字段值会被赋值两次,总是被父类的值覆盖,导致子类的字段值丢失。

解决方案:

  1. 更改序列化方式(不建议);

  2. 删掉子类中与父类同名属性( 建议 );

自定义异常被包装成RuntimeException

首先需要说明的是,出现这个问题有一定的条件。如果Provider中的api和自定义Exception定义都是在一个api.jar中,那么是不会有任何问题的。但是 如果自定义Exception是在一个单独的比如common.jar包中就会出现这个问题 (此时api和model在另一个api.jar中)。

下面是一段调用一个会抛出自定义异常的服务的代码:

try {
    String hello = demoService.saySomething(wordDTO);
    System.out.println(hello);
}catch (WrongArgumentException e){
    System.err.println("wrong argument 1: " + e.getMessage());
}catch (RuntimeException e){
    System.err.println("wrong argument 2: " + e.getMessage());
}

但是,调用的日志却是如下所示,通过日志我们可以发现,在Consumer中并没有捕获到自定义的WrongArgumentException异常,只能捕获到RuntimeException中的异常,且这个异常信息是封装自定义的WrongArgumentException异常:

wrong argument 2: com.afei.dev.maven.exception.WrongArgumentException: word不允许为空
com.afei.dev.maven.exception.WrongArgumentException: word不允许为空
    at com.afei.test.dubbo.provider.facade.impl.DemoServiceImpl.saySomething(DemoServiceImpl.java:11)
    at com.alibaba.dubbo.common.bytecode.Wrapper1.invokeMethod(Wrapper1.java)

这是什么原因呢?这是因为dubbo Provider的 ExceptionFilter.java 对异常统一封装导致的,其封装的核心源码我就不贴出来了,你如果有兴趣可以自己下载查看。我这里只贴出它的处理逻辑,当碰到如下这些情况时,dubbo会直接抛出异常:

  1. 如果是checked异常(不是RuntimeException但是是Exception.java类型的异常),直接抛出;

  2. 在方法签名上有声明(例如String saySomething()throws MyException ),直接抛出;

  3. 异常类和接口类在同一jar包里,直接抛出;

  4. 是JDK自带的异常(全类名以java或者javax开头,例如java.lang.IllegalStateException),直接抛出;

  5. 是Dubbo本身的异常RpcException,直接抛出;

否则,Dubbo通过如下代码将异常包装成RuntimeException抛给客户端:

return new RpcResult(new RuntimeException(StringUtils.toString(exception)));

通过上面对ExceptionFilter的源码分析可知,如果要让Provider抛出自定义异常,有如下几个解决办法:

  1. 将自定义异常和接口类放到一个包中即可( 推荐 );

  2. 方法签名上申明自定义异常;

那么Dubbo为什么这样设计?我相信没有谁比Dubbo的作者梁飞更有发言权了!这里就引用Dubbo作者梁飞在Github上的原话(原话出处:https://github.com/apache/dubbo/issues/111):

这个是为了防止服务提供方抛出了消费方没有的异常,比如数据库异常类,导致消费方反序列化失败,使异常信息更奇怪,建议在业务接口上RuntimeException也声明在throws中。

IP暴露问题

在某些复杂环境下,例如Docker、双网卡、虚拟机等环境下,Dubbo默认绑定的IP可能并不是我们期望的正确IP,Dubbo绑定IP默认行为如下(核心源码在NetUtils.java中):

  1. 通过InetAddress.getLocalHost()获取本机地址,如果本机地址有效则返回(有效地址需要满足这几点:1. 不能为空,2. 不是loopback地址(类似127.x.x.x),3. 不能是0.0.0.0,也不能是127.0.0.1);

  2. 如果本机地址无效,那么再遍历网卡地址,然后通过isValidAddress校验ip是否正常并返回第一个有效的IP地址。这样的话,Dubbo就不能保证返回的是内网IP还是外网IP。

事实上,复杂环境下这个IP绑定问题不太好自动化解决,不过我们可以利用dubbo的扩展能力解决这些问题。

  • Docker环境

如果你的dubbo部署在Docker上,那么需要注意了。我们需要解决Dubbo几个特定参数来解决这个问题:

  1. DUBBO_IP_TO_REGISTRY --- Registering to the IP address of the registration center

  2. DUBBO_PORT_TO_REGISTRY --- Registering to the port of the registration center

  3. DUBBO_IP_TO_BIND --- Listening IP addresses

  4. DUBBO_PORT_TO_BIND --- Listening ports

假设主机IP地址为30.5.97.6,docker启动dubbo服务参考命令,启动后,这个Provider服务注册的地址就是30.5.97.6:20881,我们可以通过命令(telnet 30.5.97.6 20881,invoke org.apache.dubbo.test.docker.DemoService.hello("world"))检查并调用Provider提供的服务:

docker run -e DUBBO_IP_TO_REGISTRY=30.5.97.6 -e DUBBO_PORT_TO_REGISTRY=20881 -p 30.5.97.6:20881:20880 --link zkserver:zkserver -it --rm dubbo-docker-sample

参考地址:https://github.com/apache/dubbo-samples/tree/master/dubbo-samples-docker。

  • 暴露外网IP

如果你服务的调用方和消费方不在同一个内网中,那么就会希望Dubbo服务通过外网IP暴露。不过不好意思,dubbo默认的服务暴露行为搞不定,因为dubbo默认暴露的是内网IP地址。

这个时候,我们就需要借助两个参数:dubbo.protocol.host和dubbo.protocol.port,通过这两个参数显示申明我们暴露服务的IP和Port,这两个参数即可以通过配置文件方式指定,也可以通过JVM参数方式指定,具体怎么使用,UP TO YOU!!!

  • 双网卡问题

当服务器上有多个网卡时,Dubbo服务提供者启动时,会将错误的IP注册到注册中心,从而导致消费端连接不上。这种情况的笔者提供两种解决办法:

  1. 配置dubbo.protocol.host=192.168.0.1

  2. 配置/etc/hosts,例如afeiserver01 = 192.168.0.1,其中afeiserver01是机器名;

Data length too large

这个异常的详细堆栈信息如下所示:

org.apache.dubbo.remoting.transport.ExceedPayloadLimitException:
Data length too large: 10356612, max payload: 8388608,
channel: NettyChannel [channel=[id: 0xd36132c0, L:/192.168.1.6:55078 - R:/192.168.1.6:20880]]

日志中提到max payload为8388608,等价于 8 * 1024 * 1024 ,即8k。所以这个问题的原因非常清晰了,就是请求或者响应的报文体长度超过了8k。

这个问题比较简单,笔者在这里提供两个解决方案:

  1. 修改payload的值,将其调大,例如16777216,即16k, 不推荐

  2. 减少请求/响应报文长度。例如Provider提供的服务,最大批量限制为1000,比如最多只能批量查询1000个用户ID的用户信息, 推荐

说明:

dubbo在小报文的场景下表现最佳,所以,除非确实无法饶过。否则强烈不建议调大payload的值;

线程耗尽

dubbo服务Provider侧如果线程耗尽,会跑出类似如下的异常信息:

19-10-17 00:00:00.033 [New I/O server worker #1-6] WARN  com.alibaba.dubbo.common.threadpool.support.AbortPolicyWithReport -  [DUBBO] Thread pool is EXHAUSTED! Thread Name: DubboServerHandler-10.0.0.77:20703, Pool Size: 200 (active: 200, core: 200, max: 200, largest: 200), Task: 5897697 (completed: 5897197), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false), in dubbo://10.0.0.77:20703!, dubbo version: 2.5.3, current host: 127.0.0.1

对dubbo有基本了解的都知道,Provider默认是fixed线程池,且线程数为200。那么什么时候会出现这种异常呢:

  1. Provider侧接口处理太慢。如果是这种原因的话,我们可以通过jstack命令,在线程栈中看到超过200个状态为RUNNING、且命名为"DubboServerHandler-"的线程,通过线程栈我们大概知道是哪部分代码引起的,然后优化问题代码,提升处理能力从而解决这个问题;

  2. Provider处理能力确实不够。这个原因是指,Consumer可能会达到10000TPS,但是Provider单机处理能力可能只有1000TPS,如果没有10台以上的Provider服务实例,那么就确实需要扩容了。

  3. Provier由于某些原因阻塞。这个原因一般是Provider侧依赖的某些服务或者中间件出问题导致的;

根据下面这段日志可知,Dubbo线程都阻塞在发送ActiveMQ消息的地方,我们可以通过异步发送MQ消息,或者检查是不是ActiveMQ服务吞吐量不行并优化它的吞吐量等手段来解决:

"DubboServerHandler-127.0.0.1:20880-thread-128" daemon prio=10 tid=0x00007fd574193811 nid=0x16cf1 waiting for monitor entry [0x00007fd691887000..0x00007fd691888810]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at org.apache.activemq.transport.MutexTransport.oneway(MutexTransport.java:40)
    - waiting to lock <0x00007fd6c9fa4ba8> (a java.lang.Object)
    at org.apache.activemq.transport.ResponseCorrelator.oneway(ResponseCorrelator.java:60)
    at org.apache.activemq.ActiveMQConnection.doAsyncSendPacket(ActiveMQConnection.java:1265)
    at org.apache.activemq.ActiveMQConnection.asyncSendPacket(ActiveMQConnection.java:1259)

服务调用失败

异常堆栈信息如下所示:

Forbid consumer 0 access service com.afei.dubbo.demo.api.QueryService from registry 127.0.0.1:2181 use dubbo version 2.5.3,
Please check registry access list (whitelist/blacklist)

或者异常堆栈信息如下所示:

org.apache.dubbo.rpc.RpcException:
Failed to invoke the method saySomething in the service com.afei.test.dubbo.provider.facade.DemoService.
No provider available for the service com.afei.test.dubbo.provider.facade.DemoService:2.0.0
from registry 224.5.6.7:1234

我相信,每一个使用过dubbo服务的同学,肯定会碰到上面这两个ERROR日志。这两个问题一般有如下几种原因:

  1. Provider服务全部下线,即没有一个存活的Provider服务进程。

  2. 存在一个或多个Provider服务,但是 version或者group不匹配 。例如Consumer侧申明version=1.0.0,而Provider侧申明version=2.0.0,或者group不匹配,都会出现这个ERROR。

  3. 暴露的IP有问题。例如暴露的是内网IP,但是调用却是通过外网IP;

dubbo spring schema

在dubbo进入apache之前,dubbo的spring schema申明如下:

http://code.alibabatech.com/schema/dubbo

当dubbo进入apache后,dubbo的spring schema能兼容两种方式:

http://dubbo.apache.org/schema/dubbo
http://code.alibabatech.com/schema/dubbo

这是由dubbo.jar中META-INF/spring.schemas文件决定的:

http\://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
http\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/compat/dubbo.xsd

dubbo新版本是即能兼容http://dubbo.apache.org/schema/dubbo,也能兼容http://code.alibabatech.com/schema/dubbo。但是dubbo老版本只能兼容http://code.alibabatech.com/schema/dubbo。如果老版本也配置http://dubbo.apache.org/schema/dubbo,就会抛出如下日常:

org.springframework.beans.factory.parsing.BeanDefinitionParsingException:
Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://dubbo.apache.org/schema/dubbo]

【相关阅读】

↓↓↓

惊:Dubbo居然有必现StackOverflowError的Bug

高并发dubbo服务,每次重启后都大量超时,我懵圈了

I7Fruqb.jpg!web

【阿飞的博客】 公众号二维码

↓↓↓↓

UZ7B7n7.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK