链路传播(Propagate)机制及使用场景

news2024/12/26 23:19:26

服务间链路追踪传播机制是指在微服务架构中,通过记录和跟踪服务之间的请求和响应信息,来实现对服务间链路的追踪和监控。这种机制可以帮助开发人员快速定位服务间出现的问题,并进行优化和调整。

具体来说,服务间链路追踪传播机制可以通过在每个服务的请求和响应中添加唯一标识符来实现。当一个服务发送请求到另一个服务时,它会将自己的唯一标识符添加到请求头中,并发送给目标服务。目标服务收到请求后,会将请求头中的唯一标识符复制到响应头中,并返回给调用方。调用方收到响应后,可以通过唯一标识符来追踪服务间的调用链路。

为了实现服务间链路追踪传播机制,通常会使用一些开源工具或框架,比如OpenTelemetryDataDogZipkinSkyWalking 等。这些工具可以自动记录服务间的请求和响应信息,并提供可视化的界面来帮助开发人员进行调试和优化。

通常,我们会将这种特殊标识的请求头称之为传播协议

常见传播协议

不同的 APM 工具支持一种或者多种传播协议,以便更好的服务于应用。

  • b3
  • uber
  • sw8
  • w3c
  • datadog
  • ot

传播协议与APM产品

OpenTelemetry 除了原生支持 otw3cb3uber几种协议外,通过:opentelemetry-collector-contrib 支持 sw8datadog传播协议。

B3 协议

B3 传播协议是第一个链路追踪协议,这里重点讲述 B3 传播协议相关内容。

B3 有两种编码:Single Header 和 Multiple Header。

  • 多个标头编码 X-B3-在跟踪上下文中使用每个项目的前缀标头
  • 单个标头将上下文分隔为一个名为 b3. 提取字段时,单头变体优先于多头变体。

这是一个使用多个标头编码的示例流程,假设 HTTP 请求带有传播的跟踪:

在这里插入图片描述

Multiple Headers

需要配合多个 header key 使用。

TraceId

X-B3-TraceId 标头编码为 32 或 16 个低十六进制字符。例如,128 位 TraceId 标头:X-B3-TraceId:463ac35c9f6413ad48485a3953bb6124。除非仅传播“采样状态”,否则需要 X-B3-TraceId 标头。

SpanId

X-B3-SpanId 标头编码为16个较低的十六进制字符。例如:X-B3-SpanId:a2fb4a1d1a96d312。除非仅传播“采样状态”,否则需要 X-B3-SpanId 标头。

ParentSpanId

X-B3-ParentSpanId标头可能存在于子跨度上,而在根跨度上必须不存在。它被编码为16个低十六进制字符。例如,ParentSpanId标头可能看起来像:X-B3-ParentSpanId:00020000000000001

Sampling State

接受采样决定编码为X-B3-Sampled:1,拒绝编码为X-B3-Sampled:0。缺席意味着将决定推迟到该报头的接收方。例如:X-B3-Sampled:1

注:在编写本规范之前,一些示踪剂传播X-B3-Sampledtruefalse,而不是10。虽然您不应该将X-B3-Sampled编码为truefalse,但宽松的实现可能会接受它们。

Debug Flag

调试编码为X-B3-Flags: 1。可以忽略 Absent 或任何其他值。调试意味着接受决定,所以不要同时发送X-B3-Sampled标头。

Single Header

Single Header 只有一个header 为b3的 key 作为标记。

b3={TraceId}-{SpanId}-{SamplingState}-{ParentSpanId}

b3: 80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-d

实现场景

目前主要有两大实现场景:

  • APM:主要是服务端之间传播
  • RUM:用户端(web、小程序等)与服务端传播

APM

OpenTelemetry 传播器源码

OpenTelemetry 传播器源码

传播器接口:TextMapPropagator

opentelemetry-java 通过定义统一的接口TextMapPropagator来实现各种 Propagate。

TextMapPropagator默认提供了一个空实现noop()

TextMapPropagator 定义了三个接口方法:

  • fields() : 规定了哪些字段作为传播器识别的对象
  • inject():定义了传播器字段注入规则
  • extract():定义传播器字段提取规则,返回Context对象

