OpenTelemetry框架

news2024/11/17 8:55:33

在这里插入图片描述

文章目录

  • 1、分布式监控系统
  • 2、OpenTelemetry
  • 3、OpenTelemetry-Trace相关组件
  • 4、Context Propagation搭配HTTP Header传递信息
  • 5、Span相关

1、分布式监控系统

随着单体架构演变为微服务架构,线上问题的追踪和排查变的越来越困难,想解决这个问题就得实现线上业务的可观测性。由此,涌现出许多链路追踪和服务监控的中间件,如:

  • Java玩家常用的SpringCloud Seluth + MQ + Zipkin
  • skywalking
  • 阿里的鹰眼
  • 大众点评的Cat

而监控系统主要有三个维度:

  • Mertic:度量系统
  • Tracing:追踪系统
  • Logging:日志系统

在这里插入图片描述

Metric:

Metric,即度量,可聚合性是Metric的特征,比如服务的QPS是多少、当天用户的登录次数、CPU和内存占用情况,这时就需要需要将一段时间内的一部分时间进行聚合或者计数。

在这里插入图片描述

Logging:

和Metric相比,Logging则是离散事件,日志是系统运行时发生的一个个事件的记录,为问题排查提供详细信息。

Tracing:

Trace,即追踪,是一个最外层请求下所包含的所有调用信息,如请求过来,先到gateway服务,再路由到服务A,再远程调用服务B,再调数据库、缓存等一条完整的链路。

在这里插入图片描述

Metric、Logging、Trace在监控中是相辅相成的:

在这里插入图片描述

看这个图中两两相交的部分:通过度量和日志维度,我们可以作一些事件的聚合统计(如某应用每分钟的错误日志数)。通过链路追踪和日志系统,我们可以得到某个请求详细的请求信息(如请求的入参、出参、链路中途方法打印出的日志信息等)。通过度量和链路系统,我们可以查到某种类请求(通常是慢请求)的调用信息(如慢请求的问题排查及优化)。可见,通过这三种类型数据相互作用,可以得到很多在单单某一种数据中无法呈现的信息。

2、OpenTelemetry

OpenTelemetry is an Observability framework. OpenTelemetry is not an observability back-end like Jaeger, Prometheus, or commercial vendors. OpenTelemetry is focused on the generation, collection, management, and export of telemetry data. The storage and visualization of that data is intentionally left to other tools.

即OpenTelemetry 是一个可观测性框架,它不像Jaeger、Prometheus,而是专注于 生成、收集 、导出 Telemetry遥测数据(Metrics,Logs and traces)。存储和可视化数据的事儿则留给其他工具,比如Zipkin、Jageger、skywalking、Prometheus等…

官方文档:

https://opentelemetry.io/docs/instrumentation/java/manual/

3、OpenTelemetry-Trace相关组件

整理下Trace相关的概念,Metric 和 Log 的移步官网文档。

Traces give us the big picture of what happens when a request is made to an application. Whether your application is a monolith with a single database or a sophisticated mesh of services, traces are essential to understanding the full “path” a request takes in your application.

Trace向我们提供了向程序发出请求时发生的全局,无论程序是具有单个数据库的整体还是复杂的服务网格,trace对于了解请求在程序中的完整路径都是必不可少的。接下来整理下在整个Trace中发挥作用的组件:

1)TraceProvider :

TraceProvider是创建Tracer的工厂,它在服务中只会初始化一次,生命周期和服务的生命周期相同,初始化traceProvider对象,需要带上两个参数:Exporter和Resource,前者为导出的目的端信息,后者为一些资源信息。创建这个对象是使用OpenTelemetry的第一步,示例程序:

//....
		SpanProcessor spanProcessor = getOtlpProcessor();
		//resource通常用于添加非临时的底层元数据信息,如服务名、实例名、命名空间等
        Resource serviceNameResource = Resource.create(Attributes.of(
        		ResourceAttributes.SERVICE_NAME, "myServiceName"));

        // Set to process the spans by the Zipkin Exporter
        SdkTracerProvider tracerProvider =SdkTracerProvider.builder()
                        .addSpanProcessor(spanProcessor)   //Exporter导出端
                        .setResource(Resource.getDefault().merge(serviceNameResource))  //设置上面的resource
                        .build();

//....

2)Tracer :

