Spring Cloud 服務消費(Ribbon)

以前介紹了使用 Eureka 做爲服務發現組件,構建了 Eureka Server 做爲服務註冊中心,使用 Eureka Client 去註冊服務 Spring Cloud 服務註冊與發現、高可用(Eureka),那服務間又是怎樣相互調用的呢?這裏介紹使用 Ribbon 實現負載均衡java

準備

構建一個 Eureka Server 服務註冊中心 eureka-server 和兩個註冊服務 product-service (用來提供服務) 和 order-service-ribbon (消費服務者) 參考,這裏咱們啓動兩個 product-service 實例git

這裏分別給出配置信息github

spring:
  application:
    name: eureka-server
server:
  port: 8761
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

咱們啓動兩個 product-service 服務實例,在 idea 中的 Edit Configurations 中複製一個 product-service 改變端口爲 8072算法

spring:
  application:
    name: product-service
server:
  port: 8071
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

spring-cloud-consumer-ribbon-1-1.jpg

order-service-ribbon 須要添加 Ribbon 的依賴,以下:spring

spring:
  application:
    name: order-service-ribbon
server:
  port: 8081
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

啓動服務註冊中心 eureka-server 和兩個註冊服務,打開 http://localhost:8761/eureka/ 查看服務註冊狀況json

spring-cloud-consumer-ribbon-1-2.jpg

Ribbon 實現負載均衡

實際生產中,基本上每一個服務都會部署多個實例,那麼服務消費者的請求該怎麼分配到多個服務提供者實例上呢?服務器

Ribbon 簡介

Ribbon 是 Netflix 發佈的負載均衡器,是在客戶端實現負載均衡,對客戶端的HTTP和TCP行爲有很好的控制。它能夠在客戶端爲其配置服務提供者地址列表 ribbonServerList,而後基於某種負載均衡算法(輪詢、隨機等),自動幫助客戶端去請求網絡

當 Eureka 與 Ribbon 結合使用時,ribbonServerList 將被擴展爲DiscoveryEnabledNIWSServerList,擴展爲 Eureka 的服務器註冊實例列表。同時還會用 NIWSDiscoveryPing 替換 IPing 接口,讓 Eureka 來肯定服務端是否啓動app

RestTemplate 做爲負載均衡客戶端

RestTemplate 能夠自動配置爲使用 ribbon,要建立一個負載均衡的 RestTemplate,須要加上註解 @LoadBalanced負載均衡

@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceRibbonApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceRibbonApplication.class, args);
    }

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

建立一個 Controller 來測試請求

@RestController
public class ProductController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/product/{id}")
    public Product getProduct(@PathVariable Long id){
        return restTemplate.getForObject("http://product-service/product/" + id, Product.class);
    }
}

從新啓動服務,訪問路徑 http://localhost:8081/product/1 獲得以下結果

{"id":1,"name":"秋褲","descriptionl":"花秋褲","price":9.9,"count":99}

有上面的代碼咱們能夠看到請求的地址爲 http://product-service/product/1,其中的 product-service 就是咱們要請求的服務的虛擬主機名,Ribbon 會自動把這個虛擬主機名映射成要請求的服務網絡地址

LoadBalancerClient

LoadBalancerClient 是 Spring Cloud Commons 中提供的一個抽象接口,可使用它來調用 Ribbon API

@RestController
@Log4j2
public class ProductController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/product/{id}")
    public Product getProduct(@PathVariable Long id){
        return restTemplate.getForObject("http://product-service/product/" + id, Product.class);
    }

    @GetMapping("/product/log/{id}")
    public void logProduct(@PathVariable Long id){
        ServiceInstance serviceInstance = loadBalancerClient.choose("product-service");
        // URI storesUri = URI.create(String.format("https://%s:%s/"+ id, serviceInstance.getHost(), serviceInstance.getPort()));
        log.info("{}:{}:{}", serviceInstance.getHost(), serviceInstance.getPort());
    }
}

如今咱們從新啓動服務,訪問 http://localhost:8081/product/log/1 ,上面代碼能夠看到咱們會打印查詢日誌,會記錄下請求服務的實例id、主機名、端口,屢次訪問上面的地址