一些厂商有自己的 APM 标准,通过扩展TextMapPropagator接口,实现对接厂商特定的传播器。

@ThreadSafe
public interface TextMapPropagator {

  static TextMapPropagator composite(TextMapPropagator... propagators) {
    return composite(Arrays.asList(propagators));
  }

  static TextMapPropagator composite(Iterable<TextMapPropagator> propagators) {
    List<TextMapPropagator> propagatorsList = new ArrayList<>();
    for (TextMapPropagator propagator : propagators) {
      propagatorsList.add(propagator);
    }
    if (propagatorsList.isEmpty()) {
      return NoopTextMapPropagator.getInstance();
    }
    if (propagatorsList.size() == 1) {
      return propagatorsList.get(0);
    }
    return new MultiTextMapPropagator(propagatorsList);
  }

  static TextMapPropagator noop() {
    return NoopTextMapPropagator.getInstance();
  }

  Collection<String> fields();

  <C> void inject(Context context, @Nullable C carrier, TextMapSetter<C> setter);

  <C> Context extract(Context context, @Nullable C carrier, TextMapGetter<C> getter);
}

OtTracePropagator

OtTracePropagator 是 OpenTelemetry 特有的传播器,识别解析ot-开头的header,部分源码如下:

@Immutable
public final class OtTracePropagator implements TextMapPropagator {

  static final String TRACE_ID_HEADER = "ot-tracer-traceid";
  static final String SPAN_ID_HEADER = "ot-tracer-spanid";
  static final String SAMPLED_HEADER = "ot-tracer-sampled";
  static final String PREFIX_BAGGAGE_HEADER = "ot-baggage-";
  private static final Collection<String> FIELDS =
      Collections.unmodifiableList(Arrays.asList(TRACE_ID_HEADER, SPAN_ID_HEADER, SAMPLED_HEADER));
    ....
  @Override
  public Collection<String> fields() {
    return FIELDS;
  }

  @Override
  public <C> void inject(Context context, @Nullable C carrier, TextMapSetter<C> setter) {
    if (context == null || setter == null) {
      return;
    }
    SpanContext spanContext = Span.fromContext(context).getSpanContext();
    if (!spanContext.isValid()) {
      return;
    }
    // Lightstep trace id MUST be 64-bits therefore OpenTelemetry trace id is truncated to 64-bits
    // by retaining least significant (right-most) bits.
    setter.set(
        carrier, TRACE_ID_HEADER, spanContext.getTraceId().substring(TraceId.getLength() / 2));
    setter.set(carrier, SPAN_ID_HEADER, spanContext.getSpanId());
    setter.set(carrier, SAMPLED_HEADER, String.valueOf(spanContext.isSampled()));

    // Baggage is only injected if there is a valid SpanContext
    Baggage baggage = Baggage.fromContext(context);
    if (!baggage.isEmpty()) {
      // Metadata is not supported by OpenTracing
      baggage.forEach(
          (key, baggageEntry) ->
              setter.set(carrier, PREFIX_BAGGAGE_HEADER + key, baggageEntry.getValue()));
    }
  }

  @Override
  public <C> Context extract(Context context, @Nullable C carrier, TextMapGetter<C> getter) {
    if (context == null) {
      return Context.root();
    }
    if (getter == null) {
      return context;
    }
    String incomingTraceId = getter.get(carrier, TRACE_ID_HEADER);
    String traceId =
        incomingTraceId == null
            ? TraceId.getInvalid()
            : StringUtils.padLeft(incomingTraceId, MAX_TRACE_ID_LENGTH);
    String spanId = getter.get(carrier, SPAN_ID_HEADER);
    String sampled = getter.get(carrier, SAMPLED_HEADER);
    SpanContext spanContext = buildSpanContext(traceId, spanId, sampled);
    if (!spanContext.isValid()) {
      return context;
    }

    Context extractedContext = context.with(Span.wrap(spanContext));

    // Baggage is only extracted if there is a valid SpanContext
    if (carrier != null) {
      BaggageBuilder baggageBuilder = Baggage.builder();
      for (String key : getter.keys(carrier)) {
        if (!key.startsWith(PREFIX_BAGGAGE_HEADER)) {
          continue;
        }
        String value = getter.get(carrier, key);
        if (value == null) {
          continue;
        }
        baggageBuilder.put(key.replace(OtTracePropagator.PREFIX_BAGGAGE_HEADER, ""), value);
      }
      Baggage baggage = baggageBuilder.build();
      if (!baggage.isEmpty()) {
        extractedContext = extractedContext.with(baggage);
      }
    }

    return extractedContext;
  }
 ...
}

