目錄
一、LoadBalancer 負載均衡
1.1、前言
1.2、LoadBalancer 負載均衡底層實現原理
二、整合 OpenFeign + LoadBalancer
2.1、所需依賴
2.2、具體實現
?2.3、自定義負載均衡策略
一、LoadBalancer 負載均衡
1.1、前言
在 2020 年以前的 SpringCloud 采用 Ribbon 作為負載均衡,但是 2020 年之后,SpringCloud 吧 Ribbon 移除了,而是使用自己編寫的 LoadBalancer 替代.
因此,如果在沒有加入 LoadBalancer 依賴的情況下,使用 RestTemplate 或?OpenFeign 遠程調用,就會報以下錯誤:
這就是在告訴你 LoadBalancing是未定義的(OpenFeign 中引入的依賴會使用 LoadBalancing),然后問你是不是忘記加入 spring-cloud-starter-loadbalancer 依賴.
1.2、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
2.1、所需依賴
在需要進行遠程調用的服務中引入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>
2.2、具體實現
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 {@Autowiredprivate 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):
?2.3、自定義負載均衡策略
LoadBalancer默認提供了兩種負載均衡策略:
- RandomLoadBalancer - 隨機分配策略
- (默認)?RoundRobinLoadBalancer - 輪詢分配策略
現在希望修改默認的負載均衡策略為隨機分配策略,就需要創建隨機分配策略的配置類(不用加 @Configuration):
//這里不用加 @Configuration 注解
public class LoadBalancerConfig {//將官方提供的 RandomLoadBalancer 注冊為Bean@Beanpublic 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();}