spring cloud Ribbon的使用和實現原理


轉載鏈接:https://blog.csdn.net/qq_20597727/article/details/82860521

簡介

這篇文章主要介紹一下ribbon在程序中的基本使用,在這里是單獨拿出來寫用例測試的,實際生產一般是配置feign一起使用,更加方便開發。同時這里也通過源碼來簡單分析一下ribbon的基本實現原理。

基本使用

這里使用基於zookeeper注冊中心+ribbon的方式實現一個簡單的客戶端負載均衡案例。

服務提供方

首先是一個服務提供方。代碼如下。

application.properties配置文件

spring.application.name=discovery-service
server.port=0
service-B.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

bootstrap.properties配置文件

spring.cloud.zookeeper.connect-string=192.168.0.15:2181

引導程序,提供了一個ribbonService的rest接口服務,注冊程序到zookeeper中。

 1 @SpringBootApplication
 2 @EnableDiscoveryClient
 3 @RestController
 4 public class DiscoverClient {
 5     public static void main(String[] args) {
 6         SpringApplication.run(DiscoverClient.class, args);
 7     }
 8  9     @RequestMapping("/ribbonService")
10     public String ribbonService(){
11         return "hello too ribbon";
12     }
13 }

服務調用方

服務調用方就是進行負載均衡的一方,利用ribbo的RestTemplate進行負載調用服務。

RibbonConfig,配置ribbon的RestTemplate,通過@LoadBalanced注解實現,具體原理稍后分析。

 1 @Configuration
 2 public class RibbonConfig {
 3  4     /**
 5      * 實例化ribbon使用的RestTemplate
 6      * @return
 7      */
 8     @Bean
 9     @LoadBalanced
10     public RestTemplate rebbionRestTemplate(){
11         return new RestTemplate();
12     }
13     
14     /**
15     * 配置隨機負載策略,需要配置屬性service-B.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
16     */
17     @Bean
18     public IRule ribbonRule() {
19         return new RandomRule();
20     }
21 }
22

引導程序

 1 @SpringBootApplication(scanBasePackages = "garine.learn.ribbon.loadblance")
 2 @EnableDiscoveryClient
 3 @RestController
 4 public class TestRibbonApplocation {
 5     public static void main(String[] args) {
 6         SpringApplication.run(TestRibbonApplocation.class, args);
 7     }
 8  9     @Autowired
10     @LoadBalanced
11     RestTemplate restTemplate;
12 13     @GetMapping("/{applicationName}/ribbonService")
14     public String ribbonService(@PathVariable("applicationName") String applicationName){
15         return restTemplate.getForObject("http://" + applicationName+"/ribbonService", String.class);
16     }
17 }

配置文件同上,服務名稱修改即可。

測試

  1. 啟動兩個discovery-service,由於端口設置為0,所以是隨機端口。

  2. 啟動服務調用方

  3. 瀏覽器訪問服務調用方的提供的接口,路徑參數需要加上調用的服務名稱,例如http://localhost:8080/discovery-service/ribbonService,然后服務調用方使用ribbon的RestTemplate調用服務提供方的接口。

  4. 結果返回:hello too ribbon ,同時服務提供方啟動的兩個服務都可能被調用,取決於怎么配置負載策略。

上面就是一個簡單使用ribbon的例子,結合feign使用基本上是做類似上面所寫的工作,那么ribbon到底是怎么實現的呢?

原理與源碼分析

ribbon實現的關鍵點是為ribbon定制的RestTemplate,ribbon利用了RestTemplate的攔截器機制,在攔截器中實現ribbon的負載均衡。負載均衡的基本實現就是利用applicationName從服務注冊中心獲取可用的服務地址列表,然后通過一定算法負載,決定使用哪一個服務地址來進行http調用。

Ribbon的RestTemplate

RestTemplate中有一個屬性是List<ClientHttpRequestInterceptor> interceptors,如果interceptors里面的攔截器數據不為空,在RestTemplate進行http請求時,這個請求就會被攔截器攔截進行,攔截器實現接口ClientHttpRequestInterceptor,需要實現方法是

ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
      throws IOException;

也就是說攔截器需要完成http請求,並封裝一個標准的response返回。

ribbon中的攔截器

在Ribbon 中也定義了這樣的一個攔截器,並且注入到RestTemplate中,是怎么實現的呢?

在Ribbon實現中,定義了一個LoadBalancerInterceptor,具體的邏輯先不說,ribbon就是通過這個攔截器進行攔截請求,然后實現負載均衡調用。

攔截器定義在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig#ribbonInterceptor

 1 @Configuration
 2 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
 3 static class LoadBalancerInterceptorConfig {
 4    @Bean
 5     //定義ribbon的攔截器
 6    public LoadBalancerInterceptor ribbonInterceptor(
 7          LoadBalancerClient loadBalancerClient,
 8          LoadBalancerRequestFactory requestFactory) {
 9       return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
10    }
11 12    @Bean
13    @ConditionalOnMissingBean
14     //定義注入器,用來將攔截器注入到RestTemplate中,跟上面配套使用
15    public RestTemplateCustomizer restTemplateCustomizer(
16          final LoadBalancerInterceptor loadBalancerInterceptor) {
17       return restTemplate -> {
18                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
19                        restTemplate.getInterceptors());
20                list.add(loadBalancerInterceptor);
21                restTemplate.setInterceptors(list);
22            };
23    }
24 }

 

ribbon中的攔截器注入到RestTemplate

定義了攔截器,自然需要把攔截器注入到、RestTemplate才能生效,那么ribbon中是如何實現的?上面說了攔截器的定義與攔截器注入器的定義,那么肯定會有個地方使用注入器來注入攔截器的。