初始化传播器

opentelemetry-java 通过PropagatorConfiguration 对象对Propagate进行初始化操作,源码如下:


final class PropagatorConfiguration {

  private static final List<String> DEFAULT_PROPAGATORS = Arrays.asList("tracecontext", "baggage");

  static ContextPropagators configurePropagators(
      ConfigProperties config,
      ClassLoader serviceClassLoader,
      BiFunction<? super TextMapPropagator, ConfigProperties, ? extends TextMapPropagator>
          propagatorCustomizer) {
    Set<TextMapPropagator> propagators = new LinkedHashSet<>();
    List<String> requestedPropagators = config.getList("otel.propagators", DEFAULT_PROPAGATORS);

    NamedSpiManager<TextMapPropagator> spiPropagatorsManager =
        SpiUtil.loadConfigurable(
            ConfigurablePropagatorProvider.class,
            ConfigurablePropagatorProvider::getName,
            ConfigurablePropagatorProvider::getPropagator,
            config,
            serviceClassLoader);

    if (requestedPropagators.contains("none")) {
      if (requestedPropagators.size() > 1) {
        throw new ConfigurationException(
            "otel.propagators contains 'none' along with other propagators");
      }
      return ContextPropagators.noop();
    }
    for (String propagatorName : requestedPropagators) {
      propagators.add(
          propagatorCustomizer.apply(getPropagator(propagatorName, spiPropagatorsManager), config));
    }

    return ContextPropagators.create(TextMapPropagator.composite(propagators));
  }

  private static TextMapPropagator getPropagator(
      String name, NamedSpiManager<TextMapPropagator> spiPropagatorsManager) {
    if (name.equals("tracecontext")) {
      return W3CTraceContextPropagator.getInstance();
    }
    if (name.equals("baggage")) {
      return W3CBaggagePropagator.getInstance();
    }

    TextMapPropagator spiPropagator = spiPropagatorsManager.getByName(name);
    if (spiPropagator != null) {
      return spiPropagator;
    }
    throw new ConfigurationException(
        "Unrecognized value for otel.propagators: "
            + name
            + ". Make sure the artifact including the propagator is on the classpath.");
  }

  private PropagatorConfiguration() {}
}

启动 agent 时,通过指定传播器类型来实现对应实例构造,通过方法得知,otel 支持同时指定多种类型的传播器协议,默认采用tracecontext传播器——即w3c标准的传播器:
List<String> requestedPropagators = config.getList("otel.propagators", DEFAULT_PROPAGATORS);

获取传播器

opentelemetry-java 通过OpenTelemetry对象的propagating()方法构造当前支持的传播器,且通过getPropagators()方法来获取已有的传播器,OpenTelemetry是一个接口类,部分代码如下:

public interface OpenTelemetry {
    static OpenTelemetry noop() {
        return DefaultOpenTelemetry.getNoop();
    }

    static OpenTelemetry propagating(ContextPropagators propagators) {
        return DefaultOpenTelemetry.getPropagating(propagators);
    }

    TracerProvider getTracerProvider();
    
    ContextPropagators getPropagators();

    ...
}

每一个Instrumente都需要通过InstrumenterBuilder获取OpenTelemetry 对象来实现 trace、metric 相关业务逻辑。比如,启动一个 span。

如何获取OpenTelemetry对象

通过上面的研究,我们知道如何获取传播器,但是获取传播器需要获取OpenTelemetry对象,opentelemetry-java提供了一个全局的对象GlobalOpenTelemetry来获取OpenTelemetry对象。我们来看看具体代码

public final class GlobalOpenTelemetry {
...
    @Nullable
    private static volatile ObfuscatedOpenTelemetry globalOpenTelemetry;

