【微服务】SpringCloud中OpenFeign请求处理及负载均衡流程

news/2024/4/19 17:25:37/文章来源:https://blog.csdn.net/qq_57756904/article/details/128167388

💖 Spring家族及微服务系列文章 

✨【微服务】SpringCloud中Ribbon的WeightedResponseTimeRule策略

✨【微服务】SpringCloud中Ribbon的轮询(RoundRobinRule)与重试(RetryRule)策略

✨【微服务】SpringCloud中Ribbon集成Eureka实现负载均衡

✨【微服务】SpringCloud轮询拉取注册表及服务发现源码解析

✨【微服务】SpringCloud微服务续约源码解析

✨【微服务】SpringCloud微服务注册源码解析

✨【微服务】Nacos2.x服务发现?RPC调用?重试机制?

✨【微服务】Nacos通知客户端服务变更以及重试机制

✨【微服务】Nacos服务发现源码分析

✨【微服务】SpringBoot监听器机制以及在Nacos中的应用

✨【微服务】Nacos服务端完成微服务注册以及健康检查流程

✨【微服务】Nacos客户端微服务注册原理流程

✨【微服务】SpringCloud中使用Ribbon实现负载均衡的原理

✨【微服务】SpringBoot启动流程注册FeignClient

✨【微服务】SpringBoot启动流程初始化OpenFeign的入口

✨Spring Bean的生命周期

✨Spring事务原理

✨SpringBoot自动装配原理机制及过程

✨SpringBoot获取处理器流程

✨SpringBoot中处理器映射关系注册流程

✨Spring5.x中Bean初始化流程

✨Spring中Bean定义的注册流程

✨Spring的处理器映射器与适配器的架构设计

✨SpringMVC执行流程图解及源码

目录

💖 Spring家族及微服务系列文章 

一、前言

二、OpenFeign请求处理流程

1、入口

1.1、异常后决定是否重试

2、创建rest请求模板

2.1、SpringMvcContract转换处理

2.2、使用所提供的变量值替换解析所有表达式

3、执行请求并将返回结果解码

4、处理拦截器的apply()方法

5、LoadBalancerFeignClient负载均衡处理feign请求

5.1、根据服务名clientName获取FeignLoadBalancer 

5.2、feign如何实现负载均衡


一、前言

    前面的文章已经分析了Ribbon实现负载均衡的原理以及一些负载均衡策略,里面涉及到一些算法以及设计思想还是值得我们去汲取其中的精华的。在面试的情况下,面试官也喜欢面试这些问题,所以有必要对其中的原理有所深入理解。本篇文章我们来探究一下,OpenFeign是如何实现负载均衡的?重试机制是怎么实现的、线程睡眠?如何创建Rest请求模板,使用所提供的变量值替换解析所有表达式?拦截器?LoadBalancerFeignClient负载均衡处理feign请求,底层实现是什么、如何负载均衡选取服务、最终的请求URI是怎样的、负载平衡器的作用、记录响应统计信息(以便计算权重)?带着这些问题,我们往下读,找出我们的答案吧:

二、OpenFeign请求处理流程

在讲解下面的流程前先说明下,笔者在SpringCloud Alibaba项目中整合了sentinel了,所以debugger进来时会首先进到的是Sentinel的类进行处理,然后才进到下面流程的,后面时间允许得话就补充一篇文章详细梳理其中的细节。

1、入口

  @Overridepublic Object invoke(Object[] argv) throws Throwable {// 创建请求模板RequestTemplate template = buildTemplateFromArgs.create(argv);// 将参数封装为OptionsOptions options = findOptions(argv);Retryer retryer = this.retryer.clone();// 如果有必要循环重试while (true) {try {// 执行并将返回结果解码return executeAndDecode(template, options);} catch (RetryableException e) {try {// 重试retryer.continueOrPropagate(e);} catch (RetryableException th) {// 重试过程中抛异常,则抛出异常结束请求Throwable cause = th.getCause();if (propagationPolicy == UNWRAP && cause != null) {throw cause;} else {throw th;}}if (logLevel != Logger.Level.NONE) {logger.logRetry(metadata.configKey(), logLevel);}// 继续请求continue;}}}

主要逻辑:

  1. 创建请求模板,下面2大点分析
  2. 将参数封装为Options,克隆Retryer
  3. 如果有必要循环以重试:1)执行并将返回结果解码;2)如果执行过程中抛出重试异常,则走第一个catch逻辑;3)决定是否重试,下面分析;决定是否重试过程中,抛出了异常即不再重试,异常外抛结束该调用;否则continue继续