2019-04-30 09:19:01.437  INFO 22024 --- [nio-8081-exec-3] c.t.order.controller.ProductController   : DESKTOP-G11TC44.mshome.net:8071
2019-04-30 09:19:01.687  INFO 22024 --- [nio-8081-exec-4] c.t.order.controller.ProductController   : DESKTOP-G11TC44.mshome.net:8072
2019-04-30 09:19:01.843  INFO 22024 --- [nio-8081-exec-5] c.t.order.controller.ProductController   : DESKTOP-G11TC44.mshome.net:8071
2019-04-30 09:19:01.984  INFO 22024 --- [nio-8081-exec-6] c.t.order.controller.ProductController   : DESKTOP-G11TC44.mshome.net:8072
2019-04-30 09:19:02.140  INFO 22024 --- [nio-8081-exec-7] c.t.order.controller.ProductController   : DESKTOP-G11TC44.mshome.net:8071
2019-04-30 09:19:02.297  INFO 22024 --- [nio-8081-exec-8] c.t.order.controller.ProductController   : DESKTOP-G11TC44.mshome.net:8072

能夠看到請求的各個服務節點依次交替,說明已經實現負載均衡

restTemplate.getForObject() 不能和 loadBalancerClient.choose() 同時使用,由於註釋了 @LoadBalanced 的 restTemplate 實際上就是一個 Ribbon 客戶端,包含了 choose 的功能

自定義 Ribbon 客戶端

在某些場景下,可能會須要自定義 Ribbon 的配置,如修改 Ribbon 的負載均衡規則。可使用 @RibbonClient 聲明自定義的配置,或者使用 <clientName>.Ribbon.* 中的外部屬性配置 Ribbon 客戶端

代碼自定義 Ribbon 客戶端

默認狀況下,Ribbon 客戶端已經實現瞭如下的 bean (BeanType beanName:ClassName

  • IClientConfig ribbonClientConfig: DefaultClientConfigImpl
  • IRule ribbonRule: ZoneAvoidanceRule
  • IPing ribbonPing: DummyPing
  • ServerList<Server> ribbonServerList: ConfigurationBasedServerList
  • ServerListFilter<Server> ribbonServerListFilter: ZonePreferenceServerListFilter
  • ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer
  • ServerListUpdater ribbonServerListUpdater: PollingServerListUpdater

可經過自定義配置覆蓋默認配置

@Configuration
public class RibbonConfiguration {

    @Bean
    public IPing ribbonPing() {
        return new PingUrl();
    }

    @Bean
    public IRule ribbonRule() {
        // 負載均衡規則:隨機
        return new RandomRule();
    }
}
@Configuration
@RibbonClient(name = "product-service", configuration = RibbonConfiguration.class)
public class TestConfiguration {
}

這種配置是細粒度的,不一樣的 Ribbon 客戶端可使用不一樣的配置,使用 @RibbonClient 的 configuration 屬性,就能夠自定義 指定名稱的 Ribbon 客戶端的配置

這裏的 RibbonConfiguration 類不能包含在主應用程序的上下文中的 @ComponentScan 中,不然該類的配置會被全部的 @RibbonClient 共享。所以只想自定義某一個 Ribbon 客戶端的配置,必須防止 @Configuration 的註解的類所在包和 @ComponentScan 掃描的包重合,或顯示指定 @ComponentScan 不掃描 @Configuration 類所在包

爲全部Ribbon客戶端自定義默認值

經過使用 @RibbonClients 註釋並註冊一個默認配置,能夠爲全部 Ribbon 客戶端提供一個默認配置

@RibbonClients(defaultConfiguration = DefaultRibbonConfig.class)
public class RibbonClientDefaultConfigurationTestsConfig {

	public static class BazServiceList extends ConfigurationBasedServerList {

		public BazServiceList(IClientConfig config) {
			super.initWithNiwsConfig(config);
		}

	}

}

@Configuration
class DefaultRibbonConfig {

	@Bean
	public IRule ribbonRule() {
		return new BestAvailableRule();
	}

	@Bean
	public IPing ribbonPing() {
		return new PingUrl();
	}

	@Bean
	public ServerList<Server> ribbonServerList(IClientConfig config) {
		return new RibbonClientDefaultConfigurationTestsConfig.BazServiceList(config);
	}

	@Bean
	public ServerListSubsetFilter serverListFilter() {
		ServerListSubsetFilter filter = new ServerListSubsetFilter();
		return filter;
	}
}

屬性自定義 Ribbon 客戶端

使用屬性自定義 Ribbon 客戶端會比代碼自定義更加方便。屬性配置的前綴爲 <clientName>.Ribbon.*

  • NFLoadBalancerClassName: 實現 ILoadBalancer
  • NFLoadBalancerRuleClassName: 實現 IRule
  • NFLoadBalancerPingClassName: 實現 IPing
  • NIWSServerListClassName: 實現 ServerList
  • NIWSServerListFilterClassName: 實現 ServerListFilter

這些屬性中定義的類優先於使用 @RibbonClient(configuration=MyRibbonConfig.class) 定義的 bean 和Spring Cloud Netflix 提供的默認值定義的 bean。

product-service:
  ribbon:
    NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule

參考代碼:demo

相關文章
相關標籤/搜索