SpringCloud Alibaba系列——4Dubbo简介与应用(三)

老老老JR老北
发布于 2022-8-24 16:37
浏览
0收藏

作者 | 一起撸Java
来源 |今日头条

承接上文,本文将介绍Dubbo的高级应用

第3章 Dubbo高级用法
3.1 启动时检查
通过spring配置文件

关闭某个服务的启动时检查 (没有提供者时报错):

<dubbo:reference interface="com.foo.BarService" check="false" />

关闭所有服务的启动时检查 (没有提供者时报错):

<dubbo:consumer check="false" />

关闭注册中心启动时检查 (注册订阅失败时报错):

<dubbo:registry check="false" />

通过 dubbo.properties

dubbo.reference.com.foo.BarService.check=falsedubbo.consumer.check=falsedubbo.registry.check=false

通过 -D 参数

java -Ddubbo.reference.com.foo.BarService.check=falsejava -Ddubbo.consumer.check=falsejava -Ddubbo.registry.check=false
  • api
public interface ClusterService {    String getClusterService(String str);}
  • 服务端
@DubboService(timeout = 1000000)public class ClusterServiceImpl implements ClusterService {    @Override    public String getClusterService(String str) {        return "高级应用集群容错:"+str;    }}
  • 消费端
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = ConsumerConfiguration.class)public class TestClusterService {    //启动时检查和直连    @DubboReference(check = false)    private ClusterService clusterService;    @Test    public void test(){        System.out.println(clusterService.getClusterService("eclipse2019"));    }}

3.2 直连提供者
Dubbo 中点对点的直连方式

在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直连方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。

配置:

<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />

api和服务提供端代码跟启动是检查一样

  • 消费端
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = ConsumerConfiguration.class)public class TestClusterService {    //启动时检查和直连    @DubboReference(check = false,url = "dubbo://localhost:20880")    private ClusterService clusterService;    @Test    public void test(){        System.out.println(clusterService.getClusterService("eclipse2019"));    }}​

3.3 集群容错
集群调用失败时,Dubbo 提供的容错方案

在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。SpringCloud Alibaba系列——4Dubbo简介与应用(三)-鸿蒙开发者社区各节点关系:

  • 这里的Invoker是Provider的一个可调用Service的抽象,Invoker封装了Provider地址及Service接口信息
  • Directory代表多个 Invoker,可以把它看成List<Invoker>,但与List不同的是,它的值可能是动态变化的,比如注册中心推送变更
  • Cluster将Directory中的多个Invoker伪装成一个Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个
  • Router负责从多个Invoker中按路由规则选出子集,比如读写分离,应用隔离等
  • LoadBalance负责从多个Invoker中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选
    3.3.1 集群容错模式
    Failover Cluster

失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过retries="2" 来设置重试次数(不含第一次)。该配置为缺省配置

Failfast Cluster

快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

Failsafe Cluster

失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

Failback Cluster

失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

Forking Cluster

并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过forks="2"来设置最大并行数。

Broadcast Cluster

广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

Available Cluster

调用目前可用的实例(只调用一个),如果当前没有可用的实例,则抛出异常。通常用于不需要负载均衡的场景。

配置:

@reference(cluster = "broadcast", parameters = {"broadcast.fail.percent", "20"})

api端与启动时检查代码一致

  • 服务端
@DubboService(timeout = 1000000)public class ClusterServiceImpl implements ClusterService {    @Override    public String getClusterService(String str) {        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        return "高级应用集群容错:"+str;    }}​
  • 消费端
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = ConsumerConfiguration.class)public class TestClusterService {    @DubboReference(check = false,cluster="broadcast",parameters = {"broadcast.fail.percent", "20"},timeout = 100)    private ClusterService clusterService;    @Test    public void test(){        System.out.println(clusterService.getClusterService("eclipse2019"));    }}​

3.4 负载均衡
Dubbo 提供的集群负载均衡策略

在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。

具体实现上,Dubbo 提供的是客户端负载均衡,即由 Consumer 通过负载均衡算法得出需要将请求提交到哪个Provider 实例。