1.1、异常后决定是否重试

    public void continueOrPropagate(RetryableException e) {// 重试次数大于maxAttempts(默认5次)抛异常结束请求if (attempt++ >= maxAttempts) {throw e;}// 睡眠时长long interval;if (e.retryAfter() != null) {interval = e.retryAfter().getTime() - currentTimeMillis();if (interval > maxPeriod) {// 不能大于最大期限interval = maxPeriod;}if (interval < 0) {return;}} else {interval = nextMaxInterval();}try {// 让当前线程睡眠一下Thread.sleep(interval);} catch (InterruptedException ignored) {// 发生中断异常,中断当前线程Thread.currentThread().interrupt();// 抛异常结束请求throw e;}sleptForMillis += interval;}

主要逻辑:

  1. 如果重试次数大于maxAttempts(默认5次)抛异常结束请求
  2. 计算睡眠时长,interval < 0立即请求
  3. 否则让当前线程睡眠一下,发生中断异常,中断当前线程,异常结束请求

2、创建rest请求模板

    @Overridepublic RequestTemplate create(Object[] argv) {// 获取rest请求模板RequestTemplate mutable = RequestTemplate.from(metadata.template());mutable.feignTarget(target);if (metadata.urlIndex() != null) {int urlIndex = metadata.urlIndex();checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);mutable.target(String.valueOf(argv[urlIndex]));}Map<String, Object> varBuilder = new LinkedHashMap<>();// indexToName为Map<Integer, Collection<String>>类型// 遍历indexToNamefor (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {// 如:0int i = entry.getKey();// 参数值,如:CeaMObject value = argv[entry.getKey()];if (value != null) { // Null values are skipped.跳过空值。// 如果indexToExpander包含该keyif (indexToExpander.containsKey(i)) {// SpringMvcContract会对value进行转换处理value = expandElements(indexToExpander.get(i), value);}// 参数名-参数值for (String name : entry.getValue()) {varBuilder.put(name, value);}}}// 将参数填充到rest模板RequestTemplate template = resolve(argv, mutable, varBuilder);if (metadata.queryMapIndex() != null) {// add query map parameters after initial resolve so that they take// precedence over any predefined values// 在初始解析之后添加查询映射参数,使它们优先于任何预定义的值Object value = argv[metadata.queryMapIndex()];Map<String, Object> queryMap = toQueryMap(value);template = addQueryMapQueryParameters(queryMap, template);}if (metadata.headerMapIndex() != null) {// add header map parameters for a resolution of the user pojo object// 为用户 pojo 对象的解析添加头映射参数Object value = argv[metadata.headerMapIndex()];Map<String, Object> headerMap = toQueryMap(value);template = addHeaderMapHeaders(headerMap, template);}// 经过上面处理,返回完整的请求模板return template;}

                                图2-1

                                 图2-2

主要逻辑:

  1. 初始化rest请求模板,初始化数据如图2-1
  2. 遍历indexToName,为Map<Integer, Collection<String>>类型:获取key以及参数值,如果参数值不为空、indexToExpander包含该key,则SpringMvcContract会对value进行转换处理;遍历参数名,put到varBuilder以便进一步解析成请求模板
  3. 将参数填充到rest模板,解析成相当完整的请求模板,下面分析

                    图2-3 ndexToName数据样例

2.1、SpringMvcContract转换处理

	private static class ConvertingExpanderFactory {private final ConversionService conversionService;ConvertingExpanderFactory(ConversionService conversionService) {this.conversionService = conversionService;}Param.Expander getExpander(TypeDescriptor typeDescriptor) {// 转化return value -> {Object converted = conversionService.convert(value, typeDescriptor,STRING_TYPE_DESCRIPTOR);return (String) converted;};}}

Feign不具备转换SpringMVC功能,于是OpenFeign提供了SpringMvcContract,底层委托Spring去做转换处理(提供各种类型转换器)。

2.2、使用所提供的变量值替换解析所有表达式

  public RequestTemplate resolve(Map<String, ?> variables) {StringBuilder uri = new StringBuilder();/* create a new template form this one, but explicitly* 创建一个新的模板这一个,但显式*/RequestTemplate resolved = RequestTemplate.from(this);// uriTemplate模板空则新建if (this.uriTemplate == null) {/* create a new uri template using the default root */this.uriTemplate = UriTemplate.create("", !this.decodeSlash, this.charset);}// 解析,不为空追加到uriString expanded = this.uriTemplate.expand(variables);if (expanded != null) {uri.append(expanded);}/** for simplicity, combine the queries into the uri and use the resulting uri to seed the* resolved template.* 为了简单起见,将查询组合到 uri 中,并使用生成的 uri 为解析的模板播种。*/if (!this.queries.isEmpty()) {/** since we only want to keep resolved query values, reset any queries on the resolved copy* 因为我们只想保留已解析的查询值,所以请重置已解析副本上的任何查询*/resolved.queries(Collections.emptyMap());StringBuilder query = new StringBuilder();Iterator<QueryTemplate> queryTemplates = this.queries.values().iterator();while (queryTemplates.hasNext()) {QueryTemplate queryTemplate = queryTemplates.next();String queryExpanded = queryTemplate.expand(variables);if (Util.isNotBlank(queryExpanded)) {query.append(queryExpanded);if (queryTemplates.hasNext()) {query.append("&");}}}String queryString = query.toString();if (!queryString.isEmpty()) {Matcher queryMatcher = QUERY_STRING_PATTERN.matcher(uri);if (queryMatcher.find()) {/* the uri already has a query, so any additional queries should be appended*  Uri 已经有了一个查询,所以任何附加的查询都应该被追加*/uri.append("&");} else {uri.append("?");}uri.append(queryString);}}/* add the uri to result将 uri 添加到 result 中 */resolved.uri(uri.toString());/* headers */if (!this.headers.isEmpty()) {/** same as the query string, we only want to keep resolved values, so clear the header map on* the resolved instance*/resolved.headers(Collections.emptyMap());for (HeaderTemplate headerTemplate : this.headers.values()) {/* resolve the header */String header = headerTemplate.expand(variables);if (!header.isEmpty()) {/* append the header as a new literal as the value has already been expanded.* 作为新的文字追加标题,因为值已经展开。 */resolved.appendHeader(headerTemplate.getName(), Collections.singletonList(header), true);}}}if (this.bodyTemplate != null) {resolved.body(this.bodyTemplate.expand(variables));}/* mark the new template resolved标记已解析的新模板 */resolved.resolved = true;return resolved;}

                               图2-5

该方法主要逻辑就是使用所提供的变量值替换解析所有表达式:如图2-5中初始uriTemplate为“/admin/feign/feign”,变量值variables:{"str": "CeaM"}。经过解析追加后,得到的uri为“/admin/feign/feign/?str=CeaM”,如果有多个查询参数会通过&符号拼接,平时用postman或者谷歌浏览器按F12发送请求得话我们都比较熟悉了吧。

           图2-6

3、执行请求并将返回结果解码

    Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {Request request = this.targetRequest(template);if (this.logLevel != Level.NONE) {this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);}long start = System.nanoTime();Response response;try {response = this.client.execute(request, options);response = response.toBuilder().request(request).requestTemplate(template).build();} catch (IOException var13) {if (this.logLevel != Level.NONE) {this.logger.logIOException(this.metadata.configKey(), this.logLevel, var13, this.elapsedTime(start));}throw FeignException.errorExecuting(request, var13);}long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);if (this.decoder != null) {return this.decoder.decode(response, this.metadata.returnType());} else {CompletableFuture<Object> resultFuture = new CompletableFuture();this.asyncResponseHandler.handleResponse(resultFuture, this.metadata.configKey(), response, this.metadata.returnType(), elapsedTime);try {if (!resultFuture.isDone()) {throw new IllegalStateException("Response handling not done");} else {return resultFuture.join();}} catch (CompletionException var12) {Throwable cause = var12.getCause();if (cause != null) {throw cause;} else {throw var12;}}}}

主要逻辑:

  1. 处理拦截器的apply()方法,下面分析
  2. 通过LoadBalancerFeignClient负载均衡处理feign请求,下面详细分析
  3. 将返回结果处理,如果指定解码器则调用解码器解码处理;否则异步处理返回结果

4、处理拦截器的apply()方法

  Request targetRequest(RequestTemplate template) {for (RequestInterceptor interceptor : requestInterceptors) {interceptor.apply(template);}return target.apply(template);}

遍历拦截器,然后调用拦截器的apply方法,主要是对RequestTemplate进行处理。注意拦截器是Feign的,我们也可以自己定义,在日常开发中也是会用到的。然后根据模板template调用target.apply()生成请求对象request。

5、LoadBalancerFeignClient负载均衡处理feign请求

顾名思义它使FeignClient具备负载均衡功能,它是OpenFeign项目里面的一个重要组件。

	@Overridepublic Response execute(Request request, Request.Options options) throws IOException {try {// 将url封装为URI以便获取服务名,http://ceam-admin/admin/feign/feign?str=CeaMURI asUri = URI.create(request.url());// 从URI获取服务名,如:ceam-adminString clientName = asUri.getHost();// 移除服务名,如:http:///admin/feign/feign?str=CeaMURI uriWithoutHost = cleanUrl(request.url(), clientName);// 将请求数据封装为ribbonRequestFeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost);// 根据options以及服务名获取请求配置,没有指定则使用默认的IClientConfig requestConfig = getClientConfig(options, clientName);// 根据服务名从缓存获取负载均衡器,没有则新建// 负载均衡执行return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();}catch (ClientException e) {IOException io = findIOException(e);if (io != null) {throw io;}throw new RuntimeException(e);}}

主要逻辑:

  1. 将url封装为URI以便获取服务名,http://ceam-admin/admin/feign/feign?str=CeaM
  2. 从URI获取服务名,如:ceam-admin
  3. 移除服务名,如:http:///admin/feign/feign?str=CeaM
  4. 将请求数据封装为ribbonRequest,RibbonRequest是FeignLoadBalancer的静态内部类,实现了ClientRequest接口。
  5. 根据options以及服务名,如果是DEFAULT_OPTIONS则从SpringClientFactory(SpringCloud上下文)获取请求配置IClientConfig;否则封装为FeignOptionsClientConfig来得到IClientConfig。
  6. 根据服务名,从CachingSpringLoadBalancerFactory获取FeignLoadBalancer

5.1、根据服务名clientName获取FeignLoadBalancer 

	public FeignLoadBalancer create(String clientName) {// 首先根据服务名从缓存获取,如果有直接返回;否则新建缓存起来FeignLoadBalancer client = this.cache.get(clientName);if (client != null) {// 缓存有直接返回return client;}// 根据服务名从SpringClientFactory获取配置IClientConfig config = this.factory.getClientConfig(clientName);// 根据服务名从SpringClientFactory获取负载均衡器ILoadBalancer lb = this.factory.getLoadBalancer(clientName);// 根据服务名从SpringClientFactory服务器内部检查器ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,ServerIntrospector.class);// 封装为FeignLoadBalancerclient = this.loadBalancedRetryFactory != null? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,this.loadBalancedRetryFactory): new FeignLoadBalancer(lb, config, serverIntrospector);// 缓存起来提高系统性能,不需要多次创建造成资源浪费this.cache.put(clientName, client);return client;}

                    图5-1 

主要逻辑:

  1. 首先根据服务名从缓存获取,如果有直接返回;否则新建缓存起来,第一次的话新建
  2. 根据服务名从SpringClientFactory获取配置IClientConfig、获取负载均衡器ILoadBalancer、获取服务器内部检查器ServerIntrospector。由feign执行流程到图5-1可见,IClientConfig为DefaultClientConfigImpl、ILoadBalancerZoneAwareLoadBalancer、ServerIntrospector为NacosServerIntrospector。同时你打开config中可以看到一些配置,默认的连接超时时间connectTimeout1秒、执行操作超时时间readTimeout也为1秒
  3. 将配置IClientConfig、负载均衡器ILoadBalancer、服务器内部检查器ServerIntrospector封装到FeignLoadBalancer
  4. 缓存起来提高系统性能,不需要多次创建造成资源浪费

5.2、feign如何实现负载均衡

feign并没有自己实现负载均衡功能,从上面也看到了FeignLoadBalancer封装了ILoadBalancer,可见它是委托ILoadBalancer实现负载均衡的。从前面的文章我们也已经介绍了Ribbon实现负载均衡的原理,而ILoadBalancer是Ribbon项目里面的一个核心组件接口,所以我们还是会用到之前的一些原理的。而在讲解之前我们先看看FeignLoadBalancer的父子关系图:

从关系图可见只有 FeignLoadBalancer是OpenFeign项目里面的,其它的都是Ribbon项目的。我们点击一下executeWithLoadBalancer()方法,来到了FeignLoadBalancer的抽象父类AbstractLoadBalancerAwareClient。

    public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);try {// ServerOperation在这里创建了匿名内部类,那么call()方法是由匿名内部类调用的return command.submit(new ServerOperation<T>() {@Overridepublic Observable<T> call(Server server) {// 拼接最终的URIURI finalUri = reconstructURIWithServer(server, request.getUri());// 替换为最终的URIS requestForServer = (S) request.replaceUri(finalUri);try {// 处理请求return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));} catch (Exception e) {return Observable.error(e);}}}).toBlocking().single();} catch (Exception e) {Throwable t = e.getCause();if (t instanceof ClientException) {throw (ClientException) t;} else {throw new ClientException(e);}}}

主要逻辑:

  1. 将请求数据以及配置数据封装到LoadBalancerCommand中,LoadBalancerCommand一个命令,用于从负载平衡器执行中生成 Observer。负载平衡器负责以下工作:1)选择一个服务器;2)调用 Server onSubscribe 调用方法;3)如果有,调用ExecutionListener;4)异常时重试,由 RetryHandler 控制;5)向 LoadBalancerStats 提供反馈,即记录请求响应的结果,以便计算权重等等,在上一篇分析权重计算时就用到了其中的统计数据。这些功能在5.3体现。
  2. command.submit()下面分析。其中的参数为ServerOperation类型,ServerOperation在这里创建了匿名内部类,那么call()方法是由匿名内部类调用的。call()方法逻辑就是根据负载均衡选取的服务以及前面移除服务名的uri拼接成最终的请求URI,然后替换为最终请求URI,接着就是真正执行请求的逻辑了。
  3. command.submit()后就是command.toBlocking() .single(),即阻塞同步执行,等待返回结果。

5.3、负载均衡调用

public Observable<T> submit(final ServerOperation<T> operation) {final ExecutionInfoContext context = new ExecutionInfoContext();if (listenerInvoker != null) {try {listenerInvoker.onExecutionStart();} catch (AbortExecutionException e) {return Observable.error(e);}}// 在同一个服务器上最大重试次数final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();// 在下一个服务器上最大重试次数final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();// Use the load balancer使用负载平衡器Observable<T> o = (server == null ? selectServer() : Observable.just(server)).concatMap(new Func1<Server, Observable<T>>() {@Override// Called for each server being selected为选定的每个服务器调用public Observable<T> call(Server server) {context.setServer(server);final ServerStats stats = loadBalancerContext.getServerStats(server);// Called for each attempt and retry调用每次尝试和重试Observable<T> o = Observable.just(server).concatMap(new Func1<Server, Observable<T>>() {@Overridepublic Observable<T> call(final Server server) {context.incAttemptCount();loadBalancerContext.noteOpenConnection(stats);if (listenerInvoker != null) {try {listenerInvoker.onStartWithServer(context.toExecutionInfo());} catch (AbortExecutionException e) {return Observable.error(e);}}final Stopwatch tracer = loadBalancerContext.getExecuteTracer().start();// ServerOperation匿名内部类调用callreturn operation.call(server).doOnEach(new Observer<T>() {private T entity;@Overridepublic void onCompleted() {recordStats(tracer, stats, entity, null);// TODO: What to do if onNext or onError are never called?}@Overridepublic void onError(Throwable e) {recordStats(tracer, stats, null, e);logger.debug("Got error {} when executed on server {}", e, server);if (listenerInvoker != null) {listenerInvoker.onExceptionWithServer(e, context.toExecutionInfo());}}@Overridepublic void onNext(T entity) {this.entity = entity;if (listenerInvoker != null) {listenerInvoker.onExecutionSuccess(entity, context.toExecutionInfo());}}                            private void recordStats(Stopwatch tracer, ServerStats stats, Object entity, Throwable exception) {tracer.stop();// 记录请求完成的信息,用于统计,如:权重等loadBalancerContext.noteRequestCompletion(stats, entity, exception,tracer.getDuration(TimeUnit.MILLISECONDS), retryHandler);}});}});// 重试if (maxRetrysSame > 0) // retryPolicy()中返回Func2匿名内部类,// 该类的call方法会根据最大重试次数判断是否重试o = o.retry(retryPolicy(maxRetrysSame, true));return o;}});if (maxRetrysNext > 0 && server == null) o = o.retry(retryPolicy(maxRetrysNext, false));return o.onErrorResumeNext(new Func1<Throwable, Observable<T>>() {@Overridepublic Observable<T> call(Throwable e) {if (context.getAttemptCount() > 0) {if (maxRetrysNext > 0 && context.getServerAttemptCount() == (maxRetrysNext + 1)) {e = new ClientException(ClientException.ErrorType.NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED,"Number of retries on next server exceeded max " + maxRetrysNext+ " retries, while making a call for: " + context.getServer(), e);}else if (maxRetrysSame > 0 && context.getAttemptCount() == (maxRetrysSame + 1)) {e = new ClientException(ClientException.ErrorType.NUMBEROF_RETRIES_EXEEDED,"Number of retries exceeded max " + maxRetrysSame+ " retries, while making a call for: " + context.getServer(), e);}}if (listenerInvoker != null) {listenerInvoker.onExecutionFailed(e, context.toFinalExecutionInfo());}return Observable.error(e);}});}

主要逻辑:

  1. 如果监听器调用不为空,则执行监听器调用
  2. 使用负载均衡器。在使用前,第一次server为空,从selectServer()负载均衡出一个服务,下面分析。Observable的concatMap()方法接收Func1类型参数,该参数是Func1的匿名内部类引用,里面涉及一个call()调用:1)如果有监听器则调用监听器逻辑;2)ServerOperation匿名内部类调用call(),即上面5.2说的call();3)不管响应正常还是出错,都记录响应结果以便在计算权重等情况下需用到这部分数据;4)retryPolicy()中返回Func2匿名内部类,该类的call方法会根据最大重试次数判断是否重试

