SpringCloud Netflix复习之Ribbon

news2024/11/27 6:15:53

文章目录

    • 写作背景
    • SpringCloud Ribbon是什么,干了什么事情
    • Ribbon组件的核心功能
      • Ribbon内置了哪些负载均衡算法
    • 上手实战
      • 在SpringCloud里Ribbon实战
    • 从源码角度看下Ribbon实现原理
      • SpringCloud与Ribbon整合的原理
      • LoadBalancerInterceptor拦截器改变了RestTemplate什么行为
      • ZoneAwareLoadBalancer默认负载均衡选择server的源码
      • 轮询负载的算法源码
      • 服务名与URI替换并发起Http的源码
      • Ribbon从Eureka Client获取服务列表的源码
      • Ribbon定时更新ServerList源码
      • 自定义更改Ribbon负载规则的原理

写作背景

本文是接上一篇《SpringCloud Netflix复习之Eureka》来写的,有了Eureka之后,微服务之间调用可以通过Eureka Server拉取到服务注册表,就知道了要调用的下游服务的访问IP加端口等信息。但是呢,现在一般微服务都是多实例部署,一来防止单点问题,二来可以水平扩展提高并发能力。那么问题来了,服务A调用服务B,服务B是多实例部署的,我服务A怎么知道要调用服务B哪个实例呢?是随机还是轮询,还是有更智能一些的根据负载和机器配置来选择,Ribbon就是来干这件事的。
本文来复习SpringCloud Ribbon的相关知识,书写思路是以下几个方面

  1. SpringCloud Ribbon是什么,干了什么事情
  2. Ribbon组件的核心功能
  3. 上手实战
  4. 从源码角度来看下Ribbon的实现原理

SpringCloud Ribbon是什么,干了什么事情

Ribbon本身是Netflix公司(类似国内爱奇艺)研发的一个用于客户端负载均衡的组件,SpringCloud官方拿来封装了一下,成为SpringCloud Netflix生态的一员。刚刚说到了客户端的负载均衡,对应的还有个服务端负载均衡,常见的比如Nginx就是服务端负载均衡,相信你已经懂了一个是从客户端自己发起并执行的,一个是请求到达服务器之后再根据负载算法选择目标服务器。

Ribbon组件的核心功能

Ribbon提供了一系列完善的配置,比如超时配置,重试配置。通过ILoadBalancer获取所有服务实例列表ServerList,然后基于IRule实现的某种负载均衡算法,比如随机、轮询等选出一个服务实例,然后通过RestTemplate发起一个Rest请求。
可以说Ribbon这个中间件最核心的组件就是ILoadBalancer,然后服务实例列表的ServerList,以及用于负载算法IRule从ServerList中选出一个服务实例,还有一个用于ping每个服务实例判断其是否存活的IPing组件。

Ribbon内置了哪些负载均衡算法

Ribbon内置了许多负载均衡算法规则,这些规则的顶级接口是com.netflix.loadbalancer.IRule,可以通过实现IRule接口自定义负载均衡算法,但是一般用内置的负载均衡算法就够了。
在这里插入图片描述

RandomRule:随机找一个服务实例,这种基本不会用。

RoundRobinRule:轮询,从一堆server list中轮询选择出来一个server,每个server平摊到的这个请求,基本上是平均的,比如你100个请求,然后5个实例,基本每个实例会打到20个请求。有个限制默认超过10次获取的server都不可用,会返回空

AvailabilityFilteringRule:顾名思义,这个规则会考察服务器的可用性。如果3次连接失败,就会等待30秒后再次访问;如果某个服务器的连接数超限也就是并发请求太高了,那么会绕过去,不再访问

WeightedResponseTimeRule:带着权重的,每个服务器可以有权重,权重越高优先访问,如果某个服务器响应时间比较长,那么权重就会降低,减少访问

ZoneAvoidanceRule:根据区域来进行负载均衡,说白了就是优先在同一个机房内的节点轮询选取

BestAvailableRule:最小链接数策略,遍历ServerList从中选出一个可用且连接数最小的Server。

RetryRule:重试,默认继承RoundRobinRule就是通过轮询找到的服务器不可用或者请求失败,可以重新找一个服务器,而且没有RoundRoinRule超过10的限制,只要serverList不挂会不停选取判断。

其中ZoneAvoiddanceRule是默认策略,这个在后面源码中也有体现

上手实战

在SpringCloud里Ribbon实战

Springcloud项目中使用Ribbon只需要注入RestTemplate,然后加一个@LoadBalanced注解就可以了

/**
     * Ribbon负载均衡
     *
     * @return RestTemplate
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate;
    }

连依赖都不用加,因为Eureka客户端里已经有Ribbon的依赖,所以只要你项目里已经有了eureka客户端的依赖
在这里插入图片描述
RestTemplate本身只是一个Http组件,指定一个url发起一个Http请求,它是不具备负载均衡的功能的,但是这里加了@LoadBalanced注解之后,底层就会用Ribbon实现负载均衡。

我这边演示fc-service-portal(端口是8002)调用fc-service-screen服务,其中fc-service-screen启动两个实例,一个端口是8003,一个端口是8004。
下面是fc-service-portal服务里的Controller代码,通过RestTemplate调用fc-service-screen的/getPort接口,主要是查端口,根据端口号来区分调用的是哪个实例

@RestController
public class HelloWorldController {

    @Resource
    RestTemplate restTemplate;

    @GetMapping("/getPort")
    public int getPort() {
        return restTemplate.getForObject("http://fc-service-screen/getPort", Integer.class);
    }
}

fc-service-sceen8003和8004里Controller里的代码都是下面这样的

@RestController
public class HelloWordController {

    @Value("${server.port}")
    int port;

    @GetMapping("/getPort")
    public int getPort() {
        return port;
    }

}

启动所有服务后,看下Eureka的注册情况
在这里插入图片描述
可以看到fc-service-screen已经有两个实例了
我们请求http://localhost:8002/getPort 看返回结果情况
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看的出来是轮询的访问方式

从源码角度看下Ribbon实现原理

SpringCloud与Ribbon整合的原理

从@LoadBalanced注解入手


/**
 * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient.
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {

}

看注释的意思是@LoadBalanced注解的意思是将一个RestTemplate标记为底层采用LoadBalancerClient来执行实际的Http的请求,支持负载均衡。还是老套路找下XXAutoConfiguration类,找到了LoadBalancerAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

	@Autowired(required = false)
	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
			for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
			//定制restTemplate
				for (RestTemplateCustomizer customizer : customizers) {
					customizer.customize(restTemplate);
				}
			}
		});
	}

可以看到有个restTemplate的列表然后每个restTemplate都被定制了,我们找下RestTemplateCustomizer

@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
		final LoadBalancerInterceptor loadBalancerInterceptor) {
		return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				//往restTemplate里设置了拦截器LoadBalancerInterceptor
				restTemplate.setInterceptors(list);
		};
}
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
      LoadBalancerClient loadBalancerClient,
      LoadBalancerRequestFactory requestFactory) {
   return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}		

LoadBalancerInterceptor拦截器改变了RestTemplate什么行为

LoadBalancerInterceptor里有个intercept()方法

@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		//获取原始uri信息,参考下面截图
		final URI originalUri = request.getURI();
		//获取服务名就是fc-service-screen
		String serviceName = originalUri.getHost();
		Assert.state(serviceName != null,
				"Request URI does not contain a valid hostname: " + originalUri);
		//拦截请求的服务名,然后构建一个LoadBalancerRequest,然后将服务名和request一起作为参数调用
		//loadBalancer的execute方法,这个方法里会有负载均衡		
		return this.loadBalancer.execute(serviceName,
				this.requestFactory.createRequest(request, body, execution));
	}

originalUri是什么结构
在这里插入图片描述
我们源码跟进去看下

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
		//获取一个LoadBalancer	
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		//负载均衡选取一个server
		Server server = getServer(loadBalancer, hint);
		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);
	}

打个断点看下
在这里插入图片描述
可以看到在SpringCloud整合Ribbon的环境下,使用的默认的LoadBalancer其实就是ZoneAwareLoadBalancer,然后将服务名fc-service-screen根据聚在均衡算法选取一个server,这里面就有和Eureka整合的东西,
通过服务名 ==>ip+端口
将http://fc-service-screen/getPort ==>http://10.100.27.108:8003/getPort
10.100.27.108这个ip就是我本机的ip等同于localhost

ZoneAwareLoadBalancer默认负载均衡选择server的源码

@Override
    public Server chooseServer(Object key) {
        //如果只有一个机房进入这个流程,我本地演示会进入这里
        if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
            logger.debug("Zone aware logic disabled or there is only one zone");
            //调用父类的chooseServer
            return super.chooseServer(key);
        }
        Server server = null;
        try {
            LoadBalancerStats lbStats = getLoadBalancerStats();
            //负载策略是ZoneAvoidanceRule
            Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
            ...省略不用管的代码,多机房一般公司都用不到
    }

单机房逻辑会调用父类也就是BaseLoadBalancer的chooseServer()方法,我打断点进入发现rule其实就是ZoneAvoidanceRule,从源码这里也证明了ZoneAvoidanceRule是默认的负载均衡算法。
在这里插入图片描述
源码继续跟进去,会发现ZoneAvoidanceRule的choose()方法又会调用父类PredicateBasedRule的choose()方法
在这里插入图片描述
我们看下获取server的源码实现,AbstractServerPredicate#chooseRoundRobinAfterFiltering
在这里插入图片描述

轮询负载的算法源码

private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextIndex.get();
            int next = (current + 1) % modulo;
            if (nextIndex.compareAndSet(current, next) && current < modulo)
                return current;
        }
    }

这个算法很简单就是简单的轮询
nextIndex是一个AtomicInteger,一开始是0,也就是current是0,然后我们fc-service-screen是两个实例,modulo是2
那么next = (0+1) % 2 = 1,然后设置nextIndex=1 返回的是current=0就是取列表第一个是8003那个实例;
接着第二次请求过来,current就等于1,然后next = (1+1) % 2 = 0,然后设置nextIndex=0,返回current=1就是取列表第二个也就是8004那个实例

服务名与URI替换并发起Http的源码

RibbonLoadBalancerClient#execute

@Override
	public <T> T execute(String serviceId, ServiceInstance serviceInstance,
			LoadBalancerRequest<T> request) throws IOException {
		Server server = null;
		if (serviceInstance instanceof RibbonServer) {
		//这个server已经选好了,比如是10.100.27.108:8004
			server = ((RibbonServer) serviceInstance).getServer();
		}
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		//这个看名字是RibbonLoadBalancer的上下文信息,应该包括了Ribbon的重试配置信息
		RibbonLoadBalancerContext context = this.clientFactory
				.getLoadBalancerContext(serviceId);
		RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

		try {
		//看这里调用LoadBalancerRequest的apply()方法
			T returnVal = request.apply(serviceInstance);
			statsRecorder.recordStats(returnVal);
			return returnVal;
		}
		。。。
	}

LoadBalancerRequest是一个匿名内部类,就是在这里定义的

public LoadBalancerRequest<ClientHttpResponse> createRequest(
			final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) {
		return instance -> {
		//把请求信息封装为一个ServiceRequestWrapper
			HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,
					this.loadBalancer);
			if (this.transformers != null) {
				for (LoadBalancerRequestTransformer transformer : this.transformers) {
					serviceRequest = transformer.transformRequest(serviceRequest,
							instance);
				}
			}
			//交给ClientHttpRequestExecution去执行Http请求,这里面就是Spring-web包的事情了
			return execution.execute(serviceRequest, body);
		};
	}

ClientHttpRequestExecution肯定会从ServiceRequestWrapper获取请求的URI信息,我们打断点看下
在这里插入图片描述

Ribbon从Eureka Client获取服务列表的源码

Ribbon肯定会和Eureka整合,整合的类EurekaRibbonClientConfiguration,它里面有两个感兴趣的东西

//这个是Ribbon里面关于IPing的
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
	if (this.propertiesFactory.isSet(IPing.class, serviceId)) {
		return this.propertiesFactory.get(IPing.class, config, serviceId);
	}
	NIWSDiscoveryPing ping = new NIWSDiscoveryPing();
	ping.initWithNiwsConfig(config);
	return ping;
}
//这个定义了ServerList
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(IClientConfig config,
		Provider<EurekaClient> eurekaClientProvider) {
	if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
		return this.propertiesFactory.get(ServerList.class, config, serviceId);
	}
	DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
				config, eurekaClientProvider);
	DomainExtractingServerList serverList = new DomainExtractingServerList(
			discoveryServerList, config, this.approximateZoneFromHostname);
	return serverList;
}

在与Eureka整合提供的ServerList里有这个DiscoveryEnabledNIWSServerList类,它里面有个getUpdatedListOfServers()方法,会从Eureka Client获取服务列表

@Override
    public List<DiscoveryEnabledServer> getUpdatedListOfServers(){
    	//看这个方法,里面有一大坨eureka的东西
        return obtainServersViaDiscovery();
    }
    
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
        List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();

        if (eurekaClientProvider == null || eurekaClientProvider.get() == null) {
            logger.warn("EurekaClient has not been initialized yet, returning an empty list");
            return new ArrayList<DiscoveryEnabledServer>();
        }

        EurekaClient eurekaClient = eurekaClientProvider.get();
        if (vipAddresses!=null){
            for (String vipAddress : vipAddresses.split(",")) {
                // if targetRegion is null, it will be interpreted as the same region of client			//看这里
                List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
                for (InstanceInfo ii : listOfInstanceInfo) {
                    if (ii.getStatus().equals(InstanceStatus.UP)) {

                        if(shouldUseOverridePort){
                            if(logger.isDebugEnabled()){
                                logger.debug("Overriding port on client name: " + clientName + " to " + overridePort);
                            }

                            // copy is necessary since the InstanceInfo builder just uses the original reference,
                            // and we don't want to corrupt the global eureka copy of the object which may be
                            // used by other clients in our system
                            InstanceInfo copy = new InstanceInfo(ii);

                            if(isSecure){
                                ii = new InstanceInfo.Builder(copy).setSecurePort(overridePort).build();
                            }else{
                                ii = new InstanceInfo.Builder(copy).setPort(overridePort).build();
                            }
                        }

                        DiscoveryEnabledServer des = createServer(ii, isSecure, shouldUseIpAddr);
                        serverList.add(des);
                    }
                }
                if (serverList.size()>0 && prioritizeVipAddressBasedServers){
                    break; // if the current vipAddress has servers, we dont use subsequent vipAddress based servers
                }
            }
        }
        return serverList;
    }

我打断点看下
在这里插入图片描述

初次获取服务列表之后,我们知道Eureka Client默认每30s会从Eureka Server增量拉取注册表并更新本地注册表的,那么Ribbon初次拉取注册表后续如何更新呢?

Ribbon定时更新ServerList源码

大胆猜测一下,Eureka Client是默认每30s更新一次本次注册的表,如果是你来设计Ribbon,你多就更新一次,应该也是30s。Ribbon默认使用的ILoadBalancer是ZoneAwareLoadBalancer,它里面包含IRule、IPing和ServerList组件,我们看下ZoneAwareLoadBalancer的构造方法

public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
                                 IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
                                 ServerListUpdater serverListUpdater) {
        super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
}

调用的是父类DynamicServerListLoadBalancer的构造方法

public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
                                         ServerList<T> serverList, ServerListFilter<T> filter,
                                         ServerListUpdater serverListUpdater) {
        super(clientConfig, rule, ping);
        this.serverListImpl = serverList;
        this.filter = filter;
        //看这里
        this.serverListUpdater = serverListUpdater;
        if (filter instanceof AbstractServerListFilter) {
            ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
        }
        //这个方法里面就有调用从EurekaClient初次拉取注册表以及定时拉取注册表的逻辑
        restOfInit(clientConfig);
    }

serverListUpdater是个啥玩意,我们回到RibbonClientConfiguration类,它里面有注册一个ribbonServerListUpdater,你发现是PollingServerListUpdater

@Bean
@ConditionalOnMissingBean
public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
	return new PollingServerListUpdater(config);
}

打个断点验证一下
在这里插入图片描述

我们看下restOfInit()方法

void restOfInit(IClientConfig clientConfig) {
        boolean primeConnection = this.isEnablePrimingConnections();
        // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
        this.setEnablePrimingConnections(false);
        //初始化定时拉取任务
        enableAndInitLearnNewServersFeature();
		
        updateListOfServers();
        if (primeConnection && this.getPrimeConnections() != null) {
            this.getPrimeConnections()
                    .primeConnections(getReachableServers());
        }
        this.setEnablePrimingConnections(primeConnection);
        LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
    }

跟进去,看到serverListUpdater.start(updateAction),这个start一看就很重要

public void enableAndInitLearnNewServersFeature() {
        LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
        serverListUpdater.start(updateAction);
    }

看下PollingServerListUpdater类的start()方法

@Override
    public synchronized void start(final UpdateAction updateAction) {
        if (isActive.compareAndSet(false, true)) {
            final Runnable wrapperRunnable = new Runnable() {
                @Override
                public void run() {
                    if (!isActive.get()) {
                        if (scheduledFuture != null) {
                            scheduledFuture.cancel(true);
                        }
                        return;
                    }
                    try {
                    //这个doUpdate()就会调DynamicServerListLoadBalancer.this.updateListOfServers(),从Eureka Client拉取注册表
                        updateAction.doUpdate();
                        lastUpdated = System.currentTimeMillis();
                    } catch (Exception e) {
                        logger.warn("Failed one update cycle", e);
                    }
                }
            };
			//开启定时任务了	
            scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                    wrapperRunnable,
                    initialDelayMs,
                    refreshIntervalMs,
                    TimeUnit.MILLISECONDS
            );
        } else {
            logger.info("Already active, no-op");
        }
    }

initialDelayMs和refreshIntervalMs这两个参数没看到地方初始化,直接打断点然后重启fc-service-portal服务,结果发现断点没进来,发起一个请求后进来了,说明Ribbon默认是懒加载的
在这里插入图片描述
可以看到默认延迟1s,然后每30s更新一次注册表
最后再看一眼updateAction.doUpdate();其实它就是实际去拉取注册表的,上面有说到过

public void doUpdate() {
     DynamicServerListLoadBalancer.this.updateListOfServers();
}

自定义更改Ribbon负载规则的原理

我们只需要在配置类里注入IRule这个Bean就可以了,比如我做了如下设置,就是更改负载均衡策略为重试

@Bean
public IRule iRule() {
    //设置重试策略
    return new RetryRule();
}

我们分析一下原理,首先在RibbonClientConfiguration类中有注册IRule为ZoneAvoidanceRule,也就是默认的,它有一个@ConditionalOnMissingBean注解,也就是默认没有IRule的时候用这个

@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
	if (this.propertiesFactory.isSet(IRule.class, name)) {
		return this.propertiesFactory.get(IRule.class, config, name);
	}
	ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
	rule.initWithNiwsConfig(config);
	return rule;
}

再看ILoadBalancer

@Bean
	@ConditionalOnMissingBean
	public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
			ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
			IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
		if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
			return this.propertiesFactory.get(ILoadBalancer.class, config, name);
		}//rule默认就是ZoneAvoidanceRule
		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
				serverListFilter, serverListUpdater);
	}

所以我们配置自己的IRule后,就用我们这个,可以打断点证明一下

@Bean
public IRule iRule() {
    //设置重试策略
    return new RetryRule();
}

在ZoneAwareLoadBalancer父类构造方法里打了断点
在这里插入图片描述

源码可以跟进去看下,在父类BaseLoadBalancer里看看肯定有设置rule的地方

public BaseLoadBalancer(IClientConfig config, IRule rule, IPing ping) {
        initWithConfig(config, rule, ping, createLoadBalancerStatsFromConfig(config));
    }
	//看这个
    void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping) {
        initWithConfig(clientConfig, rule, ping, createLoadBalancerStatsFromConfig(config));
    }

void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
        ...省略
        setRule(rule);
        setPing(ping);

        setLoadBalancerStats(stats);
        rule.setLoadBalancer(this);
       
        。。。省略
        init();

    }    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/143257.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第三章:OAuth协议流程

应用场景 1、 原生app授权&#xff1a;app登录请求后台接口&#xff0c;为了安全认证&#xff0c;所有请求都带token信息&#xff0c;如果登录验证、 请求后台数据。 2、前后端分离单页面应用&#xff1a;前后端分离框架&#xff0c;前端请求后台数据&#xff0c;需要进行oauth…

Codeforces Round #842 (Div. 2) C. Elemental Decompress

翻译&#xff1a;您将得到一个由&#x1d45b;个整数组成的数组&#x1d44e;。找到两个排列组合&#x1d45d;长度和&#x1d45e;&#x1d45b;这样马克斯(&#x1d45d;&#x1d456;,&#x1d45e;&#x1d456;)&#x1d44e;&#x1d456;所有1≤&#x1d456;≤&#x1…

Spring Boot学习篇(七)

Spring Boot学习篇(七) 1.thymeleaf模板引擎使用篇(一) 1.1 准备工作 1.1.1 在pom.xml中导入所需要的依赖 a thymeleaf模板引擎所需要的依赖 <!--thymeleaf模板引擎,前后端不分离的时候用 普通的html格式--> <dependency><groupId>org.springframework.…

计算机网络实验---Wireshark 实验

数据链路层 实作一/熟悉 Ethernet 帧结构 实作二/了解子网内/外通信时的 MAC 地址 实作三/掌握 ARP 解析过程 网络层 实作一 /熟悉 IP 包结构 实作二 IP 包的分段与重组 实作三 考察 TTL 事件 传输层 实作一 熟悉 TCP 和 UDP 段结构 实作二 分析 TCP 建立和释放连接 应用层 …

数据结构基本介绍

1.what is Data Structure? A data structure is a memory used to store and organize dataIt is also used for processing, retrieving, and storing data 2. what is classification of Data Structure? 2.1 what is Linear data structure? Data structure in which…

你还不懂递归么?不允许你不懂,进来折腾下自己吧

举例数组 const arr [ {id: "1175310929766055936", pid: "", name: "总裁办" },---返回空数组, {id: "1175311213774962688", pid: "", name: "行政部" },---返回空数组, {id: "1175311267684352000&qu…

随想录一刷Day27——回溯算法

文章目录Day27_回溯算法13. 子集 II14. 491.递增子序列15. 全排列全排列 IIDay27_回溯算法 13. 子集 II 90. 子集 II class Solution { private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& nums, int startI…

[SwiftUI 开发] Widget 小组件

小组件简述 小组件可以在主屏幕上实现内容展示和功能跳转。 系统会向小组件获取时间线&#xff0c;根据当前时间对时间线上的数据进行展示。点击正在展示的视觉元素可以跳转到APP内&#xff0c;实现对应的功能。 小组件是一个独立于 App 环境(即 App Extension)&#xff0c;小…

谷粒学院——第二十一章、spring security详细

一、Spring Security介绍 1、Spring Security简介 Spring 是非常流行和成功的 Java 应用开发框架&#xff0c;Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架&#xff0c;提供了一套 Web 应用安全性的完整解决方案。 正如你可能知道的关于安全…

年度征文|2022年「博客之星」,花落谁家? 大家来竞猜吧

一年一度的「博客之星」大赛如火如荼地进行着&#xff0c;大家都忙着评分、发帖、回帖.....今天发现我在分组的排名只有40多名&#xff0c;基本上算是放弃了。但是&#xff0c;看到本文的朋友&#xff0c;还是可以帮我拉拉票的&#xff01;请点开链接给个五星评分&#xff1a; …

CentOS服务器署Springboot的java项目最简单操作步骤

CentOS服务器署Springboot的java项目最简单操作步骤 准备工作 1.首先本地有一个能跑起来正常的 java 项目的 jar 包; 2.有一个前端项目, 可以仅是一个 index.html 文件; 3.需要备案好的域名 (可选, 否则只能 ip 访问) 4.购买阿里云或者腾讯云等等任意 CentOS 服务器一个 1. 获…

(机器学习深度学习常用库、框架|Pytorch篇)第(待定)节:卷积神经网络CNN中一些经典网络结构写法

文章目录一&#xff1a;LeNet-5二&#xff1a;AlexNet三&#xff1a;VGG四&#xff1a;ResNet五&#xff1a;MobileNetV1六&#xff1a;InceptionNet一&#xff1a;LeNet-5 LeNet-5&#xff1a;LeNet-5是一个较简单的卷积神经网络。下图显示了其结构&#xff1a;输入的二维图像…

ansible通过多种方法配置yum源仓库

目录 1.挂载本地光盘到/mnt ​2.配置yum源仓库文件通过多种方式实现 仓库1 &#xff1a;Name: RH294_BaseDescription&#xff1a; RH294 base softwareBase urt: file:///mnt/BaseOS不需要验证钦件包 GPG 签名启用此软件仓库 ​编辑仓库 2:Name: RH294_StreamDescription &…

第四十二篇 nextTick

在前面封装swiper组件当中&#xff0c;通过许多种方式方法&#xff0c;其一从mounted初始化过早转到updated后出现初始化重复&#xff0c;再者通过设置key值和使用v-if控制swiper组件&#xff0c;然后通过Vue.diretive自定义指令的方式来封装swiper组件&#xff0c;那么本篇的n…

排序算法之快速排序

目录 排序算法介绍 快速排序 算法流程 算法实现 python C 快排为什么快 算法优化 基准数优化 python C 尾递归优化 python C 排序算法介绍 《Hello算法》是GitHub上一个开源书籍&#xff0c;对新手友好&#xff0c;有大量的动态图&#xff0c;很适合算法初学者自…

Struts2框架标签

Struts2框架标签1、前言2、UI标签2.1、表单标签2.1、非表单标签3、通用标签4、例子4.1、实体类User4.2、控制器UserAction4.3、配置文件struts.xml4.4、页面users.jsp4.5、测试1、前言 Struts2有丰富的tag标签可以使用&#xff0c;即Struts2的标签库&#xff0c;如果能够灵活运…

彻底理解动态规划:编辑距离

本篇的题目非常经典&#xff0c;几乎是面试必备&#xff0c;即&#xff0c;编辑距离问题&#xff0c;edit distance&#xff1b; 给定两个字符串word1以及word2&#xff0c;返回将word1转为word2需要的最少步骤&#xff0c;在每一步中你可以针对字符串word1进行以下操作&#…

技术开发115

技术开发115 业务内容&#xff1a; 拖车用辅助脚、拖车用零件类、特殊车辆用车轴Sub。Assy产品、面向汽车产业的生产设备、面向建设机械的零部件类、面向汽车产业的检查夹具 公司简介&#xff1a; 董事长&#xff1a;佐藤安弘 资本金&#xff1a;4500万日元 员工数&#x…

shell第五天练习

题目&#xff1a; 1、编写函数&#xff0c;实现打印绿色OK和红色FAILED&#xff0c;判断是否有参数&#xff0c;存在为Ok&#xff0c;不存在为FAILED 2、编写函数&#xff0c;实现判断是否无位置参数&#xff0c;如无参数&#xff0c;提示错误 3、编写函数实现两个数字做为参数…

聊聊前端安全之CSRF

本文作者为奇舞团前端开发工程师一、什么是CSRFCSRF&#xff08;Cross Site Request Forgery&#xff0c;跨站域请求伪造&#xff09;&#xff0c;通常缩写为 CSRF。CSRF攻击是攻击者通过伪装成受信任用户向服务器发起各种请求&#xff0c;达到欺骗服务器接收并执行指令&#x…