Trace由TracerProvider创建,它自己则是用来创建Span的,Span中又包含相关操作下(如请求一个服务)所发生情况的详细信息(一条链路,共用一个traceId,再由一个个span连接起来)。示例程序:

import io.opentelemetry.api;

//...
	OpenTelemetrySdk openTelemetry =
                OpenTelemetrySdk.builder().setTracerProvider(tracerProvider)
                        .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
                        .buildAndRegisterGlobal();
	Tracer tracer =
    openTelemetry.getTracer("instrumentation-library-name", "1.0.0");

	//上面getTracer方法的形参instrumentation-library-name指定用于跟踪的库的名称
	//该方法重载,只传一个参数时是instrumentationScopeName,用于指定当前追踪器的作用域名称
	//通常情况下,作用域名称可以是应用程序的名称、模块名称、服务名称等,以便在分布式系统中明确标识追踪数据的来源

3)TraceExporter :

TraceExporter把生产的Span发送到Trace接收器,既可以发送到本地文件来调试,也可以发送到远端的链路分析端,如ZipKin

整个流程如下:

在这里插入图片描述

结合上面Tracer的示例程序看这个时序图:

  • traceExporter做为初始化traceProvider对象时的参数传入
  • traceProvider又做为初始化OpenTelemetrySdk操作对象时的参数传入
  • OpenTelemetrySdk对象去创建tracer
  • tracer生产span
  • TracerExporter来发送Trace到数据接收器(本地或远端)
  • traceId相同的span被远端链路分析端串起来,由此显示整个链路

4)Context Propagator :

Context Propagator,上下文传播器。在分布式系统中,为了实现对不同部分和组件的追踪和监控,通常需要将跟踪信息从应用程序的一部分传递到另一部分。Context Propagator提供了一种机制,用于在分布式系统中传播跟踪上下文信息。

当一个请求到达应用程序时,Context Propagator会收集请求的上下文信息,并将其传递到应用程序的各个部分。在每个部分中,Context Propagator会更新并传播上下文信息,以便在分布式系统中保持一致性。

OpenTelemetry支持几种不同的上下文格式。默认格式是W3C TraceContext。每个上下文对象都是存储在Span中。

5)TextMapPropagator :

TextMapPropagator 的作用是将跟踪上下文以文本格式存储在一个映射(map)中,将其传递到应用程序的其他部分。

//创建了一个 TextMapPropagator 实例,并使用 TextMapReader 和 TextMapWriter 分别作为读取和写入文本格式跟踪上下文的组件
TextMapPropagator textMapPropagator = new TextMapPropagator(new TextMapReader(), new TextMapWriter());
//注入一条数据,实际开发注入trace信息
textMapPpropagator.inject(TextMap.create("user_id", "123"));

//调用 extract() 方法,从分布式系统中提取文本格式的跟踪上下文
TextMap<?, ?> distributedTraceContext = propagator.extract();

以上是个简单例子。实际对传播器的应用应该是:OpenTelemetry使用W3C Trace Context HTTP请求头来传播上下文信息

4、Context Propagation搭配HTTP Header传递信息

注入:

首先要告诉Telemetry注入上下文信息到请求头里,核心方法是TextMapPropagator类里的inject方法:

在这里插入图片描述

先看官方给的例子分析直接写注释里了:

// 先创建TextMapSetter对象,泛型指定为HttpURLConnection类型
//匿名内部类,实现set方法,表示将一个键值对添加到HttpHeader中
//TextMapSetter对象也就是未来inject方法的第三个形参的值
TextMapSetter<HttpURLConnection> setter =
  new TextMapSetter<HttpURLConnection>() {
    @Override
    public void set(HttpURLConnection carrier, String key, String value) {
        // Insert the context as Header
        carrier.setRequestProperty(key, value);
    }
};

URL url = new URL("http://127.0.0.1:8080/resource");
//创建span
Span outGoing = tracer.spanBuilder("/resource").setSpanKind(SpanKind.CLIENT).startSpan();
try (Scope scope = outGoing.makeCurrent()) {
	  // 添加一堆乱七八糟的属性
	  // (Note that to set these, Span does not *need* to be the current instance in Context or Scope.)
	  outGoing.setAttribute(SemanticAttributes.HTTP_METHOD, "GET");
	  outGoing.setAttribute(SemanticAttributes.HTTP_URL, url.toString());
	  //创建个HttpURLConnection对象,即inject方法的第二个参数
	  HttpURLConnection transportLayer = (HttpURLConnection) url.openConnection();
	  // Inject the request with the *current*  Context, which contains our current Span.
	  //创建上下文传播器对象,调用inject方法
	  //注入上上下文信息到HTTP连接的HttpHeader中
	  openTelemetry.getPropagators().getTextMapPropagator().inject(Context.current(), transportLayer, setter);
	  // Make outgoing call
} finally {
  outGoing.end();
}
...