5.4、负载均衡选取一个服务器

    private Observable<Server> selectServer() {return Observable.create(new OnSubscribe<Server>() {@Overridepublic void call(Subscriber<? super Server> next) {try {Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);   next.onNext(server);next.onCompleted();} catch (Exception e) {next.onError(e);}}});}

   selectServer()会负载均衡选取一个服务器,是由LoadBalancerContext#getServerFromLoadBalancer()方法获取到ILoadBalancer。而ILoadBalancer根据上图以及上面的流程也可知道是Ribbon的ZoneAwareLoadBalancer。那么ILoadBalancer#chooseServer(),实际上就是ZoneAwareLoadBalancer#chooseServer()方法调用,根据前面文章Ribbon实现负载均衡原理,我们就理解其中的意思了。

    再根据前面移除服务名的uri:http:///admin/feign/feign?str=CeaM以及负载均衡选取的服务server:192.168.88.1:9705,经过ServerOperation匿名内部类调用call()过程中,拼接最终的请求URI为:http://192.168.88.1:9705/admin/feign/feign?str=CeaM,最后根据它以及请求配置(如上面说的:连接超时时间1秒、处理业务超时时间1秒)执行execute完成请求,底层用的是自己封装的http请求工具。

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

EMQ 宣布推出 LF Edge eKuiper 全新 Logo 标识

全球领先的开源物联网数据基础设施软件供应商 EMQ 映云科技宣布,即日起,将正式启用全新的 LF Edge eKuiper(以下简称 eKuiper)产品 Logo。焕然一新的 eKuiper 产品 Logo 标志着 EMQ 在不断提升品牌全球化战略之上,对于打造高性能、高可用、高安全的世界级开源软件产品的极致追求…

【1805. 字符串中不同整数的数目】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个字符串 word &#xff0c;该字符串由数字和小写英文字母组成。 请你用空格替换每个不是数字的字符。例如&#xff0c;"a123bc34d8ef34" 将会变成 " 123 34 8 34" 。注意…

面试题: LEAD 和 LAG 求每个用户的页面停留时长

我们先来看看这两个函数的语法&#xff1a; LEAD(col,n,default) OVER() 说明&#xff1a; 用于统计窗口内向下第n行的值参数1&#xff1a; 为要取值的列名参数2&#xff1a; 为向下第n行&#xff0c;默认值为1&#xff0c;这个值是固定的&#xff0c;不能动态的变化参数3&am…

深度学习-全卷积神经网络(FCN)

1. 简介 全卷积神经网络&#xff08;Fully Convolutional Networks&#xff0c;FCN&#xff09;是Jonathan Long等人于2015年在Fully Convolutional Networks for Semantic Segmentation一文中提出的用于图像语义分割的一种框架&#xff0c;是深度学习用于语义分割领域的开山之…