在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration#loadBalancedRestTemplateInitializerDeprecated方法里面,進行注入,代碼如下。

 1 @Configuration
 2 @ConditionalOnClass(RestTemplate.class)
 3 @ConditionalOnBean(LoadBalancerClient.class)
 4 @EnableConfigurationProperties(LoadBalancerRetryProperties.class)
 5 public class LoadBalancerAutoConfiguration {
 6  7    @LoadBalanced
 8    @Autowired(required = false)
 9    private List<RestTemplate> restTemplates = Collections.emptyList();
10 11    @Bean
12    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
13          final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
14        //遍歷context中的注入器,調用注入方法。
15       return () -> restTemplateCustomizers.ifAvailable(customizers -> {
16             for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
17                 for (RestTemplateCustomizer customizer : customizers) {
18                     customizer.customize(restTemplate);
19                 }
20             }
21         });
22    }
23    //......
24    }

 

遍歷context中的注入器,調用注入方法,為目標RestTemplate注入攔截器,注入器和攔截器都是我們定義好的。

還有關鍵的一點是:需要注入攔截器的目標restTemplates到底是哪一些?因為RestTemplate實例在context中可能存在多個,不可能所有的都注入攔截器,這里就是@LoadBalanced注解發揮作用的時候了。

LoadBalanced注解

嚴格上來說,這個注解是spring cloud實現的,不是ribbon中的,它的作用是在依賴注入時,只注入實例化時被@LoadBalanced修飾的實例。

例如我們定義Ribbon的RestTemplate的時候是這樣的

@Bean
    @LoadBalanced
    public RestTemplate rebbionRestTemplate(){
        return new RestTemplate();
    }

 

因此才能為我們定義的RestTemplate注入攔截器。

那么@LoadBalanced是如何實現這個功能的呢?其實都是spring的原生操作,@LoadBalance的源碼如下

 1 /**
 2  * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
 3  * @author Spencer Gibb
 4  */
 5 @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
 6 @Retention(RetentionPolicy.RUNTIME)
 7 @Documented
 8 @Inherited
 9 @Qualifier
10 public @interface LoadBalanced {
11 }

 

很明顯,‘繼承’了注解@Qualifier,我們都知道以前在xml定義bean的時候,就是用Qualifier來指定想要依賴某些特征的實例,這里的注解就是類似的實現,restTemplates通過@Autowired注入,同時被@LoadBalanced修飾,所以只會注入@LoadBalanced修飾的RestTemplate,也就是我們的目標RestTemplate。

攔截器邏輯實現

LoadBalancerInterceptor源碼如下。

 1 public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
 2  3     private LoadBalancerClient loadBalancer;
 4     private LoadBalancerRequestFactory requestFactory;
 5  6     public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
 7         this.loadBalancer = loadBalancer;
 8         this.requestFactory = requestFactory;
 9     }
10 11     public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
12         // for backwards compatibility
13         this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
14     }
15 16     @Override
17     public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
18             final ClientHttpRequestExecution execution) throws IOException {
19         final URI originalUri = request.getURI();
20         String serviceName = originalUri.getHost();
21         Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
22         return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
23     }
24 }

 

攔截請求執行

 1 @Override
 2 public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
 3    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
 4     //在這里負載均衡選擇服務
 5    Server server = getServer(loadBalancer);
 6    if (server == null) {
 7       throw new IllegalStateException("No instances available for " + serviceId);
 8    }
 9    RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
10          serviceId), serverIntrospector(serviceId).getMetadata(server));
11 //執行請求邏輯
12    return execute(serviceId, ribbonServer, request);
13 }

 

我們重點看getServer方法,看看是如何選擇服務的

1 protected Server getServer(ILoadBalancer loadBalancer) {
2    if (loadBalancer == null) {
3       return null;
4    }
5     //
6    return loadBalancer.chooseServer("default"); // TODO: better handling of key
7 }

 

代碼配置隨機loadBlancer,進入下面代碼

 1 public Server chooseServer(Object key) {
 2     if (counter == null) {
 3         counter = createCounter();
 4     }
 5     counter.increment();
 6     if (rule == null) {
 7         return null;
 8     } else {
 9         try {
10             //使用配置對應負載規則選擇服務
11             return rule.choose(key);
12         } catch (Exception e) {
13             logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
14             return null;
15         }
16     }
17 }

 

這里配置的是RandomRule,所以進入RandomRule代碼

 1 public Server choose(ILoadBalancer lb, Object key) {
 2     if (lb == null) {
 3         return null;
 4     }
 5     Server server = null;
 6  7     while (server == null) {
 8         if (Thread.interrupted()) {
 9             return null;
10         }
11         //獲取可用服務列表
12         List<Server> upList = lb.getReachableServers();
13         List<Server> allList = lb.getAllServers();
14 15         //隨機一個數
16         int serverCount = allList.size();
17         if (serverCount == 0) {
18             /*
19              * No servers. End regardless of pass, because subsequent passes
20              * only get more restrictive.
21              */
22             return null;
23         }
24 25         int index = rand.nextInt(serverCount);
26         server = upList.get(index);
27 28         if (server == null) {
29             /*
30              * The only time this should happen is if the server list were
31              * somehow trimmed. This is a transient condition. Retry after
32              * yielding.
33              */
34             Thread.yield();
35             continue;
36         }
37 38         if (server.isAlive()) {
39             return (server);
40         }
41 42         // Shouldn't actually happen.. but must be transient or a bug.
43         server = null;
44         Thread.yield();
45     }
46 47     return server;
48 49 }

 

隨機負載規則很簡單,隨機整數選擇服務,最終達到隨機負載均衡。我們可以配置不同的Rule來實現不同的負載方式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM