6 dubbo高级用法
6.1 启动时检查
通过spring配置文件
关闭某个服务的启动时检查 (没有提供者时报错):
代码语言:javascript复制<dubbo:reference interface="com.foo.BarService" check="false" />
关闭所有服务的启动时检查 (没有提供者时报错):
代码语言:javascript复制<dubbo:consumer check="false" />
关闭注册中心启动时检查 (注册订阅失败时报错):
代码语言:javascript复制<dubbo:registry check="false" />
通过 dubbo.properties
代码语言:javascript复制dubbo.reference.com.foo.BarService.check=false
dubbo.consumer.check=false
dubbo.registry.check=false
通过 -D 参数
代码语言:javascript复制java -Ddubbo.reference.com.foo.BarService.check=false
java -Ddubbo.consumer.check=false
java -Ddubbo.registry.check=false
6.2 直连提供者
Dubbo 中点对点的直连方式
在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直连方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。
配置:
代码语言:javascript复制<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />
6.3 集群容错
集群调用失败时,Dubbo 提供的容错方案
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
各节点关系:
- • 这里的
Invoker
是Provider
的一个可调用Service
的抽象,Invoker
封装了Provider
地址及Service
接口信息 - •
Directory
代表多个Invoker
,可以把它看成List<Invoker>
,但与List
不同的是,它的值可能是动态变化的,比如注册中心推送变更 - •
Cluster
将Directory
中的多个Invoker
伪装成一个Invoker
,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个 - •
Router
负责从多个Invoker
中按路由规则选出子集,比如读写分离,应用隔离等 - •
LoadBalance
负责从多个Invoker
中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选
6.3.1 集群容错模式
Failover Cluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过retries="2"
来设置重试次数(不含第一次)。该配置为缺省配置
Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过forks="2"
来设置最大并行数。
Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
Available Cluster
调用目前可用的实例(只调用一个),如果当前没有可用的实例,则抛出异常。通常用于不需要负载均衡的场景。
配置:
代码语言:javascript复制@reference(cluster = "broadcast", parameters = {"broadcast.fail.percent", "20"})
6.4 负载均衡
Dubbo 提供的集群负载均衡策略
在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。
具体实现上,Dubbo 提供的是客户端负载均衡,即由 Consumer 通过负载均衡算法得出需要将请求提交到哪个Provider 实例。
负载均衡策略
目前 Dubbo 内置了如下负载均衡算法,用户可直接配置使用:
算法 | 特性 | 备注 |
---|---|---|
RandomLoadBalance | 加权随机 | 默认算法,默认权重相同 |
RoundRobinLoadBalance | 加权轮询 | 借鉴于 Nginx 的平滑加权轮询算法,默认权重相同 |
LeastActiveLoadBalance | 最少活跃优先 加权随机 | 背后是能者多劳的思想,最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差;使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。 |
ShortestResponseLoadBalance | 最短响应优先 加权随机 | 更加关注响应速度 |
ConsistentHashLoadBalance | 一致性 Hash | 一致性Hash,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。 |
配置:
代码语言:javascript复制<dubbo:service interface="..." loadbalance="roundrobin" />
6.5 服务分组
使用服务分组区分服务接口的不同实现
当一个接口有多种实现时,可以用 group 区分。
配置:
服务端
代码语言:javascript复制@DubboService(group = "groupImpl1")
public class GroupImpl1 implements Group {
@Override
public String doSomething(String s) {
System.out.println("===========GroupImpl1.doSomething");
return "GroupImpl1.doSomething";
}
}
代码语言:javascript复制@DubboService(group = "groupImpl2")
public class GroupImpl2 implements Group {
@Override
public String doSomething(String s) {
System.out.println("===========GroupImpl2.doSomething");
return "GroupImpl2.doSomething";
}
}
消费端
代码语言:javascript复制@DubboReference(check = false,group = "groupImpl1"/*,parameters = {"merger","true"}*/)
Group group;
6.6 分组聚合
通过分组对结果进行聚合并返回聚合后的结果
通过分组对结果进行聚合并返回聚合后的结果,比如菜单服务,用group区分同一接口的多种实现,现在消费方需从每种group中调用一次并返回结果,对结果进行合并之后返回,这样就可以实现聚合菜单项。
生产者配置:
代码语言:javascript复制@DubboService(group = "groupImpl1")
public class GroupImpl1 implements Group {
@Override
public String doSomething(String s) {
System.out.println("===========GroupImpl1.doSomething");
return "GroupImpl1.doSomething";
}
}
@DubboService(group = "groupImpl2")
public class GroupImpl2 implements Group {
@Override
public String doSomething(String s) {
System.out.println("===========GroupImpl2.doSomething");
return "GroupImpl2.doSomething";
}
}
消费者配置:
代码语言:javascript复制@DubboReference(check = false,group = "*",parameters = {"merger","true"})
Group group;
SPI文件配置 在resources下创建META-INF文件夹并在其下面创建dubbo文件夹,然后在dubbo文件夹下面创建org.apache.dubbo.rpc.cluster.Merger文件,在该文件下写好Merger的实现类,如:
代码语言:javascript复制string=cn.enjoy.merge.StringMerger
StringMerger类:
代码语言:javascript复制public class StringMerger implements Merger<String> {
//定义了所有group实现类的返回值的合并规则
@Override
public String merge(String... strings) {
if(strings.length == 0) {
return null;
}
StringBuilder builder = new StringBuilder();
for (String string : strings) {
if(string != null) {
builder.append(string).append("-");
}
}
return builder.toString();
}
}
6.7 多版本
在 Dubbo 中为同一个服务配置多个版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
- 1. 在低压力时间段,先升级一半提供者为新版本
- 2. 再将所有消费者升级为新版本
- 3. 然后将剩下的一半提供者升级为新版本
生产者配置:
代码语言:javascript复制@DubboService(version = "1.0.0")
public class VersionServiceImpl implements VersionService {
@Override
public String version(String s) {
System.out.println("========VersionServiceImpl.1.0.0");
return "========VersionServiceImpl.1.0.0";
}
}
@DubboService(version = "1.0.1")
public class VersionServiceImpl1 implements VersionService {
@Override
public String version(String s) {
System.out.println("========VersionServiceImpl1.1.0.1");
return "========VersionServiceImpl1.1.0.1";
}
}
消费者配置:
代码语言:javascript复制@DubboReference(check = false,version = "1.0.0")
VersionService versionService;
6.8 参数验证
在 Dubbo 中进行参数验证
参数验证功能是基于 JSR303 实现的,用户只需标识 JSR303 标准的验证 annotation,并通过声明 filter 来实现验证。
Maven 依赖
代码语言:javascript复制<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>
参数验证类:
代码语言:javascript复制public class ValidationParamter implements Serializable {
private static final long serialVersionUID = 32544321432L;
@NotNull
@Size(
min = 2,
max = 20
)
private String name;
@Min(18L)
@Max(100L)
private int age;
@Past
private Date loginDate;
@Future
private Date expiryDate;
}
生产者:
代码语言:javascript复制@DubboService
public class ValidationServiceImpl implements ValidationService {
@Override
public void save(ValidationParamter validationParamter) {
System.out.println("========ValidationServiceImpl.save");
}
@Override
public void update(ValidationParamter validationParamter) {
System.out.println("========ValidationServiceImpl.update");
}
@Override
public void delete(long l, String s) {
System.out.println("========ValidationServiceImpl.delete");
}
}
消费者:
代码语言:javascript复制@DubboReference(check = false,validation = "true")
ValidationService validationService;
@Test
public void validation() {
ValidationParamter paramter = new ValidationParamter();
paramter.setName("Jack");
paramter.setAge(98);
paramter.setLoginDate(new Date(System.currentTimeMillis() - 10000000));
paramter.setExpiryDate(new Date(System.currentTimeMillis() 10000000));
validationService.save(paramter);
}
6.9 使用泛化调用
选讲 做测试的,没有使用场景
实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现
泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。
消费者:
代码语言:javascript复制@Test
public void usegeneric() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo_consumer");
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://192.168.67.139:2184");
ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>();
referenceConfig.setApplication(applicationConfig);
referenceConfig.setInterface("com.xiangxue.jack.service.UserService");
//这个是使用泛化调用
referenceConfig.setGeneric(true);
GenericService genericService = referenceConfig.get();
Object result = genericService.$invoke("queryUser", new String[]{"java.lang.String"},
new Object[]{"Jack"});
System.out.println(result);
}
6.10 参数回调
通过参数回调从服务器端调用客户端逻辑
参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。Dubbo 将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑。
服务接口示例:
代码语言:javascript复制public interface CallbackService {
void addListener(String var1, CallbackListener var2);
}
CallbackListener.java
代码语言:javascript复制public interface CallbackListener {
void changed(String msg);
}
生产者:
代码语言:javascript复制@DubboService(methods = {@Method(name = "addListener", arguments = {@Argument(index = 1,callback = true)})})
public class CallbackServiceImpl implements CallbackService {
@Override
public void addListener(String s, CallbackListener callbackListener) {
//这里就是回调客户端的方法
callbackListener.changed(getChanged(s));
}
private String getChanged(String key) {
return "Changed: " new SimpleDateFormat("yyyy-MM-dd:mm:ss").format(new Date());
}
}
消费者:
代码语言:javascript复制@DubboReference(check = false)
CallbackService callbackService;
callbackService.addListener("jack", new CallbackListener() {
public void changed(String arg0) {
System.out.println("=====================callback result: " arg0);
}
});
6.11 本地存根
在 Dubbo 中利用本地存根在客户端执行部分逻辑
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成Proxy 实例,会把 Proxy 通过构造函数传给 Stub 1,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
生产者:
代码语言:javascript复制@DubboService
public class StubServiceImpl implements StubService {
@Override
public String stub(String s) {
System.out.println("==========本地存根业务逻辑=========");
return s;
}
}
消费者:
代码语言:javascript复制@DubboReference(check = false,stub = "cn.enjoy.stub.LocalStubProxy")
StubService stubService;
@Test
public void stub() {
System.out.println(stubService.stub("jingtian"));
}
代理层:
代码语言:javascript复制/*
* 1、必须实现服务端的接口
* 2、要定义构造函数(传递客户端引用的实例)
* 接管了studService的调用
*
* 只有这个类才会决定要不要远程调用
*/
public class LocalStubProxy implements StubService {
private StubService stubService;
//这个必须要,传stubService实例本身
public LocalStubProxy(StubService stubService) {
this.stubService = stubService;
}
@Override
public String stub(String s) {
//代码在客户端执行,你可以在客户端做ThreadLocal本地缓存,或者校验参数之类工作的
try {
//用目标对象掉对应的方法 远程调用
return stubService.stub(s);
}catch (Exception e) {
//用来降级
System.out.println("降级数据");
}
//掉完后又执行代码
return null;
}
}
6.12 本地伪装
mock 只关注调用异常,如果客户端调用服务端出现异常了,那么就会触发服务降级
如何在 Dubbo 中利用本地伪装实现服务降级
本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。
生成者:
代码语言:javascript复制@DubboService
public class MockServiceImpl implements MockService {
@Override
public String mock(String s) {
System.out.println("=======mockservice的业务处理=======");
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=======mockservice的业务处理完成=======");
return "MockServiceImpl.mock";
}
@Override
public String queryArea(String s) {
return s;
}
@Override
public String queryUser(String s) {
return s;
}
}
消费者:
代码语言:javascript复制//这两种方式会走rpc远程调用 fail -- 会走远程服务
@DubboReference(check = false,mock = "true")
// @DubboReference(check = false,mock = "cn.enjoy.mock.LocalMockService")
//不走服务直接降级 force -- 是不会走远程服务的,强制降级..这种方式是用dubbo-admin去配置它,服务治理的方式
// @DubboReference(check = false,mock = "force:return jack")
MockService mockService;
@Test
public void mock() {
System.out.println(mockService.mock("wy"));
}
代码语言:javascript复制/*
* MockServiceMock
*
* 1、接口名 "Mock"
* 2、mock逻辑必须定义在接口的包下面
*/
public class MockServiceMock implements MockService {
@Override
public String mock(String s) {
System.out.println(this.getClass().getName() "--mock");
return s;
}
@Override
public String queryArea(String s) {
System.out.println(this.getClass().getName() "--queryArea");
return s;
}
@Override
public String queryUser(String s) {
System.out.println(this.getClass().getName() "--queryUser");
return s;
}
}
6.13 异步调用
在 Dubbo 中发起异步调用
从 2.7.0 开始,Dubbo 的所有异步编程接口开始以 CompletableFuture 为基础
基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。
代码案例
生产者:
代码语言:javascript复制public interface AsyncService {
String asynctoDo(String var1);
}
@DubboService
public class AsyncServiceImpl implements AsyncService {
@Override
public String asynctoDo(String s) {
for (int i = 0; i < 10; i ) {
System.out.println("===============AsyncServiceImpl.asynctoDo");
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return "hello," s;
}
}
消费者:
代码语言:javascript复制@DubboReference(check = false,timeout = 1000000000,methods = {@Method(name =
"asynctoDo",async = true)})
AsyncService asyncService;
@Test
public void async() throws InterruptedException {
String aa = asyncService.asynctoDo("aa");
System.out.println("main==" aa);
System.out.println("并行调用其他接口====");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//需要拿到异步调用的返回结果
CompletableFuture<Object> resultFuture = RpcContext.getContext().getCompletableFuture();
resultFuture.whenComplete((retValue,exception)->{
if(exception == null) {
System.out.println("正常返回==" retValue);
} else {
exception.printStackTrace();
}
});
Thread.currentThread().join();
}
6.14 异步执行
Dubbo 服务提供方的异步执行
Provider端异步执行将阻塞的业务从Dubbo内部线程池切换到业务自定义线程,避免Dubbo线程池的过度占用,有助于避免不同服务间的互相影响。异步执行无异于节省资源或提升RPC响应性能,因为如果业务执行需要阻塞,则始终还是要有线程来负责执行。
注意:
Provider 端异步执行和 Consumer 端异步调用是相互独立的,你可以任意正交组合两端配置
- • Consumer同步 - Provider同步
- • Consumer异步 - Provider同步
- • Consumer同步 - Provider异步
- • Consumer异步 - Provider异步
代码案例
生产者:
代码语言:javascript复制public interface AsyncService {
CompletableFuture<String> doOne(String var1);
}
@Override
public CompletableFuture<String> doOne(String s) {
return CompletableFuture.supplyAsync(()->{
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "doOne -- OK";
});
}
消费者:
代码语言:javascript复制@Test
public void asyncDoone() throws ExecutionException, InterruptedException {
CompletableFuture<String> jingtian = asyncService.doOne("jingtian");
jingtian.whenComplete((retValue,exception)->{
if(exception == null) {
System.out.println(retValue);
} else {
exception.printStackTrace();
}
});
Thread.currentThread().join();
}
6.15 本地调用
在 Dubbo 中进行本地调用
本地调用使用了 injvm 协议,是一个伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联,但执行Dubbo 的 Filter 链。
本地调用,调用的就是本地工程的接口实例
示例:
代码语言:javascript复制@DubboReference(check = false,injvm = true)
StudentService studentService;
@Test
public void inJvm() throws InterruptedException {
System.out.println(studentService.find("xx"));
}
6.16 粘滞连接
为有状态服务配置粘滞连接
粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。
粘滞连接将自动开启延迟连接,以减少长连接数。
sticky=true
代码语言:javascript复制@DubboReference(check = false,protocol = "dubbo",retries = 3,timeout = 1000000000,cluster = "failover",loadbalance = "random",sticky = true,methods = {@Method(name = "doKill",isReturn = false)}/*,url = "dubbo://localhost:20880"*/)
UserService userService;
6.17 Protobuf
使用 IDL 定义服务
当前 Dubbo 的服务定义和具体的编程语言绑定,没有提供一种语言中立的服务描述格式,比如 Java 就是定义Interface 接口,到了其他语言又得重新以另外的格式定义一遍。2.7.5 版本通过支持 Protobuf IDL 实现了语言中立的服务定义。
1、maven插件支持
代码语言:javascript复制<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<source.level>1.8</source.level>
<target.level>1.8</target.level>
<dubbo.version>3.0.2.1</dubbo.version>
<spring.version>5.2.8.RELEASE</spring.version>
<junit.version>4.12</junit.version>
<maven-compiler-plugin.version>3.7.0</maven-compiler-plugin.version>
<skip_maven_deploy>true</skip_maven_deploy>
<dubbo.compiler.version>0.0.2</dubbo.compiler.version>
</properties>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>${source.level}</source>
<target>${target.level}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}
</protocArtifact>
<outputDirectory>build/generated/source/proto/main/java</outputDirectory>
<clearOutputDirectory>false</clearOutputDirectory>
<protocPlugins>
<protocPlugin>
<id>dubbo</id>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-compiler</artifactId>
<version>${dubbo.compiler.version}</version>
<mainClass>org.apache.dubbo.gen.dubbo.Dubbo3Generator</mainClass>
</protocPlugin>
</protocPlugins>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>build/generated/source/proto/main/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
2、定义proto文件
在dubbo-p工程的src/main下面创建proto文件夹在里面定义LoginService.proto文件,文件内容如下:
代码语言:javascript复制syntax = "proto3";
option java_multiple_files = true;
option java_package = "cn.enjoy.service.login";
option java_outer_classname = "LoginServiceProto";
option objc_class_prefix = "DEMOSRV";
package loginservice;
// The demo service definition.
service LoginService {
rpc login (LoginRequest) returns (LoginReply) {}
}
// The request message containing the user's name.
message LoginRequest {
string username = 1;
string password = 2;
}
// The response message containing the greetings
message LoginReply {
string message = 1;
}
3、mvc插件根据proto文件生成service和bean,如图
生成文件如下:
4、把生成的文件移到响应的service目录下
生产者代码:
代码语言:javascript复制@DubboService
@Slf4j
public class LoginServiceImpl implements LoginService {
@Override
public LoginReply login(LoginRequest request) {
log.info("Hello " request.getUsername() ", request from consumer: "
RpcContext.getContext().getRemoteAddress());
return LoginReply.newBuilder()
.setMessage("Hello " request.getUsername() ", response from provider: "
RpcContext.getContext().getLocalAddress())
.build();
}
@Override
public CompletableFuture<LoginReply> loginAsync(LoginRequest request) {
return null;
}
}
生产者配置:
必须要加上序列化的配置属性,在dubbo-provider.properties中配置
代码语言:javascript复制dubbo.service.cn.enjoy.service.login.LoginService.serialization=protobuf
消费者:
消费者的1,2,3,4步骤是相同的
消费者代码:
代码语言:javascript复制@Test
public void protobuf() throws IOException {
LoginRequest request =
LoginRequest.newBuilder().setUsername("jingtian").setPassword("123").build();
LoginReply reply = loginService.login(request);
System.out.println(reply.getMessage());
}
6.18 主机绑定
在 Dubbo 中绑定主机名
缺省主机 IP 查找顺序:
- • 通过 LocalHost.getLocalHost() 获取本机地址。
- • 如果是 127.* 等 loopback 地址,则扫描各网卡,获取网卡 IP。
主机配置
注册的地址如果获取不正确,比如需要注册公网地址,可以:
1、可以在 /etc/hosts 中加入:机器名 公网 IP,比如:
代码语言:javascript复制test1 205.182.23.201
2、在 dubbo.xml 中加入主机地址的配置:
代码语言:javascript复制<dubbo:protocol host="205.182.23.201">
3、或在 dubbo.properties 中加入主机地址的配置:
代码语言:javascript复制dubbo.protocol.host=205.182.23.201
端口配置
缺省主机端口与协议相关:
协议 | 端口 |
---|---|
dubbo | 20880 |
rmi | 1099 |
http | 80 |
hessian | 80 |
webservice | 80 |
memcached | 11211 |
redis | 6379 |
可以按照下面的方式配置端口:
1、在 dubbo.xml 中加入主机地址的配置:
代码语言:javascript复制<dubbo:protocol name="dubbo" port="20880">
2、或在 dubbo.properties 中加入主机地址的配置:
代码语言:javascript复制dubbo.protocol.dubbo.port=20880
6.19 主机配置
自定义 Dubbo 服务对外暴露的主机地址
背景
在 Dubbo 中, Provider 启动时主要做两个事情,一是启动 server,二是向注册中心注册服务。启动 server 时需要绑定 socket,向注册中心注册服务时也需要发送 socket 唯一标识服务地址。
- 1. dubbo 中不设置 host 时默认 host 是什么?
- 2. 那在 dubbo 中如何指定服务的 host ,我们是否可以用hostname或domain代替IP地址作为 host ?
- 3. 在使用docker时,有时需要设置端口映射,此时,启动server时绑定的socket和向注册中心注册的socket使用不同的 端口号,此时又该如何设置?
dubbo 中不设置 host 时默认 host 是什么
一般的 dubbo 协议配置如下:
代码语言:javascript复制...
<dubbo:protocol name="dubbo" port="20890" />
...
可以看到,只配置了端口号,没有配置 host,此时设置的 host 又是什么呢?
查看代码发现,在org.apache.dubbo.config.ServiceConfig#findConfigedHosts()
中,通过InetAddress.getLocalHost().getHostAddress()
获取默认 host。其返回值如下:
- 1. 未联网时,返回 127.0.0.1
- 2. 在阿里云服务器中,返回私有地址,如: 172.18.46.234
- 3. 在本机测试时,返回公有地址,如: 30.5.10.11
那在 dubbo 中如何指定服务的 socket?
除此之外,可以通过 dubbo.protocol 或 dubbo.provider 的 host 属性对 host 进行配置,支持IP地址和域名,如下:
代码语言:javascript复制...
<dubbo:protocol name="dubbo" port="20890" host="www.example.com"/>
...
在使用 docker 时,有时需要设置端口映射,此时,启动 server 时绑定的 socket 和向注册中心注册的 socket 使用不同的端口号,此时又该如何设置?
见 dubbo 通过环境变量设置 host
有些部署场景需要动态指定服务注册的地址,如 docker bridge 网络模式下要指定注册宿主机 ip 以实现外网通信。dubbo 提供了两对启动阶段的系统属性,用于设置对外通信的ip、port地址。
- • DUBBO_IP_TO_REGISTRY — 注册到注册中心的ip地址
- • DUBBO_PORT_TO_REGISTRY — 注册到注册中心的port端口
- • DUBBO_IP_TO_BIND — 监听ip地址
- • DUBBO_PORT_TO_BIND — 监听port端口
以上四个配置项均为可选项,如不配置 dubbo 会自动获取 ip 与端口,请根据具体的部署场景灵活选择配置。dubbo 支持多协议,如果一个应用同时暴露多个不同协议服务,且需要为每个服务单独指定 ip 或 port,请分别在以上属性前加协议前缀。如:
- • HESSIAN_DUBBO_PORT_TO_BIND hessian协议绑定的port
- • DUBBO_DUBBO_PORT_TO_BIND dubbo协议绑定的port
- • HESSIAN_DUBBO_IP_TO_REGISTRY hessian协议注册的ip
- • DUBBO_DUBBO_PORT_TO_BIND dubbo协议注册的ip
PORT_TO_REGISTRY 或 IP_TO_REGISTRY 不会用作默认 PORT_TO_BIND 或 IP_TO_BIND,但是反过来是成立的 如设置 PORT_TO_REGISTRY=20881 IP_TO_REGISTRY=30.5.97.6,则 PORT_TO_BIND IP_TO_BIND 不受影响 如果设置 PORT_TO_BIND=20881 IP_TO_BIND=30.5.97.6,则默认 PORT_TO_REGISTRY=20881 IP_TO_REGISTRY=30.5.97.6
总结
- 1. 可以通过 dubbo.protocol 或 dubbo.provider 的 host 属性对 host 进行配置,支持IP地址和域名.但此时注册到 注册中心的IP地址和监听IP地址是同一个值
- 2. 为了解决在虚拟环境或局域网内consumer无法与provider通信的问题,可以通过环境变量分别设置注册到注册 中心的IP地址和监听IP地址,其优先级高于 dubbo.protocol 或 dubbo.provider 的 host 配置
6.20 注册信息简化
减少注册中心上服务的注册数据
背景
Dubbo provider 中的服务配置项有接近 30 个配置项。排除注册中心服务治理需要之外,很大一部分配置项是provider 自己使用,不需要透传给消费者。这部分数据不需要进入注册中心,而只需要以 key-value 形式持久化存储。
Dubbo consumer 中的配置项也有 20 个配置项。在注册中心之中,服务消费者列表中只需要关注 application,version,group,ip,dubbo 版本等少量配置,其他配置也可以以 key-value 形式持久化存储。
这些数据是以服务为维度注册进入注册中心,导致了数据量的膨胀,进而引发注册中心(如 zookeeper)的网络开销增大,性能降低。
现有功能 sample 当前现状一个简单展示。通过这个展示,分析下为什么需要做简化配置。
参考 sample 子工程:dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-nosimple (跑sample 前,先跑下 ZKClean 进行配置项清理)
dubbo-provider.xml配置
代码语言:javascript复制<dubbo:application name="simplified-registry-nosimple-provider"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<bean id="demoService"
class="org.apache.dubbo.samples.simplified.registry.nosimple.impl.DemoServiceImpl"/>
<dubbo:service async="true"
interface="org.apache.dubbo.samples.simplified.registry.nosimple.api.DemoService"
version="1.2.3" group="dubbo-simple" ref="demoService"
executes="4500" retries="7" owner="vict" timeout="5300"/>
启动 provider 的 main 方法之后,查看 zookeeper 的叶子节点(路径为:/dubbo/org.apache.dubbo.samples.simplified.registry.nosimple.api.DemoService/providers 目录下)的内容如下:
代码语言:javascript复制dubbo://30.5.124.158:20880/org.apache.dubbo.samples.simplified.registry.nosimple.a
pi.DemoService
?anyhost=true&application=simplified-registry-xml-provider&async=true&dubbo=
2.0.2&**executes**=4500&generic=false&group=dubbo-simple&interface=
org.apache.dubbo.samples.simplified.registry.nosimple.api.DemoService&methods=
sayHello&**owner**=vict&pid=2767&**retries**=7&revision=1.2.3&side=
provider&**timeout**=5300×tamp=1542361152795&valid=true&version=1.2.3
从加粗字体中能看到有:executes, retries, owner, timeout。但是这些字段不是每个都需要传递给 dubbo ops 或者dubbo consumer。同样的,consumer 也有这个问题,可以在例子中启动 Consumer 的 main 方法进行查看。
设计目标和宗旨
期望简化进入注册中心的 provider 和 consumer 配置数量。期望将部分配置项以其他形式存储。这些配置项需要满足:不在服务调用链路上,同时这些配置项不在注册中心的核心链路上(服务查询,服务列表)。
配置
简化注册中心的配置,只在 2.7 之后的版本中进行支持。开启 provider 或者 consumer 简化配置之后,默认保留的 配置项如下:
Constant Key | Key | remark |
---|---|---|
APPLICATION_KEY | application | |
CODEC_KEY | codec | |
EXCHANGER_KEY | exchanger | |
SERIALIZATION_KEY | serialization | |
CLUSTER_KEY | cluster | |
CONNECTIONS_KEY | connections | |
DEPRECATED_KEY | deprecated | |
GROUP_KEY | group | |
LOADBALANCE_KEY | loadbalance | |
MOCK_KEY | mock | |
PATH_KEY | path | |
TIMEOUT_KEY | timeout | |
TOKEN_KEY | token | |
VERSION_KEY | version | |
WARMUP_KEY | warmup | |
WEIGHT_KEY | weight | |
TIMESTAMP_KEY | timestamp | |
DUBBO_VERSION_KEY | dubbo | |
SPECIFICATION_VERSION_KEY | specVersion | 新增,用于表述dubbo版本,如2.7.0 |
consumer:
Constant Key | Key | remark |
---|---|---|
APPLICATION_KEY | application | |
VERSION_KEY | version | |
GROUP_KEY | group | |
DUBBO_VERSION_KEY | dubbo | |
SPECIFICATION_VERSION_KEY | specVersion | 新增,用于表述dubbo版本,如2.7.0 |
Constant Key 表示来自于类 org.apache.dubbo.common.Constants 的字段。
下面介绍几种常用的使用方式。所有的 sample,都可以查看sample-2.7
方式1. 配置dubbo.properties
sample 在 dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-xml 工程下 (跑 sample 前,先跑下ZKClean 进行配置项清理)
dubbo.properties
代码语言:javascript复制dubbo.registry.simplified=true
dubbo.registry.extra-keys=retries,owner
怎么去验证呢?
provider端验证
provider端配置
代码语言:javascript复制<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- optional -->
<dubbo:application name="simplified-registry-xml-provider"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<bean id="demoService" class="org.apache.dubbo.samples.simplified.registry.nosimple.impl.DemoServiceImpl"/>
<dubbo:service async="true" interface="org.apache.dubbo.samples.simplified.registry.nosimple.api.DemoService" version="1.2.3" group="dubbo-simple" ref="demoService" executes="4500" retries="7" owner="vict" timeout="5300"/>
</beans>
得到的 zookeeper 的叶子节点的值如下:
代码语言:javascript复制dubbo://30.5.124.149:20880/org.apache.dubbo.samples.simplified.registry.nosimple.a
pi.DemoService?
application=simplified-registry-xml-provider&dubbo=2.0.2&group=dubbo-
simple&**owner**=
vict&**retries**=7&**timeout**=5300×tamp=1542594503305&version=1.2.3
和上面的现有功能 sample进行对比,上面的 sample 中,executes, retries, owner, timeout 四个配置项都进入了注册中心。但是本实例不是:
- • 配置了:dubbo.registry.simplified=true, 默认情况下,timeout 在默认的配置项列表,所以还是会进入注册中心;
- • 配置了:dubbo.registry.extra-keys=retries,owner , 所以 retries,owner 也会进入注册中心。
总结:timeout,retries,owner 进入了注册中心,而 executes 没有进入。
consumer 端配置
代码语言:javascript复制<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- optional -->
<dubbo:application name="simplified-registry-xml-consumer"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181" username="xxx" password="yyy" check="true"/>
<dubbo:reference id="demoService" interface="org.apache.dubbo.samples.simplified.registry.nosimple.api.DemoService" owner="vvv" retries="4" actives="6" timeout="4500" version="1.2.3" group="dubbo-simple"/>
</beans>
得到的 zookeeper 的叶子节点的值如下:
代码语言:javascript复制consumer://30.5.124.149/org.apache.dubbo.samples.simplified.registry.nosimple.api.De
moService?
actives=6&application=simplified-registry-xml-consumer&category=
consumers&check=false&dubbo=2.0.2&group=dubbo-
simple&owner=vvv&version=1.2.3
- • 配置了:dubbo.registry.simplified=true , 默认情况下,application,version,group,dubbo 在默认的配置项列表,所以还是会进入注册中心;
方式2. 声明spring bean
sample在dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-annotation 工程下 (跑sample 前,先跑下ZKClean 进行配置项清理)
Provider配置
privide 端 bean 配置:
代码语言:javascript复制// 等同于dubbo.properties配置,用@Bean形式进行配置
@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;
}
// 暴露服务
@Service(version = "1.1.8", group = "d-test", executes = 4500, retries = 7, owner =
"victanno", timeout = 5300)
public class AnnotationServiceImpl implements AnnotationService {
@Override
public String sayHello(String name) {
System.out.println("async provider received: " name);
return "annotation: hello, " name;
}
}
和上面 sample 中的 dubbo.properties 的效果是一致的。结果如下:
- • 默认情况下,timeout 在默认的配置项列表,所以还是会进入注册中心;
- • 配置了 retries,owner 作为额外的 key 进入注册中心 , 所以 retries,owner 也会进入注册中心。
总结:timeout,retries,owner 进入了注册中心,而 executes 没有进入。
Consumer配置
consumer 端 bean 配置:
代码语言:javascript复制@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
registryConfig.setSimplified(true);
return registryConfig;
}
消费服务:
代码语言:javascript复制@Component("annotationAction")
public class AnnotationAction {
@Reference(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);
}
}
和上面 sample 中 consumer 端的配置是一样的。结果如下:
- • 默认情况下,application,version,group,dubbo 在默认的配置项列表,所以还是会进入注册中心。
注意:
如果一个应用中既有provider又有consumer,那么配置需要合并成:
代码语言:javascript复制@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;
}
后续规划
本版本还保留了大量的配置项,接下来的版本中,会逐渐删除所有的配置项。
6.21 上下文信息
通过上下文存放当前调用过程中所需的环境信息
上下文中存放的是当前调用过程中所需的环境信息。所有配置信息都将转换为 URL 的参数,参见 schema 配置参考手册 中的对应URL参数一列。
RpcContext 是一个 ThreadLocal 的临时状态记录器,当接收到 RPC 请求,或发起 RPC 请求时,RpcContext 的状态都会变化。比如:A 调 B,B 再调 C,则 B 机器上,在 B 调 C 之前,RpcContext 记录的是 A 调 B 的信息,在 B 调 C之后,RpcContext 记录的是 B 调 C 的信息。
消费者:
代码语言:javascript复制// 远程调用
xxxService.xxx();
// 本端是否为消费端,这里会返回true
boolean isConsumerSide = RpcContext.getContext().isConsumerSide();
// 获取最后一次调用的提供方IP地址
String serverIP = RpcContext.getContext().getRemoteHost();
// 获取当前服务配置信息,所有配置信息都将转换为URL的参数
String application = RpcContext.getContext().getUrl().getParameter("application");
// 注意:每发起RPC调用,上下文状态会变化
yyyService.yyy();
生产者:
代码语言:javascript复制public class XxxServiceImpl implements XxxService {
public void xxx() {
// 本端是否为提供端,这里会返回true
boolean isProviderSide = RpcContext.getContext().isProviderSide();
// 获取调用方IP地址
String clientIP = RpcContext.getContext().getRemoteHost();
// 获取当前服务配置信息,所有配置信息都将转换为URL的参数
String application = RpcContext.getContext().getUrl().getParameter("application");
// 注意:每发起RPC调用,上下文状态会变化
yyyService.yyy();
// 此时本端变成消费端,这里会返回false
boolean isProviderSide = RpcContext.getContext().isProviderSide();
}
}
6.22 隐式参数
通过 Dubbo 中的 Attachment 在服务消费方和提供方之间隐式传递参数
可以通过 RpcContext 上的 setAttachment 和 getAttachment 在服务消费方和提供方之间进行参数的隐式传递。
注意
path, group, version, dubbo, token, timeout 几个 key 是保留字段,请使用其它值。
示例:
代码语言:javascript复制RpcContext.getContext().setAttachment("index", "1");
String index = RpcContext.getContext().getAttachment("index");