以上,就实现了上下文信息写到了Http请求头里,接下来,以上面的例子为参考,自己定义一个inject方法,对官方的inject做个封装,方便后续信息往Header的注入:

private void inject(ServerWebExchange serverWebExchange) {
	//先创建个空的HttpHeaders对象
    HttpHeaders httpHeaders = new HttpHeaders();
    //创建文本映射传播器对象,用来调用官方inject方法
    TextMapPropagator textMapPropagator = openTelemetry.getPropagators().getTextMapPropagator();
    //HttpHeaders::add是一个方法引用,表示将一个键值对添加到HttpHeaders对象中
    //HttpHeaders::add和上面官方例子中的匿名内部类,实现TextMapSetter对象set方法,一个意思
    textMapPropagator.inject(Context.current(), httpHeaders, HttpHeaders::add);
    //getRequest() 方法从serverWebExchange取出里面的原始请求对象
    //mutate() 方法创建一个新的请求对象
    ServerHttpRequest request = serverWebExchange.getRequest().mutate()
    		//将 httpHeaders 对象里的信息全部添加到新的请求对象中
            .headers(headers -> headers.addAll(httpHeaders))
            .build();  //此时这个request对象是的请求头是带上下文信息的
    //将原始的ServerWebExchange对象中的请求对象替换为新的request对象
    serverWebExchange.mutate().request(request).build();
}

//传入一个ServerWebExchange对象对象,经过修改,新的ServerWebExchange对象包含了修改后的request对象
//而request对象是有上下文信息的
//由此完成ServerWebExchange对象的改造,也就是上下文信息的注入

上面用到了一个mutate方法,mutate直译就是变异的意思。它用于创建一个新的请求对象,该对象是原始请求对象的副本,并对副本进行修改。如上面以从ServerWebExchange中取出Request对象创建副本,造我自己的request对象。

mutate() 方法的主要作用是允许对请求对象进行自定义修改,例如添加新的属性、修改现有属性或添加自定义的跟踪数据。这些修改不会影响原始请求对象,而是创建一个新的副本。

举例:

ServerWebExchange exchange = ...;
Request request = exchange.getRequest();


//以从ServerWebExchange对象中取出的request对象,创建副本,进行修改
// 创建新的请求对象,并添加自定义属性
mutator.mutate(request).addHeader("Custom-Header", "Custom Value");

// 修改请求头
mutator.mutate(request).setHeader("Header-Name", "Header Value");

// 添加自定义跟踪数据
mutator.mutate(request).setTraceId("12345678-9abc-def0-1234-56789abcdef0");

// 执行请求处理逻辑
// ...

提取:

同样的,也可以从过来的请求里读W3C Trace Context信息, 核心方法是TextMapPropagator类里的extract方法:

在这里插入图片描述

官方给的例子:

TextMapGetter<HttpExchange> getter =
  new TextMapGetter<>() {
    @Override
    public String get(HttpExchange carrier, String key) {
      if (carrier.getRequestHeaders().containsKey(key)) {
        return carrier.getRequestHeaders().get(key).get(0);
      }
      return null;
    }

   @Override
   public Iterable<String> keys(HttpExchange carrier) {
     return carrier.getRequestHeaders().keySet();
   }
};
...
public void handle(HttpExchange httpExchange) {
  // Extract the SpanContext and other elements from the request.
  Context extractedContext = openTelemetry.getPropagators().getTextMapPropagator()
        .extract(Context.current(), httpExchange, getter);
  try (Scope scope = extractedContext.makeCurrent()) {
    // Automatically use the extracted SpanContext as parent.
    Span serverSpan = tracer.spanBuilder("GET /resource")
        .setSpanKind(SpanKind.SERVER)
        .startSpan();
    try {
      // Add the attributes defined in the Semantic Conventions
      serverSpan.setAttribute(SemanticAttributes.HTTP_METHOD, "GET");
      serverSpan.setAttribute(SemanticAttributes.HTTP_SCHEME, "http");
      serverSpan.setAttribute(SemanticAttributes.HTTP_HOST, "localhost:8080");
      serverSpan.setAttribute(SemanticAttributes.HTTP_TARGET, "/resource");
      // Serve the request
      ...
    } finally {
      serverSpan.end();
    }
  }
}