国际学校妈妈哭诉IB太难:中国孩子都不知道怎么答题?

听别人说考国际学校的IB体系相对简单直到我看到IB试题才知道其实IB一点都不容易特别对于中国学生有大量写论文的部分来看看IB的真题有些学生真的很难下笔不知道怎么答题啊&#xff01;商科生物 题目都是非常考验综合能力的&#xff0c;学生需要掌握很多知识点并融会贯通&#x…

cengbox2靶机(hydra爆破、公私钥免密登录)

环境准备 靶机链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;zdpr 虚拟机网络链接模式&#xff1a;桥接模式 攻击机系统&#xff1a;kali linux 2021.1 信息收集 1.arp-scan -l探测目标靶机ip 2.nmap -p- -A -T4 192.168.1.107 探测目标靶机开放端口和服务 …

Locust学习记录5-任务属性【Task】

Task 当负载测试开始时&#xff0c;将为每个模拟用户创建一个User类的实例&#xff0c;他们将开始在自己的绿色线程中运行。当这些用户运行时&#xff0c;他们选择他们执行的任务&#xff0c;休眠一段时间&#xff0c;然后选择一个新任务。 这些任务时普通的python可调用文件…

服务访问质量(QoS)介绍与技术 一

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。个人爱好: 编程&#xff0c;打篮球&#xff0c;计算机知识个人名言&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石…

