基于 Spring Boot Starter 开发微服务应用
https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/quick-start/spring-boot/
Dubbo Demo
https://github.com/apache/dubbo-samples
高级特性和用法
框架与服务
1 版本与分组
Dubbo服务中,接口并不能唯一确定一个服务,只有 接口+分组+版本号
的三元组才能唯一确定一个服务。
- 当同一个接口针对不同的业务场景、不同的使用需求或者不同的功能模块等场景,可使用服务分组来区分不同的实现方式。同时,这些不同实现所提供的服务是可并存的,也支持互相调用。
- 当接口实现需要升级又要保留原有实现的情况下,即出现不兼容升级时,我们可以使用不同版本号进行区分。
使用方式
使用 @DubboService 注解,配置 group
参数和 version
参数:
生产者
//接口定义
public interface DevelopService {
String invoke(String param);
}
//接口实现1 都是同一个接口不同是的实现类
@DubboService(group = "group1", version = "1.0")
public class DevelopProviderServiceV1 implements DevelopService{
@Override
public String invoke(String param) {
StringBuilder s = new StringBuilder();
s.append("ServiceV1 param:").append(param);
return s.toString();
}
}
//接口实现2
@DubboService(group = "group2", version = "2.0")
public class DevelopProviderServiceV2 implements DevelopService{
@Override
public String invoke(String param) {
StringBuilder s = new StringBuilder();
s.append("ServiceV2 param:").append(param);
return s.toString();
}
}
消费者
@DubboReference(group = "group1", version = "1.0")
private DevelopService developService;
@DubboReference(group = "group2", version = "2.0")
private DevelopService developServiceV2;
//group值为*,标识匹配任意服务分组
@DubboReference(group = "*")
private DevelopService developServiceAny;
@Override
public void run(String... args) throws Exception {
//调用DevelopService的group1分组实现
System.out.println("Dubbo Remote Return ======> " + developService.invoke("1"));
//调用DevelopService的另一个实现
System.out.println("Dubbo Remote Return ======> " + developServiceV2.invoke("2"));
}
2 参数校验
特性说明
参数验证功能是基于 JSR303 实现的,用户只需标识 JSR303 标准的验证 annotation,并通过声明 filter 来实现验证。
Maven 依赖
<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>
使用场景
服务端在向外提供接口服务时,解决各种接口参数校验问题。
参考用例 https://github.com/apache/dubbo-samples/tree/master/dubbo-samples-validation
使用方式
参数标注示例
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;
}
}
分组验证示例
public interface ValidationService { // 缺省可按服务接口区分验证场景,如:@NotNull(groups = ValidationService.class)
@interface Save{} // 与方法同名接口,首字母大写,用于区分验证场景,如:@NotNull(groups = ValidationService.Save.class),可选
void save(ValidationParameter parameter);
void update(ValidationParameter parameter);
}
方法中参数验证示例
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
public interface ValidationService {
void save(@NotNull ValidationParameter parameter); // 验证参数不为空
void delete(@Min(1) int id); // 直接对基本类型参数验证
}
3 集群容错
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
各节点关系:
- 这里的
Invoker
是Provider
的一个可调用Service
的抽象,Invoker
封装了Provider
地址及Service
接口信息 Directory
代表多个Invoker
,可以把它看成List<Invoker>
,但与List
不同的是,它的值可能是动态变化的,比如注册中心推送变更Cluster
将Directory
中的多个Invoker
伪装成一个Invoker
,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个Router
负责从多个Invoker
中按路由规则选出子集,比如读写分离,应用隔离等LoadBalance
负责从多个Invoker
中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选
Failover Cluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2"
来设置重试次数(不含第一次)。
重试次数配置如下:
<dubbo:service retries="2" />
或
<dubbo:reference retries="2" />
或
<dubbo:reference>
<dubbo:method name="findFoo" retries="2" />
</dubbo:reference>
Failsafe Cluster 失败安全
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
Failback Cluster 失败自动恢复
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster 并行调用多个服务器
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2"
来设置最大并行数。
Broadcast Cluster 广播调用
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
现在广播调用中,可以通过 broadcast.fail.percent 配置节点调用失败的比例,当达到这个比例后,BroadcastClusterInvoker 将不再调用其他节点,直接抛出异常。 broadcast.fail.percent 取值在 0~100 范围内。默认情况下当全部调用失败后,才会抛出异常。 broadcast.fail.percent 只是控制的当失败后是否继续调用其他节点,并不改变结果(任意一台报错则报错)。broadcast.fail.percent 参数 在 dubbo2.7.10 及以上版本生效。
Broadcast Cluster 配置 broadcast.fail.percent。
broadcast.fail.percent=20 代表了当 20% 的节点调用失败就抛出异常,不再调用其他节点。
@reference(cluster = "broadcast", parameters = {"broadcast.fail.percent", "20"})
集群模式配置
按照以下示例在服务提供方和消费方配置集群模式
<dubbo:service cluster="failsafe" />
或
<dubbo:reference cluster="failsafe" />
4 服务降级
Dubbo服务降级通常是指在服务不可用或负载过高时,为某些服务调用提供备选方案,以防止系统崩溃或性能下降。在Dubbo中,可以使用Mock
机制来实现服务降级。
Mock伪造返回数据,可用作服务降级
- 强制返回null
<dubbo:reference id="demoService" interface="com.example.DemoService" mock="return null"/>
- 这种方式强制执行本地伪装逻辑,即使远程调用正常也会执行降级逻辑。
- 一般用这种,直接降级
<dubbo:reference id="demoService" interface="com.example.DemoService" mock="force:true"/>
或者
@DubboService(version = "1.0.0",timeout = 3000, mock = "force:true")
- 1 mock = force:return+null:强制服务返回null,不会进行RPC调用
- 2 mock = fail:return+null:调用服务失败后返回null,会进行RPC调用。
- 3 mock = throw:直接跑RpcException,不会RPC调用
以下是使用Dubbo Mock机制的步骤:
-
实现
Mock
接口。 -
配置
Mock
规则。
1 定义一个服务接口:
public interface MyService {
String sayHello(String name);
}
2 实现Mock接口:
public class MyServiceMock implements MyService {
@Override
public String sayHello(String name) {
return "Mocked: Hello, " + name;
}
}
3 在provider的配置文件中添加Mock规则:
<dubbo:service interface="MyService" ref="myService" mock="MyServiceMock"/>
或者在注解配置中使用:
@Service(version = "1.0.0", mock = "MyServiceMock")
public class MyServiceImpl implements MyService {
// ...
}
当MyService
不可用时,Dubbo会自动使用MyServiceMock
的实现。
注意:
-
确保
Mock
类和服务接口在同一个ClassLoader
下。 -
如果使用注解配置,请确保
Mock
类在服务提供者的类路径下。 -
如果服务提供者不可用,Dubbo也会使用
Mock
。
以上是Dubbo服务降级的一种简单实现方式,具体的降级策略可能需要根据实际情况进行定制。