负载均衡策略

目前 Dubbo 内置了如下负载均衡算法,用户可直接配置使用:SpringCloud Alibaba系列——4Dubbo简介与应用(三)-鸿蒙开发者社区配置:

<dubbo:service interface="..." loadbalance="roundrobin" />

3.5 服务分组
使用服务分组区分服务接口的不同实现

当一个接口有多种实现时,可以用 group 区分。

  • api
public interface GroupService {    String getGroupServiceStr(String str);}
  • 服务端
@DubboService(group = "group01")public class GroupServiceImpl01 implements GroupService {    @Override    public String getGroupServiceStr(String str) {        return "我是group1111,返回:"+str;    }}
@DubboService(group = "group02")public class GroupServiceImpl02 implements GroupService {    @Override    public String getGroupServiceStr(String str) {        return "我是group2222,返回:"+str;    }}​
  • 消费端
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = ConsumerConfiguration.class)public class TestGroupService {    @DubboReference(check = false,group = "group01")    private GroupService groupService;    @Test    public void test(){        System.out.println(groupService.getGroupServiceStr("eclipse2019"));    }}​

3.6 分组聚合
通过分组对结果进行聚合并返回聚合后的结果

通过分组对结果进行聚合并返回聚合后的结果,比如菜单服务,用group区分同一接口的多种实现,现在消费方需从每种group中调用一次并返回结果,对结果进行合并之后返回,这样就可以实现聚合菜单项。

  • api和服务端代码跟服务分组的代码一致
  • 消费者配置:
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = ConsumerConfiguration.class)public class TestGroupService {    @DubboReference(check = false,group = "*",parameters = {"merger","true"})    private GroupService groupService;    @Test    public void test(){        System.out.println(groupService.getGroupServiceStr("eclipse2019"));    }}​

SPI文件配置 在resources下创建META-INF文件夹并在其下面创建dubbo文件夹,然后在dubbo文件夹下面创建
org.apache.dubbo.rpc.cluster.Merger文件,在该文件下写好Merger的实现类,如:

string=com.example.merge.StringMerger

SpringCloud Alibaba系列——4Dubbo简介与应用(三)-鸿蒙开发者社区StringMerger类:

public class StringMerger implements Merger<String> {    //定义了所有group实现类的返回值的合并规则    @Override    public String merge(String... items) {        if(items.length == 0) {            return null;        }        StringBuilder builder = new StringBuilder();        for (String string : items) {            if(string != null) {                builder.append(string).append("----");            }        }        return builder.toString();    }}

3.7 多版本
在 Dubbo 中为同一个服务配置多个版本

当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。

可以按照以下的步骤进行版本迁移:

  1. 在低压力时间段,先升级一半提供者为新版本
  2. 再将所有消费者升级为新版本
  3. 然后将剩下的一半提供者升级为新版本
  • api
public interface VersionService {    String getVersionServiceStr(String str);}​
  • 服务端

@DubboService(version = "1.0.0")public class VersionServiceImpl01 implements VersionService {    @Override    public String getVersionServiceStr(String str) {     

 return "调用第一个版本,返回值:"+str;    }}
@DubboService(version = "1.0.1")public class VersionServiceImpl02 implements VersionService {    @Override    public String getVersionServiceStr(String str) {        return "调用第二个版本,返回值:"+str;    }}
  • 消费端
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = ConsumerConfiguration.class)public class TestVersionService {    @DubboReference(check = false,version = "1.0.0")    private VersionService versionService;    @Test    public void test(){        System.out.println(versionService.getVersionServiceStr("eclipse2019"));    }}

3.8 参数验证
在 Dubbo 中进行参数验证

参数验证功能是基于 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>
  • api

参数验证类:

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;}
  • 服务端
