springcloudfeign原理和流程

news2024/11/17 7:32:29

什么是Feign?

Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。 Feign被广泛应用在Spring Cloud 的解决方案中,是学习基于Spring Cloud 微服务架构不可或缺的重要组件。 

Feign解决了什么问题?

封装了Http调用流程,更适合面向接口化的变成习惯 在服务调用的场景中,我们经常调用基于Http协议的服务,而我们经常使用到的框架可能有HttpURLConnection、Apache HttpComponnets、OkHttp3 、Netty等等,这些框架在基于自身的专注点提供了自身特性。而从角色划分上来看,他们的职能是一致的提供Http调用服务。具体流程如下:

调用方 Client框架 服务方 构造Http请求URL 填写Http请求头信息 填写消息报文信息 发送Http请求 处理请求,返回结 果 返回报文 提取报文信息,转换成对应的Java bean 根据Bean中 的定义,业务处理 调用方 Client框架 服务方

Feign是如何设计的?

PHASE 1. 基于面向接口的动态代理方式生成实现类

在使用feign 时,会定义对应的接口类,在接口类上使用Http相关的注解,标识HTTP请求参数信息,如下所示:

interface GitHub { 

@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
}
public static class Contributor { 

String login;
int contributions;
}
public class MyApp { 

public static void main(String... args) { 

GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
// Fetch and print a list of the contributors to this library.
List<Contributor> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) { 

System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
}

复制

在Feign 底层,通过基于面向接口的动态代理方式生成实现类,将请求调用委托到动态代理实现类,基本原理如下所示:

