【SkyWalking】SkyWalking是如何实现跨进程传播链路数据?
文章目录
- 一、简介
- 1 为什么写这篇文章
- 2 跨进程传播协议-简介
- 二、协议
- 1 Standard Header项
- 2 Extension Header项
- 3 Correlation Header项
- 三、跨进程传播协议的源码分析
- 1 OpenTracing规范
- 2 通过dubbo插件分析跨进程数据传播
- 3 分析跨进程传播协议的核心源码
- 四、小结
- 参考
一、简介
1 为什么写这篇文章
写这篇文章是为了让自己和大家梳理这些内容:
- SkyWalking的链路串联依赖跨进程数据传播,他的跨进程传播协议是怎样的?
- 如果我想借助SkyWalking的跨进程传播协议实现传递全链路业务数据(如全局userId等),该如何实现?
2 跨进程传播协议-简介
SkyWalking 跨进程传播协议是用于上下文的传播,之前经历过sw3协议、sw6协议,本文介绍是当前(2023年)最新的sw8协议。
该协议适用于不同语言、系统的探针之间传递上下文。
二、协议
Header项分为三类:
- Standard Header项,Header名称:sw8
- Extension Header项,Header名称:sw8-x
- Correlation Header项,Header名称:sw8-correlation
协议的整体设计:
下面详细讲解协议的Header项:
1 Standard Header项
该Header项是上下文传播必须包含的。
- Header名称:sw8.
- Header值:由-分隔的8个字段组成。Header值的长度应该小于2KB。
Header值中具体包含以下8个字段:
- 采样(Sample),0 或 1,0 表示上下文存在,但是可以(也很可能)被忽略而不做采样;1 表示这个trace需要采样并发送到后端。
- 追踪ID(Trace Id),是 Base64 编码的字符串,其内容是由 . 分割的三个 long 类型值, 表示此trace的唯一标识。
- 父追踪片段ID(Parent trace segment Id),是 Base64 编码的字符串,其内容是字符串且全局唯一。
- 父跨度ID(Parent span Id),是一个从 0 开始的整数,这个跨度ID指向父追踪片段(segment)中的父跨度(span)。
- 父服务名称(Parent service),是 Base64 编码的字符串,其内容是一个长度小于或等于50个UTF-8编码的字符串。
- 父服务实例标识(Parent service instance),是 Base64 编码的字符串,其内容是一个长度小于或等于50个UTF-8编码的字符串。
- 父服务的端点(Parent endpoint),是 Base64 编码的字符串,其内容是父追踪片段(segment)中第一个入口跨度(span)的操作名,由长度小于或等于50个UTF-8编码的字符组成。
- 本请求的目标地址(Peer),是 Base64 编码的字符串,其内容是客户端用于访问目标服务的网络地址(不一定是 IP + 端口)。
示例值: 1-TRACEID-SEGMENTID-3-PARENT_SERVICE-PARENT_INSTANCE-PARENT_ENDPOINT-IPPORT
2 Extension Header项
该Header项是可选的。扩展Header项是为高级特性设计的,它提供了部署在上游和下游服务中的探针之间的交互功能。
Header名称:sw8-x
Header值:由-分割,字段可扩展。
扩展Header值
当前值包括的字段:
追踪模式(Tracing Mode),空、0或1,默认为空或0。表示在这个上下文中生成的所有跨度(span)应该跳过分析。在默认情况下,这个应该在上下文中传播到服务端,除非它在跟踪过程中被更改。
客户端发送的时间戳:用于异步RPC,如MQ。一旦设置,消费端将计算发送和接收之间的延迟,并使用key transmission.latency自动在span中标记延迟。
示例值:1-1621344125000
3 Correlation Header项
该Header项是是可选的。并非所有语言的探针都支持,已知的是Java的探针是支持该协议。
该Header项用于跨进程传递用户自定义数据,例如userId、orgId。
这个协议跟OpenTracing 的 Baggage很类似,但是Correlation Header项相比,在默认设置下会更有更严格的限制,例如,只能存放3个字段,且有字段长度限制,这个是为了安全、性能等考虑。
数据格式:
Header名称:sw8-correlation
Header值:由,分割一对对key、value,每对key、value逗号分割,key、value的由Base64编码。
示例值:a2V5MQ==:dmFsdWUx,a2V5LTI=:dmFsdWUy
三、跨进程传播协议的源码分析
1 OpenTracing规范
SkyWalking是基于OpenTracing标准的追踪系统,参考吴晟老师翻译的OpenTracing规范的文章opentracing之Inject和Extract,OpenTracing定义了跨进程传播的几个要素:
SpanContext:SpanContext代表跨越进程边界,传递到下级span的状态。在SkyWalking中的实现类是org.apache.skywalking.apm.agent.core.context.TracingContext
Carrier:传递跨进程数据的搬运工,负责将追踪状态从一个进程"carries"(携带,传递)到另一个进程
Inject 和 Extract:SpanContexts可以通过Inject(注入)操作向Carrier增加,或者通过Extract(提取)从Carrier中获取,跨进程通讯数据(例如:HTTP头)。通过这种方式,SpanContexts可以跨越进程边界,并提供足够的信息来建立跨进程的span间关系(因此可以实现跨进程连续追踪)
2 通过dubbo插件分析跨进程数据传播
我们以SkyWalking java agent的dubbo-2.7.x-plugin插件为例,其中跨进程传播数据的核心代码在org.apache.skywalking.apm.plugin.asf.dubbo.DubboInterceptor,下面是该类跨进程传播的核心代码:
public class DubboInterceptor implements InstanceMethodsAroundInterceptor {/*** Consumer: The serialized trace context data will* inject to the {@link RpcContext#attachments} for transport to provider side.* <p>* Provider: The serialized trace context data will extract from* {@link RpcContext#attachments}. current trace segment will ref if the serialization context data is not null.*/@Overridepublic void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,MethodInterceptResult result) throws Throwable {......if (isConsumer) { // 1、consumer端// ContextCarrierfinal ContextCarrier contextCarrier = new ContextCarrier();// 1.1 createExitSpan()内部会调用TracerContext.inject(carrier),将TracerContext中的context数据inject(注入)到ContextCarrier的context中span = ContextManager.createExitSpan(generateOperationName(requestURL, invocation), contextCarrier, host + ":" + port);CarrierItem next = contextCarrier.items();// 1.2 遍历ContextCarrier,从ContextCarrier的context获取数据,注入到dubbo的attachment,从consumer端传递到provider端while (next.hasNext()) {next = next.next();rpcContext.setAttachment(next.getHeadKey(), next.getHeadValue());if (invocation.getAttachments().containsKey(next.getHeadKey())) {invocation.getAttachments().remove(next.getHeadKey());}}} else { // 2 provider端// 2.1 从consumer端传递到provider端的attachment中获取跨进程协议数据,然后设置到contextContextCarrier contextCarrier = new ContextCarrier();CarrierItem next = contextCarrier.items();while (next.hasNext()) {next = next.next();next.setHeadValue(rpcContext.getAttachment(next.getHeadKey()));}// 2.2 createEntrySpan()内部会调用TracerContext.extract(carrier),将ContextCarrier的context数据extract(提取)到将TracerContext中的context中span = ContextManager.createEntrySpan(generateOperationName(requestURL, invocation), contextCarrier);span.setPeer(rpcContext.getRemoteAddressString());}}
}
从上面的源码可以看出在服务调用方和被调用方,都会用到ContextCarrier,他是临时搬运工,负责两个进程的TracerContext数据的传递。
下面分析ContextCarrier等类的核心源码。
3 分析跨进程传播协议的核心源码
TracingContext
org.apache.skywalking.apm.agent.core.context.TracingContext是OpenTracing的SpanContext的一种实现,里面包含了span的上下文,包含在segment、correlationContext、extensionContext,而inject()、extract()负责跨进程上下文透传。
public class TracingContext implements AbstractTracerContext {/*** The final {@link TraceSegment}, which includes all finished spans.*/private TraceSegment segment;@Getter(AccessLevel.PACKAGE)private final CorrelationContext correlationContext;@Getter(AccessLevel.PACKAGE)private final ExtensionContext extensionContext;/*** Prepare for the cross-process propagation. How to initialize the carrier, depends on the implementation.** @param carrier to carry the context for crossing process.*/void inject(ContextCarrier carrier);/*** Build the reference between this segment and a cross-process segment. How to build, depends on the* implementation.** @param carrier carried the context from a cross-process segment.*/void extract(ContextCarrier carrier);
}
ContextCarrier
ContextCarrier作为传递跨进程数据的搬运工,负责将追踪状态从一个进程"carries"(携带,传递)到另一个进程,其中包含了sw8协议里的Standard Header项、Extension Header项、Correlation Header项相关的上下文数据,具体参考下面的代码:
public class ContextCarrier implements Serializable {/*** extensionContext包含了在某些特定场景中用于增强分析的可选上下文,对应sw8的Extension Header项*/private ExtensionContext extensionContext = new ExtensionContext();/*** 用户的自定义上下文容器。此上下文与主追踪上下文一同传播。对应sw8的Correlation Header项*/private CorrelationContext correlationContext = new CorrelationContext();/*** @return 存在于当前tracing上下文中的item清单*/public CarrierItem items() {SW8ExtensionCarrierItem sw8ExtensionCarrierItem = new SW8ExtensionCarrierItem(extensionContext, null);SW8CorrelationCarrierItem sw8CorrelationCarrierItem = new SW8CorrelationCarrierItem(correlationContext, sw8ExtensionCarrierItem);SW8CarrierItem sw8CarrierItem = new SW8CarrierItem(this, sw8CorrelationCarrierItem);return new CarrierItemHead(sw8CarrierItem);}/*** Extract the extension context to tracing context*/void extractExtensionTo(TracingContext tracingContext) {tracingContext.getExtensionContext().extract(this);// The extension context could have field not to propagate further, so, must use the this.* to process.this.extensionContext.handle(tracingContext.activeSpan());}/*** Extract the correlation context to tracing context*/void extractCorrelationTo(TracingContext tracingContext) {tracingContext.getCorrelationContext().extract(this);// The correlation context could have field not to propagate further, so, must use the this.* to process.this.correlationContext.handle(tracingContext.activeSpan());}/*** 序列化sw8的Standard Header项,使用 '-' 分割各个字段* Serialize this {@link ContextCarrier} to a {@link String}, with '|' split.* @return the serialization string.*/String serialize(HeaderVersion version) {if (this.isValid(version)) {return StringUtil.join('-',"1",Base64.encode(this.getTraceId()),Base64.encode(this.getTraceSegmentId()),this.getSpanId() + "",Base64.encode(this.getParentService()),Base64.encode(this.getParentServiceInstance()),Base64.encode(this.getParentEndpoint()),Base64.encode(this.getAddressUsedAtClient()));}return "";}/*** 反序列化sw8的Standard Header项* Initialize fields with the given text.* @param text carries {@link #traceSegmentId} and {@link #spanId}, with '|' split.*/ContextCarrier deserialize(String text, HeaderVersion version) {if (text == null) {return this;}if (HeaderVersion.v3.equals(version)) {String[] parts = text.split("-", 8);if (parts.length == 8) {try {// parts[0] is sample flag, always trace if header exists.this.traceId = Base64.decode2UTFString(parts[1]);this.traceSegmentId = Base64.decode2UTFString(parts[2]);this.spanId = Integer.parseInt(parts[3]);this.parentService = Base64.decode2UTFString(parts[4]);this.parentServiceInstance = Base64.decode2UTFString(parts[5]);this.parentEndpoint = Base64.decode2UTFString(parts[6]);this.addressUsedAtClient = Base64.decode2UTFString(parts[7]);} catch (IllegalArgumentException ignored) {}}}return this;}
}
CorrelationContext
ContextCarrier里包含里sw8的Correlation Header项存放于CorrelationContext,这个类非常有用,适合我们去在全链路跨进程传递自定义的数据。
sw8协议里的Standard Header项、Extension Header项是比较固定的协议格式,我们可以扩展这些协议,例如Standard Header项,当前固定是8位的,对应8个字段,我们可以扩展为9位,第九位可以定义为userId。但是如果要这样改造,就得修改ContextCarrier类序列化、反序列的逻辑,要重新发布agent,并考虑好新旧版本兼容性问题、以及不同语言的agent是否兼容。
而sw8的Correlation Header项使用起来就非常方便。先看下对应实现了CorrelationContext的源码:
/*** Correlation context, use to propagation user custom data.* Correlation上下文,用于传播用户自定义数据*/
public class CorrelationContext {private final Map<String, String> data;/*** Add or override the context. 添加或覆盖上下文数据** @param key to add or locate the existing context* @param value as new value* @return old one if exist.*/public Optional<String> put(String key, String value) {// 可以存放于span的tag中if (AUTO_TAG_KEYS.contains(key) && ContextManager.isActive()) {ContextManager.activeSpan().tag(new StringTag(key), value);}// settingdata.put(key, value);return Optional.empty();}/*** @param key to find the context 获取上下文数据* @return value if exist.*/public Optional<String> get(String key) {return Optional.ofNullable(data.get(key));}/*** Serialize this {@link CorrelationContext} to a {@link String} 序列化** @return the serialization string.*/String serialize() {if (data.isEmpty()) {return "";}return data.entrySet().stream().map(entry -> Base64.encode(entry.getKey()) + ":" + Base64.encode(entry.getValue())).collect(Collectors.joining(","));}/*** Deserialize data from {@link String} 反序列化*/void deserialize(String value) {if (StringUtil.isEmpty(value)) {return;}for (String perData : value.split(",")) {// Only data with limited count of elements can be addedif (data.size() >= Config.Correlation.ELEMENT_MAX_NUMBER) {break;}final String[] parts = perData.split(":");if (parts.length != 2) {continue;}data.put(Base64.decode2UTFString(parts[0]), Base64.decode2UTFString(parts[1]));}}/*** Prepare for the cross-process propagation. Inject the {@link #data} into {@link* ContextCarrier#getCorrelationContext()}*/void inject(ContextCarrier carrier) {carrier.getCorrelationContext().data.putAll(this.data);}/*** Extra the {@link ContextCarrier#getCorrelationContext()} into this context.*/void extract(ContextCarrier carrier) {......}/*** Clone the context data, work for capture to cross-thread. 克隆数据,用于跨线程传递*/@Overridepublic CorrelationContext clone() {final CorrelationContext context = new CorrelationContext();context.data.putAll(this.data);return context;}/*** Continue the correlation context in another thread.传递到另外的线程** @param snapshot holds the context.*/void continued(ContextSnapshot snapshot) {this.data.putAll(snapshot.getCorrelationContext().data);}
}
通过源码可知,CorrelationContext通过Map<String, String>来存放数据,CorrelationContext数据支持跨线程、跨进程透传。
四、小结
分析Dubbo插件的跨进程核心代码,了解了跨进程传播协议的核心实现逻辑。
其实在其他分布式追踪系统(如Zipkin、Jager)、全链路灰度系统等涉及到跨进程数据传播的系统中,也是使用了类似于上面SkyWalking协议的思路。
参考
SkyWalking Cross Process Propagation Headers Protocol
SkyWalking Cross Process Correlation Headers Protocol
详解 Apache SkyWalking 的跨进程传播协议
相关文章:
【SkyWalking】SkyWalking是如何实现跨进程传播链路数据?
文章目录 一、简介1 为什么写这篇文章2 跨进程传播协议-简介 二、协议1 Standard Header项2 Extension Header项3 Correlation Header项 三、跨进程传播协议的源码分析1 OpenTracing规范2 通过dubbo插件分析跨进程数据传播3 分析跨进程传播协议的核心源码 四、小结参考 一、简介…...
px4仿真实现无人机自主飞行
一,确定消息类型 无人机通过即在电脑是现自主飞行:思路如下。 通过Mavros功能包,将ROS消息转换为Mavlink消息。实现对无人机的控制。 几种消息之间的关系如下: 对于ROS数据,就是我们机载电脑执行ROS系统的数据。 对于Mavros消息,就是Mavros功能包内部的消息。查询网站…...
详解Linux的系统调用fork()函数
在Linux系统中,fork()是一个非常重要的系统调用,它的作用是创建一个新的进程。具体来说,fork()函数会在当前进程的地址空间中复制一份子进程,并且这个子进程几乎完全与父进程相同,包括进程代码、数据、堆栈以及打开的文…...
构建捡垃圾机器人的 ROS 2 项目
一、说明 本系列是关于学习如何使用 ROS2、Docker 和 Github 设计、设置和维护机器人项目。 先决条件 — ROS2 软件包的基本知识、实现发布者、订阅者、操作并连接它们。 我们之前在 ROS2 中了解了不同的部分。但是,在我们转向实际的基于硬件的项目之前,…...
Spring常用注解(2)
6、切面(AOP)相关注解 Spring AOP详细介绍 Spring支持AspectJ的注解式切面编程。 Aspect 声明一个切面 After 在方法执行之后执行(方法上) Before 在方法执行之前执行(方法上) Around 在方法执行之前与之后…...
upload-labs靶场通关
文章目录 Pass-01 前端检测(JS检测)1.1 原理分析1.2 实验 Pass-02 后端检测(MIME检测)2.1 原理分析2.2 实验 Pass-03 后端检测(黑名单绕过,特殊后缀名)3.1 原理分析3.2 实验 Pass-04 后端检测&a…...
git拉取代码过程
第一步:先在本地创建文件夹 ,比如我这里的文件夹名称是 fengkgong_zntjfx 第二步:执行命令:git init 第三步:git clone 第四步:git fetch 第五步:git branch -a 第六步:cd 项目 【…...
Swift | 属性包装器
Swift | 属性包装器 1. 什么是 Swift Property Wrapper? Swift Property Wrapper 是一种特性,它允许我们在声明属性时添加自定义的包装逻辑。通过使用属性包装器,我们可以在不修改类或结构体定义的情况下,定制属性的访问和存储方…...
Android改造CardView为圆形View,Kotlin
Android改造CardView为圆形View,Kotlin 可以利用androidx.cardview.widget.CardView的cardCornerRadius特性,将CardView改造成一个圆形的View,技术实现的关键首先设定CardView为一个宽高相等的View(正方形),…...
Idea下面git的使用:变基、合并、优选、还原提交、重置、回滚、补丁
多分支和分支切换 变基和合并 变基是把本项目的所有提交都列出来按顺序一个个提交到目标分支上去 而合并是把两个分支合并起来,但是旧的分支还是可以启动其他分支,在旧的分支上继续开发 master: A -- B -- C -- M/ feature: D -- Emaster: A -…...
【数据结构】什么是算法
🦄个人主页:修修修也 🎏所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 目录 一.算法的定义 1.算法的概念 2.数据结构与算法的关系 二.算法的特性 输入 输出 有穷性 确定性 可行性 三.算法的设计要求 1.正确性 2.可读性 3.健壮性 4.效…...
复旦大学EMBA:揭秘科创企业,领略未来战略!
智能制造,国之重器。作为制造强国建设的主攻方向,智能制造的发展水平关系到我国未来制造业在全球的地位与影响力。发展智能制造,是加快建设现代化产业体系的重要手段,提升供给体系适配性的有力抓手,也是建设数字中国的…...
根据您的数据量定制的ChatGPT,改变客户服务的方式
在当今竞争激烈的商业环境中,提供优质的客户服务对于保持忠诚的客户群和推动业务增长至关重要。客户满意度已成为各行各企业的首要任务,因为它直接影响客户留存和品牌声誉。随着技术的进步,公司不断探索创新解决方案,以增强客户服…...
《Unity Shader 入门精要》笔记03
UnityShader的内置变量(数学篇) Unity内置的变换矩阵摄像机和屏幕参数float3 _WorldSpaceCameraPosfloat4 _ProjectionParamsfloat4 _ZBufferParamsfloat4 unity_OrthoParamsfloat4x4 unity_CameraProjectionfloat4x4 unity_CameraInvProjectionfloat4 u…...
LINUX系统使用软件异地同步数据(灾备)
rsync是linux系统下的数据镜像备份工具。使用快速增量备份工具Remote Sync可以远程同步,支持本地复制,或者与其他SSH、rsync主机同步 一、宝塔环境: 有宝塔软件商城支持,参考:https://www.bt.cn/bbs/thread-98022-1-1.html 二、…...
IDEA Rogstry中找不到compiler.automake.allow.when.app.running问题解决
网上大部分人教我们 先 File > Settings 然后 勾选 Build 下的 Compiler中的 Build project automatically 这些步骤都不会有问题 然后就会让我们 ctrl shift alt / 点 Rogstry 打开后 我人就麻了 根本没有什么 compiler.automake.allow.when.app.running 也不用慌 我们…...
c#设计模式-行为型模式 之 状态模式
🚀简介 状态模式是一种行为设计模式,它允许对象在其内部状态改变时改变其行为,我们可以通过创建一个状态接口和一些实现了该接口的状态类来实现状态模式。然后,我们可以创建一个上下文类,它会根据其当前的状态对象来改…...
使用Docker安装JupyterHub
安装JupyterHub 拉取Jupyter镜像并运行容器 docker run -d -p 8000:8000 --name jupyterhub jupyterhub/jupyterhub jupyterhub # -d:后台运行 # -p 8000:8000:宿主机的8000端口映射容器中的8000端口 # --name jupyterhub:给运行的容器起名…...
SpringCloudGateway网关整合swagger3+Knife4j3,basePath丢失请求404问题
在集成 Spring Cloud Gateway 网关的时候,会出现没有 basePath 的情况,例如定义的 /jeeplus-auth、/jeeplus-system 等微服务前缀导致访问接口404: maven依赖: swagger2于17年停止维护,现在最新的版本为 Swagger3&am…...
html通过使用图像源的协议(protocol)相对 URL 来防止安全/不安全错误
有人知道使用 protocol relative URLs 是否有问题吗?用于图像源以防止混合内容安全警告。 例如链接一张图片: <img src"//domain.com/img.jpg" /> 代替: <img src"http://domain.com/img.jpg" /> or <img src"https…...
【SpringBoot】| Thymeleaf 模板引擎
目录 Thymeleaf 模板引擎 1. 第一个例子 2. 表达式 ①标准变量表达式 ②选择变量表达式(星号变量表达式) ③链接表达式(URL表达式) 3. Thymeleaf的属性 ①th:action ②th:method ③th:href ④th:src ⑤th:text ⑥th:…...
Vue Router的进阶
进阶 导航守卫 官方文档上面描述的会比较深奥,而守卫类型也比较多,其中包含了全局前置守卫、全局解析守卫、全局后置钩子、路由独享守卫、组件内守卫。每一种守卫的作用和用法都不相同。这会使得大家去学习的时候觉得比较困难,这边主要介绍…...
方案:快递站智能视频监控3大亮点汇总
快递站智能视频监控方案是一种利用先进的技术和设备,来提升快递站安全管理和快递流程监控的解决方案。具体包括哪些方面呢?今天小编就带大家来看看! 1、视频监控系统 在快递站的关键区域安装旭帆科技高清摄像头,如快递仓库、操作…...
Direct3D网格
创建网格 我们可以用D3DXCreateMeshFVF函数创建一个"空"网格对象 ,空网格对象是指我们指定了网格的面片总数和顶点总数,然后由该函数为顶点缓存、索引缓存和属性缓存分配大小合适的内存,之后即可手工填入网格数据。 HRESULT WINA…...
docker安装wiki
1.docker pull mediawiki 2.docker run -d --name mywiki -p 8666:80 mediawiki 访问ip:8666,就可以看到配置页面了 3.docker pull mysql docker run -d --name my-mysql -e MYSQL_ROOT_PASSWORD123456 -p 3307:3306 mysql 4.在配置页面链接ip:3307,连接数据库,接下…...
bigemap在林业勘测规划设计行业的一些应用
选择Bigemap的原因: 主要注重影像的时效性,软件的影像时效性比其他的更新快,更清晰。 使用场景: 1.林业督查,主要是根据国家下发的图斑,结合测绘局的影像以及bigemap的较新影像对比去年和今年的林地变化。…...
设计模式学习
文章目录 前言设计模式的七大原则单一职责原则开放封闭原则里氏替换原则依赖倒转原则接口隔离原则合成复用原则迪米特原则总结 GoF二十三种设计模式创建型模式(五种)结构型模式(七种)行为型模式(十一种) 游…...
Openfire身份认证绕过漏洞
漏洞详情: Openfire是采用Java编程语言开发的实时协作服务器,Openfire的管理控制台是一个基于Web的应用程序,被发现可以使用路径遍历的方式绕过权限校验。未经身份验证的用户可以访问Openfire管理控制台中的后台页面。同时由于Openfire管理控…...
类目体系设计总结
一、背景 公司窗帘产品在做分类调整,从原先二级类目调整为三级类目,相对于平台电商我们的类目层次结构要简单很多(没有定义商品动态属性等),但对于也有上万款SKU的系统来讲,做好基础的分类对于采购、商品促销、数据报…...
gRPC之proto数据验证
1、proto数据验证 本篇将介绍grpc_validator,它可以对gRPC数据的输入和输出进行验证。 这里复用上一篇文章的代码。 1.1 创建proto文件,添加验证规则 这里使用第三方插件go-proto-validators 自动生成验证规则。 地址:https://github.co…...
颍东网站建设/北京seo软件
请用C语言实现 输出和为一个给定整数的所有组合启动2012/*请用C语言实现 输出和为一个给定整数的所有组合 */#include <stdio.h> //包含头文件stdio.h 为程序提供基本输入输出功能 #include <stdlib.h> //包含标准库头文件stdlib.h 以便调用函数system("pa…...
asp+sql server典型网站建设案例 光盘/最新旅游热点
如果不看glibc的代码,那么也许你永远也不知道什么叫境界,仅仅认为简单的可读性强的代码就是最好的代码的人也一定停留在应届毕业生的水平,程序很大意义上是给机器看的而不是给人看的,人看程序很大意义上是维护和经验学习ÿ…...
软件下载网站 知乎/搜索引擎免费登录入口
1.安装查看是否安装了FTP:rpm -qa|grep vsftpd安装:yum -y install vsftpdchkconfig vsftpd on 启动vsftpd: systemctl start vsftpd.service vsftpd几种用户:本地用户、虚拟用户、匿名用户。 主动模式、被动模式: POR…...
济南手机网站设计/李勇seo的博客
【题目】下图是"班级"表中的内容,记录了每个学生所在班级,和对应的成绩。现在需要按成绩来排名,如果两个分数相同,那么排名要是并列的。正常排名是1,2,3,4,但是现在前3名是…...
做相关性分析的网站/模板建站哪个平台好
java无效的发行源Java PureFaces团队宣布该项目源代码现已可用。 Java PureFaces是JavaServer Faces(JSF)的Web开发扩展,它使用JSF框架创建使用纯Java代码开发Web应用程序的API,而无需创建多个静态JSP模板。 翻译自: https://ja…...
淄博乐达信息技术网站/ds2600ii色带
int表示中断的含义。 mov ah,4ch int 21h 表示,执行中断指令4c (查下面的表格可知 带返回码结束程序) AH是ax的高位 AH 功能 调用参数 返回参数 00 程序终止(同INT 20H) CS程序段前缀 01 键盘输入并回显 AL输入字符 02 显示输出 DL输出字…...