    public static OpenTelemetry get() {
        OpenTelemetry openTelemetry = globalOpenTelemetry;
        if (openTelemetry == null) {
            synchronized(mutex) {
                openTelemetry = globalOpenTelemetry;
                if (openTelemetry == null) {
                    OpenTelemetry autoConfigured = maybeAutoConfigureAndSetGlobal();
                    if (autoConfigured != null) {
                        return autoConfigured;
                    }

                    set(OpenTelemetry.noop());
                    return OpenTelemetry.noop();
                }
            }
        }

        return openTelemetry;
    }

    public static void set(OpenTelemetry openTelemetry) {
        synchronized(mutex) {
            if (globalOpenTelemetry != null) {
                throw new IllegalStateException("GlobalOpenTelemetry.set has already been called. GlobalOpenTelemetry.set must be called only once before any calls to GlobalOpenTelemetry.get. If you are using the OpenTelemetrySdk, use OpenTelemetrySdkBuilder.buildAndRegisterGlobal instead. Previous invocation set to cause of this exception.", setGlobalCaller);
            } else {
                globalOpenTelemetry = new ObfuscatedOpenTelemetry(openTelemetry);
                setGlobalCaller = new Throwable();
            }
        }
    }
    @Nullable
    private static OpenTelemetry maybeAutoConfigureAndSetGlobal() {
        Class openTelemetrySdkAutoConfiguration;
        try {
            openTelemetrySdkAutoConfiguration = Class.forName("io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk");
        } catch (ClassNotFoundException var7) {
            return null;
        }

        boolean globalAutoconfigureEnabled = Boolean.parseBoolean(ConfigUtil.getString("otel.java.global-autoconfigure.enabled", "false"));
        if (!globalAutoconfigureEnabled) {
            logger.log(Level.INFO, "AutoConfiguredOpenTelemetrySdk found on classpath but automatic configuration is disabled. To enable, run your JVM with -Dotel.java.global-autoconfigure.enabled=true");
            return null;
        } else {
            try {
                Method initialize = openTelemetrySdkAutoConfiguration.getMethod("initialize");
                Object autoConfiguredSdk = initialize.invoke((Object)null);
                Method getOpenTelemetrySdk = openTelemetrySdkAutoConfiguration.getMethod("getOpenTelemetrySdk");
                return new ObfuscatedOpenTelemetry((OpenTelemetry)getOpenTelemetrySdk.invoke(autoConfiguredSdk));
            } catch (IllegalAccessException | NoSuchMethodException var5) {
                throw new IllegalStateException("AutoConfiguredOpenTelemetrySdk detected on classpath but could not invoke initialize method. This is a bug in OpenTelemetry.", var5);
            } catch (InvocationTargetException var6) {
                logger.log(Level.SEVERE, "Error automatically configuring OpenTelemetry SDK. OpenTelemetry will not be enabled.", var6.getTargetException());
                return null;
            }
        }
    }

    ...

GlobalOpenTelemetry 通过单例模式实现获取全局OpenTelemetry对象。而OpenTelemetry则主要是通过AutoConfiguredOpenTelemetrySdkgetOpenTelemetrySdk方法初始化构建。

至此,基本上完成了opentelemetry-java从传播器实现、构造、初始化及获取相关操作。

DataDog(DDtrace)传播器源码

DataDog(DDtrace)传播器源码

DDtrace除了支持上面表格中的传播协议之外,还支持其他传播协议,如:

