Dubbo使用总结

负载均衡

使用

多个服务提供者配置,server.port:9000不能相同,protocol.port: 20890不能相同,
dubbo.application.name必须相同,否则无法负载均衡。然后scan扫码的包名多个也必须相同,否则无法负载均衡,说明不同的服务提供者的包结构要相同

server:
    port: 9000
spring:
    dubbo:
        application:
            name: springboot-dubbo-provider
        registry:
            address: zookeeper://127.0.0.1
            port: 2181
        protocol:
            name: dubbo
            port: 20890
        scan: com.lolxxs.consumer.service

服务提供者service,@Service注解表示暴露接口

@Service(version = "1.0.0", timeout = 3000)
public class ProviderServiceImpl implements ProviderService {
    @Override
    public String provider() {
        System.out.println("调用了");
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

消费者配置,注意扫描的包要改变,server.port端口要改变,application.name要改变

server:
    port: 9002
spring:
    dubbo:
        application:
            name: springboot-dubbo-consumer
        registry:
            address: zookeeper://127.0.0.1
            port: 2181
        protocol:
            name: dubbo
            port: 20890
        scan: com.lolxxs.provider.controller

消费者的Controller代码,其中 ProviderService必须在pom导入服务提供者模块,然后再依赖引入,接口实现是Dubbo通过RPC调用来实现的

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Reference(version = "1.0.0", timeout = 300)
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        return providerService.provider();
    }
}

负载均衡策略

策略名作用
random随机,默认值,可以通过weight属性调节权重,权重越大几率越大
roundrobin轮询模式, 可以通过weight属性调节权重,权重越大轮询次数越多
leastactive最少活跃调用数模式,选择此时最少活跃调用数的节点
consistenthash一致性hash模式,将相同接口相同参数的请求将固定发送到某一个节点

随机(random)

修改两个服务提供者的代码

@Service(version = "1.0.0", timeout = 3000, 
loadbalance = "random", weight = 400)
public class ProviderServiceImpl implements ProviderService {
    
    @Override
    public String provider() {
        System.out.println("调用了");
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}
@Service(version = "1.0.0", timeout = 3000,
 loadbalance = "random", weight = 100)
public class ProviderServiceImpl implements ProviderService {
    
    @Override
    public String provider() {
        System.out.println("调用了");
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

轮询(round robin)

将服务消费者代码改为如下,运行后知道,当服务者和消费者同时配置loadbalance以消费者的为准,为什么服务者和消费者都能配置loadbalance呢,因为消费者无法配置weight权重,所以需要在服务者配置。所以无须权重的loadbalance直接在消费者侧配置即可

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Reference(version = "1.0.0", timeout = 300,
     loadbalance = "roundrobin")
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        return providerService.provider();
    }
}

最少活跃调用数(least active)

将消费者代码改为如下,最少活跃调用数模式选择此时最少活跃调用数的节点,拥有最少活跃调用数的节点表明其处理能力强,能很快处理请求,所以活跃的调用数最少

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Reference(version = "1.0.0", timeout = 300,
     loadbalance = "leastactive")
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        return providerService.provider();
    }
}

一致性hash(consistent hash)

一致性hash模式,将相同接口相同参数的请求将固定发送到某一个节点,注意这里是要在服务提供者定义的接口和参数,如果在消费者定义了不同接口和不同参数没有用,如果仍然是使用无参的服务提供者接口,仍然会只路由到一个服务器

将服务提供者一个接口一个类的代码改为如下,这里的loadbalance="random"不会影响,因为消费者也配置了loadbalance

public interface ProviderService {
    String provider();
    String consistenthash(int id);
}

