JVM 启动 -D 参数优先,这样可以使用户在部署和启动时进行参数重写,比如在启动时需改变协议的端口。
XML 次之,如果在 XML 中有配置,则 dubbo.properties 中的相应配置项无效。
Properties 最后,相当于缺省值,只有 XML 没有配置时,dubbo.properties 的相应配置项才会生效,通常用于共享公共配置,比如应用名。
优先权:dubbo.properties < dubbo.xml < -D 参数
我们复制一节的项目,来演示。
dubbo.protocol.port=20882
#就是服务名,不能跟别的dubbo提供端重复
dubbo.application.name=gmall-user-provider
#是指定注册中心协议
dubbo.registry.protocol=zookeeper
#注册中心的地址加端口号
dubbo.registry.address=127.0.0.1:2181
#注解方式要扫描的包
dubbo.scan.base-package=com.gf
#是分布式固定是dubbo,不要改
dubbo.protocol.name=dubbo
#服务暴露端口
dubbo.protocol.port=20881
#表示从注册中心发现监控中心地址
dubbo.monitor.protocol=registry
我们运行项目测试结果如下:
这说明VM参数配置的优先权最高。
我们去掉VM参数测试如下:
我们最后再去掉application.properties中的 服务暴露端口配置,测试如下:
所以我得出dubbo的配置优先权:dubbo.properties < dubbo.xml < -D 参数
当我们先启动 服务消费者,没有先启动 服务提供者进服务注册到注册中心时,是会报错的。报错信息 为没有可用的服务提供者
我们可以通过设置来关闭 服务消费者 ,对远程服务的检查:
方式1
注解 @Reference 中有个属性 check 我为设置为 false ,就可以关闭启动是时对该服务的检查,到真正使用时才检查是否可用:
@Service
public class OrderServiceImpl implements OrderService {
@Reference(check = false)
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
System.out.println( "用户id : " + userId );
//查询用户地址
List<UserAddress> addressList = userService.getUserAddressList( userId );
return addressList;
}
}
我重启动消费者 ,发现已经正常了。
我们的演示项目中远程服务少 ,如果服务远程服务比较多,又想去掉启东时检测,我们一个个的设置 ,比较麻烦 ,Dubbo为我提供了统一配置:
dubbo.consumer.check=false
以 timeout 为例,显示了配置的查找顺序,其它 retries, loadbalance, actives 等类似:
Dubbo官方建议由服务提供方设置超时,因为一个方法需要执行多长时间,服务提供方更清楚,如果一个消费方同时引用多个服务,就不需要关心每个服务的超时设置。
服务端 通过 Dubbo 的@Service 注解设置实例:
@Service(timeout = 3000)
@Component
public class UserServiceImpl implements UserService{
@Override
public List<UserAddress> getUserAddressList(String userId) {
List<UserAddress> list = new ArrayList<>();
UserAddress address1 = new UserAddress(1 , "上海市杨浦区xxx路xxx号" , "1" , "王某某","0");
UserAddress address2 = new UserAddress(2 , "上海市徐汇区xxx路xxx号" , "1" , "张某某","1");
list.add( address1 );
list.add( address2 );
try {
Thread.sleep( 4000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
return list;
}
}
我们远程访问接口测试结果如下,出现了等待服务器端响应超时的错误。
timeout 一般配合 retries(重试次数,不包含第一次调用)使用
@Service(timeout = 3000 , retries = 3)
@Component
public class UserServiceImpl implements UserService{
@Override
public List<UserAddress> getUserAddressList(String userId) {
System.out.println("UserServiceImpl 被调用...");
List<UserAddress> list = new ArrayList<>();
UserAddress address1 = new UserAddress(1 , "上海市杨浦区xxx路xxx号" , "1" , "王某某","0");
UserAddress address2 = new UserAddress(2 , "上海市徐汇区xxx路xxx号" , "1" , "张某某","1");
list.add( address1 );
list.add( address2 );
try {
Thread.sleep( 4000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
return list;
}
}
访问远程接口 测试结果如下,“UserServiceImpl 被调用...” 会打印 4 次
通常正式环境,我们的服务提供者会部署多个 ,那么一般是调用失败后,会轮询重试各个服务,本例如果我们部署3台机器,访问的是server1 ,那么server1 会先打印1次 ,然后重试 server1、server2、server3 会各打印1次。
retries 应该在合适的时候使用,一般如果方法的访问是幂等 的,我们可以使用(一般情况下我们应该把 查询、删除、修改的方法 设计成幂等的)。
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
我们在服务提供方再创建一个UserService的实现类 UserServiceImpl2
@Service(version = "1.0.0")
@Component
public class UserServiceImpl implements UserService{
@Override
public List<UserAddress> getUserAddressList(String userId) {
System.out.println("UserServiceImpl 旧版本...");
List<UserAddress> list = new ArrayList<>();
UserAddress address1 = new UserAddress(1 , "上海市杨浦区xxx路xxx号" , "1" , "王某某","0");
UserAddress address2 = new UserAddress(2 , "上海市徐汇区xxx路xxx号" , "1" , "张某某","1");
list.add( address1 );
list.add( address2 );
return list;
}
}
@Service(version = "2.0.0")
@Component
public class UserServiceImpl2 implements UserService {
@Override
public List<UserAddress> getUserAddressList(String userId) {
System.out.println( "UserServiceImpl 新版本..." );
List<UserAddress> list = new ArrayList<>();
UserAddress address1 = new UserAddress( 1, "上海市杨浦区xxx路xxx号", "1", "王某某", "0" );
UserAddress address2 = new UserAddress( 2, "上海市徐汇区xxx路xxx号", "1", "张某某", "1" );
list.add( address1 );
list.add( address2 );
return list;
}
}
我们之后就可以在服务消费方的 @Reference 设置调用的服务版本。
@Service
public class OrderServiceImpl implements OrderService {
@Reference(version = "2.0.0")
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
System.out.println( "用户id : " + userId );
//查询用户地址
List<UserAddress> addressList = userService.getUserAddressList( userId );
return addressList;
}
}
启动服务提供者和消费者 ,访问远程接口测试结果如下,打印的是 “新版本” 说明我们调用的就是 “2.0.0” 版本的服务。这也就实现Dubbo官方说的 灰度发布
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub ,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
我们在服务消费方创建 UserServiceSub 并实现 UserService 接口
public class UserServiceStub implements UserService{
private final UserService userService;
// 构造函数传入真正的远程代理对象
public UserServiceStub(UserService userService) {
this.userService = userService;
}
@Override
public List<UserAddress> getUserAddressList(String userId) {
System.out.println("UserServiceStub ...");
// 此代码在客户端执行, 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
if (!StringUtils.isEmpty( userId )) {
return userService.getUserAddressList( userId );
}
// 你可以容错,可以做任何AOP拦截事项
return null;
}
}
通过Dubbo 的**@Reference** 的 stub 属性 使用本地存根
@Service
public class OrderServiceImpl implements OrderService {
@Reference(stub = "com.gf.service.impl.UserServiceStub")
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
System.out.println( "用户id : " + userId );
//查询用户地址
List<UserAddress> addressList = userService.getUserAddressList( userId );
return addressList;
}
}
我们访问远程接口时,服务消费者会先走存根 ,验证参数userId是否为空:
评论