APM建设踩了哪些坑?去哪儿旅行分布式链路追踪系统实践

一分钟精华速览

分布式链路追踪系统在企业的APM体系中扮演着重要的角色。本文分享了去哪儿旅行构建分布式链路追踪系统的实践经验。从APM整体架构设计入手,讲述了日志收集、Kafka传输和Flink任务处理等环节的性能优化实践和踩坑经验。

同时,作者结合丰富的分布式系统架构经验,探讨了APM系统和Trace数据的价值。通过阅读本文,你将了解到去哪儿旅行在构建APM体系中所面临的挑战,并学习如何应对这些挑战,实现更高效的性能监控和管理。 file 作者介绍

file

去哪儿旅行基础平台架构师——王 鹏 TakinTalks稳定性社区专家团成员。毕业于大连理工大学,10年以上大型分布式基础架构经验,专注于大型分布式基础架构和大数据处理领域。曾就职于58集团,主要负责58到家基础架构工作。后进入去哪儿旅行,负责分布式链路追踪系统的建设以及APM体系的搭建。在大数据、高并发的场景有丰富的经验。

温馨提醒:本文约7000字,预计花费10分钟阅读。

TakinTalks稳定性社区,公众号后台回复 “交流” 进入读者交流群;回复“1012”获取课件资料;

背景

APM并不是一个新的概念。从2012年到2016年,市场上涌现了许多开源的APM组件,如SkyWalking、Jaeger等。可以说,在过去几年中,随着技术的发展和系统的复杂性增加,分布式链路追踪系统和应用性能管理(APM)已成为许多公司不可或缺的工具。

大多数公司都会根据其技术栈和业务体系来构建自己的APM体系和分布式链路追踪系统。以去哪儿旅行为例,我们的主要业务涉及搜索查询和电子商务交易等领域。由于业务体系的不同,在技术选择、方案设计方面可能存在一些差异。

在构建和应用APM体系的过程中,去哪儿遇到了许多挑战。例如,在日志收集、Kafka传输以及Flink任务处理等环节遇到了一些问题。这次会分享如何克服这些性能瓶颈,以及在这个过程中积累的经验和教训,希望这些经验对其他公司在解决类似问题时能够有所帮助。

一、APM整体架构是如何设计的?

作为一个OTA交易平台,去哪儿旅行的业务系统分为搜索和下单交易部分。从流量来看不会特别大,OTA的交易量无法与零售电商交易相比。因此,去哪儿在构建APM体系上与其他电商公司可能存在一些不同。例如,大多数公司APM的Trace部分采用采样策略,但对我们来说,每一次请求、每一次查询或者每一次交易,尤其是交易链路的Trace非常少,每天几百万的交易量,产生的Trace不会太多。因此,最终采用了交易链路全采样的技术方案。

此外,从2020年开始,全公司技术栈之前的虚拟机模式转向了云原生的开发架构。在这三年中,从应用层、开发层和容器层,都已经完成了迁移。在迁移完成后,需要将可观测性的相关体系迁移到云上,或者说适配云原生的开发环境。

在构建APM体系的早期,由于众多开源技术尚未出现,进行了大量的自研工作。例如,早期我们依赖自研的Java中间件记录Trace。但现在,开源社区已经提供了许多优秀的方案,可以根据公司环境、阶段和技术路线做出合理的选择。

经过技术分析与选型后,去哪儿旅行的APM体系采用插桩Agent模式和中间件埋点并行的方式(如图)。其中,红色部分是重点改造部分。

file

(去哪儿旅行APM体系整体架构)

首先,左上部分主要是如何记录和获取Trace信息。部分中间件以硬编码的方式获取Trace数据,另外,一些开源的第三方组件通过Agent模式获取了Trace。

其次,关于如何收集这些数据,去哪儿旅行采用了开源的Apache Flume日志收集组件,并对其进行了一系列改造。改造的原因是原生组件并未提供如配置管理功能、限流控制等一些必要的功能。

第三,处理层的任务非常繁重,选择了Flink作为数据处理任务框架。数据处理不仅需要实时处理Trace的详细数据,还需要存储并分析出的Trace异常、后置采样、链路分析等等。

最后,Metrics部分则是一个标准的收集和处理流程,通过Statsd将数据存储到时序DB,最终有Prometheus + Grafana去展示。

总的来说,去哪儿旅行的APM体系整体架构设计结合了开源技术和自研成果,以实现更高效的性能监控和管理。