写个简略版的实现:这个方法的核心是提取httpServletRequest对象里的Header信息,顺便再返回一个Span,这个Span通过setParent方法,传入提取到的信息的新context对象,从而完成链接。


private Span getServerSpan(Tracer tracer, HttpServletRequest httpServletRequest) {

    TextMapPropagator textMapPropagator = openTelemetry.getPropagators().getTextMapPropagator();
    Context context = textMapPropagator.extract(Context.current(), httpServletRequest, new TextMapGetter<HttpServletRequest>() {
        @Override
        public Iterable<String> keys(HttpServletRequest request) {
            List<String> headers = new ArrayList();
            for (Enumeration names = request.getHeaderNames(); names.hasMoreElements();) {
                String name = (String)names.nextElement();
                headers.add(name);
            }
            return headers;
        }

        @Override
        public String get(HttpServletRequest request, String s) {
            return request.getHeader(s);
        }
    });


    return tracer.spanBuilder(httpServletRequest.getRequestURI())
    	.setParent(context)
    	.setSpanKind(SpanKind.SERVER)
    	.setAttribute(SemanticAttributes.HTTP_METHOD, httpServletRequest.getMethod())
    	.startSpan();
}

再贴上官方给的完整示例,即利用 HttpHeaders 获取用于上下文传播的跟踪父标:

TextMapGetter<HttpHeaders> getter =
  new TextMapGetter<HttpHeaders>() {
    @Override
    public String get(HttpHeaders headers, String s) {
      assert headers != null;
      return headers.getHeaderString(s);
    }

    @Override
    public Iterable<String> keys(HttpHeaders headers) {
      List<String> keys = new ArrayList<>();
      MultivaluedMap<String, String> requestHeaders = headers.getRequestHeaders();
      requestHeaders.forEach((k, v) ->{
        keys.add(k);
      });
    }
};

TextMapSetter<HttpURLConnection> setter =
  new TextMapSetter<HttpURLConnection>() {
    @Override
    public void set(HttpURLConnection carrier, String key, String value) {
        // Insert the context as Header
        carrier.setRequestProperty(key, value);
    }
};

//...
public void handle(<Library Specific Annotation> HttpHeaders headers){
        Context extractedContext = opentelemetry.getPropagators().getTextMapPropagator()
                .extract(Context.current(), headers, getter);
        try (Scope scope = extractedContext.makeCurrent()) {
            // Automatically use the extracted SpanContext as parent.
            Span serverSpan = tracer.spanBuilder("GET /resource")
                .setSpanKind(SpanKind.SERVER)
                .startSpan();

            try(Scope ignored = serverSpan.makeCurrent()) {
                // Add the attributes defined in the Semantic Conventions
                serverSpan.setAttribute(SemanticAttributes.HTTP_METHOD, "GET");
                serverSpan.setAttribute(SemanticAttributes.HTTP_SCHEME, "http");
                serverSpan.setAttribute(SemanticAttributes.HTTP_HOST, "localhost:8080");
                serverSpan.setAttribute(SemanticAttributes.HTTP_TARGET, "/resource");

                HttpURLConnection transportLayer = (HttpURLConnection) url.openConnection();
                // Inject the request with the *current*  Context, which contains our current Span.
                openTelemetry.getPropagators().getTextMapPropagator().inject(Context.current(), transportLayer, setter);
                // Make outgoing call
            }finally {
                serverSpan.end();
            }
      }
}

5、Span相关

Span就是整个链路的一个操作单元,同一个traceId下的一个个span连接起来就是整个调用链路。接下来整理Span的基本操作,先是创建span:

//only need to specify the name of the span,即只需指定名字,起止时间SDK自动做完了
//The start and end time of the span is automatically set by the OpenTelemetry SDK.
Span span = tracer.spanBuilder("my span").startSpan();

//Scope是一个跟踪范围的概念,它表示一次分布式追踪中的所有操作和请求的集合
// Make the span the current span
try (Scope ss = span.makeCurrent()) {
  //span.makeCurrent()即当前范围里的操作的span就是我上面创建的那个span
  // In this scope, the span is the current/active span
} finally {
    span.end();  //结束span
}