@DubboServicepublic class ValidationServiceImpl implements ValidationService {    @Override    public void save(ValidationParamter parameter) {        System.out.println("参数验证案例的save方法");    }    @Override    public void update(ValidationParamter parameter) {        System.out.println("参数验证案例的update方法");    }    @Override    public void delete(long id, String operation) {        System.out.println("参数验证案例的delete方法");    }}
  • 消费端
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = ConsumerConfiguration.class)public class TestValidationService {    @DubboReference(check = false,validation = "true")    private ValidationService validationService;    @Test    public void validation() {        ValidationParamter paramter = new ValidationParamter();        paramter.setName("eclipse2019");        paramter.setAge(98);        paramter.setLoginDate(new Date(System.currentTimeMillis() - 10000000));        paramter.setExpiryDate(new Date(System.currentTimeMillis() + 10000000));        validationService.save(paramter);    }}

 

3.9 使用泛化调用
做测试的,没有使用场景
实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现

泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。

消费者:

@Testpublic 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.eclipse2019.service.UserService");    //这个是使用泛化调用    referenceConfig.setGeneric(true);    GenericService genericService = referenceConfig.get();    Object result = genericService.$invoke("queryUser", new String[]{"java.lang.String"},                                           new Object[]{"eclipse2019"});    System.out.println(result);}

3.10 参数回调
通过参数回调从服务器端调用客户端逻辑

参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。Dubbo 将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑。

  • api
public interface CallbackService {    void addListener(String key, CallbackListener listener);}
public interface CallbackListener {    void changed(String msg);}
  • 服务端
@DubboService(methods = {@Method(name = "addListener", arguments = {@Argument(index = 1,callback = true)})})public class CallbackServiceImpl implements CallbackService {    @Override    public void addListener(String key, CallbackListener listener) {        listener.changed(getChanged(key));    }    private String getChanged(String key) {        return "Changed: " + new SimpleDateFormat("yyyy-MM-dd:mm:ss").format(new Date());    }}
  • 消费者:
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = ConsumerConfiguration.class)public class TestCallbaskService {    @DubboReference(check = false)    private CallbackService callbackService;    @Test    public void test(){        callbackService.addListener("eclipse2019", new CallbackListener() {            @Override            public void changed(String msg) {                System.out.println("又回调回来了" + msg);            }        });    }}

3.11 本地存根
在 Dubbo 中利用本地存根在客户端执行部分逻辑

  • 远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成Proxy 实例,会把 Proxy 通过构造函数传给 Stub 1,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。SpringCloud Alibaba系列——4Dubbo简介与应用(三)-鸿蒙开发者社区
  • api
public interface StubService {    String stub(String param);}
  • 服务端
@DubboServicepublic class StubServiceImpl implements StubService {    @Override    public String stub(String param) {        return "本地存根就是在消费端干一些事,返回:"+param;    }}​
  • 消费端
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = ConsumerConfiguration.class)public class TestStubService {    @DubboReference(check = false,stub = "com.example.stub.LocalStubProxy")    private StubService stubService;    @Test    public void stub() {        System.out.println(stubService.stub("eclipse2019"));    }}​

代理层:

/* * 1、必须实现服务端的接口 * 2、要定义构造函数(传递客户端引用的实例) * 接管了studService的调用 * * 只有这个类才会决定要不要远程调用 */public class LocalStubProxy implements StubService {    private StubService stubService;    //这个必须要,传stubService实例本身    public LocalStubProxy(StubService stubService) {        this.stubService = stubService;    }    @Override    public String stub(String param) {        //代码在客户端执行,你可以在客户端做ThreadLocal本地缓存,或者校验参数之类工作的        try {            //用目标对象掉对应的方法 远程调用            return stubService.stub(param);        }catch (Exception e) {            //用来降级            System.out.println("降级数据");        }        //掉完后又执行代码        return null;    }}

3.12 本地伪装

mock

只关注调用异常,如果客户端调用服务端出现异常了,那么就会触发服务降级

如何在 Dubbo 中利用本地伪装实现服务降级

本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。

  • api
public interface MockService {    String mock(String param);    String queryArea(String areaCode);    String queryUser(String userCode);}
  • 服务端
@DubboServicepublic 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 "本地伪装案例返回:"+s;    }    @Override    public String queryArea(String s) {        return s;    }    @Override    public String queryUser(String s) {        return s;    }}
  • 消费端
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = ConsumerConfiguration.class)public class TestMockService {    //这两种方式会走rpc远程调用 fail -- 会走远程服务    @DubboReference(check = false,mock = "true")//    @DubboReference(check = false,mock = "com.course.mock.LocalMockService")    //不走服务直接降级 force -- 是不会走远程服务的,强制降级.//    @DubboReference(check = false,mock = "force:return eclipse2019")//    @DubboReference(check = false,mock = "throw java.lang.RuntimeException")    private MockService mockService;    @Test    public void test() {        System.out.println(mockService.mock("eclipse2019"));    }}

默认mock实现

/* * MockServiceMock * * 1、接口名称 + Mock * MockService + 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;    }}

自定义mock实现

public class LocalMockService 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;    }}

3.13 异步调用
在 Dubbo 中发起异步调用

从 2.7.0 开始,Dubbo 的所有异步编程接口开始以 CompletableFuture 为基础

基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。SpringCloud Alibaba系列——4Dubbo简介与应用(三)-鸿蒙开发者社区代码案例

  • api
public interface AsyncService {    String asynctoDo(String name);}
  • 服务端
@DubboServicepublic 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;    }}
  • 消费端
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = ConsumerConfiguration.class)public class TestAsyncService {    @DubboReference(check = false,timeout = 1000000000,methods = {@Method(name =            "asynctoDo",async = true)})    private 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();    }}

3.14 异步执行
Dubbo 服务提供方的异步执行

Provider端异步执行将阻塞的业务从Dubbo内部线程池切换到业务自定义线程,避免Dubbo线程池的过度占用,有助于避免不同服务间的互相影响。异步执行无异于节省资源或提升RPC响应性能,因为如果业务执行需要阻塞,则始终还是要有线程来负责执行。

注意:

Provider 端异步执行和 Consumer 端异步调用是相互独立的,你可以任意正交组合两端配置

  • Consumer同步 - Provider同步
  • Consumer异步 - Provider同步
  • Consumer同步 - Provider异步
  • Consumer异步 - Provider异步
    代码案例
  • api
public interface AsyncService {    String asynctoDo(String name);    CompletableFuture<String> doOne(String var1);}
  • 服务端
@DubboServicepublic 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;    }    @Override    public CompletableFuture<String> doOne(String var1) {        return CompletableFuture.supplyAsync(()->{            try {                Thread.sleep(5000);            } catch (InterruptedException e) {                e.printStackTrace();            }            return "doOne -- OK";        });    }}
  • 消费端
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = ConsumerConfiguration.class)public class TestAsyncDoOne {    @DubboReference(check = false,timeout = 1000000000,methods = {@Method(name =            "asynctoDo",async = true),@Method(name = "doOne",async = false)})    private AsyncService asyncService;    @Test    public void asyncDoone() throws ExecutionException, InterruptedException {        CompletableFuture<String> eclipse2019 = asyncService.doOne("eclipse2019");        eclipse2019.whenComplete((retValue,exception)->{            if(exception == null) {                System.out.println(retValue);            } else {                exception.printStackTrace();            }        });        Thread.currentThread().join();    }}

3.15 本地调用
在 Dubbo 中进行本地调用

本地调用使用了 injvm 协议,是一个伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联,但执行Dubbo 的 Filter 链。

本地调用,调用的就是本地工程的接口实例

示例:

@DubboReference(check = false,injvm = true)StudentService studentService;@Testpublic void inJvm() throws InterruptedException {    System.out.println(studentService.find("xx"));}

3.16 粘滞连接
为有状态服务配置粘滞连接

粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。

粘滞连接将自动开启延迟连接,以减少长连接数。

sticky=true
@DubboReference(check = false,sticky = true)UserService userService;

3.17 小结
Dubbo的高级应用不仅仅只是这些,由于篇幅问题,本文只总结以上的应用,具体的其他应用可以参考官方文档

分类
已于2022-8-24 16:37:00修改
收藏
回复
举报
回复
    相关推荐