K8S Pod控制器详细讲解

文章目录一、Pod控制器介绍二、ReplicaSet(RS)三、Deployment(Deploy)1.镜像更新&#xff1a;2.版本回退3.金丝雀发布/灰度发布四、Horizontal Pod Autoscaler(HPA)五、DaemonSet(DS)六、Job七、CronJob(CJ)结尾一、Pod控制器介绍 Pod是kubernetes的最小管理单元&#xff0c;在…

使用JAR签名进行代码签名

JavaArchive(JAR)包格式可用于打包Java应用程序和库。 签名的JAR文件可以选择包含来自TSA时间戳响应&#xff0c;使用RFC#3161格式。 添加JAR签名者添加JAR签名者的链接&#xff0c;SignServer中的JAR签名器称为JArchiveSigner。 要配置JArchiveSigner&#xff0c;请按照以下…

用代码画两棵圣诞树送给你【附详细代码】

大家好&#xff0c;我是宁一 代码的魔力之处在于&#xff0c;可以帮我们实现许多奇奇怪怪、有趣的想法。 比如&#xff0c;用Python的Turtle库&#xff0c;可以帮我们在电脑上画出好看的图像。 下面这张樱花图就是用Turtle库实现的。 这不圣诞节快到啦。 那么就用代码来画一…