获取当前span,直接调静态方法:

Span span = Span.current()

也可获取某个Context对象里的Span对象:

Span span = Span.fromContext(context)

Span可以嵌套,Span的子Span即这个操作下的一个子操作。比如一个方法调用一个方法,就可以手动的将span链接起来:

void parentOne() {
  Span parentSpan = tracer.spanBuilder("parent").startSpan();
  try {
    childOne(parentSpan);
  } finally {
    parentSpan.end();
  }
}

void childOne(Span parentSpan) {
  Span childSpan = tracer.spanBuilder("child")
        .setParent(Context.current().with(parentSpan))
        .startSpan();
  try {
    // do stuff
  } finally {
    childSpan.end();
  }
}

若要链接来自远程进程的Span,只需将远程上下文设置为父级即可,不用再with()

Span childRemoteParent = tracer.spanBuilder("Child")
	.setParent(remoteContext)
	.startSpan();

OpenTelemetry框架下的Span包含以下信息:

  • Name
  • Parent span ID (empty for root spans)
  • Start and End Timestamps
  • Span Context
  • Attributes
  • Span Events
  • Span Links
  • Span Status

举个例子:

{
  "name": "hello",
  "context": {
    "trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2",
    "span_id": "0x051581bf3cb55c13"
  },
  "parent_id": null,  # 父span的id,为空即和其他span平级
  "start_time": "2022-04-29T18:52:58.114201Z",
  "end_time": "2022-04-29T18:52:58.114687Z",
  "attributes": {
    "http.route": "/v1/sys/health"
  },
  "events": [
    {
      "name": "Guten Tag!",
      "timestamp": "2022-04-29T18:52:58.114561Z",
      "attributes": {
        "event_attributes": 1
      }
    }
  ]
}

名称、父Span的id,开始结束时间戳不再细说,看看其他的属性:

1)Span Context :

Span的不可变对象,里面又包含:

  • 所属trace的TraceId
  • 自己的SpanId
  • TraceFlags,二进制形式的调用链标志位,用于表示这条trace链是否被采样(isSampled)
  • TraceState,承载调用链信息的K-V(键对值)结构列表

在这里插入图片描述

2)Attributes :

即属性,一系列元数据信息的键值对,用来携带这个span对应的操作的一些信息。

Span span = tracer.spanBuilder("/resource/path").setSpanKind(SpanKind.CLIENT).startSpan();
span.setAttribute("http.method", "GET");
span.setAttribute("http.url", url.toString());

//Keys must be non-null string values
//Values must be a non-null string, boolean, floating point value, integer, or an array of these values

3)Span Events :

用来记录Span在某一时刻发生的有意义的事件。

span.addEvent("Init");
...
span.addEvent("End");

当然也不止只能写个字符串,这个方法是重载的:

Attributes eventAttributes = Attributes.of(
    AttributeKey.stringKey("key"), "value",
    AttributeKey.longKey("result"), 0L);

span.addEvent("End Computation", eventAttributes);

4)Span Links :

用来关联异步操作的另一个span的SpanContext。一个响应下面对应一些操作,另一个操作将排队等待执行,但其执行是异步的。可以链接第一条trace的最后一个span到第二条trace的第一个span,如此,它们之间就有了因果关系。

//创建span的时候去addLink
Span child = tracer.spanBuilder("childWithLink")
        .addLink(parentSpan1.getSpanContext())
        .addLink(parentSpan2.getSpanContext())
        .addLink(parentSpan3.getSpanContext())
        .addLink(remoteSpanContext)
    .startSpan();

5)Span Status :

就是给Span加个状态,比如出现异常时设置span状态为error。共有三种状态:

  • Unset
  • Ok
  • Error
Span span = tracer.spanBuilder("my span").startSpan();
// put the span into the current Context
try (Scope scope = span.makeCurrent()) {
	// do something
} catch (Throwable throwable) {
  span.setStatus(StatusCode.ERROR, "Something bad happened!");
  //Record exceptions in spans
  span.recordException(throwable);
} finally {
  span.end(); // Cannot set a span after this call
}


