前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >dubbo的应用场景与高级特性之高级用法篇

dubbo的应用场景与高级特性之高级用法篇

作者头像
Tom弹架构
发布2023-11-27 16:55:22
2990
发布2023-11-27 16:55:22
举报
文章被收录于专栏:Tom弹架构Tom弹架构

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 重试。

各节点关系:

  • • 这里的InvokerProvider的一个可调用Service的抽象,Invoker封装了Provider地址及Service接口信息
  • Directory代表多个 Invoker,可以把它看成List<Invoker>,但与List不同的是,它的值可能是动态变化的,比如注册中心推送变更
  • ClusterDirectory中的多个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. 1. 在低压力时间段,先升级一半提供者为新版本
  2. 2. 再将所有消费者升级为新版本
  3. 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. 1. dubbo 中不设置 host 时默认 host 是什么?
  2. 2. 那在 dubbo 中如何指定服务的 host ,我们是否可以用hostname或domain代替IP地址作为 host ?
  3. 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. 1. 未联网时,返回 127.0.0.1
  2. 2. 在阿里云服务器中,返回私有地址,如: 172.18.46.234
  3. 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. 1. 可以通过 dubbo.protocol 或 dubbo.provider 的 host 属性对 host 进行配置,支持IP地址和域名.但此时注册到 注册中心的IP地址和监听IP地址是同一个值
  2. 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%3A%2F%2F30.5.124.158%3A20880%2Forg.apache.dubbo.samples.simplified.registry.nosimple.a
pi.DemoService
%3Fanyhost%3Dtrue%26application%3Dsimplified-registry-xml-provider%26async%3Dtrue%26dubbo%3D
2.0.2%26**executes**%3D4500%26generic%3Dfalse%26group%3Ddubbo-simple%26interface%3D
org.apache.dubbo.samples.simplified.registry.nosimple.api.DemoService%26methods%3D
sayHello%26**owner**%3Dvict%26pid%3D2767%26**retries**%3D7%26revision%3D1.2.3%26side%3D
provider%26**timeout**%3D5300%26timestamp%3D1542361152795%26valid%3Dtrue%26version%3D1.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%3A%2F%2F30.5.124.149%3A20880%2Forg.apache.dubbo.samples.simplified.registry.nosimple.a
pi.DemoService%3F
application%3Dsimplified-registry-xml-provider%26dubbo%3D2.0.2%26group%3Ddubbo-
simple%26**owner**%3D
vict%26**retries**%3D7%26**timeout**%3D5300%26timestamp%3D1542594503305%26version%3D1.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%3A%2F%2F30.5.124.149%2Forg.apache.dubbo.samples.simplified.registry.nosimple.api.De
moService%3F
actives%3D6%26application%3Dsimplified-registry-xml-consumer%26category%3D
consumers%26check%3Dfalse%26dubbo%3D2.0.2%26group%3Ddubbo-
simple%26owner%3Dvvv%26version%3D1.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");
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-11-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Tom弹架构 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 6 dubbo高级用法
    • 6.1 启动时检查
      • 6.2 直连提供者
        • 6.3 集群容错
          • 6.3.1 集群容错模式
        • 6.4 负载均衡
          • 6.5 服务分组
            • 6.6 分组聚合
              • 6.7 多版本
                • 6.8 参数验证
                  • 6.9 使用泛化调用
                    • 6.10 参数回调
                      • 6.11 本地存根
                        • 6.12 本地伪装
                          • 6.13 异步调用
                            • 6.14 异步执行
                              • 6.15 本地调用
                                • 6.16 粘滞连接
                                  • 6.17 Protobuf
                                    • 6.18 主机绑定
                                      • 6.19 主机配置
                                        • 6.20 注册信息简化
                                          • 6.21 上下文信息
                                            • 6.22 隐式参数
                                            相关产品与服务
                                            微服务引擎 TSE
                                            微服务引擎(Tencent Cloud Service Engine)提供开箱即用的云上全场景微服务解决方案。支持开源增强的云原生注册配置中心(Zookeeper、Nacos 和 Apollo),北极星网格(腾讯自研并开源的 PolarisMesh)、云原生 API 网关(Kong)以及微服务应用托管的弹性微服务平台。微服务引擎完全兼容开源版本的使用方式,在功能、可用性和可运维性等多个方面进行增强。
                                            领券
                                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档