我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第10篇文章,点击查看活动详情
在2019年7月3日,在Hoxton.M1的发布会上,Spring 已经宣布用 Spring Cloud LoadBalancer 来代替 Netfix Ribbon 。这宣告着新时代的微服务负载均衡器诞生了,开源的负载均衡器loadbalancer作为ribbon的替代称为了spring cloud生态的主流。
因为Feign默认集成了Ribbon,同样的OpenFeign也默认集成了LoadBalancer。因此,这里LoadBalancer主要搭配OpenFeign进行测试,并且会使用Caffeine本地缓存替换LoadBalancer的默认缓存进行性能优化(实际上官方也建议进行替换)。
下面进行Spring Cloud LoadBalancer的集成和优化:
1. 创建LoadBalancer子模块
创建 service-loadbalancer 子模块:
2. 添加Maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-starter</artifactId>
<groupId>com.deepinsea</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-loadbalancer</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos 服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.1.0</version>
<exclusions>
<!-- 排除commons-io冲突依赖 -->
<exclusion>
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- openfeign 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 需要集成loadbalancer依赖实现服务名称调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
</dependencies>
</project>
注意:一定要添加 spring-cloud-loadbalancer 依赖,因为服务名的调用依赖于loadbalancer组件。
3. 添加主启动类服务发现声明
编写主启动类src/main/java/com/deepinsea/ServiceLoadBalancerApplication.java:
package com.deepinsea;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* Created by deepinsea on 2022/6/16.
* LoadBalancer主启动类
*/
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceLoadBalancerApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceLoadBalancerApplication.class,args);
}
}
4. 创建yml配置文件
创建application.yml配置文件:
server:
# 服务运行端口
port: 9060
spring:
application:
# 应用名称
name: service-loadbalancer
cloud:
nacos:
discovery:
# 服务名称(默认就是应用名)
# service: ${spring.application.name}
# 服务注册地址
server-addr: localhost:8848
# Nacos认证信息
username: nacos
password: nacos
# 注册到 nacos 的指定 namespace,默认为 public
namespace: public
5. 添加负载均衡配置类
创建RestTemplateConfig配置类:
package com.deepinsea.common.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
/**
* Created by deepinsea on 2022/6/16.
* RestTemplate配置类
*/
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // 使用服务名进行负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
public RestTemplate byIpRestTemplate() { //使用ip:端口调用
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}
@Bean(name = "ipRestTemplate") //使用ip:端口调用
public RestTemplate ipRestTemplate() {
return new RestTemplate();
}
}
6. 创建服务调用Controller
package com.deepinsea.controller;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
/**
* Created by deepinsea on 2022/6/16.
*/
@RestController
@RequestMapping("/consumer-loadbalancer")
public class LoadBalancerController {
@Resource
@Qualifier("ipRestTemplate")
private RestTemplate ipRestTemplate; //ip
@Resource
private RestTemplate restTemplate; //服务名
private static final String ipURL = "http://localhost:9010/provider-nacos/hello"; //ip
private static final String nameURL = "http://service-provider-nacos/provider-nacos/hello"; //服务名
@GetMapping("/testIp") //ip调用
public String testIp(){ //如果@LoadBalanced与负载均衡实现组件同时存在,则会变更为服务名调用,停用ip调用
String result = ipRestTemplate.getForObject(ipURL, String.class);
return "loadbalancer的ip服务调用结果为: " + result;
}
@GetMapping("/testName")
public String testName(){ //服务名负载调用
String result = restTemplate.getForObject(nameURL, String.class);
return "loadbalancer的服务名调用结果为: " + result;
}
}
7. 测试RestTemplate服务调用
同样, 先启动service-provider-nacos 和service-provider-api 子模块项目,再点击"▶"启动 service-loadbalancer 子模块项目:
项目正常启动,下面使用curl命令快速测试使用RestTemplate进行Ip和服务调用:
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/testIp
loadbalancer的ip服务调用结果为: hi, this is service-provider-nacos!
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/testName
loadbalancer的服务名调用结果为: hi, this is service-provider-nacos!
可以看到,控制台打印了缓存相关日志,后续搭配OpenFeign使用也会打印替换默认缓存的日志,可以进行缓存替换优化:
2022-06-16 15:12:20.030 WARN 17848 --- [nio-9060-exec-1] c.l.c.ServiceInstanceListSupplierBuilder : LoadBalancerCacheManager not available, returning delegate without caching.
下面使用OpenFeign搭配LoadBalancer进行服务负载调用:
8. 启动类启用FeignClients
package com.deepinsea;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* Created by deepinsea on 2022/6/16.
* LoadBalancer主启动类
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients //开启FeignClients
public class ServiceLoadBalancerApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceLoadBalancerApplication.class,args);
}
}
9. 创建Feign客户端接口
添加service目录,创建OpenFeignClients接口:
package com.deepinsea.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Created by deepinsea on 2022/6/19.
* OpenFeign客户端接口
*/
@FeignClient(name = "service-provider-nacos", path = "/provider-nacos") //调用服务名和路径前缀
public interface OpenFeignClients {
// hello接口
@GetMapping("/hello")
String hello();
}
格式:@FeignClient(name = "service-name", url = "${feign.urls.service-name:}", fallback =ApiFallBack.class, configuration = Interceptor.class)
@FeignClient参数的用法解析:
- value:等同于 name,指定调用的服务名;
- name:指定FeignClient的名称,对应的是调用的微服务的服务名,用于服务发现、网关调用等获取服务信息的方式;
- url: 访问地址,可以直接提供给外部调用,格式如192.168.1.11:8800/hello。⼀般用于调试,可以手动指定@FeignClient调用的地址,不用创建服务消费者controller的调用接口;
- decode404:当发⽣http 404错误时,如果该字段位true,会调用decoder进⾏解码,否则抛出FeignException;
- configuration: Feign配置类,可以⾃定义Feign的Encoder、Decoder、LogLevel、Contract;
- fallback: 定义容错的处理类,当调用远程接⼝失败或超时时,会调用对应接⼝的容错逻辑,fallback指定的类必须实现@FeignClient 标记的接口;
- fallbackFactory: 工厂类,⽤于⽣成fallback类⽰例,通过这个属性我们可以实现每个接⼝通⽤的容错逻辑,减少重复的代码;
- path: 定义当前FeignClient的统一前缀。
feign两种降级方式 fallback 和 fallfactory 的区别
fallback比fallbackfactory优先级高,若都存在,会走fallback调用。fallback只是重写了回退方法。不能捕获异常打印堆栈信息,不利于问题排查。fallfactory使用线程抛出异常。可以捕获异常信息并返回默认降级结果,可以打印堆栈信息。
因此,推荐使用fallfactory指定服务降级。
10. 创建服务消费者访问接口
在服务调用LoadBalancerController中创建OpenFeign调用的相关接口,添加以下内容:
@Resource
private OpenFeignClients openFeignClients; // 注入Feign客户端服务接口
@GetMapping("/hello")
public String hello(){ //openfeign调用
String result = openFeignClients.hello();
return "openfeign负载均衡调用成功,返回结果为: " + result;
}
11. 测试OpenFeign服务负载调用
依次启动 service-provider-nacos、service-provider-api 和 service-loadbalancer 三个子模块项目,启动成功后,使用curl命令测试openfeign负载调用:
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-nacos!
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-api!
测试成功,并且采用的是默认轮询算法!
12. 切换LoadBalancer负载均衡策略
因为从Spring Cloud Netflix 3.0 以后的版本将默认的负载均衡组件由Ribbon更改为LoadBalancer,它的负载均衡策略使用的是Spring Cloud的负载均衡策略。并且只支持两种负载均衡策略:轮询策略和随机策略,通过实现ReactorLoadBalancer 接口实现:
spring cloud loadbalancer的负载均衡算法,类关系图如下所示:
可以看到,对比Ribbon的7大负载均衡策略,很明显 spring cloud loadbalancer 少了很多支持的负载算法。
参考官方文档:docs.spring.io/spring-clou…
可以了解到:其实修改算法策略的方式相同,只是构造方法和注解发生变化了而已。
下面是官方给的说明和参考负载策略配置:
默认使用的
ReactiveLoadBalancer实现是RoundRobinLoadBalancer. 要为选定的服务或所有服务切换到不同的实现,您可以使用自定义 LoadBalancer 配置机制。
创建LoadBalancer负载策略配置类CustomLoadBalancerRuleConfig:
package com.deepinsea.common.config;
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;
/**
* Created by deepinsea on 2022/6/19.
* LoadBalancer负载策略配置类
*/
public class CustomLoadBalancerRuleConfig {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
然后创建LoadBalancer配置类LoadBalancerConfig:
package com.deepinsea.common.config;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
/**
* Created by deepinsea on 2022/6/19.
* LoadBalancer配置类
*/
@LoadBalancerClient(name = "service-provider-nacos", configuration = CustomLoadBalancerConfiguration.class)
public class LoadBalancerConfig {
}
注意:这里配置类可以不用添加@Configuration注解,因为@LoadBalancerClient有实现了ImportBeanDefinitionRegistrar接口的LoadBalancerClientConfigurationRegistrar的Bean注册器;有包扫描和注册到BeanFactory的功能,独立于Spring的Bean扫描注册体系存在。
下面是Spring官方给的提示:
@LoadBalancerClient作为配置参数传递给@LoadBalancerClients类,不应使用注释@Configuration或超出组件扫描范围。
可以通过@LoadBalancerClient注释传递以下配置以切换到使用RandomLoadBalancer:
13. 测试负载策略效果
重新启动项目,然后使用curl命令测试LoadBalancer的负载均衡算法:
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-api!
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-api!
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-nacos!
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-nacos!
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-nacos!
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-nacos!
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-api!
可以看到,服务调用已经由默认的轮询算法更换为了随机算法!
14. 手写负载均衡算法
有没有一种两全其美的方式,既能使用最新版本的负载组件LoadBalancer,又能支持老牌但停止维护的负载均衡组件Ribbon的负载均衡策略?
我们的目的是使LoadBalancer支持Ribbon的负载策略。
1.添加ribbon-loadbalancer依赖
我们尝试像配置Ribbon的负载均衡那样创建配置类,然后自定义IRule接口返回的负载策略:
首先创建RibbonLoadBalancerRuleConfig配置类,但是引入IRule接口的时候,提示我们应该引入ribbon-loadbalancer包才能支持这个类的导入:
导入完成后,pom文件中新增了一个ribbon-loadbalancer的适配依赖(类似于feign-okhttp):
<!-- loadbalancer支持ribbon的7大负载策略 -->
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-loadbalancer</artifactId>
<version>2.3.0</version>
<scope>compile</scope>
</dependency>
2.添加负载策略配置类
Ribbon-LoadBalancer配置类,如下所示:
package com.deepinsea.common.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
/**
* Created by deepinsea on 2022/6/19.
*/
public class RibbonLoadBalancerRuleConfig {
// 自定义负载均衡规则
@Bean
public IRule myRule(){
return new RandomRule();
}
}
再次放一下Ribbon支持的7种负载均衡策略:
| 策略名 | 描述 |
|---|---|
| BestAvailableRule | 选择一个最小的并发请求的server。逐个考察 Server,如果 Server 被 tripped(跳闸)了,则忽略,再选择其中 ActiveRequestsCount 最小的 server。 |
| AvailabilityFilteringRule | 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值) |
| WeightedResponseTimeRule | 根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。 |
| RetryRule | 对选定的负载均衡策略机上重试机制。在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server |
| RoundRobinRule | 轮询index,选择index对应位置的server |
| RandomRule | 随机选择一个server。在index上随机,选择index对应位置的server |
| ZoneAvoidanceRule | 复合判断server所在区域的性能和server的可用性选择server |
3.loadbalancer客户端策略配置
将CustomLoadBalancerRuleConfig.class配置类,更换为RibbonLoadBalancerRuleConfig.class配置类:
package com.deepinsea.common.config;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
/**
* Created by deepinsea on 2022/6/19.
* LoadBalancer配置类
*/
@LoadBalancerClient(name = "service-provider-nacos", configuration = RibbonLoadBalancerRuleConfig.class)
public class LoadBalancerConfig {
}
完成上面的配置后,发现还是使用的loadbalancer默认的轮询算法进行负载调用的,这种方式失败了!
说明,loadbalancer并不能兼容ribbon的策略配置!
那么,我们需要使loadbalancer支持更多的负载策略,该怎么做呢?
- 适配ribbon和loadbalancer的负载策略配置抽象接口,桥接两种负载均衡器配置,完成后开源;
- 手写loadbalancer负载均衡算法。
第一种方式比较耗时间,后续会去做这件事,目前综合时间和代价考虑还是采用第二种方式代价比较小。
从现在开始,正式开始手写loadbalancer负载均衡算法的进程(之前只是修改官方已有的负载均衡策略而已):
1.首先创建CustomRandomLoadBalancer自定义负载均衡器
通过了解loadbalancer的负载均衡实现的底层原理可知,loadbalancer的负载均衡器是通过实现:ReactorServiceInstanceLoadBalancer(loadbalancer) => ReactorLoadBalancer(loadbalancer) => ReactiveLoadBalancer(spring cloud commons) 来实现负载均衡器的。最底层实现还是spring cloud commons的公共抽象负载均衡器ReactiveLoadBalancer,loadbalancer组件最后的封装是ReactorServiceInstanceLoadBalancer负载均衡器,因此我们只需要实现最外层的ReactorServiceInstanceLoadBalancer负载均衡器即可!
添加common/loadbalancer目录,创建CustomRandomLoadBalancer自定义负载均衡器类:
package com.deepinsea.common.loadbalancer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Random;
/**
* Created by deepinsea on 2022/6/19.
* 手写loadbalancer随机负载均衡器
*/
public class CustomRandomLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider; // 服务列表
private Logger log = LoggerFactory.getLogger(CustomRandomLoadBalancer.class); //slf4j日志
// 构造方法
public CustomRandomLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable();
return supplier.get().next().map(this::getInstanceResponse);
}
/**
* 使用随机数获取服务
*
* @param instances
* @return
*/
private Response<ServiceInstance> getInstanceResponse(
List<ServiceInstance> instances) {
if (instances.isEmpty()) {
return new EmptyResponse();
}
// 随机算法
int size = instances.size();
Random random = new Random();
ServiceInstance instance = instances.get(random.nextInt(size));
log.info("正在使用手写的负载均衡器,进行随机选取服务");
log.info("当前服务为: "+instance.getServiceId() + ",当前主机信息为: "+instance.getUri());
return new DefaultResponse(instance);
}
}
2.创建手写负载均衡策略配置类MyLoadBalancerRuleConfig
在config目录,区别于修改官方的负载均衡配置类LoadBalancerRuleConfig,创建手写的负载算法配置类MyLoadBalancerRuleConfig:
package com.deepinsea.common.config;
import com.deepinsea.common.loadbalancer.CustomRandomLoadBalancer;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.context.annotation.Bean;
/**
* Created by deepinsea on 2022/6/19.
* 自定义loadbalancer负载均衡算法配置类
*/
//@Configuration //可不加,@LoadBalancerClient中的LoadBalancerClientConfigurationRegistrar会自动扫描并注册为bean
public class MyLoadBalancerRuleConfig {
// 参数 serviceInstanceListSupplierProvider 会自动注入
@Bean
public ReactorServiceInstanceLoadBalancer customLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
return new CustomRandomLoadBalancer(serviceInstanceListSupplierProvider);
}
}
3.loadbalancer服务策略配置类中,指定配置类为MyLoadBalancerRuleConfig
package com.deepinsea.common.config;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
/**
* Created by deepinsea on 2022/6/19.
* LoadBalancer配置类
*/
@LoadBalancerClient(name = "service-provider-nacos", configuration = MyLoadBalancerRuleConfig.class)
public class LoadBalancerConfig {
}
点击"▶"启动 service-loadbalancer 和其他两个负载均衡子模块项目,使用curl命令测试随机负载策略是否生效:
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-api!
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-nacos!
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-nacos!
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-nacos!
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-api!
控制台输出如下所示:
c.d.c.l.CustomRandomLoadBalancer : 正在使用手写的负载均衡器,进行随机选取服务
c.d.c.l.CustomRandomLoadBalancer : 当前服务为: service-provider-nacos,当前主机信息为: http://192.168.174.1:9040
c.d.c.l.CustomRandomLoadBalancer : 正在使用手写的负载均衡器,进行随机选取服务
c.d.c.l.CustomRandomLoadBalancer : 当前服务为: service-provider-nacos,当前主机信息为: http://192.168.174.1:9010
c.d.c.l.CustomRandomLoadBalancer : 正在使用手写的负载均衡器,进行随机选取服务
c.d.c.l.CustomRandomLoadBalancer : 当前服务为: service-provider-nacos,当前主机信息为: http://192.168.174.1:9010
c.d.c.l.CustomRandomLoadBalancer : 正在使用手写的负载均衡器,进行随机选取服务
c.d.c.l.CustomRandomLoadBalancer : 当前服务为: service-provider-nacos,当前主机信息为: http://192.168.174.1:9010
c.d.c.l.CustomRandomLoadBalancer : 正在使用手写的负载均衡器,进行随机选取服务
c.d.c.l.CustomRandomLoadBalancer : 当前服务为: service-provider-nacos,当前主机信息为: http://192.168.174.1:9040
测试成功,loadbalancer成功使用了随机负载算法进行负载调用!
loadbalancer的集成先告一段落,下面进行loadbalancer的缓存优化,还有像包括超时和重试、请求压缩、日志、请求方式等优化在上面已经做过了就不重复分析了。
下面直接进行OpenFeign搭配LoadBalancer的优化:
OpenFeign和LoadBalancer优化
Spring Cloud OpenFeign是一个服务远程调用框架,同时又具备负载均衡器、超时重试器等功能,因此我们可以从它支持的功能维度来进行多方面的优化。
可以以下5大方面进行优化:
- 请求连接
- 超时和重试
- 日志增强
- 数据压缩
- 缓存
其中,前面4项其实是OpenFeign层面做的(请求重试应该交给OpenFeign做),后面一项缓存才是Spring Cloud LoadBalancer层面做的。
1. 请求连接优化
因为okhttp采用netty作为底层实现,因此具有异步非阻塞、高性能的特点。
首先,先添加feign-okhttp适配pom依赖:
<!-- feign-okhttp(包含okhttp3依赖) -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>11.8</version>
</dependency>
这里不用指定version,是因为 spring-boot-starter-parent 中的 spring-boot-dependencies,但也只是限于 groupId = org.springframework.boot 的以依赖不用指定版本。如果不是指定groupId的依赖,并且父模块中也没有指定依赖的版本,那可能就会使用本地maven仓库中的依赖或者报错提示要添加依赖版本了。
我们可以在配置文件中关闭httpclient,启用okhttp作为请求连接池:
feign:
# 连接池优化
httpclient:
enabled: false
okhttp:
enabled: true
因此,后面的日志、连接超时和重试以及日志,都可以在okhttp层面做。
然后,创建okhttp配置类:
package com.deepinsea.common.config;
import okhttp3.ConnectionPool;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
/**
* Created by deepinsea on 2022/6/21.
* Okhttp配置类
*/
@Configuration
//@ConditionalOnClass(Feign.class) //判断当前classpath下是否存在指定类,若是则将当前的配置装载入spring容器
//@AutoConfigureBefore(FeignAutoConfiguration.class) //在指定的自动配置类前加载
public class OkHttpConfig {
/**
* 配置okhttp连接池
* ConnectionPool默认创建5个线程,保持5分钟长连接
*/
@Bean
public okhttp3.OkHttpClient okHttpClient(){
return new okhttp3.OkHttpClient.Builder()
//设置连接超时
// .connectTimeout(10, TimeUnit.SECONDS)
// //设置读超时
// .readTimeout(10, TimeUnit.SECONDS)
// //设置写超时
// .writeTimeout(10,TimeUnit.SECONDS)
//是否自动重连
// .retryOnConnectionFailure(true)
.connectionPool(new ConnectionPool(10 , 5L, TimeUnit.MINUTES))
// 添加自定义日志拦截器
// .addInterceptor(new OkHttpLogInterceptor())
//构建OkHttpClient对象
.build();
}
}
设置连接池最大连接数、存活时间、时间单位等参数,通过构造构建请求代理对象。
2. 超时和重试优化
1.openfeign配置超时时间
超时时间
feign:
# 连接池优化
httpclient:
enabled: false
okhttp:
enabled: true
# 配置刷新和超时时间
client:
# 不重启刷新配置开关
refresh-enabled: true
config:
# 全局服务缺省配置
default:
#建立连接所用的时间,适用于网络状况正常的情况下,两端连接所需要的时间,默认为1s
connectTimeout: 5000
#指建立连接后从服务端读取到可用资源所用的时间,默认为1s
readTimeout: 5000
# 指定服务超时时间(通过contextId可以指定到@FeignClients中)
service-provider-nacos:
connectTimeout: 6000
readTimeout: 6000
# 短路器
# 注意:要想看到 断路器 hystrix 效果,一定要加上 spring-cloud-starter-netflix-hystrix 依赖
# circuitbreaker:
# enabled: true
重新断点调试如下:
可以看到,配置成功!
2.loadbalancer配置请求重试
请求重试
loadbalancer:
# 请求重试开关
retry:
enabled: true
3.okhttp超时和重试机制(推荐)
因为feign采用okhttp进行请求的话,那么相当于替换了默认的feign请求,因此请求的质量保证也相应的转移到了okhttp上面。
我们可以关闭上面openfeign的请求超时和loadbalancer的请求重试配置了。
package com.deepinsea.common.config;
import okhttp3.ConnectionPool;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
/**
* Created by deepinsea on 2022/6/21.
* Okhttp配置类
*/
@Configuration
//@ConditionalOnClass(Feign.class) //判断当前classpath下是否存在指定类,若是则将当前的配置装载入spring容器
//@AutoConfigureBefore(FeignAutoConfiguration.class) //在指定的自动配置类前加载
public class OkHttpConfig {
/**
* 配置okhttp连接池
* ConnectionPool默认创建5个线程,保持5分钟长连接
*/
@Bean
public okhttp3.OkHttpClient okHttpClient(){
return new okhttp3.OkHttpClient.Builder()
//设置连接超时
.connectTimeout(10, TimeUnit.SECONDS)
//设置读超时
.readTimeout(10, TimeUnit.SECONDS)
//设置写超时
.writeTimeout(10,TimeUnit.SECONDS)
//是否自动重连
.retryOnConnectionFailure(true)
.connectionPool(new ConnectionPool(10 , 5L, TimeUnit.MINUTES))
// 添加自定义日志拦截器
// .addInterceptor(new OkHttpLogInterceptor())
//构建OkHttpClient对象
.build();
}
}
因为之前有配置过,因此放开注释掉的内容即可。
3. 日志优化
feign默认日志增强功能(不推荐)
首先创建日志配置类:
package com.deepinsea.common.config.log;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created by deepinsea on 2022/6/21.
* FeignClient日志配置类
*/
@Configuration
public class OpenFeignLogConfig {
@Bean
public Logger.Level feignLogConfiguration(){
return Logger.Level.NONE;
}
}
然后在yml配置文件中指定logging监听的日志目录:
logging:
level:
# 可以配置具体到接口或指定包的请求日志,以及日志级别
#com.deepinsea.service.FeignClientService: debug
com.deepinsea.service: debug
支持监听的等级有7种:TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF。
然后启动项目,使用curl命令测试:
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-nacos!
# 控制台日志如下:
: [OpenFeignClients#hello] ---> END HTTP (0-byte body)
: LoadBalancerCacheManager not available, returning delegate without caching.
: [OpenFeignClients#hello] ---> GET http://service-provider-nacos/provider-nacos/hello HTTP/1.1
: [OpenFeignClients#hello] <--- HTTP/1.1 200 (131ms)
: [OpenFeignClients#hello] connection: keep-alive
: [OpenFeignClients#hello] content-length: 35
: [OpenFeignClients#hello] content-type: text/plain;charset=UTF-8
: [OpenFeignClients#hello] date: Tue, 21 Jun 2022 11:42:27 GMT
: [OpenFeignClients#hello] keep-alive: timeout=60
: [OpenFeignClients#hello]
: [OpenFeignClients#hello] hi, this is service-provider-nacos!
可以看到,请求后控制台成功输出了日志,说明日志启用成功。但是,却发现日期比正常时间慢了8小时。
由于Feign中默认时区用的是美国时间,所以会导致服务端接收的Date与实际会相差8小时。这个问题,我尝试了网上的改Jackson时区和调用客户端、服务端重写日期转换Bean的方式,都没有成功。就暂时不再纠结了,既然有bug,提PR就行(或许issues里有解决)。我们因为是用到okhttp作为请求实现,因此日志可以交给okhttp,另外日期类型序列化错误问题可以交给okhttp的拦截器中进行细粒度的自定义。
okhttp自定义日志拦截器(推荐)
首先创建okhttp日志拦截器:
package com.deepinsea.common.config.intercepter;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* Created by deepinsea on 2022/6/22.
* okhttp日志拦截器
*/
public class OkHttpLogInterceptor implements Interceptor {
Logger logger = LoggerFactory.getLogger(OkHttpLogInterceptor.class);
// 重写okhttp3的拦截器
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
logger.info("okhttp 发送请求, method: {}, url: {}", request.method(), request.url());
logger.info("okhttp request body: {}", request.body());
logger.info("okhttp 接收响应, response body: {}", response.peekBody(1024 * 1024).string());
// System.out.println(response.headers().names());
// [Connection, Content-Length, Content-Type, Date, Keep-Alive]
logger.info("<--- Start HTTP Headers");
logger.info("Connection: {}", response.headers().get("Connection"));
logger.info("Content-Length: {}", response.headers().get("Content-Length"));
logger.info("Content-Type: {}", response.headers().get("Content-Type"));
logger.info("Date: {}", response.headers().getDate("Date"));
logger.info("Keep-Alive: {}", response.headers().get("Keep-Alive"));
logger.info("---> END HTTP Headers");
// logger.info("okhttp response headers: \n{}", response.headers().getInstant("Date"));
return response;
}
}
然后将日志拦截器通过Builder(建造者)构建到okhttp对象中:
package com.deepinsea.common.config;
import com.deepinsea.common.config.intercepter.OkHttpLogInterceptor;
import okhttp3.ConnectionPool;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
/**
* Created by deepinsea on 2022/6/21.
* Okhttp配置类
*/
@Configuration
//@ConditionalOnClass(Feign.class) //判断当前classpath下是否存在指定类,若是则将当前的配置装载入spring容器
//@AutoConfigureBefore(FeignAutoConfiguration.class) //在指定的自动配置类前加载
public class OkHttpConfig {
/**
* 配置okhttp连接池
* ConnectionPool默认创建5个线程,保持5分钟长连接
*/
@Bean
public okhttp3.OkHttpClient okHttpClient(){
return new okhttp3.OkHttpClient.Builder()
//设置连接超时
.connectTimeout(10, TimeUnit.SECONDS)
//设置读超时
.readTimeout(10, TimeUnit.SECONDS)
//设置写超时
.writeTimeout(10,TimeUnit.SECONDS)
//是否自动重连
.retryOnConnectionFailure(true)
.connectionPool(new ConnectionPool(10 , 5L, TimeUnit.MINUTES))
// 添加自定义日志拦截器
.addInterceptor(new OkHttpLogInterceptor())
//构建OkHttpClient对象
.build();
}
}
这里同样也是放开注释的内容即可。
启动项目,使用curl命令进行测试:
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-nacos!
# 控制台输出如下
c.d.c.c.i.OkHttpLogInterceptor : okhttp 发送请求, method: GET, url: http://192.168.174.1:9010/provider-nacos/hello
c.d.c.c.i.OkHttpLogInterceptor : okhttp request body: null
c.d.c.c.i.OkHttpLogInterceptor : okhttp 接收响应, response body: hi, this is service-provider-nacos!
c.d.c.c.i.OkHttpLogInterceptor : <--- Start HTTP Headers
c.d.c.c.i.OkHttpLogInterceptor : Connection: keep-alive
c.d.c.c.i.OkHttpLogInterceptor : Content-Length: 35
c.d.c.c.i.OkHttpLogInterceptor : Content-Type: text/plain;charset=UTF-8
c.d.c.c.i.OkHttpLogInterceptor : Date: Wed Jun 22 03:07:30 CST 2022
c.d.c.c.i.OkHttpLogInterceptor : Keep-Alive: timeout=60
c.d.c.c.i.OkHttpLogInterceptor : ---> END HTTP Headers
成功打印了请求日志,并且日期时间正确!
4. 请求压缩优化
feign:
# 压缩配置
compression:
request:
# 开启feign请求压缩
enabled: true
# 配置压缩文档类型及最小压缩的文档大小
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
# 开启feign响应压缩
response:
enabled: true
因为原来做过feign相关请求压缩配置,因此这里直接复制过来上面的配置就行。
参考配置:
# feign gzip
# 局部配置。只配置feign技术相关的http请求-应答中的gzip压缩。
# 配置的是application client和application service之间通讯是否使用gzip做数据压缩。
# 和浏览器到application client之间的通讯无关。
# 开启feign请求时的压缩, application client -> application service
feign.compression.request.enabled=true
# 开启feign技术响应时的压缩, application service -> application client
feign.compression.response.enabled=true
# 设置可以压缩的请求/响应的类型。
feign.compression.request.mime-types=text/xml,application/xml,application/json
# 当请求的数据容量达到多少的时候,使用压缩。默认是2048字节。
feign.compression.request.min-request-size=512
配置全局的GZIP压缩
# spring boot gzip
# 开启spring boot中的gzip压缩。就是针对和当前应用所有相关的http请求-应答的gzip压缩。
server.compression.enabled=true
# 哪些客户端发出的请求不压缩,默认是不限制
server.compression.excluded-user-agents=gozilla,traviata
# 配置想压缩的请求/应答数据类型,默认是 text/html,text/xml,text/plain
server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain
# 执行压缩的阈值,默认为2048
server.compression.min-response-size=512
5. 缓存优化
在启动loadbalancer项目的时候,控制台就建议我们替换默认的缓存实现为caffine了,可见Caffeine的确对请求性能有一定提升:
2022-06-22 03:15:50.843 WARN 36024 --- [ main] iguration$LoadBalancerCaffeineWarnLogger : Spring Cloud LoadBalancer is currently working with the default cache. While this cache implementation is useful for development and tests, it's recommended to use Caffeine cache in production.You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath.
在调用请求时,也出现没有缓存的警告日志:
2022-06-22 09:29:05.005 WARN 4464 --- [nio-9012-exec-1] c.l.c.ServiceInstanceListSupplierBuilder : LoadBalancerCacheManager not available, returning delegate without caching.
并且对比主流的本地缓存框架,像EhCache、Guava Cache以及基于ConcurrentHashMap实现的本地缓存,Caffeine可以说是Guava Chache的增强版。缓存性能接近理论最优,Caffeine采用了一种结合LRU、LFU优点的W-TinyLFU算法,极大地提供了缓存性能。
spring官方文档说明:
3.3. Spring Cloud LoadBalancer 缓存
除了通过每次必须选择实例ServiceInstanceListSupplier来检索实例的基本实现之外,我们还提供了两种缓存实现。DiscoveryClient
3.3.1。咖啡因支持的 LoadBalancer 缓存实现
如果您com.github.ben-manes.caffeine:caffeine在类路径中有,将使用基于 Caffeine 的实现。有关如何配置它的信息,请参阅LoadBalancerCacheConfiguration部分。
如果您使用的是咖啡因,您还可以通过在属性中传递您自己的咖啡因规范来覆盖 LoadBalancer 的默认咖啡因缓存设置。spring.cloud.loadbalancer.cache.caffeine.spec
警告:传递您自己的 Caffeine 规范将覆盖任何其他 LoadBalancerCache 设置,包括常规 LoadBalancer 缓存配置字段,例如ttl和capacity。
如果类路径中没有 Caffeine ,将使用DefaultLoadBalancerCache自动附带的。有关如何配置它的信息,spring-cloud-starter-loadbalancer请参阅LoadBalancerCacheConfiguration部分。
要使用 Caffeine 而不是默认缓存,请将
com.github.ben-manes.caffeine:caffeine依赖项添加到类路径。
您可以设置自己的ttl值(写入后条目应过期的时间),表示为Duration,通过将String符合Spring Boot的转换语法传递String给Duration转换器语法。作为spring.cloud.loadbalancer.cache.ttl财产的价值。spring.cloud.loadbalancer.cache.capacity您还可以通过设置属性的值来设置自己的 LoadBalancer 缓存初始容量。
默认设置包括ttl设置为 35 秒,默认设置initialCapacity为256.
您还可以通过将值设置为spring.cloud.loadbalancer.cache.enabled 来完全禁用 loadBalancer 缓存false。
尽管基本的、非缓存的实现对于原型设计和测试很有用,但它的效率远低于缓存版本,因此我们建议始终在生产中使用缓存版本。
DiscoveryClient例如,如果DiscoveryClient实现(例如EurekaDiscoveryClient)已经完成了缓存,则应禁用负载平衡器缓存以防止双重缓存。
实战为LoadBalancer引入Caffeine缓存
上面spring官方的提示对我们使用caffeine缓存很有用,我们知道了caffeine适合生产环境使用,并且注册中心存在缓存的话,应该禁用caffine缓存。那其实,nacos本来就有持久化机制,因此相当于可以禁用caffeine缓存了。当然,因为注册中心也有可能是ZK或者Consul,我们这里给出使用和不使用caffeine两种方案:
方案一:引入caffine作为loadbalancer缓存(推荐)
首先需要添加caffeine缓存依赖:
<!-- nacos 服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.1.0</version>
<exclusions>
<!-- 排除commons-io冲突依赖 -->
<exclusion>
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
</exclusion>
<!-- 排除guava与caffeine冲突的依赖 -->
<exclusion>
<artifactId>checker-qual</artifactId>
<groupId>org.checkerframework</groupId>
</exclusion>
<exclusion>
<artifactId>error_prone_annotations</artifactId>
<groupId>com.google.errorprone</groupId>
</exclusion>
</exclusions>
</dependency>
<!--caffeine 替换LB 默认缓存实现-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.3</version>
</dependency>
<!-- cache 缓存支持(必须引入) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
然后再yml配置文件中开启缓存开关:
loadbalancer:
# 缓存
cache:
enabled: true
# 过期时间10s
ttl: 10
# 容量256M
capacity: 256
caffeine:
spec: initialCapacity=500,expireAfterWrite=5s
# 请求重试开关
# retry:
# enabled: true
测试环境,服务本地缓存开关可以禁用。生产环境中,注册中心集群宕机将导致服务调用不可用,将严重影响高可用。因此,需要开启缓存来保存本地服务列表。
首先是启动项目后,控制台没有报切换为caffeine缓存的警告了:
2022-06-22 13:36:13.987 INFO 41160 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1361 ms
2022-06-22 13:36:14.203 INFO 41160 --- [ main] o.s.c.openfeign.FeignClientFactoryBean : For 'service-provider-nacos' URL not provided. Will try picking an instance via load-balancing.
2022-06-22 13:36:16.996 INFO 41160 --- [ main] o.s.cloud.commons.util.InetUtils : Cannot determine local hostname
2022-06-22 13:36:18.974 INFO 41160 --- [ main] o.s.cloud.commons.util.InetUtils : Cannot determine local hostname
2022-06-22 13:36:19.396 INFO 41160 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9060 (http) with context path ''
2022-06-22 13:36:19.412 INFO 41160 --- [ main] c.a.c.n.registry.NacosServiceRegistry : nacos registry, DEFAULT_GROUP service-loadbalancer 192.168.174.1:9060 register finished
2022-06-22 13:36:21.331 INFO 41160 --- [ main] o.s.cloud.commons.util.InetUtils : Cannot determine local hostname
2022-06-22 13:36:21.335 INFO 41160 --- [ main] c.d.ServiceLoadBalancerApplication : Started ServiceLoadBalancerApplication in 10.978 seconds (JVM running for 12.206)
然后使用curl进行请求调用,测试缓存生效结果:
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-api!
# 控制台输出日志
2022-06-22 13:43:27.038 INFO 41160 --- [nio-9060-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-06-22 13:43:27.038 INFO 41160 --- [nio-9060-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-06-22 13:43:27.038 INFO 41160 --- [nio-9060-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms
2022-06-22 13:43:27.321 INFO 41160 --- [nio-9060-exec-1] c.d.c.c.i.OkHttpLogInterceptor : okhttp 发送请求, method: GET, url: http://192.168.174.1:9010/provider-nacos/hello
2022-06-22 13:43:27.321 INFO 41160 --- [nio-9060-exec-1] c.d.c.c.i.OkHttpLogInterceptor : okhttp request body: null
2022-06-22 13:43:27.321 INFO 41160 --- [nio-9060-exec-1] c.d.c.c.i.OkHttpLogInterceptor : okhttp 接收响应, response body: hi, this is service-provider-nacos!
2022-06-22 13:43:27.321 INFO 41160 --- [nio-9060-exec-1] c.d.c.c.i.OkHttpLogInterceptor : <--- Start HTTP Headers
2022-06-22 13:43:27.321 INFO 41160 --- [nio-9060-exec-1] c.d.c.c.i.OkHttpLogInterceptor : Connection: keep-alive
2022-06-22 13:43:27.321 INFO 41160 --- [nio-9060-exec-1] c.d.c.c.i.OkHttpLogInterceptor : Content-Length: 35
2022-06-22 13:43:27.321 INFO 41160 --- [nio-9060-exec-1] c.d.c.c.i.OkHttpLogInterceptor : Content-Type: text/plain;charset=UTF-8
2022-06-22 13:43:27.321 INFO 41160 --- [nio-9060-exec-1] c.d.c.c.i.OkHttpLogInterceptor : Date: Wed Jun 22 13:43:27 CST 2022
2022-06-22 13:43:27.321 INFO 41160 --- [nio-9060-exec-1] c.d.c.c.i.OkHttpLogInterceptor : Keep-Alive: timeout=60
2022-06-22 13:43:27.321 INFO 41160 --- [nio-9060-exec-1] c.d.c.c.i.OkHttpLogInterceptor : ---> END HTTP Headers
可以看到控制台也正常输出日志,并且没有报当前缓存不可用的警告,说明Caffeine缓存配置成功生效了。
注意:
- 不要引入过高的版本,建议在3.X以下版本。否则会导致引入无效,仍然会抛出警告。
- 无效不是CaffeineManager没有加载。当我们引入错误版本的caffeine后会报出版本兼容性的问题。也许你升高jdk的版本可能会解决这个问题,但基于jdk1.8的环境还是建议在3.x之下版本。
版本可用性测试结果:
- 成功的版本:2.8.8、2.9.3
- 失败的版本:3.X。
引入caffeine依赖即生效,无需其他配置,这种方式启动和请求时都没有警告!
方案2:使用默认缓存
默认情况下缺少依赖,导致默认的缓存无法使用。虽然日志输出了当前使用的是本地缓存,但经过代码跟踪,发现根本没有使用本地缓存,甚至没有使用缓存。Evictor是一个 Java 库,提供了一个java.util.concurrent.ConcurrentMap支持定时条目驱逐以进行缓存的实现。
引入evictor依赖:
<dependency>
<groupId>com.stoyanr</groupId>
<artifactId>evictor</artifactId>
<version>1.0.0</version>
</dependency>
启动项目,使用curl命令进行测试,发现这种方式启动时有警告但调feign时无警告!
综合考虑,还是使用Caffeine实现的缓存更好,毕竟是成熟且性能优秀的新一代缓存框架。
LoadBalancer的优化部分就到这里为止,另外还有上面提到的调用熔断,可以在后面集成sentinel熔断器时详细了解。
下面进行Spring Cloud Gateway服务网关的整合,进行网关集成后进行Sentinel熔断器的整合:
欢迎点赞还有评论,谢谢大佬ヾ(◍°∇°◍)ノ゙