文档章节

spring cloud(第四部)Ribbon负载均衡原理解析

白中墨
 白中墨
发布于 06/18 13:17
字数 3238
阅读 36
收藏 0

spring cloud如何配置负载均衡

  • 基于RestTemplate+Ribbon配置
    说明:本示例依然选用eureka作为服务发现的注册中心,本示例是用户服务user_service,会调用前面文章提到的order_service服务
    1、pom引入
    因为要访问order_service,所以需要从注册中心获取order_service的服务列表,要获取服务列表所以需要连接eureka,因此需要引入eureka的client包

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

    2、yml配置
    这里我们没有通过前面的通过指定registerWithEureka和fetchRegistry的方式,换做使用@EnableDiscoveryClient注解的实现方式指定服务的注册和获取

    spring:
      application:
        name: userService
    server:
      port: 8762
    
    eureka:
      client:
        serviceUrl:
          defaultZone: http://node2:10002/eureka/,http://node3:10003/eureka/,http://node1:10001/eureka/

    3、入口函数注解配置:

    @EnableDiscoveryClient
    //@EnableEurekaClient
    @SpringBootApplication
    public class UserApplication {
        public static void main(String[] args) {
            //DiscoveryClient tt = null;
            SpringApplication.run(UserApplication.class, args);
        }
     
    }

    4、RestTempalte调用示例:
    4.1、先定一个RestTemplate对象,需要@LoadBalanced注解做声明,表示这是一个采用了负载均衡策略的RestTemplate

        @LoadBalanced
        RestTemplate restTemplate() {
            return new RestTemplate();
        }

    4.2、远程接口调用,调用user_service服务,order_service项目的服务名称是orderService,通过yml的spring.application.name指定,注意服务名称调用的时候一定使用大写,示例如下:

        public Properties testUserOrder() {        
            Properties properties = restTemplate.getForObject("http://ORDERSERVICE/hello", Properties.class);
            properties.setProperty("userorder", "add user order");
            return properties;
        }

    ORDERSERVICE是服务名称(一个或多个订单服务组成的集群)
    hello可以理解成接口名,其实就是一个uri,因为暴露出的是http服务,所以schema是http
    Properties是接口的返回值

  • 基于Feign的负载均衡配置
    上面的使用方式,有一个比较大的缺陷,即服务提供方完成部署以后还需要告知服务调用方接口名称,这个需要硬编码到调用方的逻辑里,不太友好
    相反,使用Feign的方式可以避免这种情况,服务提供方完成服务部署,提供一个interface的client给调用方,使用起来更方便,向调用本地服务一样使用
    1、pom引入

    	     <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-feign</artifactId>
                <version>1.4.0.RELEASE</version>
                </dependency>

    2、main函数引入feign注解

    @EnableDiscoveryClient
    //@EnableEurekaClient
    @EnableFeignClients
    @SpringBootApplication
    public class UserApplication {
        public static void main(String[] args) {
            //DiscoveryClient tt = null;
            SpringApplication.run(UserApplication.class, args);
        }
    
        
    }

    3、新增一个orderClient,正常情况下是有服务提供方提供该接口

    @FeignClient(value = "orderService")
    public interface OrderClient {
        @RequestMapping("/hello")
        public Properties testHello();
    }

    4、调用示例:

    @RestController
    public class UserRibbonController {
    
        @Autowired
        private OrderClient orderClient;
    
        @RequestMapping("/userorder_feign")
        public Properties testUserOrderByFeign() {        
            return orderClient.testHello();
        }
        
    
    }

    至此,使用方式告一段落,且看下文原理剖析