二、遇到了哪些“坑”?

2.1 日志收集组件的性能瓶颈优化

在Trace数据收集过程中,确实存在许多挑战。由于许多业务和监控系统基于Trace和Metrics来判断是否发出告警,因此这种收集需要比离线日志更实时。然而,这也意味着它们需要在保持高性能的同时,保证不会影响宿主机的性能,这是一个非常大的挑战。

2.1.1 问题表现:Trace数据中断

file

在对系统进行改造前,发现有大量的Trace出现了数据中断的问题。这种问题表现为Trace的某些部分(比如Span ID为1.4和1.4.2的部分)突然消失,而后面的部分(比如Span ID为1.5)又突然出现。这种情况可能由两种原因导致,一种是中间件里没有这部分数据,另一种是中间件里有数据但是没有传送过来。无论是哪种原因,都需要对系统进行深入地分析。

2.1.2 抽样分析:日志组件和配置问题

尝试把以上问题拆解成可视化的指标,以便于理解和处理它。首先,查看了整个Trace的问题比例,可以通过图上的监控看到Trace中断率达到了惊人的70%~80%。这个结果说明大部分的Trace数据都存在问题。

file 为了进一步确定问题的原因,我们对100个问题数据进行了抽样分析。分析结果显示,有一半的问题是日志收集组件的问题,也就是Qflume组件的问题;另一部分问题则是配置问题,即没有正确配置数据收集,导致数据没有被收集到。除此之外,还存在一些其他的问题。

对于配置问题,可以通过统一刷新配置来解决。

而对于日志收集组件的问题,则需要进行更深入的分析和改造。首先需要对其内部工作流程进行了解。从左到右,这个流程图展示了日志处理过程。

file

在最左侧,业务应用通过异步队列将日志输出到磁盘文件。这个队列的长度是有限的,用来控制内存占用。如果队列满了,新的日志就会被丢弃。这就是日志丢失的一个原因。

还有其他两个瓶颈——一个是系统通过单线程同步读取日志,这个读取速度跟不上日志生成的速度。另一个是系统使用同步发送Kafka来传输日志。

2.1.3 优化效果

为了解决这些问题,采取了两个措施。首先,增加了异步队列的长度,但是也不能让它过长,以免消耗过多的内存。其次,将单线程读取改为批量读取,每次读取一个批次的日志。此外,还将同步发送改为Kafka异步发送,发送完一批日志后立马发送下一批,这样可以大大的提高吞吐量。

file

这样优化后,分钟级传输的数据量有了显著的提升,可以达到每分钟80亿甚至100亿条数据量。

2.1.4 踩坑点1:传输失败率大幅提高

问题描述: 尽管解决了日志组件的性能问题,可以快速地读取和发送数据。然而,随之而来的问题是Kafka的数据量非常不稳定,呈现出明显的波动。

file

原因分析: 在查看相关数据后,发现了一个严重问题:频繁的内存溢出(OOM)。这个问题的原因在于,优化后的日志组件以批量的方式读取数据,这会占用大量的内存。如果内存不足,就会发生OOM。

file

一种解决这个问题的方法是增大内存,但这并不是一个长久之计,因为资源有限,不能无限制地增加内存。另外,Trace日志的实时性要求并不像业务那么高,晚几秒甚至十几秒都是可以接受的,只要数据最终能够传输过去就可以。

如何解决? 解决思路是在保证高性能的前提下,对数据传输进行限流,让日志组件在一个可控的内存使用范围内高效的工作。

file

限流方案包括设定时间窗口和对单条日志大小做限制。在设定时间窗口的方法中,时间窗口是滑动时间窗口,也可以是一条日志的处理时间。比如设置时间窗口为一秒、两秒或五秒,然后规定在这个时间窗口内,限制数据传输的大小,比如不能超过200M。

尽管设置了时间窗口,但日志组件仍可能会出现OOM的问题。原因在于有些日志非常大,一条日志就有几兆甚至几十兆。这种情况虽然不合理,但却确实存在。如果遇到这样的日志传输过来,很可能会导致服务瘫痪。因此,团队还需要对单条日志的大小进行限制,如果日志过长,就需要进行截断处理。

另外,如果出现OOM,团队也需要进行断点续传的工作。不能重新传输已经传输过的Trace,因为这样会造成大量的资源浪费,且重复传输本身也是有问题的。