jsp servlet mysql实现的二手车汽车管理系统项目源码附带视频指导运行教程

今天给大家演示一下由jsp servlet mysql实现的一款简单的二手车汽车管理系统&#xff0c;系统设计采用了mvc分层的模式&#xff0c;结构非常清晰&#xff0c;功能虽简单&#xff0c;但是把所有可能用到的功能都实现了&#xff0c;往上面添加功能很简单&#xff0c;直接复制代码…

计算机网络学习笔记(Ⅲ):数据链路层

目录 1 数据链路层概述 1.1 基本概念 1.2 主要功能 2 封装成帧和透明传输 2.1 封装成帧 2.2 透明传输 1.字符计数法 2.字符填充法 3.零比特填充法 4.违规编码法 3 差错控制 3.1 差错 3.1 检错编码 1.奇偶校验码 2.CRC循环冗余码 3.2 纠错编码 1.确定校验码位数…

docker安装nginx代理nacos2.1.2版本集群

目录 安装docker最新版本 创建一个docker network&#xff0c;使之固定docker局域ip docker安装mysql主从 配置挂载的my.cnf配置文件 进入mysql主数据库容器命令 登录主数据库创建用于从数据连接主数据的账号密码 输入show master status;查看master数据库状态 在从数据库…

关于JVM:内容以及流程释义