 public class ReflectiveFeign extends Feign{ 

///省略部分代码
@Override
public <T> T newInstance(Target<T> target) { 

//根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) { 

if (method.getDeclaringClass() == Object.class) { 

continue;
} else if(Util.isDefault(method)) { 

DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else { 

methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
// 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理。
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{ 
target.type()}, handler);
for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { 

defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
//省略部分代码

复制

PHASE 2. 根据Contract协议规则,解析接口类的注解信息,解析成内部表现:

Feign 定义了转换协议,定义如下:

/** * Defines what annotations and values are valid on interfaces. */
public interface Contract { 

/** * Called to parse the methods in the class that are linked to HTTP requests. * 传入接口定义,解析成相应的方法内部元数据表示 * @param targetType {@link feign.Target#type() type} of the Feign interface. */
// TODO: break this and correct spelling at some point
List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);
}

复制

默认Contract 实现

Feign 默认有一套自己的协议规范,规定了一些注解,可以映射成对应的Http请求,如官方的一个例子:

public interface GitHub { 

@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> getContributors(@Param("owner") String owner, @Param("repo") String repository);
class Contributor { 

String login;
int contributions;
}
}

复制

上述的例子中,尝试调用GitHub.getContributors(“foo”,“myrepo”)的的时候,会转换成如下的HTTP请求:

GET /repos/foo/myrepo/contributors
HOST XXXX.XXX.XXX

复制

Feign 默认的协议规范

注解

接口Target

使用说明

@RequestLine

方法上

定义HttpMethod 和 UriTemplate. UriTemplate 中使用{} 包裹的表达式,可以通过在方法参数上使用@Param 自动注入

@Param

方法参数

定义模板变量,模板变量的值可以使用名称的方式使用模板注入解析

@Headers

类上或者方法上

定义头部模板变量,使用@Param 注解提供参数值的注入。如果该注解添加在接口类上,则所有的请求都会携带对应的Header信息;如果在方法上,则只会添加到对应的方法请求上

@QueryMap

方法上

定义一个键值对或者 pojo,参数值将会被转换成URL上的 query 字符串上

@HeaderMap

方法上

定义一个HeaderMap, 与 UrlTemplate 和HeaderTemplate 类型,可以使用@Param 注解提供参数值

具体FeignContract 是如何解析的,不在本文的介绍范围内,详情请参考代码: feign/Contract.java at master · OpenFeign/feign · GitHub

基于Spring MVC的协议规范SpringMvcContract:

当前Spring Cloud 微服务解决方案中,为了降低学习成本,采用了Spring MVC的部分注解来完成 请求协议解析,也就是说 ,写客户端请求接口和像写服务端代码一样:客户端和服务端可以通过SDK的方式进行约定,客户端只需要引入服务端发布的SDK API,就可以使用面向接口的编码方式对接服务:

我们团队内部就是按照这种思路,结合Spring Boot Starter 的特性,定义了服务端starter, 服务消费者在使用的时候,只需要引入Starter,就可以调用服务。这个比较适合平台无关性,接口抽象出来的好处就是可以根据服务调用实现方式自有切换:

  1. 可以基于简单的Http服务调用;
  2. 可以基于Spring Cloud 微服务架构调用;
  3. 可以基于Dubbo SOA服务治理

这种模式比较适合在SaSS混合软件服务的模式下自有切换,根据客户的硬件能力选择合适的方式部署,也可以基于自身的服务集群部署微服务

至于Spring Cloud 是如何实现 协议解析的,可参考代码: spring-cloud-openfeign/SpringMvcContract.java at main · spring-cloud/spring-cloud-openfeign · GitHub

当然,目前的Spring MVC的注解并不是可以完全使用的,有一些注解并不支持,如@GetMapping,@PutMapping 等,仅支持使用@RequestMapping 等,另外注解继承性方面也有些问题;具体限制细节,每个版本能会有些出入,可以参考上述的代码实现,比较简单。

Spring Cloud 没有基于Spring MVC 全部注解来做Feign 客户端注解协议解析,个人认为这个是一个不小的坑。在刚入手Spring Cloud 的时候,就碰到这个问题。后来是深入代码才解决的… 这个应该有人写了增强类来处理,暂且不表,先MARK一下,是一个开源代码练手的好机会。

PHASE 3. 基于 RequestBean,动态生成Request

根据传入的Bean对象和注解信息,从中提取出相应的值,来构造Http Request 对象:

PHASE 4. 使用Encoder 将Bean转换成 Http报文正文(消息解析和转码逻辑)

Feign 最终会将请求转换成Http 消息发送出去,传入的请求对象最终会解析成消息体,如下所示:

在接口定义上Feign做的比较简单,抽象出了Encoder 和decoder 接口:

public interface Encoder { 

/** Type literal for {@code Map<String, ?>}, indicating the object to encode is a form. */
Type MAP_STRING_WILDCARD = Util.MAP_STRING_WILDCARD;
/** * Converts objects to an appropriate representation in the template. * 将实体对象转换成Http请求的消息正文中 * @param object what to encode as the request body. * @param bodyType the type the object should be encoded as. {@link #MAP_STRING_WILDCARD} * indicates form encoding. * @param template the request template to populate. * @throws EncodeException when encoding failed due to a checked exception. */
void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
/** * Default implementation of {@code Encoder}. */
class Default implements Encoder { 

@Override
public void encode(Object object, Type bodyType, RequestTemplate template) { 

if (bodyType == String.class) { 

template.body(object.toString());
} else if (bodyType == byte[].class) { 

template.body((byte[]) object, null);
} else if (object != null) { 

throw new EncodeException(
format("%s is not a type supported by this encoder.", object.getClass()));
}
}
}
}

复制

public interface Decoder { 

/** * Decodes an http response into an object corresponding to its {@link * java.lang.reflect.Method#getGenericReturnType() generic return type}. If you need to wrap * exceptions, please do so via {@link DecodeException}. * 从Response 中提取Http消息正文,通过接口类声明的返回类型,消息自动装配 * @param response the response to decode * @param type {@link java.lang.reflect.Method#getGenericReturnType() generic return type} of * the method corresponding to this {@code response}. * @return instance of {@code type} * @throws IOException will be propagated safely to the caller. * @throws DecodeException when decoding failed due to a checked exception besides IOException. * @throws FeignException when decoding succeeds, but conveys the operation failed. */
Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;
/** Default implementation of {@code Decoder}. */
public class Default extends StringDecoder { 

@Override
public Object decode(Response response, Type type) throws IOException { 

if (response.status() == 404) return Util.emptyValueOf(type);
if (response.body() == null) return null;
if (byte[].class.equals(type)) { 

return Util.toByteArray(response.body().asInputStream());
}
return super.decode(response, type);
}
}
}

复制

目前Feign 有以下实现:

Encoder/ Decoder 实现

说明

JacksonEncoder,JacksonDecoder

基于 Jackson 格式的持久化转换协议

GsonEncoder,GsonDecoder

基于Google GSON 格式的持久化转换协议

SaxEncoder,SaxDecoder

基于XML 格式的Sax 库持久化转换协议

JAXBEncoder,JAXBDecoder

基于XML 格式的JAXB 库持久化转换协议

ResponseEntityEncoder,ResponseEntityDecoder

Spring MVC 基于 ResponseEntity< T > 返回格式的转换协议

SpringEncoder,SpringDecoder

基于Spring MVC HttpMessageConverters 一套机制实现的转换协议 ,应用于Spring Cloud 体系中

PHASE 5. 拦截器负责对请求和返回进行装饰处理

在请求转换的过程中,Feign 抽象出来了拦截器接口,用于用户自定义对请求的操作:

public interface RequestInterceptor { 

/** * 可以在构造RequestTemplate 请求时,增加或者修改Header, Method, Body 等信息 * Called for every request. Add data using methods on the supplied {@link RequestTemplate}. */
void apply(RequestTemplate template);
}

复制

比如,如果希望Http消息传递过程中被压缩,可以定义一个请求拦截器:

public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor { 

/** * Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}. * * @param properties the encoding properties */
protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) { 

super(properties);
}
/** * {@inheritDoc} */
@Override
public void apply(RequestTemplate template) { 

// 在Header 头部添加相应的数据信息
addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
HttpEncoding.DEFLATE_ENCODING);
}
}

复制

PHASE 6. 日志记录

在发送和接收请求的时候,Feign定义了统一的日志门面来输出日志信息 , 并且将日志的输出定义了四个等级:

级别

说明

NONE

不做任何记录

BASIC

只记录输出Http 方法名称、请求URL、返回状态码和执行时间

HEADERS

记录输出Http 方法名称、请求URL、返回状态码和执行时间 和 Header 信息

FULL

记录Request 和Response的Header,Body和一些请求元数据

public abstract class Logger { 

protected static String methodTag(String configKey) { 

return new StringBuilder().append('[').append(configKey.substring(0, configKey.indexOf('(')))
.append("] ").toString();
}
/** * Override to log requests and responses using your own implementation. Messages will be http * request and response text. * * @param configKey value of {@link Feign#configKey(Class, java.lang.reflect.Method)} * @param format {@link java.util.Formatter format string} * @param args arguments applied to {@code format} */
protected abstract void log(String configKey, String format, Object... args);
protected void logRequest(String configKey, Level logLevel, Request request) { 

log(configKey, "---> %s %s HTTP/1.1", request.method(), request.url());
if (logLevel.ordinal() >= Level.HEADERS.ordinal()) { 

for (String field : request.headers().keySet()) { 

for (String value : valuesOrEmpty(request.headers(), field)) { 

log(configKey, "%s: %s", field, value);
}
}
int bodyLength = 0;
if (request.body() != null) { 

bodyLength = request.body().length;
if (logLevel.ordinal() >= Level.FULL.ordinal()) { 

String
bodyText =
request.charset() != null ? new String(request.body(), request.charset()) : null;
log(configKey, ""); // CRLF
log(configKey, "%s", bodyText != null ? bodyText : "Binary data");
}
}
log(configKey, "---> END HTTP (%s-byte body)", bodyLength);
}
}
protected void logRetry(String configKey, Level logLevel) { 

log(configKey, "---> RETRYING");
}
protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response,
long elapsedTime) throws IOException { 

String reason = response.reason() != null && logLevel.compareTo(Level.NONE) > 0 ?
" " + response.reason() : "";
int status = response.status();
log(configKey, "<--- HTTP/1.1 %s%s (%sms)", status, reason, elapsedTime);
if (logLevel.ordinal() >= Level.HEADERS.ordinal()) { 

for (String field : response.headers().keySet()) { 

for (String value : valuesOrEmpty(response.headers(), field)) { 

log(configKey, "%s: %s", field, value);
}
}
int bodyLength = 0;
if (response.body() != null && !(status == 204 || status == 205)) { 

// HTTP 204 No Content "...response MUST NOT include a message-body"
// HTTP 205 Reset Content "...response MUST NOT include an entity"
if (logLevel.ordinal() >= Level.FULL.ordinal()) { 

log(configKey, ""); // CRLF
}
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
bodyLength = bodyData.length;
if (logLevel.ordinal() >= Level.FULL.ordinal() && bodyLength > 0) { 

log(configKey, "%s", decodeOrDefault(bodyData, UTF_8, "Binary data"));
}
log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
return response.toBuilder().body(bodyData).build();
} else { 

log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
}
}
return response;
}
protected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) { 

log(configKey, "<--- ERROR %s: %s (%sms)", ioe.getClass().getSimpleName(), ioe.getMessage(),
elapsedTime);
if (logLevel.ordinal() >= Level.FULL.ordinal()) { 

StringWriter sw = new StringWriter();
ioe.printStackTrace(new PrintWriter(sw));
log(configKey, sw.toString());
log(configKey, "<--- END ERROR");
}
return ioe;
}

复制

PHASE 7 . 基于重试器发送HTTP请求

Feign 内置了一个重试器,当HTTP请求出现IO异常时,Feign会有一个最大尝试次数发送请求,以下是Feign核心 代码逻辑:

final class SynchronousMethodHandler implements MethodHandler { 

// 省略部分代码
@Override
public Object invoke(Object[] argv) throws Throwable { 

//根据输入参数,构造Http 请求。
RequestTemplate template = buildTemplateFromArgs.create(argv);
// 克隆出一份重试器
Retryer retryer = this.retryer.clone();
// 尝试最大次数,如果中间有结果,直接返回
while (true) { 

try { 

return executeAndDecode(template);
} catch (RetryableException e) { 

retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) { 

logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}

复制

重试器有如下几个控制参数:

重试参数

说明

默认值

period

初始重试时间间隔,当请求失败后,重试器将会暂停 初始时间间隔(线程 sleep 的方式)后再开始,避免强刷请求,浪费性能

100ms

maxPeriod

当请求连续失败时,重试的时间间隔将按照:long interval = (long) (period * Math.pow(1.5, attempt - 1)); 计算,按照等比例方式延长,但是最大间隔时间为 maxPeriod, 设置此值能够避免 重试次数过多的情况下执行周期太长

1000ms

maxAttempts

最大重试次数

5

具体的代码实现可参考: feign/Retryer.java at master · OpenFeign/feign · GitHub

PHASE 8. 发送Http请求

Feign 真正发送HTTP请求是委托给 feign.Client 来做的:

public interface Client { 

/** * Executes a request against its {@link Request#url() url} and returns a response. * 执行Http请求,并返回Response * @param request safe to replay. * @param options options to apply to this request. * @return connected response, {@link Response.Body} is absent or unread. * @throws IOException on a network error connecting to {@link Request#url()}. */
Response execute(Request request, Options options) throws IOException;
}

复制

Feign 默认底层通过JDK 的 java.net.HttpURLConnection 实现了feign.Client接口类,在每次发送请求的时候,都会创建新的HttpURLConnection 链接,这也就是为什么默认情况下Feign的性能很差的原因。可以通过拓展该接口,使用Apache HttpClient 或者OkHttp3等基于连接池的高性能Http客户端,我们项目内部使用的就是OkHttp3作为Http 客户端。

如下是Feign 的默认实现,供参考:

public static class Default implements Client { 

private final SSLSocketFactory sslContextFactory;
private final HostnameVerifier hostnameVerifier;
/** * Null parameters imply platform defaults. */
public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) { 

this.sslContextFactory = sslContextFactory;
this.hostnameVerifier = hostnameVerifier;
}
@Override
public Response execute(Request request, Options options) throws IOException { 

HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection).toBuilder().request(request).build();
}
HttpURLConnection convertAndSend(Request request, Options options) throws IOException { 

final HttpURLConnection
connection =
(HttpURLConnection) new URL(request.url()).openConnection();
if (connection instanceof HttpsURLConnection) { 

HttpsURLConnection sslCon = (HttpsURLConnection) connection;
if (sslContextFactory != null) { 

sslCon.setSSLSocketFactory(sslContextFactory);
}
if (hostnameVerifier != null) { 

sslCon.setHostnameVerifier(hostnameVerifier);
}
}
connection.setConnectTimeout(options.connectTimeoutMillis());
connection.setReadTimeout(options.readTimeoutMillis());
connection.setAllowUserInteraction(false);
connection.setInstanceFollowRedirects(true);
connection.setRequestMethod(request.method());
Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
boolean
gzipEncodedRequest =
contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
boolean
deflateEncodedRequest =
contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE);
boolean hasAcceptHeader = false;
Integer contentLength = null;
for (String field : request.headers().keySet()) { 

if (field.equalsIgnoreCase("Accept")) { 

hasAcceptHeader = true;
}
for (String value : request.headers().get(field)) { 

if (field.equals(CONTENT_LENGTH)) { 

if (!gzipEncodedRequest && !deflateEncodedRequest) { 

contentLength = Integer.valueOf(value);
connection.addRequestProperty(field, value);
}
} else { 

connection.addRequestProperty(field, value);
}
}
}
// Some servers choke on the default accept string.
if (!hasAcceptHeader) { 

connection.addRequestProperty("Accept", "*/*");
}
if (request.body() != null) { 

if (contentLength != null) { 

connection.setFixedLengthStreamingMode(contentLength);
} else { 

connection.setChunkedStreamingMode(8196);
}
connection.setDoOutput(true);
OutputStream out = connection.getOutputStream();
if (gzipEncodedRequest) { 

out = new GZIPOutputStream(out);
} else if (deflateEncodedRequest) { 

out = new DeflaterOutputStream(out);
}
try { 

out.write(request.body());
} finally { 

try { 

out.close();
} catch (IOException suppressed) { 
 // NOPMD
}
}
}
return connection;
}
Response convertResponse(HttpURLConnection connection) throws IOException { 

int status = connection.getResponseCode();
String reason = connection.getResponseMessage();
if (status < 0) { 

throw new IOException(format("Invalid status(%s) executing %s %s", status,
connection.getRequestMethod(), connection.getURL()));
}
Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
for (Map.Entry<String, List<String>> field : connection.getHeaderFields().entrySet()) { 

// response message
if (field.getKey() != null) { 

headers.put(field.getKey(), field.getValue());
}
}
Integer length = connection.getContentLength();
if (length == -1) { 

length = null;
}
InputStream stream;
if (status >= 400) { 

stream = connection.getErrorStream();
} else { 

stream = connection.getInputStream();
}
return Response.builder()
.status(status)
.reason(reason)
.headers(headers)
.body(stream, length)
.build();
}
}

复制

Feign 的性能怎么样?

Feign 整体框架非常小巧,在处理请求转换和消息解析的过程中,基本上没什么时间消耗。真正影响性能的,是处理Http请求的环节。 如上所述,由于默认情况下,Feign采用的是JDK的HttpURLConnection,所以整体性能并不高,刚开始接触Spring Cloud 的同学,如果没注意这些细节,可能会对Spring Cloud 有很大的偏见。 我们项目内部使用的是OkHttp3 作为连接客户端。

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

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

相关文章

【Cisco Packet Tracer| 四.跨交换机VLAN实验】

文章目录 一.连接实验拓扑图并设置主机IP地址1.连接实验拓扑图2.设置主机的IP地址 二.划分VLAN前测试是否ping通三.划分VLAN1.给主机划分VLAN2.给交换机之间的f0/24端口从Access模式切换到Trunk模式 四.划分VLAN后测试是否ping通五.另一种测试方式-信封图标 一.连接实验拓扑图并…

nginx加tomcat动静分离,负载均衡

tomcat是什么&#xff1a;java开发的开源服务器 处理动态页面&#xff0c;静态页面处理能力一般 jvm参数优化 配置添加 在119行之前 以2cpu 4G内存为例 JAVA_OPTS"$JAVA_OPTS -server -Xms2048m -Xmx2048m -Xmn768m -XX:ParallelGCThreads2 -XX:PermSize1024m -XX:MaxP…

IS230TCISH6C集成电路分类有哪些呢

​ IS230TCISH6C集成电路分类有哪些呢 集成电路分类有哪些 集成电路是一种微型电子器件或部件&#xff0c;集成电路有很多种&#xff0c;那么集成电路分类有哪些呢&#xff0c;下面小编就为大家介绍集成电路的分类。 按功能结构区分 模拟集成电路 数字集成电路 数/模混合集成电…

JavaSE学习进阶day06_04 集合的嵌套和不可变集合

接昨天讲的内容&#xff0c;今天还要继续学习集合。学完今天的内容&#xff0c;集合就告于段落了&#xff0c;坚持&#xff01; 集合的嵌套&#xff08;掌握&#xff01;&#xff09;&#xff1a; 什么是集合的嵌套&#xff1f;为什么要集合的嵌套&#xff1f;为了搞清楚这个…

算法篇——层序遍历大集合(js版)

102.二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 链接&#xff1a;力扣 var levelOrder function(root) {var res [], queue [];if(!root) return res;// 队列…

海斯坦普Gestamp EDI 需求分析

海斯坦普Gestamp&#xff08;以下简称&#xff1a;Gestamp&#xff09;是一家总部位于西班牙的全球性汽车零部件制造商&#xff0c;目前在全球23个国家拥有超过100家工厂。Gestamp的业务涵盖了车身、底盘和机电系统等多个领域&#xff0c;其产品范围包括钣金、车身结构件、车轮…

非极大值抑制详细原理(NMS含代码及详细注释)

作者主页&#xff1a;爱笑的男孩。的博客_CSDN博客-深度学习,YOLO,活动领域博主爱笑的男孩。擅长深度学习,YOLO,活动,等方面的知识,爱笑的男孩。关注算法,python,计算机视觉,图像处理,深度学习,pytorch,神经网络,opencv领域.https://blog.csdn.net/Code_and516?typecollect 个…

配置vscode arcpy环境 基于ArcGIS的python

最近可能要用到arcpy&#xff0c;我以前这是知道有这个东西&#xff0c;但是没用过&#xff0c;今天正好记录下利用vsCode编译器&#xff0c;python开发来配置arcpy环境。 1.安装ArcGIS desktop 2.下载安装vscode 上面的两步没啥可说的&#xff0c;很简单&#xff0c;我要说…

ArcGIS、ENVI、InVEST、FRAGSTATS多技术融合提升环境、生态、水文、土地、土壤、农业、大气领域应用

基于ArcGIS、ENVI、InVEST、FRAGSTATS等多技术融合提升环境、生态、水文、土地、土壤、农业、大气等领域的数据分析能力与项目科研水平 点击查看原文 一、空间数据获取与制图 1.1 软件安装与应用讲解 1.2 空间数据介绍 1.3海量空间数据下载 1.4 ArcGIS软件快速入门 1.5 …

性能优化之-更高效的数据渲染

前言&#xff1a;中心思想还是让请求的资源得到更快响应的方法&#xff0c;比如压缩资源&#xff0c;减少数据量的大小&#xff0c;缓存数据以减少请求数量&#xff0c;http/2让网络传输变得更快这些&#xff0c;下面就让我们来看看浏览器是如何解析这些数据&#xff0c;最终又…

数据结构——红黑树

红黑树 概念与性质树节点的定义插入红黑树的验证红黑树与AVL树的对比 概念与性质 概念&#xff1a; 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色…

数字IC设计——功耗分析

一、概述 芯片的整体功耗很难通过简单的电流&#xff0c;电压或者电阻值的的相乘来计算。其原因在于&#xff0c;由于芯片作为具有复杂功能的器件&#xff0c;其功耗会根据其不同时段的不同行为&#xff0c;不同的外部条件而发生很大的变化。 1.1 功耗的分类 数字IC芯片的功…

Three.js+TypeScript+Webpack学习记录(二)

使用环境参考 Node.js v16.19.1 正文 跟着文档画个线 看看 Three 的官方文档&#xff0c;起步 -> 画线 -> 没了&#xff1f;&#xff01;&#xff01; 不管怎么说&#xff0c;先画个线吧。 import * as THREE from threeconst scene new THREE.Scene() const camer…

HummerRisk V1.0 开发手册(微服务版)

HummerRisk 是开源的云原生安全平台&#xff0c;以非侵入的方式解决云原生环境的安全和治理问题。核心能力包括混合云的安全治理和容器云安全检测。 本文将介绍HummerRisk 1.0以后的开发准备工作。 v1.0.0 以后的版本&#xff0c;代码在 dev 分支。欢迎大家在 dev 开发分支提…

多通道振弦传感器无线采集仪 数字传感器起始通道分配

多通道振弦传感器无线采集仪 数字传感器起始通道分配 寄存器 DS_CHNUM(299)用于设置读取到的数字传感器数据从哪个通道开始占用&#xff0c;默认为 1。 单个数字传感器占用的通道数量与具体的传感器类型有关&#xff0c;例如&#xff1a;每个激光测距仪会占用 1 个通道&#xf…

Linux Shell 实现一键部署二进制go+caddy+filebrowser

filebrowser filebrowser 是一个使用go语言编写的软件&#xff0c;功能是可以通过浏览器对服务器上的文件进行管理。可以是修改文件&#xff0c;或者是添加删除文件&#xff0c;甚至可以分享文件&#xff0c;是一个很棒的文件管理器&#xff0c;你甚至可以当成一个网盘来使用。…

HCIE Datacom认证学什么内容

什么HCIE 什么是HCIE&#xff1f;HCIE的全称是Huawei Certified ICT Expert&#xff08;华为认证ICT技术专家&#xff09;。 华为认证是行业中最严谨的认证&#xff0c;含金量与行业认可度都较高。在众多的IT行业认证中&#xff0c;HCIE被誉为“网络界的博士”。 华为认证HCI…

深度学习 - 41.Word2vec、EGES 负采样实现 By Keras

目录 一.引言 二.实现思路 1.样本构建 2.Word2vec 架构 3.EGES 架构 4.基于 NEG 的 Word2vec 架构 三.Keras 实现 Word2vec 1.样本构建 2.模型构建 3.向量获取 四.keras 实现 EGES 1.样本构建 2.模型构建 3.Dot Layer 详解 3.1 init 方法 3.2 call 方法 3.3 完…

4月18号软件更新资讯合集

ModStartCMS v6.2.0&#xff0c;VIP 权益配置功能、界面 UI 优化升级 ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.…

4月19号软件更新资讯合集....

JavaWeb 微服务前后端分离 EurekaEleVue 版 v1.5.0 发布 v1.5.0 更新如下&#xff1a; 1、解决 token 过期无法跳转至登录页的问题&#xff1b; 2、授权服务进行重构与优化&#xff1b; 一款 Java 语言基于 SpringCloud、SpringSecurity、OAuth2、Eureka、Vue、ElementUI、…