通过这些限制,就可以保证堆内存的使用不会超过限制,同时保持日志组件的高性能。

2.1.5 踩坑点2:接口耗时增长,吞吐量下降

问题描述:

在解决日志组件内存溢出问题之后,又遇到了新的挑战:业务线反馈接口耗时突然大幅上升,吞吐量大幅下降,甚至有些业务应用被操作系统直接终止,导致故障出现。这个问题在一开始是令人困惑的,因为已经解决了OOM的问题,并且对内存使用进行了严格的限制。怎会出现影响业务系统的情况呢?

原因分析:

在排查问题时,发现有问题的服务器上,CPU利用率非常高,达到197%,内存的使用率也非常高,几乎已经没有剩余空间。那么,这是由什么原因引起的呢?回顾之前做的优化工作,发现在优化日志传输的步骤中,将Kafka的发送操作变成了异步发送。而在异步发送过程中,会大量占用堆外内存。

之前针对堆内内存做了限制,但是对堆外内存并没有做限制。如果Kafka大量发送数据,但是由于某些原因传输不过来,那么这些数据就需要放在堆外内存中等待发送。这就是导致内存使用率高涨,最后操作系统终止业务进程。

如何解决?

那么,如何限制堆外内存的大小呢?或者说,如何限制进程使用的内存空间呢?

从Docker这个容器技术中找到了灵感。熟悉Docker都知道,Docker容器中使用的资源是固定的,包括磁盘空间、CPU以及内存等,不能超过容器在初始时分配的大小。那么,Docker是如何实现这个功能的呢?

这就涉及到CGroup技术,也就是Linux内核提供的控制组(Control Group)技术。CGroup技术主要用于限制和隔离进程组应用的物理资源,简单来说,可以通过CGroup技术将硬件资源切分成很多块,并对每一块资源设定使用限制,进程和其产生的子进程都不能超过这个限制。

通过CGroup技术,限制进程的堆外内存使用和CPU使用,保证它们都在一个合理的范围之内。

2.1.6 优化效果

CGroup进行资源限制后,系统的效率得到了显著提升。Root失败率从原来的80%降低到了20%,优化效果十分明显。

file

2.2 高并发下Kafka集群传输优化

在解决了日志收集问题,资源使用得到了限制之后,大量的日志被发送到了Kafka集群,然后Kafka集群再将日志传输给Flink任务进行处理。这就引出了新的问题:如何保证Kafka集群能够高效稳定地传输日志?

2.2.1 问题表现

随着日志收集客户端在全公司范围内的推广,大约有1万多个实例部署,Kafka集群开始出现了不稳定的情况。Kafka集群会出现大量的连接失败,整个的数据接收量和发送量都会急剧下降。在这期间并没有对集群做任何改动,收集组件也没有做任何改动,但是却频繁出现这种不稳定问题。

file

2.2.2 问题分析

通过查看了Kafka集群的监控,发现网络空闲连接和线程数急剧下降。这可能是导致Kafka集群连接失败,数据传输量下降的主要原因。

file

在Kafka的架构中,客户端首先与网络接收线程组进行连接,发送日志数据。网络接收线程组在接收到数据后,将任务转发给RequestQueue进行处理。 file

Processor处理器的主要工作是将请求放入请求通道队列(RequestChannel)。理论上,处理器的空闲量应该较大,因为其工作并不复杂,仅仅是进行内存操作,将网络接收的任务提交到队列。然而,监控数据却显示处理器的空闲数急剧下降,变得非常繁忙,并没有足够的空闲链接去处理网络请求。

进一步分析,发现请求处理器(KafkaReuestHandler)从请求通道队列中取出任务进行处理,主要是将请求写入磁盘。如果请求处理器处理不过来,请求通道队列的数据就会逐渐增多。当队列满时,新的请求无法进入,导致网络连接空闲数急剧下降。

综合以上所述,问题可能出在Kafka的请求处理器这一环节。主要可能是内存不足或者刷盘速度不够快。进一步检查后,发现确实有些机器的内存不够,有些机器的刷盘速度有问题,甚至有些磁盘已经损坏,导致部分机器的性能急剧下降。

2.2.3 优化效果

将有问题的机器从集群中移除,并增大了内存。经过这些优化,整个Kafka集群恢复了正常状态。

file

(正常状态下的集群)

