一、Dubbo简介
Dubbo是 阿里开源的分布式远程服务调用框架。
1、Dubbo协议
Dubbo支持dubbo、rmi、hessian、http、webservice、thrift、redis等多种协议,Dubbo官网是推荐
我们使用Dubbo协议的,默认也是用的dubbo协议。
介绍几种常见的协议:
1)dubbo协议
缺省协议,使用基于mina1.1.7+hessian3.2.1的tbremoting交互。
连接个数:单连接
连接方式:长连接
传输协议:TCP
传输方式:NIO异步传输
序列化:Hessian二进制序列化
适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供
者,尽量不要用dubbo协议传输大文件或超大字符串
适用场景:常规远程服务方法调用
1、dubbo默认采用dubbo协议,dubbo协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务
调用,以及服务消费者机器数远大于服务提供者机器数的情况
2、他不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。
2)rmi协议
Java标准的远程调用协议。
连接个数:多连接
连接方式:短连接
传输协议:TCP
传输方式:同步传输
序列化:Java标准二进制序列化
适用范围:传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件。
适用场景:常规远程服务方法调用,与原生RMI服务互操作
RMI协议采用JDK标准的java.rmi.*实现,采用阻塞式短连接和JDK标准序列化方式。
3)hessian协议
基于Hessian的远程调用协议。
连接个数:多连接
连接方式:短连接
传输协议:HTTP
传输方式:同步传输
序列化:表单序列化
适用范围:传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或URL传入
参数,暂不支持传文件。
适用场景:需同时给应用程序和浏览器JS使用的服务。
1、Hessian协议用于集成Hessian的服务,Hessian底层采用Http通讯,采用Servlet暴露服务,Dubbo
缺省内嵌Jetty作为服务器实现。
2、Hessian是Caucho开源的一个RPC框架:http://hessian.caucho.com,其通讯效率高于
WebService和Java自带的序列化。
4)http 协议
基于http表单的远程调用协议。参见:[HTTP协议使用说明]
连接个数:多连接
连接方式:短连接
传输协议:HTTP
传输方式:同步传输
序列化:表单序列化
适用范围:传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或URL
传入参数,暂不支持传文件。
适用场景:需同时给应用程序和浏览器JS使用的服务。
5)webservice协议
基于WebService的远程调用协议。
连接个数:多连接
连接方式:短连接
传输协议:HTTP
传输方式:同步传输
序列化:SOAP文本序列化
适用场景:系统集成,跨语言调用
二、序列化
序列化是将一个对象变成一个二进制流就是序列化,反序列化是将二进制流转换成对象。
为什么要序列化?
1. 减小内存空间和网络传输的带宽
2. 分布式的可扩展性
3. 通用性,接口可共用。
Dubbo序列化支持java、compactedjava、nativejava、fastjson、dubbo、fst、hessian2、kryo,
其中默认hessian2。其中java、compactedjava、nativejava属于原生java的序列化。
1)dubbo序列化
阿里尚未开发成熟的高效java序列化实现,阿里不建议在生产环境使用它。
2)hessian2序列化
hessian是一种跨语言的高效二进制序列化方式。但这里实际不是原生的hessian2序列化,而是阿里修改过的,
它是dubboRPC默认启用的序列化方式。前提是被序列化的类得实现Serializable接口。
3)json序列化
目前有两种实现,一种是采用的阿里的fastjson库,另一种是采用dubbo中自己实现的简单json库,但其实
现都不是特别成熟,而且json这种文本序列化性能一般不如上面两种二进制序列化。
4)java序列化
主要是采用JDK自带的Java序列化实现,性能很不理想。
dubbo序列化主要由Serialization(序列化策略)、
DataInput(反序列化,二进制->对象)、
DataOutput(序列化,对象->二进制流)来进行数据的序列化与反序列化。
hessian 是一个比较老的序列化实现了,而且它是跨语言的,所以不是单独针对java进行优化的。
而dubbo RPC实际上完全是一种Java to Java的远程调用,其实没有必要采用跨语言的序列化方式
(当然肯定也不排斥跨语言的序列化)。
现在有一些新的序列化:
专门针对Java语言的:Kryo,FST等等
跨语言的:Protostuff,ProtoBuf,Thrift,Avro,MsgPack等等
这些序列化方式的性能多数都显著优于 hessian2 (甚至包括尚未成熟的dubbo序列化)。
所以我们可以为 dubbo 引入 Kryo 和 FST 这两种高效 Java 来优化 dubbo 的序列化。
三、案例分析
电商网站搞大促,整点秒杀活动,吸引了大量用户,瞬间流量暴增,商品详情系统报警,通过监控看,
所有依赖的RPC服务调用RT都很长,监控系统CPU,已经满负荷运转。
诊断步骤:
1、通过监控页面查看基本的指标,
CPU 接近100%
内存正常
系统RT很长
系统load很高
网络正常
服务方RT正常。
2、登服务器
top 命令 查看系统实时指标
tail 命令,查询 error,warn日志
jstack 命令 dump 线程查看线程堆栈信息
如果远端与本地jar版本不一致的情况,会造成反射加载类异常(ClassNotFound),同时会打印日志信息、
异常toString;而这几个动作(反射、抛异常、异常日志等)都是cpu高消耗行为;
又因为类加载失败,下次接口调用时,这些逻辑都会重新执行一遍,所以在整点活动的高并发场景下,
将会造成严重cpu性能损耗;符合以上各阶段验证结果
3、问题原因
就是服务端在对象上增加新的类型,客户端没有升级对应的版本,导致客户端使用dubbo调用服务端,hessian
序列化时,出现ClassNotFound的异常,不断的出发上述源码里的try{}catch{}里的 反射加载类,并抛
异常,这些都是cpu消费的大户。
4、问题解决
1、更换序列化方式
可更换成如kryo, fastjson等其他序列化方式,以上几种不存在此类性能缺陷,且总体性能较优
风险:可能会带来其他兼容性问题,更换、验证成本并不低
2、升级hessian-lite
升级dubbo 至 2.6.6,因为这版本已将hessian-lite升级为3.2.5
这个问题在 hessian-lite 3.2.5 已经fixed:
https://github.com/apache/dubbo/issues/351
hessian-lite 3.2.5 解决方式是将每个class的异常都缓存住,相关代码如下(红框内):