DataDog Kafka运维经验谈

  • 协调修改最大消息大小
    • 与消息相关的指标
  • Unclean Leader 选举:要启用还是不启用?
    • 为什么Unclean Leader选举会导致数据丢失
    • 要监视的指标和日志:unclean leader选举
  • 低吞吐量消费者的消费位移保留时间
    • Kafka segment-level的保留策略
    • 为什么默认偏移量保留期会导致低吞吐量主题的数据重复处理问题
    • 其他与偏移配置有关的注意事项
  • 低吞吐量主题Segment保留时间
    • 日志段保留相关配置
    • Kafka处理Segment级别数据保留的方法
  • 为什么默认的Segment保留期会导致我们的低吞吐量主题出现问题
  • 英文原文

在Datadog,我们运营着40多个Kafka和ZooKeeper集群,它们每天在多个基础架构平台,数据中心和区域中处理数万亿个数据点。 在运行和扩展这些集群以支持日益多样化和苛刻的工作负载的过程中,我们了解了很多有关Kafka的知识,以及当Kafka的默认行为与预期不符时会发生什么。 在这篇文章中,我们希望分享其中的一些经验教训,并重点介绍可以帮助您随时了解所遇到问题的指标和日志。 我们将在这篇文章中介绍的主题包括:

  • 协调修改最大消息大小的重要性
  • Unclean Leader 选举:要启用还是不启用?
  • 调查有关低吞吐量主题的数据处理问题
  • 为何主题吞吐量低会导致数据保留的比预期时间更长

Kafka在配置和体系结构方面为用户提供了极大的灵活性。 默认设置旨在适应大多数类型的工作负载,因此,除非有充分的理由,否则更改它们通常没有任何意义。 请记住,对其它人有效(或无效)的任何配置都可能不适用于您,反之亦然。 因此,我们强烈建议您在将配置更改部署到生产环境之前测试配置更改(例如,通过在复制/镜像群集上运行蓝色/绿色部署)。

协调修改最大消息大小

如果您打算将群集中的最大消息大小增加,并且没有按 消费者 -> Broker -> 生产者 的顺序逐步更改,则可能会遇到错误。

从0.10.0版本开始,Kafka批量发送消息。 因为一个批处理可以包含一个或多个消息, 所以最大消息大小实际上受最大消息批处理大小的限制。 请注意,对生产者,最大消息批处理大小是在压缩前进行限制的,对Broker和消费者则是对压缩后的消息大小进行限制。 通常,如果在生产者,Broker和消费者之间设置相同的最大消息批处理大小,则一切都应顺利运行。

但是,如果这些配置在生产者,Broker和消费者之间设置不当,则您的消息处理通道能会停滞不前。 例如,如果生产者应用程序尝试发送一条超出该库配置请求大小限制的消息,则您的Kafka客户端会产生一条如下的错误:

Caused by: org.apache.kafka.common.errors.RecordTooLargeException:
The message is XXXXXXX bytes when serialized which is larger than the
maximum request size you have configured with the max.request.size configuration

为了最大程度地减少出现错误的可能性,请确保在生产者,Broker和消费者设置之间协调对最大消息批处理大小的更改。如果您需要增加最大消息批处理大小,建议您先在消费者端更改它,然后在Broker上,最后在生产者端更改它,以减少停滞消息处理管道的风险。

如果决定增加Broker上的最大消息批处理大小,请阅读客户端库的文档,以确保您了解潜在的影响。我们在某些服务中使用librdkafka;在这个特定的库中,message.max.bytes确定每个拉取队列的最大大小,并且每个分区都有自己的队列。这意味着,如果您的某个消费者正在从100个分区中读取消息,并且将message.max.bytes设置为100 MB,则该使用者的内存使用量可能需要多达100 * 100 MB,才能容纳拉取请求。如果计划增加最大消息大小,则可能需要扩展,以使每个使用者消耗更少的分区,否则,使用者可能会遇到内存不足错误。

与消息相关的指标

Metric name MBean name Description
MessagesInPerSec kafka.server:type=BrokerTopicMetrics,name=MessagesInPerSec Messages received by the broker
BytesOutPerSec kafka.server:type=BrokerTopicMetrics,name=BytesOutPerSec Bytes sent by the broker
BytesInPerSec kafka.server:type=BrokerTopicMetrics,name=BytesInPerSec
System load, CPU, memory, network N/A (host-level metrics) System load, CPU, memory, and network utilization on the broker