优化后的效果明显,Kafka集群的收发状态保持在一个正常的水平。在约1分钟的时间里,可以收发1.7亿多条数据。而且,根据业务的波峰波谷,数据的收发呈现出稳定的状态,不再出现以前的陡增陡降的情况。

2.3 百万级QPS数据处理任务优化

在数据处理过程中,任务这一环节是最核心的部分。在对比了Spark和Flink的流式处理后,发现后者更适合Trace场景。Flink任务本身也比较复杂,如何能保证它的高可靠和高性能?

2.3.1 数据处理任务

数据存储量的QPS400w左右,峰值1000w。数据进入任务首先进行反序列化,之后开始实时计算业务线的拓扑、异常Trace拓扑、Metrics和Trace关联关系等。

file

另一个重要的任务是存储。每秒几百GB的日志存储到存储介质中,有HBase、ES、ClickHouse等存储介质。这些存储还有一些关联数据,这些关联数据需要拆分。例如,有很多Metrics和Trace的关联数据,需要先将它们解析出来,然后做分布式的存储,将它们存入数据库。这些计算都是在Flink的任务里面完成的。

2.3.2 Flink任务拆分

file

上图是一个大任务,整个数据打散后分给不同的子任务去处理。这种方式有一个问题,如果某个子任务处理速度较慢,会产生一些背压。背压会继续向上反映到总的任务分发环节。分发环节处理速度慢,所有任务的处理速度也会变慢。所以,一个小任务出问题,就导致整个链路出问题。

file

因此,将一些不关联的Trace任务进行拆分,而不是让它们耦合在一起。这样做可以大大降低问题的发生概率。

2.3.3 背压如何解决?

背压(Back Pressure)是流控制中的一种策略,主要用于保护系统在高负载情况下的稳定性。当下游处理速度跟不上,上游数据输入的速度时,就会发生背压,这就像水管出水口被堵住,压力太大后就可能会导致水管崩裂。

解决背压可以从以下几个方面着手:

观察Flink任务中子任务的消费是否均匀。Flink任务会被分解为子任务,子任务会被分配到不同的机器上执行。如果某些高耗CPU或者高耗IO的任务集中在同一台机器,会导致该机器的处理能力不足,从而影响整个任务的处理速度。因此,需要关注子任务的消费均匀性,并尝试调整资源的分配,使其更加均衡。 关注上下游算子的内存是否充足。如果输出算子的内存不足,可能会导致输入算子的数据无法正常传递。因此,需要通过内存监控,并合理设置不同算子的内存大小。 尽量使用内存的Map来替代Window。虽然Window可以保证数据的完整性,但在某些情况下,并不需要这么强的一致性,更多的时候只是对数据缓存,使用内存的Map可以极大的节省内存消耗。

善用Shared Group。Filter一定小心下游算子的拥堵导致全面的拥堵,Shared Group可以将频繁进行网络传输的算子放到一个JVM内,这样可以极大的节省网络资源和计算资源。

2.3.4 优化效果

通过优化背压问题,平均写入达到400万QPS,平均写入耗时在600ms左右,这是一个非常不错的性能表现。

file

三、如何看到APM和Trace数据的价值?

3.1 APM系统有哪些作用?

当系统经过几年的发展,可能会变得杂乱无章,各个系统之间的联系混乱不堪。在这样的情况下,可能对系统的运行逻辑一头雾水,更别说从这个混乱的拓扑中找出问题所在。而APM系统的作用就是帮助理清这些混乱的联系,然后指出可能出现问题的地方。与仅有监控系统相比,APM系统可以更清晰地定位问题所在,这是一个巨大的价值点。

file

(APM系统让查问题变得更简单)

3.2 Trace与Metrics怎么关联

在业界很多并没有关注Metrics和Trace的关联关系,常见的做法是根据时间进行随机关联。然而,这样的关联性并不强。因此,根据去哪儿的业务情况进行了一些改进。

针对三类指标进行分析:一类是Time类的指标,一类是Count类的指标,另一类是Rate类的指标。

1、Time类指标:假如一种操作的耗时是最长的,那么这种操作肯定是存在问题的。所以这类指标我们只取Top10即可。

2、Count类指标:处理方式会相对复杂一些,因为它涉及到的业务概念较多。采用了随机策略,比如在记录某个指标时,无法将所有的Metrics与Trace的关联关系全部记录下来,只需要随机抽取一部分进行记录就可以了。另外一种策略是:报警策略,假如某个指标出现报警,那么在这段时间内这个指标存在问题的概率是非常大的,所以会提高采样率,为了更可能命中存在问题的Trace。还有一种策略是:关键词策略,比如定义了一些业务异常,这种情况下,会进行全采样,因为这种Trace的价值非常大,例如ERRO FAILED EXCEPTION 等关键词。

