简介
我们继续以之前博客的代码为基础, 增加 Ribbon 组件来提供客户端负载均衡. 负载均衡是实现高并发, 高性能, 可伸缩服务的重要组成部分, 它可以把请求分散到一个集群中不同的服务器中, 以减轻每个服务器的负担. 客户端负载均衡是运行在客户端程序中的, 如我们的 web 项目, 然后通过获取集群的 IP 地址列表,随机选择一个 server 发送请求. 相对于服务端负载均衡来说, 它不需要消耗服务器的资源.
基础环境
- JDK 1.8
- Maven 3.3.9
- IntelliJ 2018.1
- Git
项目源码
Gitee 码云 https://gitee.com/zxuqian/spring-cloud/tree/spring-cloud-tutorial-ribbon
更新配置
我们这次需要在本地启动两个产品服务程序, 用来验证负载均衡, 所以需要为第二个程序提供不同的端口. Spring Cloud 配置服务中心的配置默认会覆盖本地系统环境变量, 而我们需要通过系统环境变量来设置产品服务的端口, 所以需要在配置中心 git 仓库中修改产品服务的配置文件
- product-service.yml
- server:
- port: 8081
- spring:
- cloud:
- config:
- allow-override: true
- override-system-properties: false
allow-override 的默认值即为 true, 写出它来是想作说明, 它的意思是允许远程配置中心的配置项覆盖本地的配置, 并不是说允许本地的配置去覆盖远程的配置. 当然我们可以把它设置成 false, 但是为了提供更精确的覆盖规则, 这里保留了默认值.
我们添加了
override-system-properties=false
, 即虽然远程配置中心的配置文件可以覆盖本地的配置, 但是不要覆盖本地系统变量. 修改完成后提交到 git 仓库.
另外, 在 productService 项目的 ProductController 中添加一些 log, 用来验证负载均衡是否生效:
- package cn.zxuqian.controllers;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- @RestController
- public class ProductController {
- private static Logger log = LoggerFactory.getLogger(ProductController.class);
- @RequestMapping("/products")
- public String productList() {
- log.info("Access to /products endpoint");
- return "外套, 夹克, 毛衣, T 恤";
- }
- }
为 web 配置 Ribbon
首先在 pom.xml 中添加 Ribbon 的依赖:
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
- </dependency>
然后修改 Application 类, 添加如下代码:
- @EnableCircuitBreaker
- @EnableDiscoveryClient
- @RibbonClient(name = "product-service")
- @SpringBootApplication
- public class Application {
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
- @Bean
- @LoadBalanced
- public RestTemplate rest(RestTemplateBuilder builder) {
- return builder.build();
- }
- }
这里用到了
@RibbonClient(name = "product-service")
注解, 用来标记此项目为 Ribbon 负载均衡的客户端, 它需要选择产品服务集群中其中的一台来访问所需要的服务, 这里的 name 属性对应于 productService 项目中配置的
spring.application.name
属性.
@LoadBalanced 注解标明了 RestTemplate 会被配置为自动使用 Ribbon 的 LoadBalancerClient 来选择服务的 uri 并发送请求.
在我们在 ProductService 类中添加如下代码:
- @Service
- public class ProductService {
- private final RestTemplate restTemplate;
- @Autowired
- private DiscoveryClient discoveryClient;
- public ProductService(RestTemplate restTemplate) {
- this.restTemplate = restTemplate;
- }
- @HystrixCommand(fallbackMethod = "backupProductList")
- public String productList() {
- List<ServiceInstance> instances = this.discoveryClient.getInstances("product-service");
- if(instances != null && instances.size()> 0) {
- return this.restTemplate.getForObject(instances.get(0).getUri() + "/products", String.class);
- }
- return "";
- }
- public String backupProductList() {
- return "夹克, 毛衣";
- }
- public String productListLoadBalanced() {
- return this.restTemplate.getForObject("http://product-service/products", String.class);
- }
- }
这里新添加了一个
productListLoadBalanced
方法, 跟之前的 productList 方法访问的是同一服务, 只不过是用 Ribbon Client 去做了负载均衡, 这里的 uri 的 host 变成了 product-service 即要访问的服务的名字, 跟 @RibbonClient 中配置的 name 属性保持一致. 最后在我们的 ProductController 中添加下面的代码:
- @RestController
- public class ProductController {
- @Autowired
- private ProductService productService;
- @RequestMapping("/products")
- public String productList() {
- return productService.productList();
- }
- @RequestMapping("/productslb")
- public String productListLoadBalanced() {
- return productService.productListLoadBalanced();
- }
- }
来创建一个专门处理 / productslb 请求的方法, 调用 productServie 提供负载均衡的方法.
到这里我们的代码就完成了, 代码看似简单, 其实是所有的配置都使用了默认值. Ribbon 提供了编程式和配置式两种方式来配置 Ribbon Client. 现简单介绍下, 后续深入 Ribbon 时再和大家一起看看如何修改它的配置. Ribbon 提供如下配置 (左边是接口, 右边是默认实现):
- IClientConfig ribbonClientConfig: DefaultClientConfigImpl
- IRule ribbonRule: ZoneAvoidanceRule
- IPing ribbonPing: DummyPing
- ServerList<Server> ribbonServerList: ConfigurationBasedServerList
- ServerListFilter<Server>
- ribbonServerListFilter: ZonePreferenceServerListFilter
- ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer
- ServerListUpdater ribbonServerListUpdater: PollingServerListUpdater
因为我们这个项目用了 Eureka, 所以有些配置项和默认实现有所不同, 如 Eureka 使用
DiscoveryEnabledNIWSServerList
取代 ribbonServerList 来获取在 Eureka 上注册的服务的列表. 下边有一个简单的 Congiguration 类, 来自 Spring 官网:
- public class SayHelloConfiguration {
- @Autowired
- IClientConfig ribbonClientConfig;
- @Bean
- public IPing ribbonPing(IClientConfig config) {
- return new PingUrl();
- }
- @Bean
- public IRule ribbonRule(IClientConfig config) {
- return new AvailabilityFilteringRule();
- }
- }
Ribbon 默认不会发送 Ping 检查 server 的健康状态, 默认均正常, 然后 IRune 默认实现为 ZoneAvoidanceRule 用来避免 AWS EC2 问题较多的 zone, 这在本地测试环境来说是用不到的, 然后替换成了
AvailabilityFilteringRule
, 这个可以开启 Ribbon 自带的断路器功能, 来过滤不正常工作的服务器.
测试
首先启动我们的 configserver 配置中心服务, 然后启动 registry Eureka 注册与发现服务, 然后启动两个 productService, 第一个我们可以正常使用 spring-boot:run 插件来启动, 第二个我们需要给它提供一个新的端口, 可以用如下命令启动:
$ SERVER_PORT=8082 mvn spring-boot:run
最后启动我们的 web 客户端项目, 访问
http://localhost:8080/productslb
, 然后刷新几次, 会看到运行着 productService 的两个命令行窗口会随机出现我们的 log:
Access to /products endpoint
欢迎访问我的博客张旭乾的博客 http://zxuqian.cn/spring-cloud-tutorial-ribbon/
来源: https://www.cnblogs.com/zxuqian/p/8994758.html