监视一些与消息相关的指标可以帮助确保消息在您的Kafka管道中流动。 Kafka通过MBean公开指标,以帮助您跟踪消息吞吐量(MessagesInPerSec)以及从每个Broker发送和接收的网络流量。 您还可以通过将接收的字节数(BytesInPerSec)除以接收的消息数(MessagesInPerSec)来监视在每个代理上处理的消息的近似大小。

跟踪系统负载和其他主机级资源指标还可以帮助您检测某些Broker是否在处理消息时遇到问题。 如果您已升级任何Broker以使用0.10.0中引入的新消息格式,请注意,如果它们与尚未升级的较旧使用者进行交互,则它们可能会产生额外的CPU和内存开销,因为它们需要转换消息 改为旧格式。 新的消息格式还为每个消息的大小增加了8个字节,这可能给您的代理增加额外的开销,尤其是在高吞吐量环境中; 确保监视升级的代理上的网络吞吐量,以确保它们不会达到最大带宽。

Unclean Leader 选举:要启用还是不启用?

默认情况下,较旧版本的Kafka会启用Unclean leader选举,但它们会导致数据丢失。 如果启用了Unclean leader选举(或将其保留为启用状态),如可能则对辅助群集执行双写。

在Kafka中,当unclean Broker(“不干净”,因为它尚未完成从前一个领导者复制最新数据更新)成为新的领导者时,就会发生unclean leader选举。此功能优先考虑可用性而不是持久性,如果您不小心,则可能会导致数据丢失,我们将在下面对此进行详细说明。 Kafka允许您启用或禁用unclean leader选举,具体取决于您的具体情况。在0.11.0之前的版本中,默认设置为true。
在我们的(0.11.0之前)群集中,直到发生unclean leader选举,并导致临时的数据丢失,我们才意识到这种默认行为。值得庆幸的是,我们恢复了数据,因为我们正在对主要和辅助Kafka集群执行双写操作。此事件为我们提供了两个建议:确认您的Kafka版本中的默认设置确实符合您的期望很重要-并且,如果您选择启用unclean leader选举,请通过执行双写(或多写)操作来保护数据免受潜在的数据丢失。


下面,我们将仔细研究unclean leader选举如何导致数据丢失,并探讨何时在您自己的环境中启用或禁用此设置。 然后,我们将介绍一些指标和日志,这些指标和日志可帮助您监视群集的可用性,无论您是否选择启用或禁用unclean leader选举。

为什么Unclean Leader选举会导致数据丢失

Kafka在每个主题中跨分区存储数据,并且每个分区都有一个领导者和零个或多个follower来获取和复制来自领导者的新数据。每个broker充当某些分区的领导者,而充当其他分区的follower,这降低了任何特定分区出现单点故障的可能性。对于已指定broker为领导者的任何分区,它将处理传入的消息批处理并为每个消息分配一个标识整数,称为偏移量。

只要follower与领导者保持最新并保持可用/在线状态(通过定期轮询新数据),就可以将其视为同步副本(ISR)组的一部分。如果有任何follower落在领导者后面,他们将被从ISR中移除,直到追赶分区领导者为止。

如果禁用了unclean leader选举(0.11.0.0版的默认设置),Kafka将无法在特定时间选举新的领导者(即,如果领导者不可用并且所有副本都无法赶上前领导人)。如果无法选举新的领导者,Kafka将停止对该分区的所有读取和写入(因为follower不提供读/写请求-它们仅用于复制领导者的数据)。

另一方面,如果启用了unclean leader选举,上述情况将有所不同。如果领导者不可用,即使没有同步副本(ISR = 0),Kafka仍然可以通过选举新的领导者来保持分区在线。这些unclean leader选择可以帮助提高群集的可用性,但是如果您不将数据复制到另一个群集或其他存储目标,它们可能会导致数据丢失。

可能的结果如下:
作为分区A领导者的broker脱机。分区A的任何follower都没有赶上领导者(ISR = 0)。如果启用了unclean leader选举,即使该分区“不干净”,也将当选一个follower broker作为该分区的新领导者。这使消费者和生产者可以继续向分区A发送请求。

如果先前的领导者重新上线,它将重置其偏移量以匹配新领导者的偏移量,从而导致数据丢失。例如,如果先前的领导者处理的邮件的偏移量最大为offset = 7,而新的unclean leader在被选为领导者时稍稍落后于(偏移量= 4),则某些消息(偏移量5-7)将在删除后被删除。第一个领导者重新联机,并作为副本/follower重新加入集群。