3、Rate类指标:处理起来会更为困难。因为Rate类指标往往是两个数的比值,比如成功率和失败率。这些指标上升还好,但是如果下降,那么可能是因为没有Trace关联。所以对于Rate类指标,只能关注上升的情况。

在实际操作中,发现研发同学非常喜欢使用这个功能,因为大家只要加报警就能找到问题的Trace。以前要找这个问题的Trace就需要在日志和代码中反复查找,非常费劲。而有了这个功能,他们只需要设置一个报警,只要出现问题,他们就能找到对应的Trace,这大大节省了排查问题的时间。

3.3 Trace作为基础底座如何应用

作为一个基础功能,Trace在很多公司都有广泛的应用,可以在其基础上进行许多工作。基于Trace的高连通性,通过上下文传递来降低整个调用量。例如,在用户中心的接口中,调用量通常非常大。如果不合理在上下层或同级之间进行多次调用,用户中心的调用量就会指数级增加。

如果能够获取Trace的上下文,那么在短时间内,一个Trace内的用户数据的变化可能非常小。绝大部分数据是固定的,例如User ID。通过传递Trace的上下文,可以指数级别降低其调用量。在一个Trace内,调用次数可以被限制在几百次以内。如果通过上下文传递,只需要调用一次就够了。这种优化的方法在实际应用中是可行的,还可以结合其他的技术,比如将大量访问频繁的数据存储在缓存中,以降低信息在传输层的大小。

另外,Trace的高连通性对于混沌工程和全链路压力测试非常重要。全链路压力测试是基于Trace的连通性的。在进行灰度环境的压力测试时,不允许将整个压力施加到线上,以免造成故障。如何确保只在灰度链路内进行测试而不影响线上环境?这就是基于Trace的高连通性。高连通性意味着的链路拓扑应该是一个全连通集的子集,不允许出现调用跳转到线上的情况,如果出现则表明压力测试的拓扑是有问题,需要终止。

此外,如果出现无法覆盖整个Trace的情况,可以在网络层面进行拦截。在整个网络拓扑中,不允许请求外部的线上机器或服务。如果有请求到线上机器或服务的情况,可以进行拦截,终止压力测试。

3.4 Trace数据有哪些价值

基于Trace的连通性,通过分析Trace数据,可以了解整个链路的性能瓶颈和热点,从而给业务线提出优化建议。

基于超时时间的链路拓扑分析,发现配置不合理的点,是否有环装调用 在分布式服务中,通常使用RPC框架(如Double或GRPC)进行通信。在早期,这些框架需要手动配置节点的超时时间。例如,上游的超时配置为1秒,下游的超时配置为3秒,这是一个不合理的配置,因为上游已经在1秒之后超时了,下游的超时时间设置并没有意义。这种不合理的配置可能出现在分布式系统中的许多节点中,难以寻找和识别。

通过分析每个请求的耗时数据,可以将RPC框架的超时时间拿出来,并对上下游配置进行分析,从而发现不合理的配置。这样,可以确定哪些超时配置是不合理的,以前很难解决的问题现在可以通过链路分析发现。一旦我们找到这些点,可以避免由于超时配置不合理导致的故障。

基于请求耗时占比,分析性能瓶颈 通常情况下,某个请求中会有一个或多个函数的耗时占比非常高。 file

file

通过观察图中的Span ID,某个请求的耗时占比超过了48%。如果某个函数的耗时占比过大,这可能是由于它本身的执行速度较慢,或者它的实现方式存在问题。这个函数可能成为整个链路的瓶颈,影响整体性能。在这种情况下,可以考虑将该函数改为异步操作,或者拆分为并行的请求,以帮助业务线提高性能。

基于同层并发请求重复次数占比,分析代码不合理调用。 有时候我们会在同层中重复调用某个接口,例如在某个地方调用了应用中心,又在另一个地方调用了应用中心,然后在下一层又调用了应用中心,这种重复调用是完全不合理的。

file

在同层的情况下,这样的调用可能会达到几十次甚至上百次,这显然是没有必要的。可能有些同学会说,只是把编写好的代码复制粘贴过来,没有去分析它的逻辑,长此以往,这些重复调用会导致系统性能下降。