Ribbon实现原理分析

  • 组件初始化
    1、RestTemplate类图

    2、功能介绍:
    RestOperations:定义Rest请求的基本接口
    InterceptingHttpAccessor:实现Rest请求的适配类,包含有两个核心的属性
    2.1、List<ClientHttpRequestInterceptor> http请求拦截器集合,作用是实现真正的请求策略,举例:如果RestTemplate要实现负载均衡,那就需要给RestTemplate提供负载均衡的拦截器实现,在这里提供的是LoadBalancerInterceptor
    2.2、ClientHttpRequestFactory
    这是RestTempalte在发送之前为客户端创建一个ClientRequest的工厂方法,返回的是一个InterceptingClientHttpRequestFactory,这个类如其名,包装了拦截器List<ClientHttpRequestInterceptor> 和SimpleClientHttpRequestFactory,前者的功能不需要说明,参考说明2.1,后者是提供http请求的基础封装,如超时和代理设置等等
    3、client请求拦截器装载,参看LoadBalancerAutoConfiguration源码

    
    @Configuration
    @ConditionalOnClass(RestTemplate.class)
    @ConditionalOnBean(LoadBalancerClient.class)
    @EnableConfigurationProperties(LoadBalancerRetryProperties.class)
    public class LoadBalancerAutoConfiguration {
    
    	@LoadBalanced
    	@Autowired(required = false)
    	//因为Autowired标注在了集合处,所以@LoadBalanced注解标注的RestTemplate类,都会被注入进来
    	private List<RestTemplate> restTemplates = Collections.emptyList();
    
    	//给所有的RestTemplate配置装载器
    	@Bean
    	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
    			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
    		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
                for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                    for (RestTemplateCustomizer customizer : customizers) {
                        customizer.customize(restTemplate);
                    }
                }
            });
    	}
    
    	@Autowired(required = false)
    	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
    
    	//定义一个负载均衡请求的工厂bean,给负载均衡拦截器使用
    	@Bean
    	@ConditionalOnMissingBean
    	public LoadBalancerRequestFactory loadBalancerRequestFactory(
    			LoadBalancerClient loadBalancerClient) {
    		return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
    	}
    
    	@Configuration
    	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    	static class LoadBalancerInterceptorConfig {
    		//创建一个负载均衡的拦截器,LoadBalancerClient已经在RibbonAutoConfiguration进行了定义
    		@Bean
    		public LoadBalancerInterceptor ribbonInterceptor(
    				LoadBalancerClient loadBalancerClient,
    				LoadBalancerRequestFactory requestFactory) {
    			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    		}
    		//装载器的逻辑:把拦截器载入RestTempalte
    		@Bean
    		@ConditionalOnMissingBean
    		public RestTemplateCustomizer restTemplateCustomizer(
    				final LoadBalancerInterceptor loadBalancerInterceptor) {
    			return restTemplate -> {
                    List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                            restTemplate.getInterceptors());
                    list.add(loadBalancerInterceptor);
                    restTemplate.setInterceptors(list);
                };
    		}
    	}
    
    	@Configuration
    	@ConditionalOnClass(RetryTemplate.class)
    	public static class RetryAutoConfiguration {
    
    		@Bean
    		@ConditionalOnMissingBean
    		public LoadBalancedRetryFactory loadBalancedRetryFactory() {
    			return new LoadBalancedRetryFactory() {};
    		}
    	}
    
    	@Configuration
    	@ConditionalOnClass(RetryTemplate.class)
    	public static class RetryInterceptorAutoConfiguration {
    		@Bean
    		@ConditionalOnMissingBean
    		public RetryLoadBalancerInterceptor ribbonInterceptor(
    				LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
    				LoadBalancerRequestFactory requestFactory,
    				LoadBalancedRetryFactory loadBalancedRetryFactory) {
    			return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
    					requestFactory, loadBalancedRetryFactory);
    		}
    
    		@Bean
    		@ConditionalOnMissingBean
    		public RestTemplateCustomizer restTemplateCustomizer(
    				final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
    			return restTemplate -> {
                    List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                            restTemplate.getInterceptors());
                    list.add(loadBalancerInterceptor);
                    restTemplate.setInterceptors(list);
                };
    		}
    	}
    }
    

    4、负载策略初始化,比如ping的逻辑、取服务列表、路由规则的逻辑等,可以参考RibbonClientConfiguration、EurekaRibbonClientConfiguration的源码,详细各组件的功能我们在负载策略里做分析

  • 请求入口:
    1、RestTempalte的doExecute方法,首先要生成ClientHttpRequest,根据前面的初始化知识点,我们知道对应的请求工厂生成器是InterceptingClientHttpRequestFactory,所以直接看它的createRequest的方法:

    	protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
    			@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
    
    		Assert.notNull(url, "URI is required");
    		Assert.notNull(method, "HttpMethod is required");
    		ClientHttpResponse response = null;
    		try {
    			//获取httpRequest,通过ClientHttpRequestFactory获取,根据前面的初始化步骤也就是根据InterceptingClientHttpRequestFactory创建
    			//对应的类型是InterceptingClientHttpRequest
    			ClientHttpRequest request = createRequest(url, method);
    			if (requestCallback != null) {
    				requestCallback.doWithRequest(request);
    			}
    			//看InterceptingClientHttpRequest的execute方法
    			//InterceptingClientHttpRequest类的继承关系:InterceptingClientHttpRequest(executeInternal)-》AbstractBufferingClientHttpRequest-》AbstractClientHttpRequest(execute)
    
    			response = request.execute();//execute方法是调用的子类实现,也就是InterceptingClientHttpRequest的executeInternal方法
    			handleResponse(url, method, response);
    			return (responseExtractor != null ? responseExtractor.extractData(response) : null);
    		}
    		catch (IOException ex) {
    			String resource = url.toString();
    			String query = url.getRawQuery();
    			resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
    			throw new ResourceAccessException("I/O error on " + method.name() +
    					" request for \"" + resource + "\": " + ex.getMessage(), ex);
    		}
    		finally {
    			if (response != null) {
    				response.close();
    			}
    		}
    	}

    2、InterceptingClientHttpRequest执行executeInternal目的生成一个执行器,把拦截器传入,并执行请求

    	@Override
    	protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
    		InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
    		//这里会把负载拦截器传入到execute方法
    		return requestExecution.execute(this, bufferedOutput);
    	}

    3、拦截请求执行器的执行逻辑:

    		public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
    		    //有拦截器存在,在使用拦截器执行,这里的拦截器自然是前面提到的装载进来的LoadBalancerInterceptor
    			if (this.iterator.hasNext()) {
    				ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
    				return nextInterceptor.intercept(request, body, this);
    			}
    			else {//无拦截器直接发送请求
    				HttpMethod method = request.getMethod();
    				Assert.state(method != null, "No standard HTTP method");
    				ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
    				request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
    				if (body.length > 0) {
    					if (delegate instanceof StreamingHttpOutputMessage) {
    						StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
    						streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
    					}
    					else {
    						StreamUtils.copy(body, delegate.getBody());
    					}
    				}
    				return delegate.execute();
    			}
    		}

     

  • 负载策略
    一个完整的分发:涉及http请求处理、负载策略、列表初始化、列表更新
    书接上回,初始化RestTempalte的时候会绑定一个LoadBalancerInterceptor,我们直接看它的interceptor方法

    	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));
    	}

    这里的关键点在于this.loadBalancer.execute这个方法,我们知道在前面提到初始化组件的类LoadBalancerAutoConfiguration中,会将LoadBalancerInterceptor做bean的定义,定义的时候会需要传入两个参数:一个是LoadBalancerClient、一个是LoadBalancerRequestFactory,其中
    LoadBalancerClient是在RibbonAutoConfiguration中做了声明:如下

    		@Bean
    		public LoadBalancerInterceptor ribbonInterceptor(
    				LoadBalancerClient loadBalancerClient,
    				LoadBalancerRequestFactory requestFactory) {
    			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    		}
    	@Bean
    	@ConditionalOnMissingBean(LoadBalancerClient.class)
    	public LoadBalancerClient loadBalancerClient() {
    		return new RibbonLoadBalancerClient(springClientFactory());
    	}

    所以我们只需要关注RibbonLoadBalancerClient的execute方法即可:

    	public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
    		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    		//这里先获取服务列表,从spring context可以得到ZoneAwareLoadBalancer,该类是在RibbonClientConfiguration初始化完成,如下示例
    		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);
    	}
    	@Bean
    	@ConditionalOnMissingBean
    	public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
    			ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
    			IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
    	    //config请求客户端相关的配置,在RibbonClientConfiguration做了定义
    	    //serverList如何获取服务列表,DomainExtractingServerList,在EurekaRibbonClientConfiguration做了定义
    	    //rule,做zone的选择,ZoneAvoidanceRule,在EurekaRibbonClientConfiguration做了定义
    	    //serverListFilter服务过滤,在RibbonClientConfiguration做了定义
    	    //ping,列表探活,NIWSDiscoveryPing,在EurekaRibbonClientConfiguration做了定义
    	    //ServerListUpdater,服务列表更新和维护,PollingServerListUpdater在EurekaRibbonClientConfiguration做了定义
    		if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
    			return this.propertiesFactory.get(ILoadBalancer.class, config, name);
    		}
    		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
    				serverListFilter, serverListUpdater);
    	}

    接下来,要着重看下ZoneAwareLoadBalancer在初始化的时候做了哪些事情,然后再看路由策略,是如何从一个列表中选取一个server的

        public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
                                             ServerList<T> serverList, ServerListFilter<T> filter,
                                             ServerListUpdater serverListUpdater) {
            super(clientConfig, rule, ping);//这里绑定路由规则,ping策略到client,ping的策略会以task的形式定时去刷新服务列表,也就是loadbalance的本地缓存(serverlist)
            this.serverListImpl = serverList;
            this.filter = filter;
            this.serverListUpdater = serverListUpdater;
            if (filter instanceof AbstractServerListFilter) {
                ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
            }
            restOfInit(clientConfig);//首次初始化本地服务列表
        }

    负载均衡的服务列表,来自于服务发现的本地缓存具体是(具体参考:DiscoveryClient的AtomicReference<Applications> localRegionApps),ping探活策略,在更新负载均衡的服务列表时会防止使用方并发造成脏数据的读取,使用了读写锁,代码逻辑如下:

    
                if (!pingInProgress.compareAndSet(false, true)) { 
                    return; // Ping in progress - nothing to do
                }
                
                // we are "in" - we get to Ping
    
                Server[] allServers = null;
                boolean[] results = null;
    
                Lock allLock = null;
                Lock upLock = null;
    
                try {
                    /*
                     * The readLock should be free unless an addServer operation is
                     * going on...
                     */
                    //保证此时没有正在进行的对allServerList做改写的动作,即addServer
                    allLock = allServerLock.readLock();
                    allLock.lock();
                    allServers = allServerList.toArray(new Server[allServerList.size()]);
                    allLock.unlock();
    
                    int numCandidates = allServers.length;
                    results = pingerStrategy.pingServers(ping, allServers);
    
                    final List<Server> newUpList = new ArrayList<Server>();
                    final List<Server> changedServers = new ArrayList<Server>();
    
                    for (int i = 0; i < numCandidates; i++) {
                        boolean isAlive = results[i];
                        Server svr = allServers[i];
                        boolean oldIsAlive = svr.isAlive();
    
                        svr.setAlive(isAlive);
    
                        if (oldIsAlive != isAlive) {
                            changedServers.add(svr);
                            logger.debug("LoadBalancer [{}]:  Server [{}] status changed to {}", 
                                name, svr.getId(), (isAlive ? "ALIVE" : "DEAD"));
                        }
    
                        if (isAlive) {
                            newUpList.add(svr);
                        }
                    }
                    //保证此时没有对upServerList做读取的动作,防止读取到脏数据
                    upLock = upServerLock.writeLock();
                    upLock.lock();
                    upServerList = newUpList;
                    upLock.unlock();
    
                    notifyServerStatusChangeListener(changedServers);
                } finally {
                    pingInProgress.set(false);
                }
        public List<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure,
                                                           @Nullable String region) {
            if (vipAddress == null) {
                throw new IllegalArgumentException(
                        "Supplied VIP Address cannot be null");
            }
            Applications applications;
            if (instanceRegionChecker.isLocalRegion(region)) {
                applications = this.localRegionApps.get();
            } else {
                applications = remoteRegionVsApps.get(region);
                if (null == applications) {
                    logger.debug("No applications are defined for region {}, so returning an empty instance list for vip "
                            + "address {}.", region, vipAddress);
                    return Collections.emptyList();
                }
            }
    
            if (!secure) {
                return applications.getInstancesByVirtualHostName(vipAddress);
            } else {
                return applications.getInstancesBySecureVirtualHostName(vipAddress);
    
            }
    
        }

    下面是一个服务列表的产出结构:

    ping策略默认是SerialPingStrategy,并没有真正去执行,为什么呢,我们根据上面的分析得知,这里ping的目标对象是负载均衡的本地缓存,如果集群列表比较大的话,线性化的方式对每个server做ping的话,比较耗时,目前情况是通过eureka的缓存刷新任务可以提供一个简单的实现方案,代码说明如下:

       /**
         * Default implementation for <c>IPingStrategy</c>, performs ping
         * serially, which may not be desirable, if your <c>IPing</c>
         * implementation is slow, or you have large number of servers.
         */
        private static class SerialPingStrategy implements IPingStrategy {
    
            @Override
            public boolean[] pingServers(IPing ping, Server[] servers) {
                int numCandidates = servers.length;
                boolean[] results = new boolean[numCandidates];
    
                logger.debug("LoadBalancer:  PingTask executing [{}] servers configured", numCandidates);
    
                for (int i = 0; i < numCandidates; i++) {
                    results[i] = false; /* Default answer is DEAD. */
                    try {
                        // NOTE: IFF we were doing a real ping
                        // assuming we had a large set of servers (say 15)
                        // the logic below will run them serially
                        // hence taking 15 times the amount of time it takes
                        // to ping each server
                        // A better method would be to put this in an executor
                        // pool
                        // But, at the time of this writing, we dont REALLY
                        // use a Real Ping (its mostly in memory eureka call)
                        // hence we can afford to simplify this design and run
                        // this
                        // serially
                        if (ping != null) {
                            results[i] = ping.isAlive(servers[i]);
                        }
                    } catch (Exception e) {
                        logger.error("Exception while pinging Server: '{}'", servers[i], e);
                    }
                }
                return results;
            }
        }

    获取到了服务列表,接下来最重要的是看下server的选择:
     

        @Override
        public Server chooseServer(Object key) {
        	//负载均衡中维护的zone可用实例小于等于1,走父类的选择策略,不会进行zone的选择
            if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
                logger.debug("Zone aware logic disabled or there is only one zone");
                return super.chooseServer(key);
            }
            Server server = null;
            try {
                LoadBalancerStats lbStats = getLoadBalancerStats();
                //维护的全量zone快照
                Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
                logger.debug("Zone snapshots: {}", zoneSnapshot);
                if (triggeringLoad == null) {
                    triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
                            "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
                }
    
                if (triggeringBlackoutPercentage == null) {
                    triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
                            "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
                }
                //维护的可用的zone
                Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
                logger.debug("Available zones: {}", availableZones);
                if (availableZones != null &&  availableZones.size() < zoneSnapshot.keySet().size()) {
                	//随机选择一个zone
                    String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
                    logger.debug("Zone chosen: {}", zone);
                    if (zone != null) {
                    	//真正的取server的逻辑是在loadBalancer所绑定的rule,即ZoneAvoidanceRule
                        BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
                        server = zoneLoadBalancer.chooseServer(key);
                    }
                }
            } catch (Exception e) {
                logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
            }
            if (server != null) {
                return server;
            } else {
                logger.debug("Zone avoidance logic is not invoked.");
                return super.chooseServer(key);
            }
        }

    根据rule的源码,分析看到具体的选择策略,取模:

        private int incrementAndGetModulo(int modulo) {
            for (;;) {
                int current = nextIndex.get();
                int next = (current + 1) % modulo;
                //这里有个cas原语,意义不大,只能保证单机不会重复请求到一个正在处理的server上,一般都是分布式部署,所以有些鸡肋
                if (nextIndex.compareAndSet(current, next) && current < modulo)
                    return current;
            }
        }

     

  • 最后一步发送http请求
    采用的HttpUrlConnection,如下:

    	protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
    		addHeaders(this.connection, headers);
    		// JDK <1.8 doesn't support getOutputStream with HTTP DELETE
    		if (getMethod() == HttpMethod.DELETE && bufferedOutput.length == 0) {
    			this.connection.setDoOutput(false);
    		}
    		if (this.connection.getDoOutput() && this.outputStreaming) {
    			this.connection.setFixedLengthStreamingMode(bufferedOutput.length);
    		}
    		this.connection.connect();
    		if (this.connection.getDoOutput()) {
    			FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
    		}
    		else {
    			// Immediately trigger the request in a no-output scenario as well
    			this.connection.getResponseCode();
    		}
    		return new SimpleClientHttpResponse(this.connection);
    	}

     

  • 总结
    本文从,组件装载,到服务列表获取,是如何与服务发现结合的,再到路由策略实现,到最后的请求发送,做了完整讲解,下一篇将介绍下Feign的实现原理

