Ribbon、Retryer、Hystrix扩展

本文详细介绍了SpringCloudRibbon中的负载均衡策略,包括轮询、权重、随机等七种策略,并提到在实际项目中如何调整默认的OpenFeignRibbon配置。同时,对比了SpringRetry和Guava-Retry的retryer框架,展示了如何在SpringBoot应用中使用它们实现重试机制。
摘要由CSDN通过智能技术生成

一、Ribbon

        ribbon是用来做负载均衡的,
        1)关于负载均衡:
        负载均衡是在稍微大一点的项目中都会去考虑的问题,负载均衡主要做的是请求的负载均衡,负载均衡分为两种,一种是从服务端做负载均衡,一种是在客户端做负载均衡.
        服务器端的负载均衡一般是由前端用nginx做负载均衡器,根据一定算法将请求路由到目标服务器处理。
        客户端负载均衡,比如ribbon,服务消费者客户端会有一个服务器地址列表,调用方在请求前通过一定的负载均衡算法选择一个服务器进行访问,负载均衡算法的执行是在请求客户端进行。
        我理解的区别就是,一个是前端的请求,一个是服务器内部调用的请求。

        

        2)负载均衡策略:

7种负载均衡策略:

1.轮询策略

轮询策略:RoundRobinRule,按照一定的顺序依次调用服务实例。比如一共有 3 个服务,第一次调用服务 1,第二次调用服务 2,第三次调用服务3,依次类推。 此策略的配置设置如下:

2.权重策略

权重策略:WeightedResponseTimeRule,根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。 它的实现原理是,刚开始使用轮询策略并开启一个计时器,每一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务提供者附上一个权重,权重越高被选中的概率也越大。 此策略的配置设置如下:

3.随机策略

随机策略:RandomRule,从服务提供者的列表中随机选择一个服务实例。 此策略的配置设置如下:

4.最小连接数策略

最小连接数策略:BestAvailableRule,也叫最小并发数策略,它是遍历服务提供者列表,选取连接数最小的⼀个服务实例。如果有相同的最小连接数,那么会调用轮询策略进行选取。 此策略的配置设置如下:

5.重试策略

重试策略:RetryRule,按照轮询策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回 null。 此策略的配置设置如下:

6.可用性敏感策略

可用敏感性策略:AvailabilityFilteringRule,先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例。 此策略的配置设置如下:

7.区域敏感策略

区域敏感策略:ZoneAvoidanceRule,根据服务所在区域(zone)的性能和服务的可用性来选择服务实例,在没有区域的环境下,该策略和轮询策略类似。 此策略的配置设置如下:

总结:

Ribbon 为客户端负载均衡器,相比于服务端负载均衡器的统一负载均衡策略来说,它提供了更多的灵活性。Ribbon 内置了 7 种负载均衡策略:轮询策略、权重策略、随机策略、最小连接数策略、重试策略、可用性敏感策略、区域性敏感策略,并且用户可以通过继承 RoundRibbonRule 来实现自定义负载均衡策略。

天坑!!!

我看了很多文档都说,Openfeign默认集成了ribbon,如果要修改负载均衡策略,直接在配置文件中修改就可以:

ribbon:

        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

可是修改了ribbon的配置,负载均衡策略不管怎么样都没有生效,我尝试了很多方法,不管怎么样都不行,访问服务还是按照默认的轮询策略进行的。

        我查了很多资料,重新引入ribbon的依赖,都不行。

        最后我看到我得项目中是引入了spring-cloud-starter-loadbalancer依赖,我查了一下发现,我得项目中并没有走ribbon的负载均衡,而是单独引入loadbalancer依赖起到了负载均衡的作用,然后查询得知但springcloud在2020.0.0之后,移除掉了netflix-ribbon ,因为ribbon已经停止维护了,所以pringCloud官方推荐使用spring-cloud-starter-loadbalancer进行负载均衡。
        所以最后问题就解决了,因为已经默认移除了ribbon依赖,所以我们要改变负载均衡策略是要按照loadbalancer的中提供的方式去改变,而不是在使用ribbon的方式进行修改。