  • Haystack
    • Haystack-Trace-ID
    • Haystack-Span-ID
    • Haystack-Parent-ID
  • XRAY
    • X-Amzn-Trace-Id

RUM

前面了解了传播器的原理、作用以及实现方式。传播器提到最多的是 header,也就说大多数都是基于 http 协议。基于 http 协议,进一步引申到 html、小程序、Android、iOS 等,从而引出另一个概念:RUM

RUM 全称为 Real User Monitor,即真实用户监控。

RUM 通过实现不同的传播器协议,将用户端与服务端紧密联系在一起,我们知道 APM 是后端接口及服务的性能表现,但接口用于地方很多,有的来源于 web,有的来源于小程序等等。通过引入 RUM,结合链路分析,很容易追踪到数据来源于哪一端。

当然 RUM 的作用不仅仅是这一方面,目前市面上常见的是基于 W3C(万维网联盟)定义的[navigation-timing] 标准(见下图),该标准详细定义了各种浏览器事件,通过浏览器事件的简单计算就可以算出来前端页面的首屏、白屏、DOM 加载、HTML 加载等时长,相较于测试环境的 F12 检查者模式,能有更效的收集生产环境真实用户的前端体验。

在这里插入图片描述

opentelemetry-js

opentelemetry-js是 OpenTelemetry 具体的实现,目前支持 nodejs 和 js,作用范围比较有限,主要围绕 OpenTelemetry 的集中传播器协议实现,产品成熟度仍需进一步提升。

opentelemetry-js demo

观测云 RUM

是观测云推出的 RUM 产品,可以与多种 APM 技术融合,本质上是支持多种传播器协议,进而能够与各种 APM 结合使用。

支持的传播器协议有:

  • ddtrace :x-datadog-parent-id,x-datadog-sampled,x-datadog-sampling-priority,x-datadog-trace-id。
  • skywalking: sw8。
  • jaeger: uber-trace-id。
  • zipkin: X-B3-TraceId、X-B3-SpanId、X-B3-ParentSpanId、X-B3-Sampled、X-B3-Flags。
  • zipkin_single_header: b3。
  • w3c_traceparent: traceparent。
  • opentelemetry: 该类型支持 zipkin_single_header,w3c_traceparent,zipkin、jaeger三种类型的配置方式,根据在 rum sdk 中配置的 traceType 类型 添加对应的 header。

目前支持的应用有:

  • web H5
  • 小程序
  • Android
  • iOS
  • React Native
  • Flutter
  • UniApp
  • C++

以 web 为例,展示接入方式,接入简单。

<script src="https://static.guance.com/browser-sdk/v3/dataflux-rum.js" type="text/javascript"></script>
<script>
  window.DATAFLUX_RUM &&
    window.DATAFLUX_RUM.init({
      applicationId: '<应用 ID>',
      datakitOrigin: '<DATAKIT ORIGIN>', // 协议(包括://),域名(或IP地址)[和端口号]
      env: 'production',
      version: '1.0.0',
      service: 'browser',
      sessionSampleRate: 100,
      sessionReplaySampleRate: 70,
      trackInteractions: true,
      traceType: 'ddtrace', // 非必填,默认为ddtrace,目前支持 ddtrace、zipkin、skywalking_v3、jaeger、zipkin_single_header、w3c_traceparent 6种类型
      allowedTracingOrigins: ['https://api.example.com',/https:\/\/.*\.my-api-domain\.com/],  // 非必填,允许注入trace采集器所需header头部的所有请求列表。可以是请求的origin,也可以是是正则
    })
</script>

其中 traceType 为传播器协议。

通过 CDN 加速缓存,以同步脚本引入的方式引入 SDK,此方式可以确保能够收集到所有的错误,资源,请求,性能指标,不过可能会影响页面的加载性能。
单页应用建议将下方代码复制粘贴到 html 文件 head 标签的首行,多页应用建议将下方代码复制粘贴到公共 head 模版文件的首行。

更多更详细接入方式,参考官方文档用户访问监测

RUM 与 APM 实际应用

以某开源平台访问用户资源为例,查看实际应用场景,如下图所示:

在这里插入图片描述

右侧服务列表:

