本文中的demo项目搭建参考
Spring Cloud Alibaba专题–2.2.5.RELEASE–Dubbo作为RPC调用框架的使用(十一)-三:基础Spring Boot环境搭建
前言:dubbo配置的优先级
dubbo分为consumer和provider端,在配置各个参数时,其优先级如下:
- consumer的method配置
- provider的method配置
- consumer的reference配置
- provider的service配置
- consumer的consumer节点配置
- provider的provider节点配置
方法级优先,接口级次之,全局配置再次之。
如果级别一样,则消费方优先,提供方次之。
方法级的配置优先级高于接口级,consumer的优先级高于provider。同时,在本地参数配置还存在一层优先级:
- 系统参数(-D),如-Ddubbo.protocol.port=20003
- xml配置
- property文件
优先级依次降低
一:点对点直连
应用场景:有的时候,为了测试本地的代码是否正确,需要直连本地环境来测试对应的代码
1.启动多台服务提供者
勾选允许多台同时启动,运行
两个实例已经注册进nacos
2.代码修改
@DubboReference(url = "172.16.27.230:20880",version = "2.0.0")
private EchoService point2pointEchoService;
url = “172.16.27.230:20880”,因为默认使用的是dubbo协议,默认端口是20880
3.代码测试
清空server的日志
访问point2point/echo
二:多版本测试
当我们的项目接口升级,需要新老接口同时对外提供服务来达到版本兼容的目的,这个时候就可以使用dubbo的多版本功能
1.修改代码版本为2.0.0
@DubboReference(version = "2.0.0")
private EchoService echoService;
重启后再次访问
2.去除版本测试
@DubboReference
private EchoService echoService;
启动,直接报错
无法成功注入,server已经标识了服务的版本,dubbo filter做了version的严格匹配
三:启动时检查
可以更改check属性来阻止初始化时校验服务提供者状态的逻辑,但一般情况下不会这么做。如果服务提供者真的不在线,即时时跳过检测启动起来,也没有意义。而且还会掩盖系统问题
局部设置
@DubboReference(url = "172.16.27.230:20880",version = "2.0.0" ,check = false)
全局设置
#强制改变所有 reference 的 check 值,就算配置中有声明,也会被覆盖。
dubbo.consumer.check=false
#是设置 check 的缺省值,如果配置中有显式的声明,如:<dubbo:reference check="true"/>,不会受影响
dubbo.reference.check=false
四:集群容错
集群容错指的是在调用provider时,可能出现超时,失联等等情况,集群容错就是指当出现以上问题时,进行处理使得程序更加健壮
1.集群容错模式
- Failover Cluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数(不含第一次)。(默认) - Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。 - Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。 - Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。 - Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2” 来设置最大并行数。 - Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息
2.容错模式设置
局部设置
@DubboReference(cluster = "failfast")
全局设置
dubbo.consumer.cluster=failfast
dubbo.provider.cluster=failfast
3.容错的选择
各种容错都有使用的场景以及短板,了解其特性可以为后面的特殊的业务做技术储备。
但是一般大家并不会每个Service都定制其集群容错,而是使用全局的集群容错。在这种粗粒度的场景下,从互联网对于项目的要求看,默认的Failover Cluster(失败自动切换)并非首选,我更倾向于使用Failfast Cluster(快速失败)
五:负载均衡
负载均衡方式
- Random LoadBalance(默认)
随机,按权重设置随机概率。
在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。 - RoundRobin LoadBalance
轮询,按公约后的权重设置轮询比率。
存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。 - LeastActive LoadBalance
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。 - ConsistentHash LoadBalance
一致性 Hash,相同参数的请求总是发到同一提供者。
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
算法参见:http://en.wikipedia.org/wiki/Consistent_hashing
缺省只对第一个参数 Hash,如果要修改,请配置 <dubbo:parameter key=“hash.arguments” value=“0,1” />
缺省用 160 份虚拟节点,如果要修改,请配置 <dubbo:parameter key=“hash.nodes” value=“320” />
负载均衡方面没什么特殊要求,使用默认即可
这里聊聊一致性Hash负载均衡(ConsistentHash LoadBalance)
一致性Hash负载均衡(ConsistentHash LoadBalance)
本文重点是介绍Dubbo的使用,一致性哈希算法具体逻辑,可以看看这篇写的很好的文章:一致性哈希算法原理
这里引用部分的内容方便讲解致dubbo的一性哈希算负载均衡在微服务下的使用场景。
一致性hash算法提供了一种能力,在集群的状态稳定(没有新增及下线服务的操作,事实上,正常的系统绝大多数时间都是这种状态)时,同一个请求(以请求参数计算生成的hash确定)总是打到同一台服务器上,且增添服务只会影响很小的范围
仅在集群内部服务器是有状态的场景,才会需要这种能力。
服务器有状态:指服务器会保存请求的相关数据或状态等信息,当一个请求打到服务有状态的集群后,打到不同的服务,业务处理逻辑,资源消耗情况完全不同,甚至不同服务器,结果都不相同。
例子1:
多个服务会根据热点数据做内存级别的缓存,这个时候,已经有缓存的A服务,和没有缓存的B服务处理同一个请求,资源消耗和业务逻辑差异是很大的,当然,结果是一致的
例子2:
Redis的集群模式,每个主节点只会存储部分信息,所有主节点的数据加在一起才是全量数据,比如某个key的值存在A节点上,这个时候,获取这个key的值的请求,打到A节点和打到B节点的结果都是不一致的
六:调度策略
1.Dispatcher
- all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等(默认)
- direct 所有消息都不派发到线程池,全部在 IO 线程上直接执行。
- message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
- execution 只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
- connection 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。
系统默认是all,如果后期出现单机服务用户线程满的情况,可以考虑使用message,当然这里需要根据实际情况权衡
2.ThreadPool
- fixed 固定大小线程池,启动时建立线程,不关闭,一直持有。默认是200个线程(默认)
- cached 缓存线程池,空闲一分钟自动删除,需要时重建。
- limited 可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。
- eager 优先创建Worker线程池。在任务数量大于corePoolSize但是小于maximumPoolSize时,优先创建Worker来处理任务。当任务数量大于maximumPoolSize时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException。(相比于cached:cached在任务数量超过maximumPoolSize时直接抛出异常而不是将任务放入阻塞队列)
dubbo的默认长连接数是1,所以consumer中的conection在没指定的情况下默认是1
七:多协议
1.协议介绍
在当前的Dubbo版本中,共有以下几种协议:
这里仅使用常见的Dubbo、Rest、Injvm作为介绍
- Dubbo协议,官方推荐的协议,基于JBoss的NIO实现,单一长链接,特别适合互联网小数据大并发的场景,但高IO的请求是短板
- Rest协议 基于Http协议,API使用HTTP动词(GET,POST,DELETE,DETC)来描述操作
- Injvm协议 伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联,但执行 Dubbo 的 Filter 链。Dubbo 从 2.2.0 每个服务默认都会在本地暴露,无需进行任何配置即可进行本地引用
2.多协议暴露代码示例
这里沿用Spring Cloud Alibaba专题–2.2.5.RELEASE–Dubbo作为RPC调用框架的使用(十一)-三:基础Spring Boot环境搭建中的项目结构
(1)Api增加接口
public interface RestService {
String param(String param);
String params(String b);
String headers(String header, String header2, Integer param);
String pathVariables(String path1, String path2, String param);
String form(String form);
User requestBodyMap(Map<String, Object> data, String param);
Map<String, Object> requestBodyUser(User user);
Collection<User> inJvmTest(User user);
}
(2)添加Rest Pom依赖
<!-- Resolve the Dubbo REST RPC-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<!-- REST support dependencies -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-netty4</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson-provider</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxb-provider</artifactId>
</dependency>
注意:Rest是基于JSR311规范的,而基于这个规范有很多的实现,这里使用的是JBoss的实现。必须剔除项目中Java或其他的JSR311的实现,否则会jar包冲突导致不可预测的问题。这里应剔除Java的JSR311的实现
<artifactId>jsr311-api</artifactId>
<groupId>javax.ws.rs</groupId>
(3)服务提供者
@DubboService(version = "1.0.0", protocol = {"dubbo", "rest"})
@Path("/")
public class StandardRestService implements RestService {
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* 1.注入过程不受作用域关键字控制
* 2.必须面向接口注入,不能使用具体实现
*/
@DubboReference
public UserService userService;
@Override
@Path("param")
@GET
public String param(@QueryParam("param") String param) {
log("/param", param);
return param;
}
@Override
@Path("params")
@POST
public String params(@QueryParam("a") int a, @QueryParam("b") String b) {
log("/params", a + b);
return a + b;
}
@Override
@Path("headers")
@GET
public String headers(@HeaderParam("h") String header,
@HeaderParam("h2") String header2, @QueryParam("v") Integer param) {
String result = header + " , " + header2 + " , " + param;
log("/headers", result);
return result;
}
@Override
@Path("path-variables/{p1}/{p2}")
@GET
public String pathVariables(@PathParam("p1") String path1,
@PathParam("p2") String path2, @QueryParam("v") String param) {
String result = path1 + " , " + path2 + " , " + param;
log("/path-variables", result);
return result;
}
// @CookieParam does not support : https://github.com/OpenFeign/feign/issues/913
// @CookieValue also does not support
@Override
@Path("form")
@POST
public String form(@FormParam("f") String form) {
return String.valueOf(form);
}
@Override
@Path("request/body/map")
@POST
@Produces(APPLICATION_JSON_VALUE)
public User requestBodyMap(Map<String, Object> data,
@QueryParam("param") String param) {
User user = new User();
user.setId(((Integer) data.get("id")).longValue());
user.setName((String) data.get("name"));
user.setAge((Integer) data.get("age"));
log("/request/body/map", param);
return user;
}
@Path("request/body/user")
@POST
@Override
@Consumes(MediaType.APPLICATION_JSON)
@Produces(APPLICATION_JSON_VALUE)
public Map<String, Object> requestBodyUser(User user) {
Map<String, Object> map = new HashMap<>();
map.put("id", user.getId());
map.put("name", user.getName());
map.put("age", user.getAge());
return map;
}
@Path("request/injvm/test")
@POST
@Override
@Consumes(MediaType.APPLICATION_JSON)
@Produces(APPLICATION_JSON_VALUE)
public Collection<User> inJvmTest(User user) {
userService.save(user);
Collection<User> all = userService.findAll();
log("request/injvm/test", all);
return all;
}
}
可以看到,Dubbo的Rest接口暴露和Spring Mvc接口非常相似。
测试代码说明:
最后一个测试用来测试injvm,其他测试均测试Rest的功能,包括普通传参,多参数,hearder传参,动态参数,form表单,json传参等场景
3.REST协议API测试
(1)form表单
(2)application/json
(3)Injvm测试
此时的日志打印为
4.Dubbo协议API测试
消费端代码
@EnableDiscoveryClient
@EnableAutoConfiguration
@EnableFeignClients
@EnableScheduling
@EnableCaching
public class DubboSpringCloudConsumerBootstrap {
@DubboReference(validation = "true")
private UserService userService;
@DubboReference(version = "1.0.0", protocol = "dubbo",validation = "true")
private RestService restService;
@Autowired
@Lazy
private FeignRestService feignRestService;
@Autowired
@Lazy
private DubboFeignRestService dubboFeignRestService;
@Value("${provider.application.name}")
private String providerApplicationName;
@Autowired
@LoadBalanced
private RestTemplate restTemplate;
@Bean
public ApplicationRunner userServiceRunner() {
return arguments -> {
User user = new User();
user.setId(1L);
user.setName("小马哥");
user.setAge(33);
// save User
System.out.printf("UserService.save(%s) : %s\n", user,
userService.save(user));
// find all Users
System.out.printf("UserService.findAll() : %s\n", user,
userService.findAll());
// remove User
System.out.printf("UserService.remove(%d) : %s\n", user.getId(),
userService.remove(user.getId()));
};
}
@Bean
public ApplicationRunner callRunner() {
return arguments -> {
callAll();
};
}
private void callAll() {
try {
// To call /path-variables
callPathVariables();
// To call /headers
callHeaders();
// To call /param
callParam();
// To call /params
callParams();
// To call /request/body/map
callRequestBodyMap();
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
}
@Scheduled(fixedDelay = 10 * 1000L)
public void onScheduled() {
callAll();
}
private void callPathVariables() {
// Dubbo Service call
System.out.println(restService.pathVariables("a", "b", "c"));
// Spring Cloud Open Feign REST Call (Dubbo Transported)
System.out.println(dubboFeignRestService.pathVariables("c", "b", "a"));
// Spring Cloud Open Feign REST Call
System.out.println(feignRestService.pathVariables("b", "a", "c"));
// RestTemplate call
System.out.println(restTemplate.getForEntity(
"http://" + providerApplicationName + "//path-variables/{p1}/{p2}?v=c",
String.class, "a", "b"));
}
private void callHeaders() {
// Dubbo Service call
System.out.println(restService.headers("a", "b", 10));
// Spring Cloud Open Feign REST Call (Dubbo Transported)
System.out.println(dubboFeignRestService.headers("b", 10, "a"));
// Spring Cloud Open Feign REST Call
System.out.println(feignRestService.headers("b", "a", 10));
}
private void callParam() {
// Dubbo Service call
System.out.println(restService.param("mercyblitz"));
// Spring Cloud Open Feign REST Call (Dubbo Transported)
System.out.println(dubboFeignRestService.param("mercyblitz"));
// Spring Cloud Open Feign REST Call
System.out.println(feignRestService.param("mercyblitz"));
}
private void callParams() {
// Dubbo Service call
System.out.println(restService.params(1, "1"));
// Spring Cloud Open Feign REST Call (Dubbo Transported)
System.out.println(dubboFeignRestService.params("1", 1));
// Spring Cloud Open Feign REST Call
System.out.println(feignRestService.params("1", 1));
// RestTemplate call
System.out.println(restTemplate.getForEntity(
"http://" + providerApplicationName + "/param?param=小马哥", String.class));
}
private void callRequestBodyMap() {
Map<String, Object> data = new HashMap<>();
data.put("id", 1);
data.put("name", "小马哥");
data.put("age", 33);
// Dubbo Service call
System.out.println(restService.requestBodyMap(data, "Hello,World"));
// Spring Cloud Open Feign REST Call (Dubbo Transported)
System.out.println(dubboFeignRestService.requestBody("Hello,World", data));
// Spring Cloud Open Feign REST Call
// System.out.println(feignRestService.requestBody("Hello,World", data));
// RestTemplate call
System.out.println(restTemplate.postForObject(
"http://" + providerApplicationName + "/request/body/map?param=小马哥", data,
User.class));
}
@Bean
@LoadBalanced
@DubboTransported
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
new SpringApplicationBuilder(DubboSpringCloudConsumerBootstrap.class).run(args);
}
/**
* 使用feign访问rest服务
*/
@FeignClient("${provider.application.name}")
public interface FeignRestService {
@GetMapping("/param")
String param(@RequestParam("param") String param);
@PostMapping("/params")
String params(@RequestParam("b") String b, @RequestParam("a") int a);
@PostMapping(value = "/request/body/map", produces = APPLICATION_JSON_VALUE)
User requestBody(@RequestParam("param") String param,
@RequestBody Map<String, Object> data);
@GetMapping("/headers")
String headers(@RequestHeader("h2") String header2,
@RequestHeader("h") String header, @RequestParam("v") Integer value);
@GetMapping("/path-variables/{p1}/{p2}")
String pathVariables(@PathVariable("p2") String path2,
@PathVariable("p1") String path1, @RequestParam("v") String param);
}
/**
* http协议转换为dubbo(设计目的作为项目迁移过渡使用)
*/
@FeignClient("${provider.application.name}")
@DubboTransported(protocol = "dubbo")
public interface DubboFeignRestService {
@GetMapping("/param")
String param(@RequestParam("param") String param);
@PostMapping("/params")
String params(@RequestParam("b") String paramB, @RequestParam("a") int paramA);
@PostMapping(value = "/request/body/map", produces = APPLICATION_JSON_VALUE)
User requestBody(@RequestParam("param") String param,
@RequestBody Map<String, Object> data);
@GetMapping("/headers")
String headers(@RequestHeader("h2") String header2,
@RequestParam("v") Integer value, @RequestHeader("h") String header);
@GetMapping("/path-variables/{p1}/{p2}")
String pathVariables(@RequestParam("v") String param,
@PathVariable("p2") String path2, @PathVariable("p1") String path1);
}
}
这里使用了三种调用方式:
1.DubboService注入调用Dubbo协议的服务,适用于内部使用了Dubbo的系统之间调用
2.使用Feign及RestTemplate调用Rest服务,适用场景,Spring Cloud系分布式系统或Spring系统调用Rest服务。当然,基于Rest的其他请求客户端如HttpClient,openHttp等等都是可以访问的
这里有一点,你同样可以使用DubboService注入调用Rest协议的服务,但是必须在接口层面引入Rest相关标签,但是这种场景下在Api层引入Rest相关依赖且非刚需的情况下并不推荐这么干
3.使用 @FeignClient("${provider.application.name}")+@DubboTransported(protocol = “dubbo”)来调用dubbo接口,适用场景,FeignRPC系统调用Dubbo RPC系统,或Feign RPC系统解决方案过渡到Dubbo解决方案时使用
更详细的内容参见官网:Dubbo2.7.x–开发 REST 应用
5.性能对比
(1)POJO TPS
(2)1K TPS
(3)50K TPS
这里重点关注dubbo2(hessian2+netty).可以看到 当传输数据量较大后,dubbo协议的性能大幅下滑。原因主要是Dubbo协议是基于Netty实现的,
服务间基于长连接传输数据,且默认只有1个长连接。这就导致,一旦传输数据较大,该数据传输过程中将会长时间占用链接,导致其他请求阻塞。
解决这个问题两种方案
- a.增加长连接数量
首先:增加长连接会使provider连接数陡增
比如现在有Consumer数量为100,长连接数为1,每个provider有【100乘1】=100个长连接,当增加长连接数为2,则此时provider有【100乘2】=200个长连接
其次:即时增加了长连接数,高IO的请求仍然会阻塞同一长链接下的其他请求 - b.针对高IO的接口,单独走其他的协议,如Rest或Http协议
推荐使用这种方式
更加具体的测试数据,参见官网-基础测试
八:参数验证
和Spring Mvc一样,Dubbo同样支持参数验证
1.Maven
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
需要定义dubbo的依赖管理pom,如果没有,需要自己查找当前版本的dubbo pom中定义的版本,并手动添加version
2.开启参数验证
(1)局部开启
@DubboService(version = "1.0.0", protocol = {"dubbo", "rest"},validation = "true")
或者
#这里的jvalidation是java的默认唯一实现,可以自己扩展,并指定你的扩展
@DubboService(version = "1.0.0", protocol = {"dubbo", "rest"},validation = "jvalidation")
(2)全局开启
dubbo.consumer.validation=true
dubbo.provider.validation=true
或者
dubbo.consumer.validation=jvalidation
dubbo.provider.validation=jvalidation
3.消费端验证
以上的开关可以看出,消费者和提供者都是可以开启验证的,同Rest一样,如果消费者需要使用验证功能,则对应的Validate依赖及标签需放在Api层,和Rest不一样的事,个人并不反对这种加入Validation的Api依赖方式,反而比较推崇。
因为对于普通的Api接口没有表达参数那些必填,那些不能为空的能力,而Validation的标签可以填补这不部分的不足(当然写注释说明的大哥请勿喷),甚至集成了Validation的能力,运行时也可以为你的接口参数进行校验。在之前没有客户端校验能力的时候,我个人也比较喜欢在Api层使用Validation来标注参数必填及限制要求来提醒接入的开发小伙伴。但由于没有客户端校验的能力,也仅限于支持开发阶段做标记文档使用及部分运行时校验(传递的参数实体定义的Validation标签在服务端是可以使用的)。令人欣喜的是Dubbo支持客户端校验!!!
只需要在服务端开启对应的校验标签即可,这样的做法符合Fail-Faster,至于maven依赖,Validation对应的依赖已经放入API层,消费者必须依赖API,作为消费者不用关系
这里有个点,官网文档中说到
对性能有影响这个就比较烦,是增加了逻辑导致代码执行时间变长,还是加入的Validation的机制本身就存在性能问题,官方也没说清,我再使用过程中没有发现比较大的问题,如果对这方面比较敏感,建议做性能测试
九:序列化
1.序列化漫谈
序列化对于远程调用的响应速度、吞吐量、网络带宽消耗等起着至关重要的作用,是我们提升分布式系统性能的最关键因素之一。
在dubbo RPC中,同时支持多种序列化方式,例如:
- dubbo序列化:阿里尚未开发成熟的高效java序列化实现,阿里不建议在生产环境使用它
- hessian2序列化:hessian是一种跨语言的高效二进制序列化方式。但这里实际不是原生的hessian2序列化,而是阿里修改过的hessian lite,它是dubbo RPC默认启用的序列化方式
- json序列化:目前有两种实现,一种是采用的阿里的fastjson库,另一种是采用dubbo中自己实现的简单json库,但其实现都不是特别成熟,而且json这种文本序列化性能一般不如上面两种二进制序列化。
- java序列化:主要是采用JDK自带的Java序列化实现,性能很不理想。
在通常情况下,这四种主要序列化方式的性能从上到下依次递减。对于dubbo RPC这种追求高性能的远程调用方式来说,实际上只有1、2两种高效序列化方式比较般配,而第1个dubbo序列化由于还不成熟,所以实际只剩下2可用,所以dubbo RPC默认采用hessian2序列化。
但hessian是一个比较老的序列化实现了,而且它是跨语言的,所以不是单独针对java进行优化的。而dubbo RPC实际上完全是一种Java to Java的远程调用,其实没有必要采用跨语言的序列化方式(当然肯定也不排斥跨语言的序列化)。
最近几年,各种新的高效序列化方式层出不穷,不断刷新序列化性能的上限,最典型的包括:
专门针对Java语言的:Kryo,FST等等
跨语言的:Protostuff,ProtoBuf,Thrift,Avro,MsgPack等等
这些序列化方式的性能多数都显著优于hessian2(甚至包括尚未成熟的dubbo序列化)。
有鉴于此,我们为dubbo引入Kryo和FST这两种高效Java序列化实现,来逐步取代hessian2。
其中,Kryo是一种非常成熟的序列化实现,已经在Twitter、Groupon、Yahoo以及多个著名开源项目(如Hive、Storm)中广泛的使用。而FST是一种较新的序列化实现,目前还缺乏足够多的成熟使用案例,但我认为它还是非常有前途的。
在面向生产环境的应用中,我建议目前更优先选择Kryo--------以上部分引用官方序列化
2.性能对比
以下的官方测试数据可以帮我们很好的评估序列化对于系统吞吐量的影响
(1)Dubbo RPC中不同序列化生成字节大小比较
针对复杂对象的结果如下(数值越小越好):
序列化实现 | 请求字节数 | 响应字节数 |
---|---|---|
Kryo | 272 | 90 |
FST | 288 | 96 |
Dubbo Serialization | 430 | 186 |
Hessian | 546 | 329 |
FastJson | 461 | 218 |
Json | 657 | 409 |
Java Serialization | 963 | 630 |
(2)Dubbo RPC中不同序列化响应时间和吞吐量对比
远程调用方式 | 平均响应时间 | 平均TPS(每秒事务数) |
---|---|---|
REST: Jetty + JSON | 7.806 | 1280 |
REST: Jetty + JSON + GZIP | TODO | TODO |
REST: Jetty + XML | TODO | TODO |
REST: Jetty + XML + GZIP | TODO | TODO |
REST: Tomcat + JSON | 2.082 | 4796 |
REST: Netty + JSON | 2.182 | 4576 |
Dubbo: FST | 1.211 | 8244 |
Dubbo: kyro | 1.182 | 8444 |
Dubbo: dubbo serialization | 1.43 | 6982 |
Dubbo: hessian2 | 1.49 | 6701 |
Dubbo: fastjson | 1.572 | 6352 |
根据以上的数据,可以看到,使用Dubbo:Kyro序列化比默认的Dubbo:Hessian2的组合快了约20%-30%左右,如果对于性能要求比较苛刻,官方推荐使用Kryo作为系统的序列化方案