这些是我简单列举的一些Trace数据有价值的点,在实际过程中,它的应用价值远不止这些。也欢迎大家开放探讨和交流。

四、总结展望

在构建整个APM体系过程中,三个主要组件:日志收集组件、传输链路治理以及Flink任务性能优化。日志收集组件和传输链路治理主要解决日志大流量和并发的问题。在日志组件中,关注内部和外部内存的限制。在传输层,关注任务调度和集群性能优化。

最后,分析了APM系统的价值和意义。每家企业都建设自己的APM系统,同时更需要深入挖掘其价值。APM系统的真正意义在于能够通过数据客观深入了解系统的性能。通过APM系统,可以优化系统的性能、提高用户体验、减少故障和降低潜在风险。

Q&A

1、直播迁移到Docker 和使用CGroup 技术的成本怎么评估的?

2、如何及时发现未被监控的指标项,避免未被观测的指标突变引发故障?

3、接口偶发性超时,调用链只能看到超时接口名称,看不到内部方法,无法定位根因,也难以复现,怎么办?

以上问题答案,欢迎点击“阅读全文”,观看完整版解答!

本文由博客一文多发平台 OpenWrite 发布!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JVM-垃圾回收

目录 1、GC过程 2、垃圾回收算法 2.1、标记-清除 2.2、标记-整理 2.3、复制 2.4、分代收集算法 3、TLAB 4、对象如何进入老年代 5、卡片标记 6、HotSpot垃圾回收器 6.1、年轻代垃圾回收器 6.2、老年代垃圾回收器 6.3、如何配置垃圾回收器 6.4、STW 7、CMS垃圾回收…

【漏洞复现】Apache_Tomcat7+ 弱口令 后台getshell漏洞

感谢互联网提供分享知识与智慧,在法治的社会里,请遵守有关法律法规 文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞扫描3、漏洞验证 说明内容漏洞编号漏洞名称Tomcat7 弱口令 && 后台getshell漏洞漏洞评级高…

SpringBoot整合EasyExcel

springboot整合easyExcel的全流程&#xff0c;跟着做就能出来。对项目没有侵入要求。0侵入&#xff0c;可插拔 依赖 <!--操作Excel依赖--><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>…

linux下实现电脑开机后软件自启动

实现linux的软件自启动&#xff0c;需要四个文件 第一个【displayScreen.desktop】文件&#xff0c;.desktop文件就是一个用来运行程序的快捷方式,也叫启动器&#xff0c;常用来自启动用的文件&#xff0c;内容如下 [Desktop Entry] #要执行的脚本位置 Exec/home/yicaobao/te…

offsetof宏的使用、模拟实现及 (size_t)(((struct_type*)0)->mem_name)的解释

宏原型&#xff1a;offsetof(type,member) 作用&#xff1a;返回数据结构或联合体类型中成员的偏移量&#xff0c;以字节为单位 返回值&#xff1a;size_t类型的无符号整数 使用案例&#xff1a; #include <stdio.h> #include <stddef.h> struct foo {ch…

Unity热更新那些事

目录 热更新方案Unity程序的两种编译方式编译阶段执行阶段Mono方式IL2CPP方式两种方式打包以后的项目目录结构 其他 ILRuntime热更新ILRuntime使用注意ILRuntime的实现原理ILRuntime的性能优化建议ILRuntime的性能优化建议 HybridCLR热更新 参考链接 Unity热更新那些事 一小时极…

pda条码二维码扫描数据采集安卓手持终端扫码热敏标签打印一体机

HT800新一代移动物联终端是深圳联强优创信息科技有限公司自主研发的基于Android11操作系统的高性能、高可靠的工业级手持数据终端&#xff0c;能与其它设备进行无线通讯&#xff0c;提供良好的操作界面&#xff0c;支持条码扫描、RFID读写&#xff08;NFC&#xff09;、GPS定位…

Android 扩大View可点击区域范围

有时候会遇到这种需求&#xff1a;本身控件显示在很小的范围内&#xff0c;但是要求扩大可点击的区域。根据官方文档https://developer.android.com/develop/ui/views/touch-and-input/gestures/viewgroup?hlzh-cn#delegate可以得知通过 TouchDelegate 类&#xff0c;让父视图…

【复盘】记录一次JVM 异常问题 java.lang.OutOfMemoryError: unable to create new native thread

