3.Dubbo各种特性使用方法
准备
将producer模块复制出来三份,分别修改以下内容
-
service.port
-
dubbo.protocol.port
-
HelloWordImpl.java中的输出内容
public class HelloWordImpl implements HelloWord { public String helloWord(String s) { return "HelloWordImpl20881:"+s; // return "HelloWordImpl20882:"+s; // return "HelloWordImpl20883:"+s; } }
-
三个生产者全部启动
version
消费者可通过指定版本来控制要RPC调用对应版本的生产者
-
修改生产者实现类注解
//指定对外暴漏的服务版本为20882 @DubboReference(version = "20882")
-
修改消费者@DubboReference
//指定请求版本为20882的生产者 @DubboReference(version = "20882")
protocol通讯协议
dubbo可对协议扩展,例如:Dubbo\Rest\Http\⾃定义
配置单个协议
protocol:
port: 20881 #配置当前这个服务在dubbo容器中的端⼝号,每个dubbo容器内部的服务的端⼝号唯一
name: dubbo
配置多个协议
protocols:
protocol_1:
id: protocol_1
name: rest
port: 8080
host: 127.0.0.1
protocol_2:
id: protocol_2
name: dubbo
port: 21881
host: 127.0.0.1
protocol_3:
id: protocol_3
name: http
port: 8181
host: 127.0.0.1
协议指定方式
//protocol不指定,yml声明了几个协议,就会对每个协议分别创建一份对应请求协议的服务
@DubboService(version = "20883",protocol = "配置中指定的id")
public class HelloWordImpl implements HelloWord {
public String helloWord(String s) {
return "HelloWordImpl20883:"+s;
}
}
选择Rest协议
POM
添加GVA
<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-rpc-rest -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-rest</artifactId>
<version>3.0.6</version>
</dependency>
YML
server:
port: 8080
dubbo:
application:
name: hello-word-service #服务名称
registry:
address: zookeeper://192.168.0.104:2181,192.168.0.104:2182,192.168.0.104:2183,192.168.0.104:2184 #zk地址
protocol:
port: 6060 #rest暴漏端口
name: rest #选择rest
修改生产者实现类
下面最终请求地址为 127.0.0.1:6066/rest/hello/helloWord?param=value
// path相当于在@Path前又加了一个前缀,可省略
@DubboService(protocol = "rest",path = "rest")
@Path("hello") //@RequestMapping
public class HelloWordImpl implements HelloWord {
@GET //发送get请求,还有put post等
@Path("helloWord") // 方法请求路径 ,@GET和@Path = @GetMapping
@Produces({ContentType.APPLICATION_JSON_UTF_8}) //设置ContentType,可设置多个
public String helloWord(@QueryParam("param") String param) {
return "HelloWordImpl20881:"+param;
}
}
跳过注册中心,直连请求
生产者配置
假设生产者通过下面配置,并且实现类只标注了@DubboService(version = "v1"),未加protocol=‘’属性来指明到底用哪个通讯协议,则生产者启动会开启两个协议通讯端口
protocols:
protocol_20881:
id: protocol_20881
name: dubbo
port: 20881
host: 0.0.0.0
protocol_20882:
id: protocol_20882
name: dubbo
port: 20882
host: 0.0.0.0
消费者直连方式
//添加版本和直连的url/端口/接口全包名
//输入20882和20881都可调用成功,说明开启了两个通讯协议端口
@DubboReference(version = "v1",url ="dubbo://127.0.0.1:20882/myinterface.HelloWord")
private HelloWord helloWord;
@Test
void contextLoads() {
//执行代理方法
System.out.println(helloWord.helloWord("RB"));
}
超时配置
@DubboReference(version = "v1",timeout = 3000)
//消费者配置超过3秒就超时报错
@DubboService(version = "v1",timeout = 2000)
//生产者配置超过2秒就报错
-
都不配置默认超时时间1秒
-
如果消费者或生产者只配置一方,则超时时间消费者和生产者共享配置
-
如果消费者配置3秒过期,生产者配置2秒过期
-
当方法执行超过3秒后,消费者报错
-
当方法执行超过2秒后,生产者会打印WARN日志,但方法还会执行完,不会终止
[04/04/22 14:47:39:086 CST] NettyServerWorker-3-1 WARN transport.AbstractServer: [DUBBO] All clients has disconnected from /192.168.0.105:20882. You can graceful shutdown now., dubbo version: 3.0.6, current host: 192.168.0.105
-
集群容错(高可用)机制
dubbo为集群调⽤提供了容错⽅案:
-
failover(默认):
当出现失败时,会进⾏重试,默认重试2次,⼀共三次调⽤。但是会出现幂等性问题。 虽然会出现幂等性问题,但是依然推荐使⽤这种容错机制,在业务层⾯解决幂等性问题:
-
⽅案⼀:把数据的业务id作为数据库的联合主键,此时业务id不能重复。
-
⽅案⼆(推荐):使⽤分布式锁来解决重复消费问题。
-
-
failfast:当出现失败时。⽴即报错,不进⾏重试。
-
failsafe:失败不报错,记⼊⽇志。
-
failback:失败就失败,开启定时任务 定时重发。
-
forking:并⾏访问多个服务器,获取某⼀个结果既视为成功。
结论:使⽤dubbo,不推荐把重试关掉,⽽是在⾮幂等性操作的场景下,服务提供者⽅ 要做幂等性的解决⽅案(保证)。
服务降级
可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
向注册中心写入动态配置覆盖规则:
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));
其中:
mock=force:return+null
表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。- 还可以改为
mock=fail:return+null
表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。 - Demo
//消费者注解添加mock
@DubboReference(version = "v1",mock = "force:return timeout")
本地存根
类似熔断机制
远程服务后,客户端通常只剩下接⼝,⽽实现全在服务器端,但提供⽅有些时候想在客户端 也执⾏部分逻辑,⽐如:做 ThreadLocal 缓存,提前验证参数,调⽤失败后伪造容错数据等 等,此时就需要在 API 中带上 Stub,客户端⽣成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub 1,然后把 Stub 暴露给⽤户,Stub 可以决定要不要去调 Proxy。
消费端配置
@DubboReference(version = "v1",stub = "com.rb.customer.HelloWord1Stub")
private HelloWord helloWord;
//或
@DubboReference(version = "v1",stub = "true")
private HelloWord helloWord;
Stub类
public class HelloWordStub implements HelloWord {
private final HelloWord helloWord;
public HelloWord1Stub(HelloWord helloWord) {
this.helloWord = helloWord;
}
public String helloWord(String parameter) {
try {
return helloWord.helloWord(parameter);
} catch (Exception e) {
return "Stub-Error:"+parameter;
}
}
}
流程
stub配置为true
- dubbo会在接口HelloWord包路径下寻找HelloWordStub.java
stub配置为自定义*Stub.java路径
-
dubbo会在使用定义路径下的*Stub.java
然后执行下面两步
-
消费端实际调用的是HelloWordStub下helloWord()方法
-
如果HelloWordStub类中 Dubbo提供的HelloWord类报错,则调用catch方法
参数回调
个人理解就是类似支付后回调的流程,用户支付后通过Dubbo调用支付宝,支付宝入账后再通过反向代理调用用户端提供的方法。
通过参数回调从服务器端调用客户端逻辑
参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。Dubbo 将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑。可以参考 dubbo 项目中的示例代码。
创建生产者回调接口
package myinterface;
/**
* 创建生产者回调接⼝
*/
public interface CallbackListener {
void callback(String msg);
}
创建哪些功能方法需要回调
package myinterface;
/**
* 定义一个功能接口
*/
public interface HelloWord {
String helloWord(String parameter);
//创建带回调的方法,key用于让dubbo知道具体回调哪个方法,callbackListener为生产者调用回调的对象
String helloWord(String parameter,String key,CallbackListener callbackListener);
}
消费者端添加实现回调接口类
package com.rb.customer;
import myinterface.CallbackListener;
import java.io.Serializable;
public class CallbackServiceImpl implements CallbackListener, Serializable {
@Override
public void callback(String s) {
//如果是支付后回调,那这就是支付成功支付宝通知用户该修改订单状态了
System.out.println("生产者回调:"+s);
}
}
消费者发起请求
helloWord.helloWord("RB","V1",new CallbackServiceImpl())
生产者接到请求处理
package com.rb.producer.impl;
import myinterface.CallbackListener;
import myinterface.HelloWord;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.annotation.Argument;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.config.annotation.Method;
import org.apache.dubbo.rpc.RpcContext;
import java.util.concurrent.TimeUnit;
/**
* 对接口的实现
*/
//添加Dubbo注解
//name:哪个方法开启回调,arguments.index:对应方法第几个参数开启回调,可配置多个
@DubboService(version = "v1",methods = {@Method(name="helloWord",arguments = {@Argument(index = 2, callback = true)})})
public class HelloWordImpl implements HelloWord {
public String helloWord(String param) {
return "HelloWordImpl20882:"+param;
}
@Override
public String helloWord(String s, String s1, CallbackListener callbackListener) {
callbackListener.callback("abcd");//业务处理完,要传给消费端的参数,这个也是一个代理类,调用后消费端就可收到abcd
return "HelloWordImpl20882:"+s;
}
}
执行
当消费者发起调用后,消费者会打印CallbackServiceImpl.callback方法
异步调用
方法接口添加异步接口
/**
* 定义一个功能接口
*/
public interface HelloWord {
String helloWord(String parameter);
String helloWord(String parameter,String key,CallbackListener callbackListener);
//异步方法
CompletableFuture<String> helloWordAsync(String name);
}
消费者调用
@Test
void contextLoads() throws IOException {
//执行代理方法
CompletableFuture<String> rb = helloWord.helloWordAsync("RB");
rb.whenComplete((v, e) -> {
if (e != null) {
e.printStackTrace();
} else {
System.out.println("异步结果:" + v);
}
});
System.out.println("当前线程调⽤结束");
System.in.read();//阻塞,防止线程关了,回调失败
}
生产者实现
package com.rb.producer.impl;
import lombok.SneakyThrows;
import myinterface.CallbackListener;
import myinterface.HelloWord;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.annotation.Argument;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.config.annotation.Method;
import org.apache.dubbo.rpc.RpcContext;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* 对接口的实现
*/
//添加Dubbo注解
@DubboService(version = "v1",methods = {@Method(name="helloWord",arguments = {@Argument(index = 2, callback = true)})})
public class HelloWordImpl implements HelloWord {
public String helloWord(String param) {
return "HelloWordImpl20882:"+param;
}
@Override
public String helloWord(String s, String s1, CallbackListener callbackListener) {
callbackListener.callback("abcd");
return "HelloWordImpl20882:"+s;
}
@SneakyThrows
@Override
public CompletableFuture<String> helloWordAsync(String s) {
System.out.println("异步调⽤:" + s);
TimeUnit.SECONDS.sleep(2);//模拟耗时业务
return CompletableFuture.supplyAsync(() -> {
return "HelloWordImplAsync20882:"+s;
});
}
}