© 著作权归作者所有

白中墨
粉丝 1
博文 27
码字总数 41497
作品 0
昌平
私信 提问
白话SpringCloud | 第四章:服务消费者(Ribbon+Feign)

前言 上两章节,介绍了下关于注册中心-Eureka的使用及高可用的配置示例,本章节开始,来介绍下服务和服务之间如何进行服务调用的,同时会讲解下几种不同方式的服务调用。 一点知识 在体系中,...

oKong
2018/09/21
871
1
Spring Cloud 实战之服务提供与调用

eureka注册续约流程 启动注册中心 服务提供者生产服务并注册到服务中心中 消费者从服务中心中获取服务并执行 服务提供 1.在spring-cloud-manage下创建一个子项目producer-service pom.xml文件...

技术小能手
2018/10/26
0
0
Spring Cloud构建微服务架构:服务消费(Ribbon)

Spring Cloud Ribbon Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。它是一个基于HTTP和TCP的客户端负载均衡器。它可以通过在客户端中配置ribbonServerList来设置...

itcloud
03/01
24
1
Spring Cloud 学习2. 客户端负载均衡Ribbon

版权声明:(谢厂节的博客)博主文章绝大部分非原创,转载望留链接。 https://blog.csdn.net/xundh/article/details/82499580 Ribbon简介 Ribbon是Netflix开源的一款用于客户端软负载均衡的工...