loadbalancer修改负载均衡的方式:
1)创建配置类

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
 
/**
 * @Description: 负载均衡配置
 * @Author: knight
 * @Date: Created in 2022/7/19 10:30
 */
public class LoadBalancerConfig {
    @Bean
    public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory){
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}

2)在主启动类上添加注解@LoadBalancerClient,指定哪个服务(本示例为PAYMENT-SERVER)使用新的负载均衡策略:

@EnableEurekaClient
@SpringBootApplication
@LoadBalancerClient(name = "PAYMENT-SERVER", configuration = LoadBalancerConfig.class)
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication .class, args);
    }
}

配置完以上就大功告成!
还有其他的配置策略可自行查阅。

二、Retryer

        因为feign中集成的有retryer框架,感觉这个功能的用处还是很方便的,所以就想着别的普通方法有没有重试机制。

        查阅了资料有两个比较通用的retryer重试框架,Spring-Retry 和 Guava-Retry。

        1)Spring-Retry

              首先加入依赖:

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.2.2.RELEASE</version>
 </dependency>

        spring retryer中有两种可以重试的方式:
        第一种比较简单,使用Spring-Retry的注解:

        首先在启动类application加上@EnableRetry的注解:

@EnableRetry
public class Application {
 ...
}

        然后我们只要在需要重试的方法上加@Retryable注解,在重试失败的回调方法上加@Recover注解,下面是这些注解的属性:
        

        

测试代码:

controller层:

@RestController
@RequestMapping("/demo")
public class DemoController {
    
    @Autowired
    private DemoService demoService;

    @GetMapping("/test")
    public CommonResp<Boolean> demoTest() {
        return CommonResp.success(demoService.testRetryDemo());
    }
}

service层:

@Service
public class DemoService {

    private static final AtomicInteger count = new AtomicInteger();

    //value:可重试的异常类型
    //maxAttempts 尝试的最大次数(包括第一次失败,默认为3次)
    //backoff 指定用于重试此操作的backoff属性,默认为空 delay:重试的等待时间  multiplier:下一次重试的延迟倍数
    @Retryable(value = {RuntimeException.class},maxAttempts = 3,backoff = @Backoff(delay = 1000L,multiplier = 2))
    public Boolean testRetryDemo(){

        System.out.println("\n"+"第"+count.getAndIncrement()+"次重试!!!!!");
        throw new RuntimeException("测试");
//        return true;
    }


}

打印输出:

 然后加上重试失败的回调方法,需要在重试方法上加上@Recover注解,功能就是如果最后一次重试还未成功,那么就会执行这个方法。

    @Recover
    public Boolean recover(Exception e) {
        log.error("达到最大重试次数,或抛出了一个没有指定进行重试的异常:",e);
        return false;
    }

我第一次加上这个方法时候失效了,查阅资料发现有老哥说是因为参数列表不一致的问题,所以再写重试回调方法时,要注意:
@Recover未生效可能原因:
①返回值必须和被重试的函数返回值一致;
②参数中除了第一个是触发的异常外,后面的参数需要和被重试函数的参数列表一致;
③当然这里的返回值部分也可以再做一次手动重试,但是已经尝试那么多次都失败了,所以在兜底函数中再做一次也意义不大。因此我的考虑是,这里就用来做日志记录就好。

打印日志:

然后我又写了多个被 @Retryable标记的方法也就是需要重试的方法,和多个被@Recover标记的方法,我发现,首先如果参数列表能匹配的上,那么多个重试方法可以共用一个被@Recover标记的回调方法,如果多个被@Recover标记的回调方法都可以匹配上一个重试方法,那么选择异常类型最接近的方法,也就比如我有一个重试方法抛出的是RuntimeException,有两个被@Recover标记的方法,一个是接收Exception异常的参数,一个是接受RuntimeException异常的参数,那么会优先匹配最接近的也就是接受RuntimeException参数的方法作为异常回调方法,而且异常回调方法也就是被@Recover标记的方法必须跟被@Retryable标记方法在同一个类中,不然也不会生效。

以上就使用注解方法的简单调用,如果还有复杂的使用方式可以自行查阅。
 

第二中方法就是使用SpringRetryTemplate:
首先构造RetryTemplate的bean:

/**
     * 重试间隔时间ms,默认1000ms
     * */
    private long fixedPeriodTime = 1000L;
    /**
     * 最大重试次数,默认为3
     */
    private int maxRetryTimes = 3;
    /**
     * 表示哪些异常需要重试,key表示异常的字节码,value为true表示需要重试
     */
    private Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>();

    @Bean
    public RetryTemplate retryTemplate(){
        exceptionMap.put(Exception.class,true);
        // 构建重试模板实例
        RetryTemplate retryTemplate = new RetryTemplate();
        // 设置重试回退操作策略,主要设置重试间隔时间
        FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
        backOffPolicy.setBackOffPeriod(fixedPeriodTime);
        // 设置重试策略,主要设置重试次数
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryTimes, exceptionMap);
        retryTemplate.setRetryPolicy(retryPolicy);
        retryTemplate.setBackOffPolicy(backOffPolicy);
        return retryTemplate;
    }

然后再调用方法的地方注入bean:

    @Autowired
    private DemoService demoService;
    @Autowired
    private RetryTemplate retryTemplate;

    @GetMapping("/test")
    public CommonResp<Boolean> demoTest() {

        Boolean execute = retryTemplate.execute(
                //需要重试的方法
                param -> demoService.testRetryDemo2(),
                //重试失败回调的方法
                param ->{
            log.info("已达到最大重试次数或抛出了不重试的异常~~~");
            return false;
        });
        return CommonResp.success(execute);
    }

打印日志:

 SpringRetryTemplate方法感觉没那么简洁,还有别的应用方法可自行搜索。

        2)guava-retrying

         首先引入依赖:

    <dependency>
      <groupId>com.github.rholder</groupId>
      <artifactId>guava-retrying</artifactId>
      <version>2.0.0</version>
    </dependency>

         然后需要构造一个Retryer对象,我选择注入bean的方法比较方便,构造方法如下:
 

    @Bean
    public Retryer  retryer(){

        Retryer  retryer = RetryerBuilder.<String>newBuilder()
                .retryIfResult(Predicates.isNull())
                .retryIfExceptionOfType(RuntimeException.class)
                .retryIfRuntimeException()
                .retryIfException()
                //等待策略:每次请求间隔2s
                .withWaitStrategy(WaitStrategies.fixedWait(2, TimeUnit.SECONDS))
                //停止策略 : 尝试请求3次
                .withStopStrategy(StopStrategies.stopAfterAttempt(2))
                //时间限制 : 某次请求不得超过2s , 类似: TimeLimiter timeLimiter = new SimpleTimeLimiter();
                .withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(10, TimeUnit.SECONDS))
                .build();
        return retryer;
    }

        然后需要实现Callable接口,将我们自己实际的业务调用方法在call方法中

 @GetMapping("/aaa")
    public String convert()  {
        

        //定义请求实现,调用第三方接口,就会产生异常,引入重试机制
        Callable<String> callable =   new Callable<String>() {
            @Override
            public String call() throws Exception {
                try{
                    //自己的业务逻辑
                    String str = demoService.test();
                    return str;
                } catch (RuntimeException e){
                    log.error("", e);
                    throw e;
                }
            }
        };

        
        try {
            // 返回值
            return  (String) retryer.call(callable);
        } catch (ExecutionException e) {
            log.error("", e);
        } catch (RetryException e) {
            log.error("", e);
        } catch (RuntimeException e) {
            log.error("", e);
        }
        return null;
    }

    

}

然后使用retryer.call()方法进行调用即可。
以上就是最基本最基本的用法,还有复杂的方式可自行查阅。
https://www.cnblogs.com/aaacarrot/p/17217580.html
重试利器之Guava Retrying_guava-retrying-CSDN博客
       

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值