以前介绍了使用 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