谢厂节
2018/09/08
0
0
跟我学Spring Cloud(Finchley版)-07-Ribbon入门

经过前文讲述,我们已经实现了服务发现。本节来解决 跟我学Spring Cloud(Finchley版)-02-构建分布式应用 提到的如下问题: 负载均衡如何考虑?难道得在电影微服务和用户微服务之间加个NGI...

周立_ITMuch
01/06
94
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周日乱弹 —— 别问,问就是没空

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @tom_tdhzz :#今日歌曲推荐# 分享容祖儿/彭羚的单曲《心淡》: 《心淡》- 容祖儿/彭羚 手机党少年们想听歌,请使劲儿戳(这里) @wqp0010 :周...

小小编辑
今天
283
6
golang微服务框架go-micro 入门笔记2.1 micro工具之micro api

micro api micro 功能非常强大,本文将详细阐述micro api 命令行的功能 重要的事情说3次 本文全部代码https://idea.techidea8.com/open/idea.shtml?id=6 本文全部代码https://idea.techidea8....

非正式解决方案
今天
5
0
Spring Context 你真的懂了吗

今天介绍一下大家常见的一个单词 context 应该怎么去理解,正确的理解它有助于我们学习 spring 以及计算机系统中的其他知识。 1. context 是什么 我们经常在编程中见到 context 这个单词,当...

Java知其所以然
昨天
5
0
Spring Boot + Mybatis-Plus 集成与使用(二)

前言: 本章节介绍MyBatis-Puls的CRUD使用。在开始之前,先简单讲解下上章节关于Spring Boot是如何自动配置MyBatis-Plus。 一、自动配置 当Spring Boot应用从主方法main()启动后,首先加载S...

伴学编程
昨天
8
0
用最通俗的方法讲spring [一] ──── AOP

@[TOC](用最通俗的方法讲spring [一] ──── AOP) 写这个系列的目的(可以跳过不看) 自己写这个系列的目的,是因为自己是个比较笨的人,我曾一度怀疑自己的智商不适合干编程这个行业.因为在我...

小贼贼子
昨天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部