轉載鏈接: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 }
配置文件同上,服務名稱修改即可。
測試
-
啟動兩個discovery-service,由於端口設置為0,所以是隨機端口。
-
啟動服務調用方
-
瀏覽器訪問服務調用方的提供的接口,路徑參數需要加上調用的服務名稱,例如http://localhost:8080/discovery-service/ribbonService,然后服務調用方使用ribbon的RestTemplate調用服務提供方的接口。
-
結果返回: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注入攔截器。
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 }