@Service(version = "1.0.0", timeout = 3000, loadbalance = "random", weight = 400)
public class ProviderServiceImpl implements ProviderService {
    @Override
    public String provider() {
        System.out.println("调用了");
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
    @Override
    public String consistenthash(int id) {
        System.out.println("调用了");
        return  "id: "+id+" 远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

消费者代码改为如下,提供访问/consistenthash/{id},路径,并改变id的值可以看到更改了访问的服务提供者,且当id相同时,服务提供者也相同

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Reference(version = "1.0.0", timeout = 300, loadbalance = "consistenthash")
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        return providerService.provider();
    }

    @GetMapping("/consistenthash/{id}")
    public String getTest(@PathVariable("id") int id) {
        return providerService.consistenthash(id);
    }
}

集群容错

集群容错策略

策略名作用
failover默认值,当出现失败,通过其他服务器调用该接口,配合retries一起使用,retries默认值为2,表示重试两次
failfast快速失败,只发起一次调用,失败立即报错,通常用于非幂等性的写操作,就算设置了重试次数也不会重试
failsafe失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作,就算设置了重试次数也不会重试
failback失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作,就算设置了重试次数也只会调用两次,相对于重试一次
forking并行调用多个服务器,只要一个成功即返回,通常用于实时性要求较高的读操作,但需要浪费更多服务资源,可通过 forks=“2” 来设置最大并行数,设置重试次数无效,仍然是每个服务提供者调用一次
broadcast广播调用所有提供者,逐个调用,任意一台报错则报错 ,通常用于通知所有提供者更新缓存或日志等本地资源信息,设置重试次数无效,仍然是每个服务提供者调用一次

失败自动切换(failover)

修改两个服务端代码为如下,像上面新创建的一个接口用于一致性哈希策略我省略了,要不然把无关的代码贴出来,这里没有添加retries属性,但是默认会有retries=2,使用Thread.sleep(10000)延时10秒触发超时模拟调用失败。当retries=2时第一次重试在本地,最后一个重试在另一个服务器,可以将retries改为5,则在本地重试4次(加上第一次运行一共运行5次),在另一台服务器重试1次

@Service(version = "1.0.0", timeout = 3000, cluster = "failover"
//,retries = 5
)
public class ProviderServiceImpl implements ProviderService {

    @Override
    public String provider() {
        System.out.println("调用了");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

修改服务提供者代码即可运行,运行后再修改服务消费者代码,设置retries = 2,可以发现,在服务提供者的retries = 5会失效

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Reference(version = "1.0.0", timeout = 300,retries = 2)
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        return providerService.provider();
    }
}

快速失败(failfast)

服务端代码如下,可以看到配置了retries = 5,但是使用时会发现一次重试都不会有

@Service(version = "1.0.0", timeout = 3000, cluster = "failfast", retries = 5)
public class ProviderServiceImpl implements ProviderService {

    @Override
    public String provider() {
        System.out.println("调用了");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

消费端代码如下,即使在消费端添加了retries = 3,仍然不会进行重试,但是如果添加了cluster = “failover”,那么就会进行重试,可以知道消费端的cluster优先级仍然高于服务端

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Reference(version = "1.0.0", timeout = 300 
    //,retries = 3
    //,cluster = "failover"
    )
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        return providerService.provider();
    }

    @GetMapping("/consistenthash/{id}")
    public String getTest(@PathVariable("id") int id) {
        return providerService.consistenthash(id);
    }
}

失败安全(failsafe)

两个服务端代码修改为如下,发现消费者即使超时了也不会报任何错误,并且就算设置了重试次数也不会重试

@Service(version = "1.0.0", timeout = 3000, cluster = "failsafe", retries = 5)
public class ProviderServiceImpl implements ProviderService {

    @Override
    public String provider() {
        System.out.println("调用了");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }

    @Override
    public String consistenthash(int id) {
        System.out.println("调用了");
        return  "id: "+id+" 远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

消费者代码,就算设置了重试也不会重试

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Reference(version = "1.0.0", timeout = 300, retries = 3)
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        return providerService.provider();
    }

    @GetMapping("/consistenthash/{id}")
    public String getTest(@PathVariable("id") int id) {
        return providerService.consistenthash(id);
    }
}

失败自动恢复(failback)

两个服务端代码如下,设置重试retries = 5无效,失败后隔了一段时间会调用一次(只调用两次),可能在本机调用,也可能在其他服务器调用,在消费端设置retries = 5也无效

@Service(version = "1.0.0", timeout = 3000, cluster = "failback", retries = 5)
public class ProviderServiceImpl implements ProviderService {

    @Override
    public String provider() {
        System.out.println("调用了");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

并行调用OR(forking)

为了体现并行调用的一个服务成功即成功,则两个服务端代码不能一样,必须一个能成功执行,一个不能成功执行,配置了重试次数也无效,依然每个服务提供者只会调用一次

@Service(version = "1.0.0", timeout = 3000, cluster = "forking"
,retries = 5)
public class ProviderServiceImpl implements ProviderService {

    @Override
    public String provider() {
        System.out.println("调用了");
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

另一个服务端代码

@Service(version = "1.0.0", timeout = 3000, cluster = "forking"
,retries = 5)
public class ProviderServiceImpl implements ProviderService {
    @Override
    public String provider() {
        System.out.println("调用了");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

可以发现调用成功

广播调用AND(broadcast)

为了体现广播调用任意一台服务端调用不成功则报错这种AND逻辑,仍然可以使用上面的代码,配置了重试次数也无效,依然每个服务提供者只会调用一次

@Service(version = "1.0.0", timeout = 3000, cluster = "broadcast"
,retries = 5)
public class ProviderServiceImpl implements ProviderService {

    @Override
    public String provider() {
        System.out.println("调用了");
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

另一个服务端代码

@Service(version = "1.0.0", timeout = 3000, cluster = "broadcast"
,retries = 5)
public class ProviderServiceImpl implements ProviderService {
    @Override
    public String provider() {
        System.out.println("调用了");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

服务分组

当服务提供者的一个接口有多个实现,可以使用服务分组来指定调用,由于无需实验负载均衡和集群容错,则只需启动一个服务提供者和修改一个服务提供者的代码来简化实验

服务提供者代码如下,很明显一个能调用成功,一个会报错
GroupServiceImpl.java

@Service(version = "1.0.0", timeout = 3000, group = "group")
public class GroupServiceImpl implements ProviderService {
    @Override
    public String provider() {
        System.out.println("调用了");
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

ProviderServiceImpl.java

@Service(version = "1.0.0", timeout = 3000, group = "provider")
public class ProviderServiceImpl implements ProviderService {
    @Override
    public String provider() {
        System.out.println("调用了");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

修改消费者代码来选择使用的接口,先使用group = “group”,再使用group = “provider”,可以看到一次调用成功,一次失败,失败时会重试,因为cluster默认配置failover,该配置支持重试

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Reference(version = "1.0.0", timeout = 300,retries = 5
    ,group = "group"
//  ,group = "provider"
	)
    private ProviderService providerService;
    @GetMapping("/test")
    public String getTest() {
        return providerService.provider();
    }
}

多版本

当服务提供者一个接口出现不兼容升级时,可以配置其版本version,在服务消费者使用version可以指定使用的版本。并且可以过渡式升级,即先将一半的服务提供者的版本升级,留另一半的服务提供者还是低版本。为了体现该情况,以下需要使用两个服务提供者

代码如下,可以看到2.0.0,可以成功调用,1.0.0不可成功调用,且这两个代码位于不同提供者

@Service(version = "2.0.0", timeout = 3000)
public class ProviderServiceImpl implements ProviderService {
    @Override
    public String provider() {
        System.out.println("调用了");
      
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}
@Service(version = "1.0.0", timeout = 3000)
public class ProviderServiceImpl implements ProviderService {
    @Override
    public String provider() {
        System.out.println("调用了");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return  "远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

修改消费者代码如下,可以发现消费者会自动寻找拥有指定版本号接口的服务提供者去调用,不会去找另一个版本的服务提供者,所以也不会有不同版本的负载均衡的情况。当调用失败的版本1.0.0,进行重试时,也不会找其他版本的服务提供者进行重试

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Reference(version = "2.0.0", timeout = 300,retries = 5
    //,version = "1.0.0"
    )
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        return providerService.provider();
    }
}

结果缓存

缓存策略

策略名作用
lru基于最近最少使用原则删除多余缓存,保持最热的数据被缓存,不同线程之间共享
threadlocal当前线程缓存,在当前线程多次调用该接口时会缓存
jcache可以桥接各种缓存实现

缓存通过RPC调用从服务提供者中获取的结果,在缓存期间不会再次RPC调用

lru

修改消费者代码,使用lru缓存淘汰策略,在第一次调用成功后,1000次调用之内,不会再次RPC调用,且返回同样的结果,且必须多进程调用该接口,即使用不同的端口或ip,才会使得lru淘汰生效,只有一个进程多次调用即使超过1000次都返回相同的值

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Reference(version = "2.0.0", timeout = 300,retries = 5, 
    cache = "lru")
    private ProviderService providerService;
    
    @GetMapping("/test")
    public String getTest() {
        return providerService.provider();
    }
}

threadlocal

修改消费者代码,可以发现即使在一个线程里多次调用仍然返回相同值

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Reference(version = "2.0.0", timeout = 300,retries = 5, 
    cache = "lru")
    private ProviderService providerService;
    
     @GetMapping("/test")
    public String getTest() {
        for (int i = 0; i < 10; i++) {
            providerService.provider();
        }
        return providerService.provider();
    }
}

泛化调用

服务提供者代码如下,其实只是相对于官方定义了一个接口(以前是我们自己定义接口),需要我们实现,这个接口传递了,s方法名,strings是方法参数类型全限定类名数组,objects是对应的参数对象数组,提供这些参数我们就可以在这一个方法调用很多指定的方法,

@Service(version = "2.0.0", timeout = 3000)
public class GenericServiceImpl implements GenericService {
    @Override
    public Object $invoke(String s, String[] strings, Object[] objects) throws GenericException {
        System.out.println("泛化调用了");
        return  System.currentTimeMillis()+" 远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

消费者代码如下,其实仍然和上面调用方式差不多,这个GenericService仍然是官方定义的接口,它会去服务提供者里面寻找实现,提供传递三个参数,如果服务提供者实现里面确实实现了不同方法的调用,则可以达到泛化调用的目的,即一个方法能调用很多种方法

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Reference(version = "2.0.0", timeout = 300)
private GenericService genericService;

@GetMapping("/test")
public String getTest() {
    String result = (String)genericService.$invoke("provider", new String[0], new Object[0]);
    System.out.println(result);
    return result;
}

回声测试

回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,能够测试整个调用是否通畅,可用于监控,所有服务自动实现EchoService接口,只需将任意服务引用强制转型为EchoService,即可使用

修改消费者代码为如下

public class ConsumerController {
    @Reference(version = "2.0.0", timeout = 300,retries = 5)
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        EchoService echoService = (EchoService) this.providerService;
        return (String) echoService.$echo("OKOKOKOK");
    }
}

如果请求成功则返回"OKOKOKOK",即echoService.$echo(“OKOKOKOK”);传入的OKOKOKOK,如果请求失败则报错,可以检测服务提供者是否正常

异步调用

可以在消费者调用服务提供者API时直接返回,后面使用Future来进行获取结果

修改服务提供者代码,只休眠2秒,防止超时

@Service(version = "2.0.0", timeout = 3000)
public class ProviderServiceImpl implements ProviderService {
    @Override
    public String provider() {
        System.out.println("调用了");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return  System.currentTimeMillis()+" 远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

修改消费者代码
添加async=true,表示异步调用,如果是同步则会一致阻塞输出不了 “开始异步调用",如果是异步则会阻塞在future.get()操作

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Reference(version = "2.0.0",retries = 5, async = true)
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        String result = providerService.provider();
        Future<String> future = RpcContext.getContext().getFuture();
        System.out.println("开始异步调用");
        try {
            result = future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return result;
    }
}

参数回调

消费者将回调函数实现传入服务端,服务端执行该回调方法时,方法实际上在消费者端调用,需要使用method属性指定调用回调方法的方法名,使用argument的index指定0,即第一个参数为回调调用,即在消费者端调用,而不是服务端调用。使用springboot整合dubbo,如果版本低了可能没有methods、arguments属性,也就无法实现参数回调

可以使用如下依赖,而且必须是org.apache.dubbo.config.annotation.Service的@Service才有methods、arguments属性,修改了依赖可能需要使用不同的配置文件,因为博主原先使用spring-boot-starter-dubbo,后面换成dubbo-spring-boot-starter,则配置文件一开始前缀是spring.dubbo,后面直接换成dubbo

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>2.7.8</version>
</dependency>
@Service(version = "2.0.0", timeout = 3000,
        methods = {@Method(name="callback",
        arguments = {@Argument(index=0,callback = true)})},
        callbacks = 1000)
public class ProviderServiceImpl implements ProviderService{
    @Override
    public String callback(Callback callback) {
        System.out.println("调用了");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("服务端想要调用回调函数");
        callback.call();
        return " 远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

服务端还需创建一个接口,修改一个接口,以下两个接口都在服务端

public interface Callback {
    void call();
}

public interface ProviderService {
     String callback(Callback callback);
}

消费者代码如下,实现服务端定义的回调接口,等待服务端发送指令,让消费者调用此回调函数

@RestController
@RequestMapping("/consumer")
public class ConsumerController implements Serializable {
    @Reference(version = "2.0.0",retries = 5, callbacks = 1000)
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        String result = providerService.callback(new Callback() {
            @Override
            public void call() {
                System.out.println("消费者收到指令调用回调函数");
            }
        });
        return result;
    }
}

事件通知

其实就是相对于官方规定了三个参数回调方法,在服务提供者的代码调用之前、调用之后、出现异常时,会触发 oninvoke、onreturn、onthrow 三个事件,这三个事件对应的回调函数会在消费者端调用

使用注解调用一直不成功,所以没有贴代码

本地存根

在服务提供者中创建一个ProviderServiceStub类放在ProviderService接口同路径下,然后再消费者中启用Stub,则每次远程调用RPC时,会现在消费者中调用服务提供者中的个ProviderServiceStub类方法(由于消费者有服务提供者的依赖,所以其实这个Stub方法相对于在消费者本地),再远程RPC调用。注意Stub类名一定是以ServiceStub结尾的,即xxxxxServiceStub,前面的xxxx代表任意字符。

服务提供者代码

public class ProviderServiceStub implements ProviderService{

    private ProviderService providerService;

    public ProviderServiceStub(ProviderService providerService) {
        this.providerService = providerService;
    }
    @Override
    public String provider() {
        System.out.println("调用本地存根");
        return providerService.provider();
    }
}

@Service(version = "2.0.0", timeout = 3000,stub = "true")
public class ProviderServiceImpl implements ProviderService{
    @Override
    public String provider() {
        System.out.println("调用了");
        return  System.currentTimeMillis()+" 远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }

消费者代码,启动stub

@RestController
@RequestMapping("/consumer")
public class ConsumerController implements Serializable {
    @Reference(version = "2.0.0",retries = 5, stub = "true")
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        String result = providerService.provider();
        return result;
    }

}

服务降级

降级策略

策略名作用
mock = “true”执行ServiceMock类中的降级逻辑
mock=“具体的mock实现类”执行指定全类名的类降级逻辑
mock=“抛出自定义异常”抛出指定全限定类名的异常
mock=“返回 mock 数据”返回给定mock数据
mock=“force:true”当服务消费者调用服务提供者时,会直接执行 mock 配置的策略,不会进行服务调用,其他如 mock = “true”
mock=“force:具体的mock实现类”当服务消费者调用服务提供者时,会直接执行 mock 配置的策略,不会进行服务调用,其他如 mock=“具体的mock实现类”
mock=“force:抛出自定义异常”当服务消费者调用服务提供者时,会直接执行 mock 配置的策略,不会进行服务调用,其他如mock=“抛出自定义异常”
mock=“force:返回 mock 数据”当服务消费者调用服务提供者时,会直接执行 mock 配置的策略,不会进行服务调用,其他如 mock=“返回 mock 数据”

mock = “true”

类似ProviderServiceStub类,定义一个ProviderServiceMock类方法放在服务提供者代码的ProviderService接口同目录下,(由于消费者有服务提供者的依赖,所以其实这个Mock方法相当于在消费者本地),等服务提供者不可用或者超时,消费者就会调用相当于在本地的Mock方法执行降级逻辑

服务提供者代码

public class ProviderServiceMock implements ProviderService{
    @Override
    public String provider() {
        System.out.println("降级处理");
        return "降级";
    }
}

@Service(version = "2.0.0", timeout = 3000)
public class ProviderServiceImpl implements ProviderService{
    @Override
    public String provider() {
        System.out.println("调用了");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return  System.currentTimeMillis()+" 远程地址"+ RpcContext.getContext().getRemoteAddress()+" 本地地址"+ RpcContext.getContext().getLocalAddress();
    }
}

消费者代码,设置超时时间,而服务提供者又延时以此模拟出错,mock = “true”,表示降级执行ProviderServiceMock中的逻辑,并返回其中的返回值给浏览器(用户)

@RestController
@RequestMapping("/consumer")
public class ConsumerController implements Serializable {
    @Reference(version = "2.0.0",timeout = 300, retries = 5,
     mock = "true")
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        String result = providerService.provider();
        return result;
    }
}

mock=“具体的mock实现类”

在消费者目录下创建一个MyMock,然后修改消费者代码就行,服务提供者代码不用改变

注意全限定类名要和自己定义的类的位置和名字一直

@RestController
@RequestMapping("/consumer")
public class ConsumerController implements Serializable {
    @Reference(version = "2.0.0",timeout = 300, retries = 5,
     mock = "com.lolxxs.consumer.mock.MyMock")
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        String result = providerService.provider();
        return result;
    }
}

public class MyMock implements ProviderService {
    @Override
    public String provider() {
        System.out.println("自定义降级逻辑");
        return "自定义降级";
    }
}

mock=“抛出自定义异常”

只需修改消费者代码,mock处写什么异常则抛出什么异常

@RestController
@RequestMapping("/consumer")
public class ConsumerController implements Serializable {
    @Reference(version = "2.0.0",timeout = 300, retries = 5,
            mock = "throw java.io.IOException")
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        String result = providerService.provider();
        return result;
    }
}

mock=“返回 mock 数据”

只需修改消费者代码,mock处写返回什么就返回什么,如下代码返回 mock降级

@RestController
@RequestMapping("/consumer")
public class ConsumerController implements Serializable {
    @Reference(version = "2.0.0",timeout = 300, retries = 5,
            mock = "return mock降级")
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        String result = providerService.provider();
        return result;
    }
}

mock=“force:返回 mock 数据”

只需修改消费者代码,mock处写返回什么就返回什么,如下代码返回 mock降级,加上force: 后不会进行RPC调用,直接只需mock逻辑

@RestController
@RequestMapping("/consumer")
public class ConsumerController implements Serializable {
    @Reference(version = "2.0.0",timeout = 300, retries = 5,
            mock = "force:return mock降级")
    private ProviderService providerService;

    @GetMapping("/test")
    public String getTest() {
        String result = providerService.provider();
        return result;
    }
}
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在SpringBoot工具类中使用Dubbo,首先需要确保已经完成了Dubbo和SpringBoot的集成配置。在SpringBoot的配置文件中,需要设置Dubbo的相关配置,如application.id、application.name、registry.address、server等等。\[1\] 接下来,在工具类所在的包中添加Dubbo的依赖项。可以在pom.xml文件中添加以下依赖项: ```xml <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>3.1.0</version> </dependency> ``` 这样就可以引入Dubbo的Spring Boot Starter,以便在Spring Boot应用中使用Dubbo功能。\[2\] 然后,在Spring Boot的启动类上添加@EnableDubboConfiguration注解,以启用Dubbo的配置。同时,可以使用@MapperScan注解指定Dubbo的DAO包路径,以便自动扫描并注入Dubbo的Mapper接口。示例代码如下: ```java @MapperScan("com.xq.live.dubbo.dao") @EnableDubboConfiguration @SpringBootApplication public class DubboServerApplication { public static void main(String\[\] args) { SpringApplication.run(DubboServerApplication.class, args); } } ``` 这样就完成了在Spring Boot工具类中使用Dubbo的配置。可以在工具类中注入Dubbo的服务接口,并调用相应的方法来实现业务逻辑。 #### 引用[.reference_title] - *1* *3* [dubbo与springboot的集成和使用dubbo-spring-boot-starter](https://blog.csdn.net/m0_67393295/article/details/126648167)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [dubbo教程总结(springboot+dubbo)](https://blog.csdn.net/black_pp/article/details/128102018)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lolxxs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值