Kafka在版本(0.11.0)中更新了此设置,因此默认情况下将不再启用unclean leader选举(请在此处详细了解更改的原因)。您可以通过禁用unclean leader选举(如果您使用的是旧版本的Kafka)或将其禁用(在新版本的Kafka中),将持久性优先于可用性。但是,您可能需要启用unclean leader选举,以便使群集保持高可用性。在这些情况下,将数据复制到辅助群集将使您从潜在的数据丢失中恢复。

要监视的指标和日志:unclean leader选举

无论您是启用还是禁用unclean leader选举,密切关注某些指标都可以帮助确保数据的持久性和集群的可用性。

Metric name MBean name Description
UncleanLeaderElectionsPerSec kafka.controller:type=ControllerStats,name=UncleanLeaderElectionsPerSec Number of “unclean” elections per second
IsrShrinksPerSec kafka.server:type=ReplicaManager,name=IsrShrinksPerSec Rate at which the group of in-sync replicas (ISR) shrinks per second
IsrExpandsPerSec kafka.server:type=ReplicaManager,name=IsrExpandsPerSec Rate at which the group of in-sync replicas (ISR) expands per second
UnderReplicatedPartitions kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions Number of partitions that are under-replicated (not yet replicated across all followers)

如果在IsrShrinksPerSec中看到峰值,然后在IsrExpandsPerSec中看到相应的峰值,则表明节点可能已经短暂落后,然后恢复并追上来。

当follower被踢出ISR时,您会看到类似以下内容的日志:

如果unclean leader选举发生,Kafka将记录以下警告消息,指出可能会发生数据丢失。

[TIMESTAMP] WARN [OfflinePartitionLeaderSelector]:
No broker in ISR is alive for [<PARTITION_NAME>].
Elect leader <BROKER_NUMBER> from live brokers <BROKER_NUMBER>.
There's potential data loss. (kafka.controller.OfflinePartitionLeaderSelector)


如果此时旧的领导者重新联机,它将把其偏移量重置为新的(不干净的)领导者的偏移量(在下面的示例中为<BROKER_B>),即使实际上它具有比现任leader有更多的数据:

[TIMESTAMP] WARN [ReplicaFetcherThread-6-<BROKER_B>], Replica <BROKER_A> for partition <PARTITION_NAME> reset its fetch offset from <NEW_LEADER'S_OFFSET> to current leader <BROKER_B>'s latest offset <NEW_LEADER'S_OFFSET> (kafka.server.ReplicaFetcherThread)
[TIMESTAMP] INFO Truncating log <PARTITION_NAME> to offset <NEW_LEADER'S_OFFSET>. (kafka.log.Log)
[TIMESTAMP] INFO Scheduling log segment <OLD_OFFSET> for <PARTITION_NAME> for deletion. (kafka.log.Log)
[...]

低吞吐量消费者的消费位移保留时间

**
默认偏移量保留期时间对于高流量主题很有效,但是它在某些吞吐量较低的主题中引起了数据重复处理问题。
**
Kafka的默认配置设置是为高流量的主题而设计的 - 每秒大约有数百万次写入。 但是,如果您的群集包含低吞吐量主题或长时间不产生任何消息的主题(例如仅用于调试或测试目的的主题),则可能需要修改一些默认保留设置以降低可能的重复处理或数据丢失(跳过未处理)。

在遇到有关低吞吐量主题的数据重复处理问题后,我们决定增加这些主题的消费位移保留期,以匹配较新版本的Kafka(七天,而不是一天)。

Kafka segment-level的保留策略

Kafka将数据组织到分区中以支持并行处理并在多个Broker之间分配工作负载(即,您可以将每个分区存储在不同的代理上)。如前所述,每个消息都被分配了一个称为偏移量的标识整数。从0.9.0版本开始,每个消费者每隔五秒钟将其最近消费的消息的偏移量提交给__consumer_offsets·主题(默认情况下)。在此版本之前,消费者将这些信息存储在ZooKeeper中。
在一定的时间内保留使用者的偏移量,在当分区在Broker间rebalance,或者当消费者或Broker暂时不可用时, 使得消费者可以从分区日志中知道从哪里恢复读取(或“使用”)。如果消费者没有已提交的偏移量(例如,因为它刚刚启动)或请求过期的偏移量(低吞吐量主题在偏移量保留期限内未处理任何消息时可能会发生),消耗位移将被重置为所在分区的最老/最新(earliest/oldest)位移

