Dubbo用了这么久,居然有这么多坑?

如果你用过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的顺序,对于同名字段,子类的该字段值会被赋值两次,总是被父类的值覆盖,导致子类的字段值丢失。

解决方案:

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

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

自定义异常被包装成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会直接抛出异常:

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

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

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

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

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

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

returnnewRpcResult(newRuntimeException(StringUtils.toString(exception)));通过上面对ExceptionFilter的源码分析可知,如果要让Provider抛出自定义异常,有如下几个解决办法:

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

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

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

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

IP暴露问题

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

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

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

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

Docker环境

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

DUBBOIPTO_REGISTRY --- Registering to the IP address of the registration center

DUBBOPORTTO_REGISTRY --- Registering to the port of the registration center

DUBBOIPTO_BIND --- Listening IP addresses

DUBBOPORTTO_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 DUBBOIPTOREGISTRY=30.5.97.6 -e DUBBOPORTTOREGISTRY=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注册到注册中心,从而导致消费端连接不上。这种情况的笔者提供两种解决办法:

配置dubbo.protocol.host=192.168.0.1

配置/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。

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

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

减少请求/响应报文长度。例如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。那么什么时候会出现这种异常呢:

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

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

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日志。这两个问题一般有如下几种原因:

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

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

暴露的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]

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容