背景是最新运营提了一个需求&#xff0c;需要根据用户信息拉去三分机构的信贷数据&#xff0c;需要达到一天百万级别&#xff0c;但是经过实际测试&#xff0c;也只能达到40W量级&#xff0c;具体就是通过起多个Spring Boot项目&#xff0c;每个项目1S拉一个用户&#xff0c;基…

使用 Rust 进行程序

首先&#xff0c;我们需要安装必要的库。在终端中运行以下命令来安装 scraper 和 reqwest 库&#xff1a; rust cargo install scraper reqwest 然后&#xff0c;我们可以开始编写程序。以下是一个基本的爬虫程序&#xff0c;用于爬取 上的图片&#xff1a; rust use reqwe…

接口自动化测试分层设计与实践总结01

本文以笔者当前使用的自动化测试项目为例&#xff0c;浅谈分层设计的思路&#xff0c;不涉及到具体的代码细节和某个框架的实现原理&#xff0c;重点关注在分层前后的使用对比&#xff0c;可能会以一些伪代码为例来说明举例。 接口测试三要素&#xff1a; 参数构造 发起请求&…

C#,数值计算——偏微分方程,Relaxation的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { public class Relaxation { private Relaxation() { } public static void sor(double[,] a, double[,] b, double[,] c, double[,] d, double[,] e, double[,] f, double[,] u, double rjac) …

Python基础(第五期): python数据容器(序列) 列表 集合 元素 字符串 字典 序列遍历操作

python基础专栏 python基础&#xff08;第五期&#xff09; 文章目录 python基础&#xff08;第五期&#xff09;数据容器一、列表1、列表的定义2、列表的下标索引 3、列表的(添加)方法3.1 列表的查询方法3.2 修改特定下标索引的值3.3 列表指定位置插入元素3.3 列表指定元素的追…

Elasticsearch:搜索架构

Elasticsearch 全文检索的复杂性 为了理解为什么全文搜索是一个很难解决的问题&#xff0c;让我们想一个例子。 假设你正在托管一个博客发布网站&#xff0c;其中包含数亿甚至数十亿的博客文章&#xff0c;每个博客文章包含数百个单词&#xff0c;类似于 CSDN。 执行全文搜索…

【Unity细节】为什么UI移动了锚点,中心点和位置,运行的时候还是不在设置的位置当中

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 &#x1f636;‍&#x1f32b;️收录于专栏&#xff1a;unity细节和bug &#x1f636;‍&#x1f32b;️优质专栏 ⭐【…

数据约束及增删改查(CRUD)进阶-MySQL

文章目录 一、数据库约束1.1 约束类型1.2 NULL约束1.3 UNIQUE&#xff1a;唯一约束1.4 DEFAULT&#xff1a;默认值约束1.5 PRIMARY KEY&#xff1a;主键约束1.6 FOREIGN KEY&#xff1a;外键约束1.7 CHECK 约束&#xff08;了解&#xff09; 二、表的设计2.1 一对一2.2 一对多2…

kimera论文阅读

功能构成&#xff1a; Kimera包括四个关键模块: Kimera-VIO的核心是基于gtsam的VIO方法[45]&#xff0c;使用IMUpreintegration和无结构视觉因子[27]&#xff0c;并在EuRoC数据集上实现了最佳性能[19]; Kimera-RPGO:一种鲁棒姿态图优化(RPGO)方法&#xff0c;利用现代技术进…

代码随想录 Day38 完全背包问题 LeetCode T70 爬楼梯 T322 零钱兑换 T279 完全平方数

前言 在今天的题目开始之前,让我们来回顾一下之前的知识,动规五部曲 1.确定dp数组含义 2.确定dp数组的递推公式 3.初始化dp数组 4.确定遍历顺序 5.打印dp数组来排错 tips: 1.当求取物品有限的时候用0-1背包,求取物品无限的时候用完全背包 结果是排列还是组合也有说法,当结果是组…

vue 实现在线预览Excel-LuckyExcel/LuckySheet实现方案

一、准备工作 1. npm安装 luckyexcel npm i -D luckyexcel 2.引入luckysheet 注意&#xff1a;引入luckysheet&#xff0c;只能通过CDN或者直接引入静态资源的形式&#xff0c;不能npm install。 个人建议直接下载资源引入。我给你们提供一个下载资源的地址&#xff1a; …