上面这个架子,配合过滤器来手动埋点如下:

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
    Span span = getServerSpan(openTelemetry.getTracer(appConfig.getApplicationName()), httpServletRequest);
    try (Scope scope = span.makeCurrent()) {
        filterChain.doFilter(servletRequest, servletResponse);
    } catch (Exception ex) {
        span.setStatus(StatusCode.ERROR, "HTTP Code: " + ((HttpServletResponse)servletResponse).getStatus());
        span.recordException(ex);
        throw ex;
    } finally {
        span.end();
    }
}

5)Span Kind :

不同种类的span用在不同类型的操作中,span类型在创建span时设置。官方文档里说根据OpenTelemetry规范:

the parent of a server span is often a remote client span, and the child of a client span is usually a server span. Similarly, the parent of a consumer span is always a producer and the child of a producer span is always a consumer. If not provided, the span kind is assumed to be internal.

  • Client
  • Server
  • Internal
  • Producer
  • Consumer

本质是种规范,了解下,不想扣文字。

Span span = tracer.spanBuilder(httpServletRequest.getRequestURI())
	.setSpanKind(SpanKind.SERVER)  //设置为Server类型
	.startSpan();

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

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

相关文章

【高分论文密码】大尺度空间模拟预测与数字制图教程

详情点击链接&#xff1a;【高分论文密码】大尺度空间模拟预测与数字制图 一&#xff0c;R语言空间数据及数据挖掘关键技术 1、R语言空间数据及应用特点 1)R语言基础与数据科学 2)R空间矢量数据 3)R栅格数据 2、R语言空间数据挖掘关键技术 二&#xff0c;R语言空间数据高…

【并发编程】线程池

背景 线程的创建和销毁都需要很大的开销&#xff0c;当线程数量过大&#xff0c;并且线程生命周期短。这时候线程频繁地创建和销毁就很没有必要。 在 Java 中可以通过线程池来解决此问题。线程池里的每一个线程代码结束后&#xff0c;并不会死亡&#xff0c;而是再次回到线程…

[Java] 观察者模式简述

模式定义&#xff1a;定义了对象之间的一对多依赖&#xff0c;让多个观察者对象同时监听某一个主题对象&#xff0c;当主题对象发生变化时&#xff0c;他的所有依赖者都会收到通知并且更新 依照这个图&#xff0c;简单的写一个代码 package Section1.listener;import java.ut…

枚举类型

enum 枚举类型名 {命名枚举常量列表}; enum DAYS {MON, TUE, WED, THU, FRI, SAT, SUN};

POLARDB IMCI 白皮书 云原生HTAP 数据库系统 一 数据压缩和打包处理与数据更新

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

【报错】sqli-labs靶场搭建出现“Unable to connect to the database: security”

问题描述 搭建sqli-labs靶场时&#xff0c;在配置好PHP和mysql以及db-creds.inc配置文件后 初始界面可以运行&#xff0c;但点击关卡报错 提示连接不到数据库 Unable to connect to the database: security解决方案&#xff1a; 1、数据库配置出错&#xff0c;先查看db-cre…

ad+硬件每日学习十个知识点(11)23.7.22

文章目录 1.怎么使用quartus2编译工程生成sof文件&#xff1f;2.怎么使用quartus2下载程序到fpga芯片&#xff1f;3.为什么sof文件烧录后&#xff0c;fpga断电重启&#xff0c;程序会丢失&#xff1f;4.怎么使用quartus2把sof文件转换成jic文件&#xff1f;5.quartus2分配引脚的…

臻图信息以数字孪生赋能智慧文旅新发展

随着AI、元宇宙的热度持续攀升&#xff0c;以及中共中央办公厅、国务院办公厅此前印发了《“十四五”文化发展规划》、《关于推进实施国家文化数字化战略的意见》等重要文件&#xff0c;要求各地区各部门发挥好对产业的引导、扶持和监管作用。 数字孪生作为元宇宙建设的基石&am…

Pytorch个人学习记录总结 玩俄罗斯方块の深度学习小项目

目录 前言 模型成果演示 训练过程演示 代码实现 deep_network tetris test train 前言 当今&#xff0c;深度学习在各个领域展现出了惊人的应用潜力&#xff0c;而游戏开发领域也不例外。俄罗斯方块作为经典的益智游戏&#xff0c;一直以来深受玩家喜爱。在这个项目中&…

2、HAproxy调度算法