对于JVM、GC、类加载&#xff0c;很多人摸不清楚头绪&#xff0c;不知道他们之间的关系。误以为GC和类加载还有JVM区分统称垃圾回收&#xff0c;实则他们包含的东西很多&#xff0c;很细&#xff0c;完整的了解正个JVM的加载过程&#xff0c;就需要全面理解这些东西。 我认为的…

如何在 Windows 10 上启用和设置 BitLocker 加密

启用和设置 BitLocker 加密 通过控制面板启用 BitLocker通过命令提示符启用 BitLockerBitlocker 可以使用控制面板中的图形界面或在命令提示符中执行一些命令来启用。在 Windows 10 上启用 Bitlocker 非常简单,但用户通常更喜欢通过控制面板而不是命令提示符来管理 Bitlocker …

Java数据结构与Java算法学习Day05---二叉树(简略笔记记录)

目录 一、二叉树 79 1.1树的基本定义79 1.2数的相关术语 80 1.3二叉树的基本定义 81 1.4二叉查找树的创建 82 1.4.1二叉树查找树创建---插入方法&#xff08;put&#xff09; 83 1.4.2二叉树查找树创建---获取方法&#xff08;get&#xff09;84 1.4.3二叉树查找树创建…

【测试沉思录】23. 如何实现基于场景的接口自动化测试用例?

作者&#xff1a;陈爱娇 编辑&#xff1a;毕小烦 自动化本身是为了提高工作效率&#xff0c;不论选择何种框架&#xff0c;何种开发语言&#xff0c;我们最终想实现的效果&#xff0c;就是让大家用最少的代码&#xff0c;最小的投入&#xff0c;完成自动化测试的工作。 基于这…

K-Means++代码实现

K-Means代码实现 数据集 https://download.csdn.net/download/qq_43629083/87246495 import pandas as pd import numpy as np import random import math %matplotlib inline from matplotlib import pyplot as plt# 按文件名读取整个文件 data pd.read_csv(data.csv)class…

学编程:Python入门考级必备[11]

目录 1.查找字符串 2.字符串的格式化 3.字符串的转义字符 \ \" 4. 修改字符串 5.字符串连接与分割 附件代码&#xff1a; 炼 知识模块(11) 名符其实--字符串 1.查找字符串 # 1.1用 in 函数 a aa in acacacacaabaac print(a) # 1.2 用index 找不到就报错 b h…