Kafka根据一些设置确定何时以及如何重置其偏移量:

Kafka setting Description Scope Default setting
offsets.retention.minutes (v. 0.9+) Offset retention period for the __consumer_offsets topic (in minutes) Broker-level Pre-2.0.0: 1440 (1 day) 2.0.0+: 10,080 (7 days)
auto.offset.reset Determines if the consumer should reset to the latest or earliest offset; if set to none, it will generate an error. Consumer-level latest
enable.auto.commit Determines if the consumer should automatically commit its offsets to the __consumer_offsets topic Consumer-level True
auto.commit.interval.ms If enable.auto.commit is True, this setting specifies how often consumers will commit their latest offsets Consumer-level 5,000 ms (5 seconds)

为什么默认偏移量保留期会导致低吞吐量主题的数据重复处理问题

如果主题在偏移量保留期内未处理任何新提交,则其消费者将根据auto.offset.reset设置重置其偏移量。在较旧的(2.0之前)版本的Kafka中,偏移保留期要短得多,仅为一天而不是一周。这为许多用户带来了问题,以至于默认偏移量保留期在2.0.0版中已延长至7天。

我们正在运行Kafka的2.0之前的版本,因此,只要我们的某个低吞吐量主题在不处理任何新消息的情况下超过偏移保留期(一天),__ consumer_offsets主题就会删除该主题的消费者偏移。然后,我们的Kafka消费者无法获取其偏移量,因此他们重置为最早的偏移量(基于我们的auto.offset.reset配置),并开始处理仍在Broker上可用的最早消息。这意味着他们重新处理了过去已经处理过的一些数据。根据您的具体情况,这可能正是您想要避免的(例如,您只打算向新注册的用户发送一次电子邮件通知)。
对我们来说,增加偏移保留时间以匹配较新版本的Kafka(从7天而不是1天)是合理的,以减少再次发生这种情况的可能性。但是请注意,增加偏移保留期会产生某些副作用。例如,它可以增加Broker上的内存使用量,因为它必须将这些偏移量在内存中保留更长的时间。有关更多详细信息,请参阅完整的Kafka改进建议。

其他与偏移配置有关的注意事项

在遇到与偏移有关的各种问题之后,我们还获得了另外两个有用的信息:

  • 无论您如何选择auto.offset.reset配置,都要确保指定一些内容而不要留空。 否则,您的客户应用程序将遇到错误。 默认设置将消费者配置为读取存储在Broker中的最新消息,因此如有疑问,请保持不变。 但请注意,在某些情况下,读最新消息可能会导致跳过数据(例如,如果消费者在断开连接之前尚未完全使用经纪人上的最新消息)。
  • 您可以使用带有–to-datetime选项的Kafka内置命令来手动重置消费者的位移。 请注意,它假定您将提供UTC时间戳。

低吞吐量主题Segment保留时间

如果您不考虑Segment级别的保留期限,则低吞吐量主题可能会保留比预期更长的消息。

Kafka将每个分区的数据存储在日志中,日志进一步分为多个日志段。 Kafka根据主题级别和段级别的日志保留时间确定存储数据的时间。 这使得在细粒度级别上跟踪消息在Broker上实际存储了多长时间变得很棘手。

在我们的一个低吞吐量主题中,Broker存储的消息远远超出了我们配置的主题级别保留期(36小时)。 这不一定是个问题,因为每个日志使用的最大存储量仍受最大日志大小设置限制。 但是,我们仍然想了解为什么会这样,所以我们仔细研究了Kafka的数据存储方法,以及该行为在各个版本之间是如何演变的。

即使我们在主题上配置了日志保留时长为36小时, 日志数据还是基于每星期一次的频率被删除

日志段保留相关配置

在进一步介绍之前,让我们仔细了解一下相关设置,这些设置决定了Kafka将在分段级别上保留数据的时间。 请注意,如果您为任一设置配置了值,则根据优先级log.retention.ms > log.retention.minutes > log.retention.hours进行覆盖。

