以前介紹了使用 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/
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
實際生產中,基本上每一個服務都會部署多個實例,那麼服務消費者的請求該怎麼分配到多個服務提供者實例上呢?服務器
Ribbon 是 Netflix 發佈的負載均衡器,是在客戶端實現負載均衡,對客戶端的HTTP和TCP行爲有很好的控制。它能夠在客戶端爲其配置服務提供者地址列表 ribbonServerList
,而後基於某種負載均衡算法(輪詢、隨機等),自動幫助客戶端去請求網絡
當 Eureka 與 Ribbon 結合使用時,ribbonServerList
將被擴展爲DiscoveryEnabledNIWSServerList
,擴展爲 Eureka 的服務器註冊實例列表。同時還會用 NIWSDiscoveryPing
替換 IPing
接口,讓 Eureka 來肯定服務端是否啓動app
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
是 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 的負載均衡規則。可使用 @RibbonClient
聲明自定義的配置,或者使用 <clientName>.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 類所在包
經過使用 @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 客戶端會比代碼自定義更加方便。屬性配置的前綴爲 <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