  • browser :浏览器链路,由RUM端上报
  • 其他服务为 APM 上报

RUM 通过传递特定的 header 信息,将链路信息传递给 APM,APM 正确解析相关传播协议,并以 RUM 传递的 trace为基准,创建 span信息,实现前后端串联。
browser同时可以查看当前用户的请求耗时分布(128.3 ms)
通过右上角view 按钮,可以跳转到当前页面的查看器,以此来查看分析用户行为。如下图所示

在这里插入图片描述

Baggage

前面已经分析了传播器一般需要携带的参数信息,便于前后端的应用串联,但实际上这只是传播器的基础用法,通常都是用于传递 trace 信息,实际上,在上面 opentelemetry-java 源码分析中,有一个频繁出现的单词baggagebaggage主要作用于业务参数的传递。
通常,研发人员不会直接在接口上填上用户信息,所以无法准确追踪这个接口是哪个用户触发的,借助 baggage,通过 header 进行传递,能够在服务间无限传递下去。

OpenTelemetry Baggage 用法

Header key 以ot-baggage- 开头的参数,会被 OpenTelemetry 一直传递下去。

还可以采用sdk的方式:OpenTelemetry Baggage SDK用法

DataDog Baggage 用法

需要配置启动参数dd.trace.header.baggage,设置哪些header 作为传递的参数依据。用法如下:

Environment Variable: DD_TRACE_HEADER_BAGGAGE
Default: null
Example: CASE-insensitive-Header:my-baggage-name,User-ID:userId,My-Header-And-Baggage-Name
Accepts a map of case-insensitive header keys to baggage keys and automatically applies matching request header values as baggage on traces. On propagation the reverse mapping is applied: Baggage is mapped to headers.
Available since version 1.3.0.

也可以采用 SDK 的方式:DataDog Baggage SDK 用法

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

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

相关文章

nvm的简介、安装、使用

一、nvm是什么&#xff1f; .nvm是一个node的版本管理工具&#xff0c;可以简单操作node版本的切换、安装、查看。。。等等&#xff0c;与npm不同的是&#xff0c;npm是依赖包的管理工具。 二、nvm的安装。 点击如下文件进行安装&#xff1a; &#xff08;1&#xff09;安装…

超声波功率放大器工作原理是什么意思

超声波功率放大器是一种能够将低功率、小振幅的超声波信号放大至足够高功率和大振幅输出的电子设备。它通常被用于超声波清洗、焊接、切割、医疗等领域中。 超声波功率放大器的工作原理基于声学共振的原理。具体来说&#xff0c;超声波功率放大器由一个压电陶瓷换能器和一个功放…

增强型(RX651)R5F56514EDFP、R5F5651EHDFP、R5F5651CDDBP微控制器,强化工业物联网安全。

RX651 微控制器 (MCU)系列产品&#xff0c;以满足工业自动化、楼宇自动化和智能表计系统更高的安全需求。扩展的微控制器集成了Trusted SecureIP&#xff08;TSIP&#xff09;&#xff0c;以及用于工业和网络控制系统的增强型可靠闪存功能和人机界面&#xff08;HMI&#xff0…

C# HTTP Error 500.19

解决办法&#xff1a; .vs configapplicationhost.config 修改<section name"windowsAuthenticationnurununoverrideModeDefault"Allow”/>

QQ消息是如何到达接收方的?看完这个你就明白了

A通过QQ给异地的B发了条消息&#xff0c;直到B收到了消息&#xff0c;中间经历了怎样的过程? 北京的A通过QQ给深圳的B发了一条消息&#xff0c;B在QQ上接收到了消息&#xff0c;从A点击发送开始&#xff0c;到B看到消息结束&#xff0c;中间过程是如何实现的&#xff1f;中间…

.Net Core Restful Api 版本区分第一种

前言&#xff1a;在我们进行Web Api开发时&#xff0c;版本的区分&#xff0c;是必须要考虑的&#xff0c;涉及到我们的版本发布&#xff0c;切换等&#xff0c;如何从旧版本无缝的切换到新版本&#xff1f; 下面&#xff0c;我们通过使用[ApiVersion]特性&#xff0c;实现两个…

超详细的学习笔记:CSS定位装饰(附代码示例)

笔记参考b站网课&#xff1a;【前端开发入门教程&#xff0c;web前端零基础html5 css3前端项目视频教程】https://www.bilibili.com/video/BV1Kg411T7t9?p124&vd_source06e5549bf018e111f4275c259292d0da 目录 一、网页常见布局方式 1、标准流 2、浮动 3、定位 二、定…

软件鉴定测试报告需要哪些材料?

软件鉴定测试报告是对软件产品进行功能、性能和安全等方面的测试和评估后所生成的报告。软件鉴定测试报告作为软件质量的重要指标&#xff0c;为软件的发布和应用提供可靠的依据。以下是软件鉴定测试报告中常见的材料内容&#xff1a; 1. 软件测试计划&#xff1a;包括测试目的…

UncategorizedSQLException 报错

85、UncategorizedSQLException 报错 出现问题的原因&#xff1a; 本身是没有这个问题的&#xff0c;后来服务器上的一张表&#xff0c;被误删了&#xff0c;重新创建之后&#xff0c;就出现了这个问题 org.springframework.jdbc.UncategorizedSQLException: ### Error upd…

C++—类和对象

文章目录 1 类2 对象2.1 创建对象2.2 对象的操作2.3 构造函数2.4 析构函数 3 静态成员4 this指针5 友元 一切我们研究的事物&#xff0c;都可以叫做对象。对象具有状态&#xff0c;操作和行为。通常用一个数值来描述对象的状态。对象的操作用于改变对象的状态。对象和对象的操作…

BlueZ 开发学习指南(一) --- D-Bus介绍

BlueZ 开发学习指南&#xff08;一&#xff09; — D-Bus介绍 一、 BlueZ与D-Bus简介 Linux使用的蓝牙协议栈是Blue Z&#xff0c;不同于我们以往的开发方式&#xff0c;Blue Z提供的API 并不是通过头文件这样的形式&#xff0c; 而是通过D-Bus的方式来提供的。 Blue Z提供的是…

AutoDL 训练stable-diffusion lora模型

1.创建镜像实例 2. 启动实例 3.启动服务 4.配置参数 4.1 基础模型选择 4.2 文件路径设置 5.点击打印训练信息 6.训练模型&#xff08;点击Train model&#xff09;

C++ 迭代器的设计与使用

C 迭代器是一种用于访问容器&#xff08;例如数组、向量、列表等&#xff09;元素的工具。它们允许我们以一种统一的方式遍历和操作容器中的数据&#xff0c;而不用关心容器内部数据结构的细节 C 迭代器 std 重要函数 std::begin 和 std::endstd::advance(iter,n)向前移动…

从零开始,用Python编写EA实战指南

在外汇交易领域&#xff0c;EA&#xff08;Expert Advisor&#xff09;是一种基于计算机程序的交易策略&#xff0c;被广泛应用于机器人化交易和量化交易。Python作为一种高效、灵活的编程语言&#xff0c;被越来越多的投资者用于编写EA和数据分析。本文将提供一份从零开始&…

Postgresql在哪里使用列统计信息?

对pg_statistic表的查询都是走syscache的&#xff0c;要找到所有使用列统计信息地方&#xff0c;遍历系统表索引即可 enum SysCacheIdentifier {...STATEXTDATASTXOID,STATEXTNAMENSP,STATEXTOID,STATRELATTINH,... }下面是最常用的STATRELATTINH索引场景&#xff0c;即 Sear…

行业追踪,2023-07-13,新样式来了,更清晰地追踪行业趋势

自动复盘 2023-07-13 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

利用 Elasticsearch、ESRE、LLM 和 LangChain 加速制药行业的研发 — 第 1 部分

作者&#xff1a;Valerio Arvizzigno, Dimitri Marx, Francesco Di Stefano 这是一篇通过生成式 AI/LLM、自定义模型和 Elasticsearch 相关性引擎 (ESRE​​) 支持制药行业更快的药物创新和发现的综合指南。更快的药物发现带来有前途的候选药物是制药行业的主要目标。 为了支持…

Mysql表空间、段、区、页的关系

提示&#xff1a;mysql表空间、段、区、页的关系详细描述 文章目录 表空间-TABLE SPACE1 查看 表空间相关参数段&#xff08;segment&#xff09;区&#xff08;extent&#xff09;页&#xff08;page&#xff09; 表空间-TABLE SPACE 从 InnoDB 逻辑存储结构来看&#xff0c;…

顺序表 --- C语言实现

目录 1.线性表 2.顺序表 2.1 概念和结构 2.2 接口实现 2.3 数组相关面试题 2.4 顺序表的问题及思考 1.线性表 什么是线性表 &#xff1a; 线性表&#xff08;linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构&#xff0c;常…

Vue3 概述

文章目录 Vue3 概述概述Vue3对比Vue2优势使用create-vue创建项目概述创建项目目录结构 使用vue-cli创建项目概述创建项目目录结构 Vue3 概述 概述 Vue (发音为 /vjuː/&#xff0c;类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript…