zoukankan      html  css  js  c++  java
  • SpringCloud之Ribbon的使用及源码解析

    Ribbon简介

    什么是Ribbon?

    Ribbon是Netflix发布的负载均衡器,它可以帮我们控制HTTP和TCP客户端的行为。只需为Ribbon配置服务提供者地址列表,Ribbon就可基于负载均衡算法计算出要请求的目标服务地址。

    Ribbon默认为我们提供了很多的负载均衡算法,例如轮询、随机、响应时间加权等——当然,为Ribbon自定义负载均衡算法也非常容易,只需实现IRule 接口即可。

    SpringCloud提供了Ribbon用来做客户端负载均衡,通过SpringCloud对Rbbon的封装,我们可以很轻松的通过负载均衡去调用我们开发的rest服务,不需要手动去处理因负载均衡而出现的各种棘手情况,
    Ribbon并不需要像eureka和网关那样单独部署,它是和每一个微服务耦合在一起的
    使用resttemplate与ribbon整合去实现负载均衡的调用,启用方式是需要我们在RestTemplate 实例配置上面添加@LoadBalanced注解。
    可以去了解一下RestTemplate的api使用。
     
    怎么用?
     
    加依赖
    基于已经有的SpirngCloud项目,由于spring-cloud-starter-netflix-eureka-client 已经包含 spring-cloud-starter-netfilx-ribbon ,故而无需额外添加依赖。
     
    写代码
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
      return new RestTemplate();
    }

    只需在RestTemplate 上添加LoadBalanced 注解,即可让RestTemplate整合Ribbon!

    调用

    @GetMapping("/users/{id}")
    public User findById(@PathVariable Long id) {
      // 这里用到了RestTemplate的占位符能力
      User user = this.restTemplate.getForObject(
        "http://microservice-provider-user/users/{id}",
        User.class,
        id
      );
      // ...业务...
      return user;
    }

    我们将请求的目标服务改成了http://microservice-provider-user/users/{id} ,也就是http://{目标服务名称}/{目标服务端点} 的形式,Ribbon会自动在实际调用时,将目标服务名替换为该服务的IP和端口

    WARNING

    事实上,这里的目标服务名称,在Ribbon里叫虚拟主机名 ,主机名是不能包含_ 等特殊字符的——这意味着,一般不建议配置spring.application.name = xxx_xxx ,如果你的应用名称一定带有下划线这种字符,

    那么请额外配置eureka.instance.virtual-host-name = 一个合法的主机名 ,否则Ribbon将会提示虚拟主机名不合法的异常(在早期的版本则是报空指针)!

    什么时候用Ribbon,用在哪?

    上面知道Ribbon实现在客户端的负载均衡,所以当我们需要调用多台部署了相同项目的服务器时,就可以使用Ribbon。既然时客户端的负载均衡,那自然是在调用方使用了。

    比如服务调用方A调用服务提供方B/C/D,就可以再A使用Ribbon,负载均衡的调用BCD。

    深入

    那么Ribbon是如何实现负载均衡的呢?
    根据我们前面说Ribbon实现的的客户端负载均衡,所以它自己肯定有一个可用的服务列表,服务列表里面存储的是可用服务的地址,这是第一个条件;
    第二个是我们在RestTemplate上面添加了注解后,它就自动实现了负载均衡,这个我们可以想到它拦截了我们的请求,所以这里肯定会有一个拦截器在帮助我们实现此功能;
    第三个是为什么我们加上了注解后,也可以使用服务名直接去调用了呢,肯定也有组件帮我们实现了替换的
     
    看源码!!
    因为时通过注解@LoadBalanced开启的Ribbon,所以就从这个注解开始看。
     
    @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Qualifier
    public @interface LoadBalanced {
    }

    可以看到类里面是空的。那想到在springboot中,有一个申明式的注解,必定在其同名的包下面会有一个这样的(xxxAutoConfiguration)配置类,去配置这个注解,

    我们定位到该包下面,看到同名的包下面确实有这样一个配置类LoadBalancerAutoConfiguration

    点进去是这样的:

    @Configuration(//@Configuration:注解可以用java代码的形式实现spring中xml配置文件配置的效果。
        proxyBeanMethods = false
    )
    @ConditionalOnClass({RestTemplate.class})//@ConditionalOnClass:其用途是判断当前classpath下是否存在指定类,若是则将当前的配置装载入spring容器
    @ConditionalOnBean({LoadBalancerClient.class})//@ConditionalOnBean:当给定的在bean存在时,则实例化当前Bean
    @EnableConfigurationProperties({LoadBalancerRetryProperties.class})
    public class LoadBalancerAutoConfiguration {
        @LoadBalanced
        @Autowired(
            required = false
        )
    //这里面会注入有@loadbalence注解的所有的restTemplate实例
    private List<RestTemplate> restTemplates = Collections.emptyList(); @Autowired( required = false ) //还有很多。。。 }

     @EnableConfigurationProperties:如果该类只使用了@ConfigurationProperties注解,然后该类没有在扫描路径下或者没有使用@Component等注解,导致无法被扫描为bean,那么就必须在配置类上使用@EnableConfigurationProperties注解去指定这个类,这个时候就会让该类上的@ConfigurationProperties生效,然后作为bean添加进spring容器中

    再点击RestTemplate进去,看到此类继承了
    InterceptingHttpAccessor
    public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
    //。。。。
    }

    看来真的有拦截器在起作用,我们可以看到InterceptingHttpAccessor类中有一个setInterceptors方法

    public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
        Assert.noNullElements(interceptors, "'interceptors' must not contain null elements");
        if (this.interceptors != interceptors) {
            this.interceptors.clear();
            this.interceptors.addAll(interceptors);
            AnnotationAwareOrderComparator.sort(this.interceptors);
        }
    }

    然后我们看看他是在哪里调用这份方法的,我们再回到LoadBalancerAutoConfiguration配置类中,看到这段方法:

            @Bean
            @ConditionalOnMissingBean
            public RestTemplateCustomizer restTemplateCustomizer(final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
                return (restTemplate) -> {
                    List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
                    list.add(loadBalancerInterceptor);
                    restTemplate.setInterceptors(list);
                };
            }

    看到这里在往resttemplate中添加拦截器loadBalancerInterceptor,我们点击这个拦截器进去,主要看这段代码

    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
        URI originalUri = request.getURI();//获取URI
        String serviceName = originalUri.getHost();//获取serviceName 就是每个微服务的应用名称 
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        LoadBalancedRetryPolicy retryPolicy = this.lbRetryFactory.createRetryPolicy(serviceName, this.loadBalancer);
        RetryTemplate template = this.createRetryTemplate(serviceName, request, retryPolicy);
        return (ClientHttpResponse)template.execute((context) -> {
            ServiceInstance serviceInstance = null;
            if (context instanceof LoadBalancedRetryContext) {
                LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext)context;
                serviceInstance = lbContext.getServiceInstance();
            }
    
            if (serviceInstance == null) {
                serviceInstance = this.loadBalancer.choose(serviceName);//查看这个choose方法,这里通过负载均衡选择一个server
            }
    
            ClientHttpResponse response = (ClientHttpResponse)this.loadBalancer.execute(serviceName, serviceInstance, this.requestFactory.createRequest(request, body, execution));
            int statusCode = response.getRawStatusCode();
            if (retryPolicy != null && retryPolicy.retryableStatusCode(statusCode)) {
                byte[] bodyCopy = StreamUtils.copyToByteArray(response.getBody());
                response.close();
                throw new ClientHttpResponseStatusCodeException(serviceName, response, bodyCopy);
            } else {
                return response;
            }
        }, new LoadBalancedRecoveryCallback<ClientHttpResponse, ClientHttpResponse>() {
            protected ClientHttpResponse createResponse(ClientHttpResponse response, URI uri) {
                return response;
            }
        });
        

    查看choose方法:

    public ServiceInstance choose(String serviceId, Object hint) {
        Server server = this.getServer(this.getLoadBalancer(serviceId), hint);//通过serviceID找到对应的服务实例均衡器,loadBalancer 这里面保存了所有的服务实例,可用的,宕机的都在里面
        return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
    }

    使用均衡算法拿到可用的服务实例(从几个中选择一个,达到均衡的作用),返回server,然后拿到这个server就可以继续执行目标请求。

    均衡器里面会保存allServerList,里面会有upServeLlist里面是我们启动的服务实例,然后使用负载均衡算法选择一个服务。

    ribbon的常见配置

    1.禁用eureka

      当我们在resttemplate上面添加loadbalence注解后,就可以使用服务名去调用,如果我们想关闭这个功能,可以使用ribbon.eureka.enable=false

    2.配置接口地址列表

      如果我们关闭了eureka之后,还想用服务名去调用,就需要手动配置服务配置列表

      服务名.ribbon.listOfServers=IP:PORT1,IP:PORT2

    3.配置负载均衡策略

      服务名.ribbon.NFLoadBalencerRuleClassName=策略class全类名

    4.超时时间

      ribbon中有两种和超时时间相关的配置

        ribbon.ConnectTimeout=2000  请求连接的超时时间

        ribbon.ReadTimeout=5000 请求处理的超时时间

      可以在前面加上具体的服务名,为指定的服务配置

    5.并发参数

      ribbon.MaxTotalConnections=500  最大连接数

      ribbon,MaxConnectionsPerHost=500  每个host最大连接数

  • 相关阅读:
    “XXXXX” is damaged and can’t be opened. You should move it to the Trash 解决方案
    深入浅出 eBPF 安全项目 Tracee
    Unity3d开发的知名大型游戏案例
    Unity 3D 拥有强大的编辑界面
    Unity 3D物理引擎详解
    Unity 3D图形用户界面及常用控件
    Unity 3D的视图与相应的基础操作方法
    Unity Technologies 公司开发的三维游戏制作引擎——Unity 3D
    重学计算机
    windows cmd用户操作,添加,设备管理员组,允许修改密码
  • 原文地址:https://www.cnblogs.com/yunyunde/p/13536659.html
Copyright © 2011-2022 走看看