Spring Cloud 纵横研究院微服务&容器专题社区

负载均衡Ribbon

2019-04-25  本文已影响3人  董二弯

上一章讲述了服务注册和发现组件Eureka(https://www.jianshu.com/p/5d29348a3435)
,同时跟踪了Eureka的部分源码深入讲解了Eureka的机制,其中还构建了高可用的Eureka Server。本章讲解如何使用RestTemplate和Ribbon相结合作为服务消费者去消费服务,同时从源码角度深入理解Ribbon。

RestTemplate简介

RestTemplate是用来消费REST服务的,它的主要方法都与REST的HTTP协议的一些方法紧密相连,列如REST中HEAD、GET、POST、PUT、DELETE、OPTIONS等方法对应为RestTemplate中headForHeaders()、getForObject()、postForObject()、put()和delete()等方法。RestTemplate支持XML、JSON数据格式,默认实现了序列化,可以自动将JSON字符串转换为实体。

//表示将请求返回的JSON字符串转换成一个User对象
User user = restTemplate.getForObject("https://www.xxx.com/",User.class)

Ribbon简介

负载均衡是指将服务分摊到多个执行单元上,常见的有两种。

使用负载均衡带来的好处很明显:

Ribbon的子模块如下,很多子模块在开发环境中不一定用到。

RestTemplate和Ribbon结合消费服务

我们在上一章项目的基础上进行改造。

@RestController
public class HiController {
    @Value("${server.port}")
    private String port;
    @GetMapping("/hi")
    public String hi(String name) {
        return "hi " + name + ",I am from " + port;
    }
}

这么写的目的是需要以不同的端口启动两个eureka-client,访问接口时通过打印不同的端口来判断Ribbon是否实现了负载均衡的功能。

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>

在工程配置文件application.yml中做程序的相关配置如下


spring:
  application:
    name: eureka-ribbon-client
server:
  port: 8765
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
management:
  security:
    enabled: false

在启动类上添加注解@EnableEurekaClient开启 Eureka Client功能。

@SpringBootApplication
@EnableEurekaClient
public class EurekaRibbonClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaRibbonClientApplication.class, args);
    }
}

在程序的IOC容器中注入一个restTemplate的Bean,并在这个Bean上加上@LoadBalanced注解,此时RestTemplate就结合了Ribbon开启了负载均衡的功能。

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

写一个service类,其中运用restTemplate调用之前启动的eureka-client的API接口。由于Ribbon自身维护有服务信息列表,所以在请求的Uri上不需要使用硬编码(如IP地址),只需要写服务名即可。

@Service
public class RibbonService {
    @Autowired
    private RestTemplate restTemplate;

    public String hiFromClient() {
        return restTemplate.getForObject("http://eureka-client/hi?name=dzy", String.class);
    }
}

最后写一个Controller类,来调用service中的方法。

@RestController
public class RibbonController {
    @Autowired
    private RibbonService ribbonService;

    @GetMapping("/hi")
    public String hi() {
        return ribbonService.hiFromClient();
    }
}

启动项目,确定在注册中心中已经注册。然后多次访问http://localhost:8765/hi,浏览器会交替出现两个服务提供者实例的信息,说明实现了负载均衡。

LoadBalanceClient简介

负载均衡的核心类为LoadBalanceClient,用LoadBalanceClient可以获取服务提供者的服务信息,多次访问以下代码接口,会轮流得到两个服务提供者的实例信息。

@RestController
public class RibbonController {
    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/load-balancer")
    public String load() {
        ServiceInstance instance = loadBalancerClient.choose("eureka-client");
        return instance.getHost() + ":" + instance.getPort();
    }
}

LoadBalanceClient是从Eureka Client获取服务的注册列表信息,并将服务注册列表信息缓存了一份。当调用choose方法时,根据负载均衡策略选择一个服务实例的信息进行负载均衡。LoadBalanceClient也可以不从Eureka Client获取注册列表信息,这时需要自己维护一份服务注册列表信息,具体配置如下。

stores:
  ribbon:
    listOfServers: client1.com,client2.com
ribbon:
  eureka:
    enable: false

