一:dubbo是什么
随着项目的业务越来越复杂,为了提高项目的可维护性,可拓展性,我们需要将一个单体应用拆分为多个服务。而服务之间的通信我们需要使用到rpc技术,什么是rpc呢?rpc即远程过程调用,简单来说就是像调用本地方法一样调用远程服务,而dubbo就是一个当下非常热门而且优秀的rpc框架。
dubbo的总体架构:
1.provider:服务提供者
2.consumer:服务消费者
3.registry: 注册中心
4.container: dubbo服务容器
5.monitor: 监控中心
二:SpringBoot整合dubbo
1、引入依赖,以3.0.2.1版本为例,我们使用zk作为注册中心,所以还需要引入curator的依赖。zk的下载使用这里就省略了。
注:本文都是基于3.0.2.1版本,zk版本是3.7.0
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.0.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
<version>4.2.0</version>
</dependency>
2.提供一个api接口:
package com.example.api;
/**
* @author 20136
*/
public interface HelloService {
String hello(String msg);
}
- 服务端:
properties配置:
配置服务名,注册中心地址,协议以及端口号
dubbo.application.name=spring-boot-dubbo
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20881
api实现类:
@DubboService
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String msg) {
return "hello " + msg;
}
}
在启动类上加上EnableDubbo注解,并且指定需要扫描的包,用于扫描@DubboService注解
@SpringBootApplication
@EnableDubbo(scanBasePackages = "com.example")
public class DubboProviderApplication {
public static void main(String[] args) {
SpringApplication.run(DubboProviderApplication.class, args);
}
}
4.客户端
properties配置:
dubbo.application.name=dubbo-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
启动类添加@EnableDubbo注解,配置需要扫描的包,用于扫描@DubboReference注解
package com.example.dubbo.consumer;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDubbo(scanBasePackages = "com.example")
public class DubboConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(DubboConsumerApplication.class, args);
}
}
消费端调用api
@SpringBootTest
class DubboConsumerApplicationTests {
@DubboReference(check = false)
private HelloService helloService;
@Test
void test(){
System.out.println(helloService.hello("world"));
}
}
三:dubbo的负载均衡策略
通常为了服务的可用性,同一个服务我们会部署多份,避免单点故障问题。所以在服务调用的时候需要考虑到负载均衡的问题。dubbo提供了5中负载均衡策略。
1.random
随机策略,默认策略,实际上是考虑了权重之后的随机选择,如果每个服务提供者的权重都一致,那么就使用 java 的随机函数去选择一个.
2.roundrobin
轮询策略,轮询负载均衡策略,本质上也是考虑了权重之后的轮循
3.leastactive
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差,使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
4.consistenthash
一致性hash策略,根据参数做一致性hash,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
5.shortestresponse
最短响应时间策略,根据响应时间和当前服务的请求量去获得一个最优解。如果存在多个最优解,则考虑权重,如果仅有一个则权重无效。
指定负载均衡策略很简单,在@DubboReference注解上的loadbalance属性指定自己想要的策略即可。
@DubboReference(check = false,loadbalance = "random")
四:dubbo的集群容错策略
dubbo在调用的时候可能会出现错误,很多时候我们需要根据不同的业务做不同的处理,而dubbo为我们提供了8种策略。
1.failover
失败转移策略,默认的策略,如果调用失败了之后会根据我们配置的重试次数进行重试,默认会重试2次。
2.failfast
快速失败策略,如果调用服务出现错误,会立即抛出异常。
3.failsafe
失败安全策略,如果出现调用出现异常会把异常捕捉。然后继续执行业务代码
4.failback
失败自动恢复策略,调用服务失败时,记录失败请求并安排定期重试
5.forking
并发调用特定数量的服务,只要一个成功就返回,通常用于实时性要求高的操作,但需要浪费更多的服务资源
6.broadcast
广播调用所有provider,一个一个调用,有一个报错就报错
7.available
遍历所有的实例,找到第一个可用实例调用,没有可用实例就报错
8.mergeable
该集群容错模式下,可以合并结果集,一般和group一起使用
指定容错策略也很简单,在@DubboReference注解上的cluster属性指定自己想要的策略即可。
五:dubbo的版本控制
dubbo提供了版本号的概念来帮助我们解决api后续不兼容升级的问题。
使用起来很简单,在@DubboService注解的version属性指定版本号
package com.example.dubbo.provider;
import com.example.api.HelloService;
import org.apache.dubbo.config.annotation.DubboService;
@DubboService(version = "1.0.0")
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String msg) {
return "hello " + msg;
}
}
客户端在@DubboReference注解指定自己需要调用的服务的version
@SpringBootTest
class DubboConsumerApplicationTests {
@DubboReference(check = false,loadbalance = "random",version = "1.0.0")
private HelloService helloService;
@Test
void test(){
System.out.println(helloService.hello("world"));
}
}
注:所以为了防止api的不向下兼容升级,在定义发布服务的时候都应该要指定version属性。
六:dubbo的分组
如果api接口的实现类有多个,那么消费者要走哪个实现类呢?dubbo提供了group的概念来解决这个问题。
例:HelloService有两个实现类HelloServiceImpl和HelloServiceImpl2
package com.example.dubbo.provider;
import com.example.api.HelloService;
import org.apache.dubbo.config.annotation.DubboService;
@DubboService(group = "serviceImpl")
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String msg) {
return "hello HelloServiceImpl";
}
}
package com.example.dubbo.provider;
import com.example.api.HelloService;
import org.apache.dubbo.config.annotation.DubboService;
@DubboService(group = "serviceImpl2")
public class HelloServiceImpl2 implements HelloService {
@Override
public String hello(String msg) {
return "hello HelloServiceImpl2";
}
}
在消费者可以利用group属性指定走哪个实现类的逻辑
@SpringBootTest
class DubboConsumerApplicationTests {
@DubboReference(check = false,loadbalance = "random",group = "serviceImpl2")
private HelloService helloService;
@Test
void test(){
System.out.println(helloService.hello("world"));
}
}
七:dubbo本地存根
有时候我们需要在服务调用之前和之后做一些操作,dubbo为我们提供了本地存根的功能来帮助我们解决这个问题。
我们需要在本地创建一个类,同样也实现api接口,同时必须要有一个注入服务service的构造器。然后在@DubboReference注解的stub属性上指定该类的全类名即可。
例:
package com.example.dubbo.consumer.stub;
import com.example.api.HelloService;
public class HelloServiceStub implements HelloService {
public HelloService helloService;
public HelloServiceStub(HelloService helloService){
this.helloService = helloService;
}
@Override
public String hello(String msg) {
System.out.println("before....");
String result = helloService.hello("world");
System.out.println("after....");
return result;
}
}
@SpringBootTest
class DubboConsumerApplicationTests {
@DubboReference(check = false,loadbalance = "random",
stub = "com.example.dubbo.consumer.stub.HelloServiceStub")
private HelloService helloService;
@Test
void test(){
System.out.println(helloService.hello("world"));
}
}
执行结果:
注:聪明的小伙伴可能已经明白了,这不就是一个装饰器模式嘛。
八:dubbo的服务降级
当我们调用服务失败的时候,有时候需要返回一些兜底数据。dubbo提供mock的功能帮助我们解决这个问题。
首先在消费者本地创建一个实现api接口的类,然后在@DubboReference注解的mock属性指定该类的全类名。
例:
package com.example.dubbo.consumer.mock;
import com.example.api.HelloService;
public class HelloServiceMock implements HelloService {
@Override
public String hello(String msg) {
return "mock data";
}
}
![1647764835(1).png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b501539766804c7ab9b01718fcaa1ec8~tplv-k3u1fbpfcp-watermark.image?)
@SpringBootTest
class DubboConsumerApplicationTests {
@DubboReference(check = false,loadbalance = "random",
mock = "com.example.dubbo.consumer.mock.HelloServiceMock")
private HelloService helloService;
@Test
void test(){
System.out.println(helloService.hello("world"));
}
}
服务端抛出异常:
package com.example.dubbo.provider;
import com.example.api.HelloService;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.rpc.RpcException;
@DubboService()
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String msg) {
throw new RpcException("出异常啦。。");
//return "hello " + msg;
}
}
执行结果:
注:只有当服务抛出RpcException的时候才会执行mock中的逻辑。
九:dubbo如何做参数校验
dubbo支持在JSR303的基础上进行参数的校验。
例:
1.引入hibernate-validator的依赖
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.2.Final</version>
</dependency>
2.在参数上加相关的校验注解
package com.example.api;
import javax.validation.constraints.NotBlank;
/**
* @author 20136
*/
@Data
public class User {
@NotBlank(message = "name不能为空")
private String name;
@NotBlank(message = "密码不能为空")
private String password;
}
3.设置@DubboReference注解或@DubboService注解上的validation属性为"true"
@SpringBootTest
class DubboConsumerApplicationTests {
@DubboReference(check = false,loadbalance = "random",
validation = "true")
private HelloService helloService;
@Test
void test(){
User user = new User();
System.out.println(helloService.testUser(user));
}
}
十:如何异步调用服务
dubbo服务调用默认是同步的,但是dubbo也支持异步调用的方式。
例:
1.设置@DubboReference的async属性为true
2.通过future获取调用的结果
@SpringBootTest
class DubboConsumerApplicationTests {
@DubboReference(check = false,loadbalance = "random",async = true)
private HelloService helloService;
@Test
void test() throws ExecutionException, InterruptedException {
helloService.hello("world");
Future<Object> future = RpcContext.getServiceContext().getFuture();
Object o = future.get();
System.out.println(o);
}
}
十一:dubbo配置的优先级
总的来说:
消费端方法级>服务端方法级>消费端接口级别>服务端接口级别>消费端全局配置>服务端全局配置。
总结:
本篇文章只要介绍了dubbo的一些基本使用,如果有任何疑问,欢迎在下方留言,另外如果文章对你有所帮助,那么点个赞再走吧。