Kafka setting Description Scope Default setting
segment.bytes Segment size limit: The maximum size a log segment can reach before a new one is created. Topic-level 1,073,741,824 bytes (~1 GB)
segment.ms Segment-level retention period: After this period of time, a new segment will get created even if it hasn’t yet reached the segment size limit. Topic-level 604,800,000 ms (7 days)
log.retention.hours Log retention period: Period of time the broker will retain a partition log before deleting it. Broker-level 168 hours (7 days)
retention.ms Topic-level retention period: The period of time the topic will retain old log segments before deleting or compacting them (depending on the cleanup.policy). Topic-level 604,800,000 ms (7 days)
cleanup.policy How the topic should discard segments once they reach their retention period or size limit. If set to delete, it will discard the segments; if set to compact, it will use log compaction. Topic-level delete

Kafka处理Segment级别数据保留的方法

当发生以下两种情况之一时(以先到者为准),Kafka将关闭Segment并打开一个新Segment:

  • Segment达到最大大小(由segment.bytes确定)
  • Segment级别的保留期已过(基于segment.ms)

此外,当Segment仍处于“活动”状态或当前正在接受更新时,Kafka无法关闭该Segment。即使在Kafka关闭Segment后,它也可能不会立即过期/删除该Segment的消息。根据您所运行的版本,Kafka基于以下算法决定什么时候开始过期/删除消息:

  • 当前时间 >=上次修改段的时间 + 保留时间 (v0.10.1.0之前)
  • 当前时间 >= 该段上最近(最新)消息的时间戳 + 保留时间(从版本0.10.1.0开始)

为了了解其工作原理,我们以一个流量很少的分区的示例为例。如果该Segment关闭时的最近消息是三天前的(其最老的消息是7天前的),则该Segment数据将再被保留四天而不被删除,因为那是这个关闭的Segment最新消息达到7天的保留期。因此,消息将会被保留在配置的保留窗口之外,并且实际的保留时间取决于Segment的更新频率。

为什么默认的Segment保留期会导致我们的低吞吐量主题出现问题

在高流量主题上,Segment往往更频繁地替换,因为它们收到的流量足以在Segment保留期结束之前达到默认段大小限制(1 GB)。在低吞吐量主题上,通常不是这种情况。
我们已经将相关主题的Segment大小限制降低到512 MB,但这仍然不足以发挥作用-在Segment级别保留期结束之前,Segment未达到大小限制。此低吞吐量主题的分区日志通常遵循7天的周期,因为Segment级别的保留期(仍设置为7天的默认值)优先于主题级别的保留设置。
我们对低吞吐量主题进行了一些设置,以确保Kafka可以更频繁地生成新Segment,并保持对Segment级别数据保留的更多控制权。根据从这次经验中学到的知识,以下是针对低吞吐量主题的建议:

  • 将segment.ms减小到小于主题级别的保留期(retention.ms)的值。 我们将其中一个主题设置为12小时(43,200,000毫秒),以确保我们可以更频繁地推出分段。
  • 您可能还希望将Segment大小限制(segment.bytes)减小到一个小于当前Segment平均大小的值。 这迫使Kafka更加频繁地生成新Segment,并对应该存储在Segment上的最旧数据实施更严格的时间限制。 在我们的情况下,我们将此设置从之前减小的512 MB更改为〜100 MB。 但是,正确的值将取决于为您的主题存储的细分的平均大小。

    在此低吞吐量主题上,将段级别的保留时间减少到12小时后,日志段的生成/替换频率更高。

在我们的案例中,此问题并没有影响大量数据,因为它属于低吞吐量主题,但是我们仍然决定进行这两项更改,以确保我们不再保留比预期更长的数据。 但是,请记住,将段保留期或段大小限制设置得太低也不是一个好主意。 减少这两个设置会鼓励Kafka更加频繁地推出新段,这可能会减慢操作并增加所需打开文件的数量(因为Kafka需要为每个分区中的每个段使用打开文件句柄,而不仅仅是活动段) 。 因此,如果您要减少这两个设置中的一个或两个,请确保监视相应主题中的打开文件数。

英文原文

英文原文地址