HAProxy的调度算法可以大致分为以下几大类&#xff1a; 静态算法&#xff1a;这类算法的调度策略在配置时就已经确定&#xff0c;并且不会随着负载的变化而改变。常见的静态算法有&#xff1a; Round Robin(轮询) Least Connections(最少连接数) Static-Weight(静态权重) Sourc…

Java16,执行tomcat的startup.bat脚本时一闪而过

win10 JDK16 tomcat-9.0.27 只需两步即可。。并没有其他那些文章说的那么多&#xff0c;什么JRE_HOME了&#xff0c;CLASSPATH了&#xff0c;&#xff0c;完全是乱扯。。。在此之前我从来没有配置过java环境变量。要不是tomcat的启动需要JAVA_HOME&#xff0c;我估计最终我都…

cppcheck使用

cppcheck使用 cppcheck Linux下 cppcheck 是一个静态代码检查工具&#xff0c;支持c, c 代码&#xff1b;作为编译器的一种补充检查&#xff0c;cppcheck对产品的源代码执行严格的逻辑检查。 执行的检查包括&#xff1a; 自动变量检查 数组的边界检查 class类检查 过期的函数…

计算机图形学十二光线追踪原理及实现细节

Whitted-style&#xff08;递归式&#xff09;光线追踪原理及实现细节 摘要 本篇文章主要分两个部分&#xff0c;第一部分会从为什么需要从光线追踪入手&#xff0c;一步步介绍Whitted-style光线追踪的原理&#xff0c;第二部分会具体介绍一些光线追踪的细节&#xff0c;包括…

谷粒商城第六天-实现功能的前序工作(网关的配置 跨域配置)

目录 一、为什么要做这项工作 1.1 为什么要配置网关 1.2 为什么要使用网关统一配置跨域 二、网关配置 三、统一跨域配置 四、总结 一、为什么要做这项工作 1.1 为什么要配置网关 我们知道网关的作用其实主要就是进行路由的&#xff0c;也就是根据前端发送到网关的请求&…

无涯教程-jQuery - unbind()方法函数

unbind([type]&#xff0c;[fn])方法的作用与bind相反&#xff0c;它从每个匹配的元素中删除绑定事件。 unbind( [type], [fn] ) - 语法 selector.unbind( [type], [fn] ) 这是此方法使用的所有参数的描述- type - 一种或多种事件类型&#xff0c;以空格分隔。 fn …

【机器学习】基础知识点的汇总与总结!更新中

文章目录 一、监督学习1.1、单模型1.1.1、线性回归1.1.2、逻辑回归&#xff08;Logistic Regression&#xff09;1.1.3、K近邻算法&#xff08;KNN&#xff09;1.1.4、决策树1.1.5、支持向量机&#xff08;SVM&#xff09;1.1.6、朴素贝叶斯 1.2、集成学习1.2.1、Boosting1&…

本地文件夹上传到Github

本地文件夹上传到Github 步骤1. 下载git步骤2. 在github中新建一个库&#xff08;Repository&#xff09;步骤3. 设置SSH key步骤4. 添加SSH keys步骤5. 本地文件上传到github参考 步骤1. 下载git 下载git客户端&#xff0c;并在本地安装完成。 步骤2. 在github中新建一个库&a…

解决 Windows 11 原生输入法卡顿问题

文章目录 词库损坏问题方法1. 删除个人词库方法2. 删除中文词库 网络延迟问题方法3&#xff1a;关闭云服务 资源调度问题方法4&#xff1a;调整优先级 升级兼容问题方法5&#xff1a;关闭兼容性&#xff08;针对 Win10 升级 Win11 的部分用户&#xff09; 终极大招 不知道有没有…

嵌入式数据库之SQLite

1.SQLite简介 轻量化&#xff0c;易用的嵌入式数据库&#xff0c;用于设备端的数据管理&#xff0c;可以理解成单点的数据库。传统服务器型数据 库用于管理多端设备&#xff0c;更加复杂。 SQLite是一个无服务器的数据库&#xff0c;是自包含的。这也称为嵌入式数据库&#x…

项目2 | 负载均衡式在线OJ

啊我摔倒了..有没有人扶我起来学习.... &#x1f471;个人主页&#xff1a; 《 C G o d 的个人主页》 \color{Darkorange}{《CGod的个人主页》} 《CGod的个人主页》交个朋友叭~ &#x1f492;个人社区&#xff1a; 《编程成神技术交流社区》 \color{Darkorange}{《编程成神技术…