通过配置ribbon.eureka.enable为false来禁止调用从Eureka Client获取注册信息。通过stores.ribbon.listOfServices来配置服务实例的Url,此时在调用choose方法时会从配置的实例中来进行负载均衡。

Ribbon源码解析

在使用restTemplate时,只需要添加@LoadBalanced注解即可,非常的简单方便,现在我们看看它底层的原理。
LoadBalancerAutoConfiguration.java为实现客户端负载均衡器的自动化配置类。在这个类中实现了很多的功能,如下:
Ribbon要实现负载均衡自动化配置需要满足如下两个条件:

@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {}

在自动化配置中主要做三件事:

@Bean
        public LoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

@Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
            return new RestTemplateCustomizer() {
                @Override
                public void customize(RestTemplate restTemplate) {
                    List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                            restTemplate.getInterceptors());
                    list.add(loadBalancerInterceptor);
                    restTemplate.setInterceptors(list);
                }
            };
        }
@LoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();

接下来,我们看看LoadBalancerInterceptor拦截器是如何将一个普通的RestTemplate变成客户度负载均衡的。在拦截器中执行的是loadBalancer的execute方法。

@Override
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
            final ClientHttpRequestExecution execution) throws IOException {
        final URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
    }

在execute主要调用了getServer方法。

@Override
    public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
        Server server = getServer(loadBalancer);
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        }
        RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
                serviceId), serverIntrospector(serviceId).getMetadata(server));

        return execute(serviceId, ribbonServer, request);
    }

protected Server getServer(ILoadBalancer loadBalancer) {
        if (loadBalancer == null) {
            return null;
        }
        return loadBalancer.chooseServer("default"); // TODO: better handling of key
    }

在getServer函数中,我们可以看到获取具体服务实例的时候,使用了Netflix Ribbon自身的ILoadBalancer接口中定义的chooseServer函数。该接口代码如下

public interface ILoadBalancer {
        //向负载均衡器中维护的实例列表中增加服务实例。
    public void addServers(List<Server> newServers);
        //通过某种策略,从负载均衡器中挑选出一个具体的服务实例。
    public Server chooseServer(Object key);
        //用来通知和标识负载均衡器中某个具体实例已经停止服务,不然负载均衡器在下一次获取服务实例清单都会认为该服务实例时正常的。
    public void markServerDown(Server server);
        //获取当前服务的实例列表。
    public List<Server> getReachableServers();
        //获取所有已经的服务实例列表,包括正常服务和停止服务的实例。
    public List<Server> getAllServers();
}

在BaseLoadBalancer类实现了基础的负载均衡,其中chooseServer代码如下。

public Server chooseServer(Object key) {
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        if (rule == null) {
            return null;
        } else {
            try {
                return rule.choose(key);
            } catch (Exception e) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
                return null;
            }
        }
    }

在其中主要代码为rule.choose(key),调用的是IRule接口的choose方法。

public interface IRule{
    public Server choose(Object key);
    public void setLoadBalancer(ILoadBalancer lb);
    public ILoadBalancer getLoadBalancer();    
}

choose方法的实现类如图


image.png

其中常用的实现类为:
RandomRule:随机选择一个UP的服务。
RoundRobinRule:轮询获取服务。
BestAvailableRule:跳过熔断的服务,获取请求数最少的服务.通常与ServerListSubsetFilter一起使用。
RetryRule:在RoundRobinRule的基础上,增加了重试的机制。
BestAvailableRule:表示请求数最少策略。

综上可以得出用@LoadBalanced标注的restTemplate在请求之前增加了拦截机制,在拦截器中是调用了LoadBalancerClient的execute方法,在其中根据负载均衡的策略进行服务实例的访问。

总结

在这一章节中,对RestTemplate,Ribbon进行了介绍,并实现了两者的结合来实现负载均衡消费服务。最后对Ribbon部分源码进行了简单解析。在下一章介绍声明式调用feign的使用。

PS:项目github地址:https://github.com/dzydzydzy/spring-cloud-example.git

上一篇 下一篇

猜你喜欢

热点阅读