一、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博客