构建一个 Eureka Server 服务注册中心 eureka-server
和两个注册服务 product-service
(用来提供服务) 和 order-service-ribbon
(消费服务者) 参考,这里我们启动两个 product-service
实例
这里分别给出配置信息
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:
application:
name: order-service-ribbon
server:
port: 8081
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
dependency>
启动服务注册中心 eureka-server 和两个注册服务,打开 http://localhost:8761/eureka/ 查看服务注册情况
实际生产中,基本上每个服务都会部署多个实例,那么服务消费者的请求该怎么分配到多个服务提供者实例上呢?
Ribbon 是 Netflix 发布的负载均衡器,是在客户端实现负载均衡,对客户端的HTTP和TCP行为有很好的控制。它可以在客户端为其配置服务提供者地址列表 ribbonServerList
,然后基于某种负载均衡算法(轮询、随机等),自动帮助客户端去请求
当 Eureka 与 Ribbon 结合使用时,ribbonServerList
将被扩展为DiscoveryEnabledNIWSServerList
,扩展为 Eureka 的服务器注册实例列表。同时还会用 NIWSDiscoveryPing
替换 IPing
接口,让 Eureka 来确定服务端是否启动
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
声明自定义的配置,或者使用
中的外部属性配置 Ribbon 客户端
默认情况下,Ribbon 客户端已经实现了以下的 bean (BeanType
beanName:ClassName
)
IClientConfig
ribbonClientConfig: DefaultClientConfigImpl
IRule
ribbonRule: ZoneAvoidanceRule
IPing
ribbonPing: DummyPing
ServerList
ribbonServerList: ConfigurationBasedServerList
ServerListFilter
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 客户端会比代码自定义更加方便。属性配置的前缀为
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