赞
踩
目录
在 2020 年以前的 SpringCloud 采用 Ribbon 作为负载均衡,但是 2020 年之后,SpringCloud 吧 Ribbon 移除了,而是使用自己编写的 LoadBalancer 替代.
因此,如果在没有加入 LoadBalancer 依赖的情况下,使用 RestTemplate 或 OpenFeign 远程调用,就会报以下错误:
这就是在告诉你 LoadBalancing是未定义的(OpenFeign 中引入的依赖会使用 LoadBalancing),然后问你是不是忘记加入 spring-cloud-starter-loadbalancer 依赖.
a)在添加了 @LoadBalanced 注解之后,会启用拦截器对我们发起的服务调用请求进行拦截(注意,这里是针对我们发起的请求进行拦截),叫做 LoadBalancerInterceptor,它实现了 ClientHttpRequestInterceptor 接口:
- @FunctionalInterface
- public interface ClientHttpRequestInterceptor {
- ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;
- }
intercept 方法如下:
- public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
- URI originalUri = request.getURI();
- String serviceName = originalUri.getHost();
- Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
- return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
- }
主要就是这里的 intercept 方法拦截的请求.
b)这个拦截器具体做了什么事情呢,我们知道,被拦截的请求地址,并不是一个有效的主机地址,而是服务名称,因此需要通过 服务注册中心(Nacos)才能得到需要访问的主机地址.
loadBalancer.execute() 就是在获取请求对应的服务实例信息.
- //从上面给进来了服务的名称和具体的请求实体
- public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
- String hint = this.getHint(serviceId);
- LoadBalancerRequestAdapter<T, DefaultRequestContext> lbRequest = new LoadBalancerRequestAdapter(request, new DefaultRequestContext(request, hint));
- Set<LoadBalancerLifecycle> supportedLifecycleProcessors = this.getSupportedLifecycleProcessors(serviceId);
- supportedLifecycleProcessors.forEach((lifecycle) -> {
- lifecycle.onStart(lbRequest);
- });
- //可以看到在这里会调用choose方法自动获取对应的服务实例信息
- ServiceInstance serviceInstance = this.choose(serviceId, lbRequest);
- if (serviceInstance == null) {
- supportedLifecycleProcessors.forEach((lifecycle) -> {
- lifecycle.onComplete(new CompletionContext(Status.DISCARD, lbRequest, new EmptyResponse()));
- });
- //没有发现任何此服务的实例就抛异常(之前的测试中可能已经遇到了)
- throw new IllegalStateException("No instances available for " + serviceId);
- } else {
- //成功获取到对应服务的实例,这时就可以发起HTTP请求获取信息了
- return this.execute(serviceId, serviceInstance, lbRequest);
- }
- }
c)因此,实际上,在进行负载均衡的时候,会向服务的注册中心(Nacos)发起一个请求,选择一个可用的服务(如果有多个),然后返回此服务的主机地址等信息.
在需要进行远程调用的服务中引入openfeign 和 loadbalancer 依赖
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-loadbalancer</artifactId>
- </dependency>
a)启动类中添加 @EnableFeignClients 注解
- @SpringBootApplication
- @EnableFeignClients
- public class UserApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(UserApplication.class, args);
- }
-
- }
b)例如,在 user 微服务中调用 article 微服务接口,那么就需要在 user 为服务中创建一个 article 的客户端.
- @FeignClient("article")
- public interface ArticleClient {
-
- @GetMapping("/article/start")
- String userStart();
-
- }
服务提供者:
- @RestController
- @RequestMapping("/article")
- public class ArticleController {
-
- @GetMapping("/start")
- public String userStart() {
- System.out.println("article 被远程调用了!");
- return "article ok ~";
- }
-
- }
服务消费者:
- @RestController
- @RequestMapping("/user")
- public class UserController {
-
- @Autowired
- private ArticleClient articleClient;
-
- @GetMapping("/start")
- public String userStart() {
- String result = articleClient.userStart();
- return "user ok ~\n" + result;
- }
-
- }
c)访问 user 服务接口,可以看到成功进行了远程调用
d)连续访问 10 次,可以发现,在 OpenFeign 的声明式客户端中,不用加 @LoadBalancer 注解也会实现默认的 “轮询” 负载均衡策略(RestTemplate 方式必须加).
在 BlockingLoadBalancerClient 中添加断点,就可以看到我们指定的策略默认是轮询(RoundRobin):
LoadBalancer默认提供了两种负载均衡策略:
现在希望修改默认的负载均衡策略为随机分配策略,就需要创建随机分配策略的配置类(不用加 @Configuration):
- //这里不用加 @Configuration 注解
- public class LoadBalancerConfig {
- //将官方提供的 RandomLoadBalancer 注册为Bean
- @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);
- }
- }
通过 @LoadBalancerClient(value = "服务名", configuration = LoadBalancerConfig.class) 指定负载均衡策略为随机.
- @FeignClient("article")
- @LoadBalancerClient(value = "article", configuration = LoadBalancerConfig.class) //指定负载均衡策略为随机
- public interface ArticleClient {
-
- // @LoadBalanced(可以写,也可以不用写,默认所有方法都自动加 @LoadBalanced)
- @GetMapping("/article/start")
- String userStart();
-
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。