【翻译】DataDog Kafka运维经验谈相关推荐

  1. 网易即时通讯云平台99.99%可靠性的运维经验谈

    网易即时通讯云平台99.99%可靠性的运维经验谈 转载自:http://mp.weixin.qq.com/s?__biz=MzIzNjUxMzk2NQ==&mid=2247483968& ...

  2. 领取20万字《Kafka运维与实战宝典》PDF文档

    作者:石臻臻, CSDN博客之星Top5.Kafka Contributor .nacos Contributor.华为云 MVP ,腾讯云TVP, 滴滴Kafka技术专家 . KnowStreami ...

  3. 7.【kafka运维】 kafka-consumer-groups.sh消费者组管理

    文章目录 消费者组管理 kafka-consumer-groups.sh 1. 查看消费者列表`--list` 2. 查看消费者组详情`--describe` 3. 删除消费者组`--delete` ...

  4. 【kafka运维】分区副本重分配、数据迁移、副本扩缩容 (附教学视频)

    日常运维.问题排查=> 滴滴开源LogiKM一站式Kafka监控与管控平台 (后续的视频会在 公众号[首发].CSDN.B站等各平台同名号[石臻臻的杂货铺]上上传 ) 分区副本重分配+注意事项+ ...

  5. 公有云平台运维经验谈

    课程介绍 本课程主要介绍阿里云平台使用方法和策略. 课程目标 掌握公有云平台的运维方法 适合人群 运维工程师 课时列表 课时1:公有云平台优缺点对比敬请期待 课时2:阿里云平台ECS应用选型和使用指南 ...

  6. kafka 运维中遇到的问题

    1,java.lang.InternalError: a fault occurred in a recent unsafe memory access operation in compiled k ...

  7. 滴滴Logi-KafkaManager开源之路:一站式Kafka集群指标监控与运维管控平台

    导读 从2019年4月份计划开源到2021月1月14号完成开源,历时22个月终于修成正果,一路走来实属不易,没有前端.设计.产品,我们找实习生.合作方.外部资源支持,滴滴Kafka服务团队人员也几经调 ...

  8. 关于运维,新华三的经验谈

    过去,企业运维人员总是头疼. 头疼什么? 勤勤恳恳的运维攻城狮,不断面对系统故障,恢复业务常常需要花费数小时,故障原因分析则动辄数天甚至数周--人脑经验判断不及时,决策缺乏事实依据,业务恢复执行手忙脚 ...

  9. ​IT 管理进化论:若运维是眼前的苟且,运营则是诗和远方

    更多专业文档请访问 www.itilzj.com IT运维?IT运营? 都是 IT Operations,有什么区别? IT运维管理?IT运营管理? 都是 ITOM,有什么区别? 一字之差,只是翻译不 ...

最新文章

  1. python 正则表达式 re.compile() 的使用
  2. iOS开发系列--C语言之存储方式和作用域
  3. bootstrap-反色导航条
  4. Hsiaoyang: Google与站点地图Sitemap
  5. VMware Workstation 网络连接配置
  6. iTerm2 快捷键大全
  7. docker宿主机访问容器_Docker容器与宿主机器通过IP内外通讯
  8. 新学期,对同学们的要求和期望
  9. Unity Manual learning log
  10. 根据id查询数据(向前台返回json格式的数据)
  11. SLM4054独立线性锂电池充电器的芯片的学习
  12. FL Studio21.0中文版本FL水果娘发布更新
  13. 学计算机的装系统都不会,为什么刚买的新电脑,却不支持安装Win7系统,背后的真实原因...
  14. 墨卡托投影参数设置_横轴墨卡托投影坐标设置与导入导出CAD文件讲解
  15. web页面性能检测工具Lighthouse
  16. 连接到另外计算机要用户名,连接局域网电脑需要用户名密码
  17. 控制系统仿真与CAD-薛定宇-第四章matlab学习笔记
  18. Mac 使用Charles后,退出Charles后,不能浏览网页,提示:未连接到互联网代理服务器出现问题,或者地址有误。
  19. SHA-256哈希函数实现
  20. Stub和Mock的区别

热门文章

  1. Centos7安装docker并更改阿里云下载镜像地址(附带windows10安装docker教程)
  2. EI会议-计算机领域
  3. 司法背记一表通(吐血整理!!)
  4. python123查找指定字符输入m_Pyton学习—字符串
  5. Windows自带压缩文件工具makecab命令详解
  6. 在Windows下搭建React Native Android开发环境常见问题
  7. 华为服务器默认用户名和密码怎么修改,服务器默认用户名和密码
  8. 【解决方案】SkeyeVSSSkeyeARS助力水利工程视频监管-长江流域重点水域禁渔视频监控系统建设
  9. ECN Trade:全球经济疲软,美国国债成新宠
  10. 自己用JavaScript写出吉他和弦图生成器