文章目录
基本使用
参数校验
pom
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.2.0.Final</version>
</dependency>
使用场景与实例
服务端在向外提供接口服务时,
解决各种接口参数校验问题
参数标注示例
import java.io.Serializable;
import java.util.Date;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
public class ValidationParameter implements Serializable {
private static final long serialVersionUID = 7158911668568000392L;
@NotNull // 不允许为空
@Size(min = 1, max = 20) // 长度或大小范围
private String name;
@NotNull(groups = ValidationService.Save.class) // 保存时不允许为空,更新时允许为空 ,表示不更新该字段
@Pattern(regexp = "^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$")
private String email;
@Min(18) // 最小值
@Max(100) // 最大值
private int age;
@Past // 必须为一个过去的时间
private Date loginDate;
@Future // 必须为一个未来的时间
private Date expiryDate;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getLoginDate() {
return loginDate;
}
public void setLoginDate(Date loginDate) {
this.loginDate = loginDate;
}
public Date getExpiryDate() {
return expiryDate;
}
public void setExpiryDate(Date expiryDate) {
this.expiryDate = expiryDate;
}
}
参数验证示例
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
public interface ValidationService {
void save(@NotNull ValidationParameter parameter); // 验证参数不为空
void delete(@Min(1) int id); // 直接对基本类型参数验证
}
开启验证
在
客户端
验证参数
@DubboReference(methods = {},validation = "true")
private GoodsService goodsService;
在
服务器端
验证参数
@DubboService(validation = "true")
public class GoodsServiceImpl implements GoodsService { }
多版本控制version
提供者
@DubboService(version = "v1.0")
public class GoodsServiceImpl implements GoodsService {}
消费者
@DubboReference(version = "v1.0")
private GoodsService goodsService;
负载均衡
注解配置
@DubboService(loadbalance = "roundrobin",methods = {})
public class GoodsServiceImpl implements GoodsService {}
@DubboReference(loadbalance = "roundrobin",methods = {})
private GoodsService goodsService;
服务分组
使用场景
当一个接口有多种实现时
,可以用 group 区分
提供者
使用
@DubboService
注解,添加group
参数
@DubboService(group = "demo")
public class DemoServiceImpl implements DemoService {
...
}
@DubboService(group = "demo2")
public class Demo2ServiceImpl implements DemoService {
...
}
消费者
使用
@DubboReference
注解,添加group
参数
@DubboReference(group = "demo")
private DemoService demoService;
@DubboReference(group = "demo2")
private DemoService demoService2;
// group值为 *----> 标识 匹配任意服务分组
@DubboReference(group = "*")
private DemoService demoService2;
服务超时
超时后,进行重试,也就是
接下来的容错
—> 设为 1 s
// 还可以进行方法级别的 超时设置
// 谁定义 服务就大概知道什么情况,建议 设置在服务提供方
@DubboService(version = "v1.0",timeout = 1000)
public class GoodsServiceImpl implements GoodsService {}
服务重试
- 设置
超时时间
,在这个时间段内,无法完成访问,自动断开连接
- 如果出现网络抖动,则这一次请求就会失败
- Dubbo提供
重试机制
避免类似问题发生 - 通过
retries
属性设置重试次数
,默认为 2次
@DubboService(version = "v1.0",timeout = 1000,retries = 3)
public class GoodsServiceImpl implements GoodsService {}
集群容错
Failover Cluster
失败自动切换
,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。
可通过 retries="2"
来设置重试次数(不含第一次)。
重试次数配置如下:
@DubboReference(version = "v1.0",methods = {},cluster="failover")
private GoodsService goodsService;
服务降级
服务降级
是—>指服务在非正常情况下进行降级应急处理
,一般配合容错
使用场景
- 某 服务或接口负荷 超出最大承载能力范围,需要进行
降级
应急处理,避免系统崩溃 - 调用的 某非关键服务或接口暂时不可用时,返回
模拟数据或空
,业务还能继续可用 降级
非核心业务的服务或接口,腾出系统资源,尽量保证核心业务的正常运行- 某上游基础服务超时或不可用时,
执行能快速响应的降级预案
,避免服务整体雪崩
使用方式
@DubboReference(version = "v1.0", methods = {}, retries = 2, mock = "force:return null")
private GoodsService goodsService;
异步调用
定义 CompletableFuture 签名的接口
服务接口定义
public interface AsyncService {
CompletableFuture<String> sayHello(String name);
}
服务实现
import org.apache.dubbo.config.annotation.DubboService;
import java.util.concurrent.CompletableFuture;
@DubboService(version = "v1.0", timeout = 500000)
public class AsyncServiceImpl implements AsyncService {
// 还可以整合线程池!!!
@Override
public CompletableFuture<String> sayHello(String name) {
return CompletableFuture.supplyAsync(() -> {
System.out.println(name);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//返回值为String,与定义时相同!
return "async response from provider.";
});
}
}
测试
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@RestController
public class TestBean {
@DubboReference(version = "v1.0")
private AsyncService asyncService;
@GetMapping("/test")
public String test() throws InterruptedException, ExecutionException {
CompletableFuture<String> str = asyncService.sayHello("xiaoding");
return "success";
}
}
访问 秒相应!!!
启动时检查
Dubbo 缺省会在启动时检查依赖的服务是否可用
,** 不可用时 会抛出异常,阻止 Spring 初始化完成**
好处:以便上线时,能及早发现问题,默认 check="true"
可以通过
check="false"
关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动
// 推荐设置为false!
@DubboReference(check = false)
private GoodsService goodsService;
check----->只用来 启动时检查,
运行时没有相应的依赖仍然会报错!!!
诊断与调优
优雅停机
设置
优雅停机超时时间
,缺省超时时间是10 秒
,如果超时则强制关闭。 该参数可在 dubbo.properties 文件里配置例如:配置为30 秒
dubbo:
application:
name: dubbo-provider #dubbo提供者名称
shutwait: 30
调用结果缓存
缓存类型
lru
基于最近最少使用
原则删除多余缓存,保持最热的数据被缓存。
threadlocal
当前线程缓存,比如一个页面渲染,用到很多 portal,每个 portal 都要去查用户信息,通过线程缓存,可以减少这种多余访问。
使用场景
结果缓存,用于加速热门数据的访问速度
,Dubbo 提供声明式缓存,以减少用户加缓存的工作量
使用方式:可以指定方法或类
进行缓存
@DubboReference(cache = "lru",methods = {})
private GoodsService goodsService;
注册信息 简化
背景
服务为维度注册进入注册中心,导致了数据量的膨胀,进而引发注册中心 (如 zookeeper) 的网络开销增大,性能降低
使用场景
数据量大导致注册中心的网络开销增大,性能降低,减少注册中心上服务的注册数据
使用方式: 用 Spring-Bean 配置zk,yaml中 不需要配置!
提供者
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.context.annotation.Bean;
public class DubboConfig {
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
registryConfig.setSimplified(true);
registryConfig.setExtraKeys("retries,owner");
return registryConfig;
}
}
class Service {
// 暴露服务
@DubboService(version = "1.1.8", group = "d-test", executes = 4500, retries = 7, owner = "victanno", timeout = 5300)
public class AnnotationServiceImpl implements AnnotationService {
public String sayHello(String name) {
System.out.println("async provider received: " + name);
return "annotation: hello, " + name;
}
}
}
消费者
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
public class DubboConfig {
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
registryConfig.setSimplified(true);
return registryConfig;
}
}
@Component("annotationAction")
public class AnnotationAction {
@DubboReference(version = "1.1.8", group = "d-test", owner = "vvvanno", retries = 4, actives = 6, timeout = 4500)
private AnnotationService annotationService;
public String doSayHello(String name) {
return annotationService.sayHello(name);
}
}
注意
如果一个应用中既有 provider 又有 consumer,那么配置需要合并成:
@Bean
//注册中心配置
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
registryConfig.setSimplified(true);
//只对provider生效
registryConfig.setExtraKeys("retries,owner");
return registryConfig;
}
并发控制
服务器端并发执行(或
占用线程池线程数
)不能超过 10 个
@DubboService(loadbalance = "roundrobin",executes = 10)
客户端并发执行(或
占用连接的请求数
)不能超过 10 个
@DubboReference(actives=10,methods = {})
private GoodsService goodsService;
连接控制
Dubbo 中服务端和客户端的
连接控制
服务端连接控制
限制服务器端
接受的连接不能超过 10 个
dubbo:
application:
name: dubbo-provider # 注册的服务名
registry:
address: zookeeper://192.168.111.101:2181 # 注册地址 使用zookeeper
protocol:
name: dubbo # dubbo协议
port: -1
accepts: 10 # 连接控制
客户端连接控制
限制客户端 服务
使用连接不能超过 10 个
@DubboReference(connections = 10)
private GoodsService goodsService;
粘滞连接
使用场景
粘滞连接 用于 有状态服务,尽可能让客户端总是向同一提供者发起调用
,除非该提供者挂了,再连另一台
粘滞连接将自动开启 延迟连接,以减少长连接数
使用方式:类或方法级别
@DubboReference(sticky = true, methods = {})
private GoodsService goodsService;
线程模型
如何调整
线程模型
在protocol下配置dispatcher: all
dubbo:
application:
name: dubbo-springboot-demo-provider
protocol:
name: dubbo
port: -1
#配置线程模型
dispatcher: all
registry:
id: zk-registry
address: zookeeper://127.0.0.1:2181
config-center:
address: zookeeper://127.0.0.1:2181
metadata-report:
address: zookeeper://127.0.0.1:2181
各线程模型的配置值:
- All Dispatcher all
- Direct Dispatcher direct
- Execution Dispatcher execution
- Message Only Dispatcher: message
- Connection Ordered Dispatcher: connection
配置注册服务
使用
Java Config
代替注解
注意: Java Config 是 DubboService
或 DubboReference
的替代方式,对于有复杂配置需求的服务建议使用这种方式
@Configuration
public class ProviderConfiguration {
@Bean
public ServiceConfig demoService() {
ServiceConfig service = new ServiceConfig();
service.setInterface(DemoService.class);
service.setRef(new DemoServiceImpl());
service.setGroup("dev");
service.setVersion("1.0.0");
Map<String, String> parameters = new HashMap<>();
service.setParameters(parameters);
return service;
}
}
协议
dubbo
使用场景
适合大并发小数据量
的服务调用,服务消费者远大于服务提供者的情景
使用方式
dubbo:
application:
name: dubbo-provider
registry:
address: zookeeper://192.168.111.101:2181 #注册地址 使用zookeeper
protocol:
name: dubbo # 使用dubbo协议
port: -1
Triple
基于
http2
上的协议、完全兼容grpc、更加高效
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-triple</artifactId>
<version>${dubbo.version}</version>
</dependency>
yaml
dubbo:
application:
name: dubbo-provider #dubbo提供者名称
registry:
address: zookeeper://192.168.111.101:2181 #注册地址 使用zookeeper
protocol:
name: tri #使用triple协议
port: -1
其他
配置中心
Nacos
dubbo:
config-center:
address: nacos://127.0.0.1:8848
Zookeeper
dubbo:
config-center:
address: zookeeper://127.0.0.1:2181
应用级服务发现
应用 升级到 Dubbo 3.0 后,服务端自动开启
接口级 + 应用级
双注册功能,默认—>无需开发者修改任何配置
# 双注册
dubbo.application.register-mode=all
# 仅 应用级注册
dubbo.application.register-mode=instance
# 仅 接口级注册
dubbo.application.register-mode=interface
yaml文件
dubbo:
application:
name: dubbo-provider # dubbo提供者名称
register-mode: instance
registry:
address: zookeeper://192.168.111.101:2181 #注册地址 使用zookeeper
protocol:
name: dubbo #使用dubbo协议
port: -1
延迟暴露
所有服务
都将在 Spring 初始化完成后进行暴露
,如果你不需要延迟暴露服务
,无需配置delay
延迟 5 秒暴露服务
@DubboService(version = "v1.0", delay = 5000)
public class ServiceImpl implements AsyncService {}
问题
线程池资源枯竭
服务端的 线程资源耗尽
了。 默认
情况下,Dubbo 服务端的业务线程数是 200 个。如果多个并发请求量超过了 200
,就会拒绝新的请求,抛出此错误
dubbo:
provider:
threads: 300
可能的原因
- Consumer 的
并发请求量太大
,导致 Provider 端创建的线程数量超限
- 可能 Provider 端在执行业务的时候,由于业务调用外部应用接口,导致线程出现阻塞,从而
导致线程池回收不了线程
排查和解决步骤
- 开启 Dubbo 的
访问日志功能
,排查是否有短时间内大量调用 RPC 服务
的情况 - 通过 jps 和 jstack 指令检查线程池中各个线程的状态,看下是否有业务调用外部应用接口造成阻塞
- 如果是 Consumer 的并发请求量太大,那么调整 Provider 端的
dubbo.provider.threads
参数,将 Dubbo 的线程池的数目调大 - 如果 Provider 业务的 QPS 实在太大,目前的服务器数目处理不完,那么
增加 Provider 端服务器的数量
,让更多的服务器分担压力