org.apache.dubbo 2.7.x 再聚首
Dubbo 版本 :
Dubbo 社区目前主力维护的有 2.6.x 和 2.7.x 两大版本,其中,
- 2.6.x 主要以 bugfix 和少量 enhancements 为主,因此能完全保证稳定性
- 2.7.x 作为社区的主要开发版本,得到持续更新并增加了大量新 feature 和优化,同时也带来了一些稳定性挑战
Apache dubbo 官网已经出现3.0版本。目前官网展示版本为 2.7 与3.0.如若下列官网地址打不开,可能是项目更新了,进入 http://dubbo.apache.org/ 自行查阅
版本更多信息请参考官网。相信小伙伴们对于Dubbo 都有一定的了解。相关基础知识点可以参考 https://www.cnblogs.com/wuzhenzhao/p/10008824.html .
Dubbo 再聚首之自动化配置:
dubbo-spring-boot-starter(org.apache.dubbo:2.7.7):
基于目前的 Spring Boot 自动化配置的盛行,我们在使用 Dubbo的时候不再像以前集成 spring 的时候那样的繁琐,需要进行很多的配置。接下来来体验一下 dubbo-spring-boot-starter 带来的便捷。
本文注册中心采用 Spring Cloud Alibaba Nacos ,不熟悉的小伙伴可以参考 https://www.cnblogs.com/wuzhenzhao/category/1530796.html
项目目录:
springboot-dubbo-api 模块:
1.构建服务接口,api模块,导入 Rest 协议支持依赖:
<dependencies> <!--添加REST支持--> <!--Rest协议--> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxrs</artifactId> <version>3.8.1.Final</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-client</artifactId> <version>4.0.0.Final</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> <version>9.4.12.RC2</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> <version>9.4.12.RC2</version> </dependency> </dependencies>
2. 创建接口 :
@Path("/") public interface HelloService { @GET @Path("/sayRest") String sayHello() throws Exception; }
springboot-dubbo-provider 模块:
1.导入依赖:
<dependencies> <!--基于spring-boot-dependencies 2.3.0RELEASE 版本 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- dubbo 依赖--> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.7.7</version> </dependency> <!--nacos注册中心依赖--> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>1.2.1</version> </dependency> <!--zk注册中心依赖--> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>4.0.1</version> </dependency> <dependency> <artifactId>springboot-dubbo-api</artifactId> <groupId>com.wuzz.demo</groupId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
3. 创建服务实现类 :
@DubboService(loadbalance = "random", // 负载均衡 timeout = 50000, //超时 cluster = "failsafe", // 服务容错 protocol = {"dubbo", "rest"}, //多协议支持 registry = {"hangzhou", "wenzhou"} //多注册中心 ) public class HelloServiceImpl implements HelloService { @Override public String sayHello() throws Exception { return "Hello Dubbo"; } }
4. 配置文件配置
spring.application.name=springboot-dubbo # Netty -> dubbo.protocols.dubbo.name=dubbo dubbo.protocols.dubbo.port=-1 # jetty (配置了rest协议) dubbo.protocols.rest.name=rest dubbo.protocols.rest.port=-1 dubbo.protocols.rest.server=jetty # zk注册中心 dubbo.registries.hangzhou.address=zookeeper://192.168.1.101:2181 dubbo.registries.hangzhou.timeout=10000 dubbo.registries.hangzhou.default=true ## 服务启动的时候,如果注册中心有问题,那么服务就启动失败 dubbo.registries.hangzhou.check=false # nacos 注册中心 dubbo.registries.wenzhou.address=nacos://localhost:8848
5. 服务启动类,配置扫描路径
@DubboComponentScan(basePackages = "com.wuzz.demo") //dubbo服务扫描 @SpringBootApplication public class SpringBootDubboProviderApp { private final static Logger log = LoggerFactory.getLogger(SpringBootDubboProviderApp.class); public static void main(String[] args) { SpringApplication.run(SpringBootDubboProviderApp.class, args); log.info("服务启动成功"); } }
springboot-dubbo-client 模块:
1.导入相关依赖:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.7.7</version> </dependency> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>1.2.1</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>4.0.1</version> </dependency> <dependency> <artifactId>springboot-dubbo-api</artifactId> <groupId>com.wuzz.demo</groupId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
2. 创建测试类 :
@RestController public class DubboController { //Dubbo提供的注解 @DubboReference(loadbalance = "roundrobin", timeout = 9000, cluster = "failfast", mock = "com.wuzz.demo.mock.HelloServiceMock", check = false) HelloService helloService; @GetMapping("/sayhello") public String sayHello() throws Exception { return helloService.sayHello(); //我调用这个服务可能失败,如果失败了,我要怎么处理 } // dubbo 泛化调用 @DubboReference(interfaceName = "com.wuzz.demo.api.HelloService",generic = true,check = false) GenericService genericService; @GetMapping("/demo") public String demo(){ return genericService.$invoke("sayHello",new String[0],null).toString(); } }
mock 实现类:
public class HelloServiceMock implements HelloService { @Override public String sayHello() { return "服务端发生异常, 被降解了。返回兜底数据。。。"; } }
3.配置文件,启动类无需任何配置
spring.application.name=springboot-dubbo-client dubbo.registry.address=nacos://localhost:8848 server.port=8889
然后先后启动 服务提供者、服务消费者模块。可以看到 注册中心应当有两个服务的相关注册信息:
然后就可以访问对应的接口进行测试。
spring-cloud-starter-dubbo(org.apache.dubbo:2.7.6):
与springboot 集成不同,spring-cloud-alibaba 自成生态,在多注册中心的用法上有兼容问题。
项目目录
spring-cloud-alibaba-dubbo-api 模块:
1.添加相关接口
public interface HelloService { String sayHello() throws Exception; }
spring-cloud-alibaba-dubbo-provider 模块:
1.添加相关依赖:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!--dubbo 依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-dubbo</artifactId> <version>2.2.1.RELEASE</version> </dependency> <!--nacos 注册中心依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2.2.1.RELEASE</version> </dependency> <dependency> <groupId>com.wuzz.demo</groupId> <artifactId>spring-cloud-alibaba-dubbo-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
2.实现类:
@Service(loadbalance = "random",timeout = 50000,cluster = "failsafe") public class HelloServiceImpl implements HelloService { @Override public String sayHello() throws Exception { return "Hello Dubbo"; } }
3. 配置文件配置:
spring.application.name=springboot-dubbo dubbo.scan.base-packages=com.wuzz.demo dubbo.protocol.port=20882 dubbo.protocol.name=dubbo spring.cloud.nacos.discovery.server-addr=localhost:8848
4.启动类:
@EnableDiscoveryClient @SpringBootApplication public class SpringCloudAlibabaDubboProviderApp { private final static Logger log = LoggerFactory.getLogger(SpringCloudAlibabaDubboProviderApp.class); public static void main(String[] args) { SpringApplication.run(SpringCloudAlibabaDubboProviderApp.class, args); log.info("服务启动成功"); } }
spring-cloud-alibaba-dubbo-client 模块:
1.导入依赖:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-dubbo</artifactId> <version>2.2.1.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2.2.1.RELEASE</version> </dependency> <dependency> <groupId>com.wuzz.demo</groupId> <artifactId>spring-cloud-alibaba-dubbo-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
3.测试类编写:
@RestController public class DubboController { //Dubbo提供的注解 @Reference(loadbalance = "roundrobin", timeout = 1, cluster = "failfast", mock = "com.wuzz.demo.mock.HelloServiceMock", check = false) HelloService helloService; @GetMapping("/sayhello") public String sayHello() throws Exception { return helloService.sayHello(); //我调用这个服务可能失败,如果失败了,我要怎么处理 } }
mock 实现类:
public class HelloServiceMock implements HelloService { @Override public String sayHello() { return "服务端发生异常, 被降解了。返回兜底数据。。。"; } }
4.配置文件:
spring.application.name=springboot-dubbo-client server.port=8889 spring.cloud.nacos.discovery.server-addr=localhost:8848
5.启动类:
@EnableDiscoveryClient @SpringBootApplication public class SpringCloudAlibabaDubboClientApp { private final static Logger log = LoggerFactory.getLogger(SpringCloudAlibabaDubboClientApp.class); public static void main(String[] args) { SpringApplication.run(SpringCloudAlibabaDubboClientApp.class, args); log.info("服务启动成功"); } }
先后启动服务提供者、服务消费者进行测试即可。
Dubbo 常用功能简介:
多协议支持:
- dubbo:// :Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。反之,Dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。
- rmi:// : RMI 协议采用 JDK 标准的
java.rmi.*
实现,采用阻塞式短连接和 JDK 标准序列化方式。 - hessian:// : Hessian 协议用于集成 Hessian 的服务,Hessian 底层采用 Http 通讯,采用 Servlet 暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现。
- http:// :基于 HTTP 表单的远程调用协议,采用 Spring 的 HttpInvoker 实现
- webservice:// :基于 WebService 的远程调用协议,基于 Apache CXF 的
frontend-simple
和transports-http
实现 。 - thrift:// :当前 dubbo 支持 的 thrift 协议是对 thrift 原生协议 的扩展,在原生协议的基础上添加了一些额外的头信息,比如 service name,magic number 等。
- memcached:// :基于 memcached 实现的 RPC 协议 。
- redis:// :基于 Redis 实现的 RPC 协议 。
- rest:// :基于标准的Java REST API——JAX-RS 2.0(Java API for RESTful Web Services的简写)实现的REST调用支持
- grpc:// :Dubbo 自 2.7.5 版本开始支持 gRPC 协议,对于计划使用 HTTP/2 通信,或者想利用 gRPC 带来的 Stream、反压、Reactive 编程等能力的开发者来说, 都可以考虑启用 gRPC 协议。
Dubbo 的负载均衡:
在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random
随机调用。
1.Random LoadBalance:
- 随机,按权重设置随机概率。
- 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
2.RoundRobin LoadBalance:
- 轮询,按公约后的权重设置轮询比率。
- 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
3.LeastActive LoadBalance:
- 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
- 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
4.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" />
5.ShortestResponse LoadBalance:
最短响应时间负载均衡算法,筛选成功调用响应时间最短的调用程序的数量,并计算这些调用程序的权重和数量。然后根据响应时间的长短来分配目标服务的路由权重。
集群容错:
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
-
Failover Cluster :失败自动切换,当出现失败,重试其它服务器 。通常用于读操作,但重试会带来更长延迟。可通过
retries="2"
来设置重试次数(不含第一次)。 -
Failfast Cluster :快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
-
Failsafe Cluster :失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
-
Failback Cluster :失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
-
Forking Cluster :并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过
forks="2"
来设置最大并行数。 -
Broadcast Cluster :广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。
服务降级:
dubbo的降级方式: Mock。上文代码中已给出示例实现步骤:
- 在client端创建一个 HelloServiceMock 类,实现对应的接口(需要对哪个接口进行mock,就实现哪个),名称必须以Mock结尾
- 在client端的服务调用的注解配置中,添加 mock 配置,增加一个mock属性指向创建的HelloServiceMock
- 模拟错误(设置timeout),模拟超时异常,运行测试代码即可访问到HelloServiceMock 这个类。当服务端故障解除以后,调用过程将恢复正常,
Dubbo泛化:
泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map
表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 GenericService
调用所有服务实现。 上文已给出示例。
更多的泛化配置可以参考官网 :http://dubbo.apache.org/zh-cn/docs/user/demos/generic-reference.html。
主机绑定:
关于主机绑定的源码实现位于 org.apache.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol
String host = findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = findConfigedPorts(protocolConfig, name, map);
主机绑定的步骤主要有以下几个步骤:
- 查找环境变量中是否存在启动参数 [DUBBO_IP_TO_BIND] =服务注册的ip
- 读取配置文件, dubbo.protocols.dubbo.host= 服务注册的ip
- InetAddress.getLocalHost().getHostAddress() 获得本机ip地址
- 通过Socket去连接注册中心,从而获取本机IP
- 会轮询本机的网卡,直到找到合适的IP地址
- 上面获取到的ip地址是bindip,如果需要作为服务注册中心的ip, DUBBO_IP_TO_REGISTRY -dDUBBO_IP_TO_REGISTRY=ip
配置优先级:
- 方法层面的配置要优先于接口层面的配置, 接口层面的配置要优先于全局配置.
- 如果级别一样,以客户端的配置优先,服务端次之.
性能调优的参数:
dubbo 提供了针对服务端/客户端的相关参数调优,以下列举了一些比较重要的参数。
@Configuration public class DubboConfig { //服务端相关调优参数 @Bean public ProviderConfig providerConfig() { ProviderConfig config = new ProviderConfig(); //默认200 服务线程池大小(固定大小) config.setThreads(200); //默认CPU + 1 //IO线程池,接收网络读写中断,以及序列化和反序列化, // 不处理业务,业务线程池参见threads配置,此线程池和CPU相关,不建议配置。 config.setIothreads(Runtime.getRuntime().availableProcessors() + 1); //线程池类型,可选:fixed/cached/limit(2.5.3以上)/eager(2.6.x以上) config.setThreadpool("fixed"); //对每个提供者的最大连接数,rmi、http、hessian //等短连接协议表示限制连接数,dubbo等长连接协表示建立的长连接个数 config.setConnections(0); //线程池队列大小,当线程池满时,排队等待执行的队列大小, //建议不要设置,当线程池满时应立即失败,重试其它服务提供机器, //而不是排队,除非有特殊需求。 config.setQueues(0); //每服务消费者每服务每方法最大并发调用数 config.setAccepts(0); //服务提供者每服务每方法最大可并行执行请求数 config.setExecutes(0); return config; } //客户端相关调优参数 @Bean public ConsumerConfig consumerConfig() { ConsumerConfig config = new ConsumerConfig(); //每个服务对每个提供者的最大连接数, //rmi、http、hessian等短连接协议支持此配置,dubbo协议长连接不支持此配置 config.setConnections(100); //每服务消费者每服务每方法最大并发调用数 config.setActives(0); return config; } }
参数调优可以参考以下dubbo的处理流程
更多参数请参考官网:
- provider:http://dubbo.apache.org/zh-cn/docs/2.7/user/references/xml/dubbo-provider/
- consumer:http://dubbo.apache.org/zh-cn/docs/2.7/user/references/xml/dubbo-consumer/
Dubbo缓存文件:
配置服务地址的缓存,避免注册中心挂了之后对于服务通信的影响,客户端做以下配置 :
spring.application.name=springboot-dubbo-client server.port=8889 # nacos 注册中心 dubbo.registries.wenzhou.address=nacos://localhost:8848 # 配置服务地址的缓存,避免注册中心挂了之后对于服务通信的影响 dubbo.registries.wenzhou.file=${user.home}/dubbo.cache
然后启动服务提供者/服务消费者,可以到用户目录下看到一个文件,dubbo.cache
然后我们打开它:
我们会发现服务信息已经被缓存下来了。这个时候,把注册中心关了,再去访问接口 ,发现也是没问题的。
更多特性请参考官网。