客户端动态降级系统

6600909dc06a4dd9a102933d90ac2fc2.jpeg

41f2186e9ce2e22a8bdd74c6fb1005ea.gif

本文字数:4576

预计阅读时间:20分钟

c716a54f88dbc8768458a0efdc5e857f.png

01

背景

无论是iOS还是Android系统的设备,在线上运行时受硬件、网络环境、代码质量等多方面因素影响,可能会导致性能问题,这一类问题有些在开发阶段是发现不了的。如何在线上始终为用户提供一个相对顺畅的用户体验,是客户端开发需要考虑的一个问题。

02

服务降级、熔断

服务端有降级机制和熔断机制,在设计客户端降级系统时可以参照服务端现有方案。例如发生性能问题或网络拥堵的情况,需要减少设备和网络的负担,等恢复后再进行策略升级。

服务端降级机制,当服务端出现整体负载较大,或因为特殊原因出现数据错误,则会触发降级。不同的情况对应不同的降级策略。例如数据原因导致的,可以不去读DB数据库,直接返回缓存数据。从用户的角度来看,可能是数据更新不及时,但可以正常显示。

服务端熔断机制,熔断机制是比降级更严重的情况,当服务端中某个微服务不可用,或响应时间过长,则会触发熔断,不再调用这个服务。从用户的角度来看,可能是头像不能显示,或者页面部分模版未显示,购物车商品结算不能使用优惠券等。

03

方案简述

首先,我们需要捋清楚,客户端需要处理的问题都有哪些。我将其分为两类,性能和网速,性能又可以细化为CPU、内存、电量三类,这三类都会对App的运行造成影响。同样的,我们不能直接把性能分为好和坏两种,而是需要通过枚举的方式,将其细化为不同等级。

这里以iOS系统为例,我们需要对iOS设备CPU、内存、电量、网速进行实时监控,可以设定一个合理的间隔区间。在发生前面的性能问题时,通过对不同类型的问题进行阈值计算,从而得出对应的等级。如果级别发生变化,则通过通知的方式,告诉业务方降级或升级。

当发生降级时,业务方进行对应的降级处理,例如降低网络请求的图片尺寸。通过业务降级处理,降低系统性能消耗,让CPU、内存逐步恢复到正常区间,再进行业务升级,恢复原有业务处理规则。

通过上述方式,来保证发生性能或网络问题时,用户依然可以较为流畅的使用App,并且App内功能的正常使用不受影响。

04

整体设计

动态降级系统的设计,主要分为三个部分,职责划分如下。

DynamicLevelManager:调用monitordecision完成分级计算,当级别发生变化时,通过通知的方式告知业务方。

DynamicLevelMonitor:监控关键性能指标,由manager定时调用。

DynamicLevelDecision:由manager将收集到的性能指标交给decisiondesicion对于指标进行统一计算,并决定性能级别,并返回给manager

8ae50dffd87b0dfb0e108129c6c6457b.png

下面是脱敏后的伪代码,主要是表达清楚设计思路。 demo 代码也可以直接跑起来,如有需要可以直接 copy 拿去用。

05

DynamicLevelManager

DynamicLevelManager为动态降级系统的核心类,后面都称为manager,当App启动时通过openLevelAnalyze方法注册监听,从而开启一个由dispatch_source_t实现的loop,每隔1.5秒执行一次,执行时会触发dispatch_source_set_event_handler的回调方法。dispatch_source_t由手机硬件时钟触发,不受主线程卡顿影响,监听相对精确很多。

/// 开启动态降级监控系统
- (void)openLevelAnalyze {self.sourceHandle = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));dispatch_source_set_timer(self.sourceHandle, dispatch_time(DISPATCH_TIME_NOW, 0), 1.5 * NSEC_PER_SEC, 0);dispatch_source_set_event_handler(self.sourceHandle, ^{/// 计算综合性能级别CGFloat cpuUsageValue = [[DynamicLevelMonitor sharedInstance] cpuUsageForApp];NSInteger memoryUsageValue = [[DynamicLevelMonitor sharedInstance] useMemoryForApp];CGFloat batteryUsageValue = [[DynamicLevelMonitor sharedInstance] batteryUsageForApp];[[DynamicLevelDecision sharedInstance] calculatePerformanceLevelWithMemoryValue:memoryUsageValuecpuValue:cpuUsageValuebatteryValue:batteryUsageValuecompletionBlock:^(MemoryUsageLevel memoryLevel, CPUUsageLevel cpuLevel, BatteryUsageLevel batteryLevel, MultiplePerformanceLevel performanceLevel) {/// 判断级别是否发生变化,发送性能降级或恢复原有等级的通知if (performanceLevel != self.currentPerformanceLevel) {[self postPerformanceNotifiWithPerformanceLevel:performanceLevelmemoryLevel:memoryLevelcpuLevel:cpuLevelbatteryLevel:batteryLevel];}}];/// 计算网络性能级别CGFloat networkSpeed = [[QUICManager shareQUICManager] currentNetworkSpeed];[[DynamicLevelDecision sharedInstance] calculateNetworkLevelWithNetworkSpeed:networkSpeed completionBlock:^(NetworkSpeedLevel speedLevel) {/// 判断级别是否发生变化,发送网络降级或恢复原有等级的通知if (speedLevel != self.currentNetworkSpeedLevel) {[self postPerformanceNotifiWithNetworkSpeedLevel:speedLevel];}}];});dispatch_resume(self.sourceHandle);
}- (void)closeLevelAnalyze {dispatch_source_cancel(self.sourceHandle);
}/// 发送性能降级或恢复原有等级的通知
- (void)postPerformanceNotifiWithPerformanceLevel:(MultiplePerformanceLevel)performanceLevelmemoryLevel:(MemoryUsageLevel)memoryLevelcpuLevel:(CPUUsageLevel)cpuLevelbatteryLevel:(BatteryUsageLevel)batteryLevel {[[NSNotificationCenter defaultCenter] postNotificationName:@"PerformanceLevelChanged"object:niluserInfo:@{@"performanceLevel": @(performanceLevel),@"memoryLevel": @(memoryLevel),@"cpuLevel": @(cpuLevel),@"batteryLevel": @(batteryLevel)}];
}/// 发送网络降级或恢复原有等级的通知
- (void)postPerformanceNotifiWithNetworkSpeedLevel:(NetworkSpeedLevel)networkSpeedLevel {[[NSNotificationCenter defaultCenter] postNotificationName:@"NetworkSpeedLevelChanged"object:niluserInfo:@{@"networkSpeedLevel": @(networkSpeedLevel)}];
}

manager对外界提供的消息回调分为两类,一个是由CPU、内存、电量,综合计算出的性能分级performanceLevel,另一个是网速分级networkSpeedLevel

5.1 performanceLevel

handler方法中会调用monitorcpuUsageForApp方法获取CPU的使用率,取值范围是0-1,当CPU发生超频,也有超过1的情况。调用monitoruseMemoryForApp方法获取内存使用率,取值范围是0-1。调用monitorbatteryUsageForApp方法获取剩余电量,取值范围是0-100

获取这些信息后,调用decisioncalculatePerformanceLevel方法,将信息交由decision进行综合计算,计算后返回结果为四个值。

1、performanceLevel:综合性能分级

2、memoryLevel:内存占用率分级

3、cpuLevelCPU使用率分级

4、batteryLevel:电量使用分级

这里的核心就是performanceLevel综合分级,类型为MultiplePerformanceLevel,这是根据内存、电量、CPU综合计算出来的结果。上述的四个值通过枚举定义,具体定义如下。

/// 综合性能枚举
typedef NS_ENUM(NSUInteger, MultiplePerformanceLevel) {MultiplePerformanceLevelNormal,MultiplePerformanceLevelLow,MultiplePerformanceLevelVeryLow,
};/// cpu使用率枚举,overclock表示cpu已超频
typedef NS_ENUM(NSUInteger, CPUUsageLevel) {CPUUsageLevelLow,CPUUsageLevelHigh,CPUUsageLevelOverclock,
};/// 内存使用级别枚举
typedef NS_ENUM(NSUInteger, MemoryUsageLevel) {MemoryUsageLevelLow,MemoryUsageLevelMiddle,MemoryUsageLevelHigh,
};/// 电量使用枚举,high表示使用较多,电量剩余1%
typedef NS_ENUM(NSUInteger, BatteryUsageLevel) {BatteryUsageLevelLow,BatteryUsageLevelMiddle,BatteryUsageLevelHigh,
};

拿到这些性能level后,会判断performanceLevel是否发生变化,如果低于当前level,则发生降级。如果高于当前level,则表示性能恢复。随后会调用NSNotificationCenter以通知的形式进行消息通知,通知名为PerformanceLevelChanged,并这四个分级参数传递过去。如果level没有发生改变,则不会发出消息通知。

5.2 speedLevel

另一个是网速分级,这个指标并没有归类于性能分级中,因为和性能分级并不是一类。

handler方法中会调用网络库QUICManagercurrentNetworkSpeed方法,获得当前网速,单位是kb每秒。这里的QUICManager是公司自研的网络库,提供当前实时网速。

拿到网速数据后,会调用decisioncalculateNetworkLevel方法,交给decision进行计算。decision会返回一个speedLevel当前网速级别,其类型是NetworkSpeedLevel,分为三个级别。

/// 当前网速枚举
typedef NS_ENUM(NSUInteger, NetworkSpeedLevel) {NetworkSpeedLevelNormal,NetworkSpeedLevelLow,NetworkSpeedLevelVeryLow,
};

拿到这些信息后,会判断speedLevel是否发生改变,如果低于当前level,则表示网速发生劣化。如果高于当前level,则表示网速恢复。随后会调用NSNotificationCenter以通知的形式进行消息通知,通知名为NetworkSpeedLevelChanged,并将speedLevel参数传递过去。如果level没有发生改变,则不会发出消息通知。

06

DynamicLevelDecision

Decision负责接收manager传入的数据信息,返回对应的性能级别。在计算时,会先对传入的参数进行计算,计算出对应单个性能参数的level分级,再计算performanceLevel分级。

/// 进行综合性能计算
- (void)calculatePerformanceLevelWithMemoryValue:(NSInteger)memoryValuecpuValue:(CGFloat)cpuValuebatteryValue:(CGFloat)batteryValuecompletionBlock:(DynamicPerformanceLevelBlock)completionBlock {MemoryUsageLevel memoryLevel = [self calculateMemoryUsageLevelWithMemoryValue:memoryValue];CPUUsageLevel cpuLevel = [self calculateCPUUsageLevelWithCpuValue:cpuValue];BatteryUsageLevel batteryLevel = [self calculateBatteryUsageLevelWithBatteryValue:batteryValue];MultiplePerformanceLevel performanceLevel = MultiplePerformanceLevelNormal;if (batteryLevel == BatteryUsageLevelHigh) {performanceLevel = MultiplePerformanceLevelVeryLow;}else if (cpuLevel == CPUUsageLevelOverclock && memoryLevel == MemoryUsageLevelHigh) {performanceLevel = MultiplePerformanceLevelVeryLow;}else if (batteryLevel >= 1 && memoryLevel >= 1) {performanceLevel = MultiplePerformanceLevelLow;}else if (batteryLevel >= 1 && cpuLevel >= 1) {performanceLevel = MultiplePerformanceLevelLow;}else if (memoryLevel >= 1 && cpuLevel >= 1) {performanceLevel = MultiplePerformanceLevelLow;}if (completionBlock) {completionBlock(memoryLevel, cpuLevel, batteryLevel, performanceLevel);}
}/// 进行网速级别计算
- (void)calculateNetworkLevelWithNetworkSpeed:(CGFloat)networkSpeedcompletionBlock:(DynamicNetworkSpeedLevelBlock)completionBlock {[self.networkSpeedArray addObject:@(networkSpeed)];if (self.networkSpeedArray.count > 5) {[self.networkSpeedArray removeObjectsInRange:NSMakeRange(0, self.networkSpeedArray.count - 5)];}__block NSInteger middleCount = 0;__block NSInteger highCount = 0;[self.networkSpeedArray enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {if (obj.floatValue <= 200) {middleCount++;}if (obj.floatValue <= 50) {highCount++;}}];NetworkSpeedLevel networkThreshold = NetworkSpeedLevelNormal;if (highCount >= 3) {networkThreshold = NetworkSpeedLevelVeryLow;} else if (middleCount >= 3) {networkThreshold = NetworkSpeedLevelLow;}if (completionBlock) {completionBlock(networkThreshold);}
}/// 计算内存使用级别
- (MemoryUsageLevel)calculateMemoryUsageLevelWithMemoryValue:(NSInteger)memoryValue {[self.memoryUsageArray addObject:@(memoryValue)];if (self.memoryUsageArray.count > 5) {[self.memoryUsageArray removeObjectsInRange:NSMakeRange(0, self.memoryUsageArray.count - 5)];}__block NSInteger middleCount = 0;__block NSInteger highCount = 0;[self.memoryUsageArray enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {if (obj.floatValue > 0.45) {highCount++;}if (obj.floatValue > 0.4) {middleCount++;}}];MemoryUsageLevel memoryThreshold = MemoryUsageLevelLow;if (highCount >= 3) {memoryThreshold = MemoryUsageLevelHigh;} else if (middleCount >= 3) {memoryThreshold = MemoryUsageLevelMiddle;}return memoryThreshold;
}/// 计算CPU使用级别
- (CPUUsageLevel)calculateCPUUsageLevelWithCpuValue:(CGFloat)cpuValue {[self.cpuUsageArray addObject:@(cpuValue)];/// cpu level calculatereturn CPUUsageLevelLow;
}/// 计算电量使用级别
- (BatteryUsageLevel)calculateBatteryUsageLevelWithBatteryValue:(CGFloat)batteryValue {[self.batteryUsageArray addObject:@(batteryValue)];/// battery level calculatereturn BatteryUsageLevelLow;
}

6.1 单个性能参数level计算

CPU:传入数值>0.8,也就是CPU使用率超过80%CPUUsageLevel等于levelMiddle,如果CPU使用率超过100%,则发生CPU超频,CPUUsageLevel等于levelHigh

内存:因为在iOS系统中,App最多可以使用设备总内存的50%,内存使用率超过40%MemoryUsageLevel等于levelMiddle,如果内存使用率超过45%MemoryUsageLevel等于levelHigh

电量:传入数值<6%,则表示低电量,BatteryUsageLevel等于levelMiddle,传入数值<1%,则表示到达临界值,BatteryUsageLevel等于levelHigh

6.2 performanceLevel计算

得到上述三个性能参数的level后,manager会调用decisioncalculatePerformanceLevel方法,通过方法返回值获得performanceLevel,其类型为MultiplePerformanceLevel。计算performanceLevel时,根据先后顺序会有如下条件,条件之间彼此互斥。

1、判断batteryLevel是否等于levelHigh,如果是的话表示电量接近临界值,则直接将performanceLevel设置为veryLow

2、cpuLevel等于overclockmemoryLevel等于high,则表示CPU处于超频状态,并且内存占用也处于非常高的状态,此时很容易被系统强杀造成OOM,直接将performanceLevel设置为veryLow

3、batteryLevelcpuLevelmemoryLevel,任意两者构成middlehigh,则将performanceLevel设置为low

6.3 speedLevel计算

Manager调用decisioncalculateNetworkLevel方法,获取网络变化指标。在计算speedLevel时,传入的网速小于200kb/s,则表示网速较低,将speedLevel设置为low,传入的网速小于50kb/s,则表示网速非常慢,将speedLevel设置为veryLow

6.3.1 性能计算窗口

在获取性能参数时,不能以某一个时间点的性能数据作为计算依据,而是以一个时间窗口的多条性能数据作为计算依据,这样更能反映这个时间段的综合性能。

性能计算窗口是基于handler的回调,收集从当前次到前四次,这连续五次的数据,综合进行计算。例如NetworkSpeedLevel的计算,如果超过三次网速都小于50kb/s,则NetworkSpeedLevel等于veryLow,如果超过三次网速都小于200kb/s,则NetworkSpeedLevel等于low

从实现的角度,性能计算窗口时通过NSMutableArray实现的,通过FIFO策略进行淘汰,始终保留相邻的五条数据。

07

DynamicLevelMonitor

Monitor的作用是提供获取系统性能信息的方法,在handler中调用的三个monitor的方法,内部实现如下。

/// 当前app内存使用量,返回单位百分比
- (NSInteger)useMemoryForApp {task_vm_info_data_t vmInfo;mach_msg_type_number_t count = TASK_VM_INFO_COUNT;kern_return_t kernelReturn = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);if (kernelReturn == KERN_SUCCESS) {int64_t memoryUsageInByte = (int64_t) vmInfo.phys_footprint;int64_t totalMemory = [[NSProcessInfo processInfo] physicalMemory];return memoryUsageInByte / totalMemory;} else {return -1;}
}/// 当前app的CPU使用率
- (CGFloat)cpuUsageForApp {kern_return_t           kr;thread_array_t          thread_list;mach_msg_type_number_t  thread_count;thread_info_data_t      thinfo;mach_msg_type_number_t  thread_info_count;thread_basic_info_t     basic_info_th;kr = task_threads(mach_task_self(), &thread_list, &thread_count);if (kr != KERN_SUCCESS)return -1;float total_cpu_usage = 0;for (int i = 0; i < thread_count; i++) {thread_info_count = THREAD_INFO_MAX;kr = thread_info(thread_list[i], THREAD_BASIC_INFO, (thread_info_t)thinfo, &thread_info_count);if (kr != KERN_SUCCESS) {return -1;}basic_info_th = (thread_basic_info_t)thinfo;if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {total_cpu_usage += basic_info_th->cpu_usage / (float)TH_USAGE_SCALE;}}kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));assert(kr == KERN_SUCCESS);return total_cpu_usage;
}

UseMemoryForApp方法实现,通过系统task_info函数获取到当前App已使用的内存,通过NSProcessInfophysicalMemory方法获得设备的物理内存,二者的单位都是bytes,通过计算task_infophysicalMemory的百分比,得到App已使用的内存的百分比。

CpuUsageForApp方法实现,通过系统task_threads函数获得所有线程的信息thread_listthread_list是一个数组,遍历thread_list得到thread_info_t单个线程的信息,累加thread_info_tcpu_usage属性(cpu_usage属性表示当前线程使用CPU的百分比),得到总的CPU使用占比。

BatteryUsageForApp方法实现,设置系统UIDevicebatteryMonitoringEnabledtrue,开启电量监听。并通过通知接收电量变化的回调,回调的单位是0~1,再乘以100返回给manager

08

业务方

业务方收到PerformanceLevelChanged的消息后,可以基于performanceLevel的综合性能进行判断,如果是veryLow,可以暂停流内秒播处理,也就是在视频流中,滑动到下一条视频不会自动播放。

也可以基于单个性能level进行判断,例如batteryLevel指标为middlelow,也就是电量低于6%时,可以提示用户先不进行视频文件缓存等非常消耗性能的操作,以避免因为消耗性能的操作,导致手机自动关机。

业务方收到NetworkSpeedLevelChanged的消息后,可以根据通知传过来的speedLevel参数,lowveryLow可以有不同的处理。例如可以降低向服务端获取图片的尺寸,low可以将图片尺寸压缩80%,如果是veryLow可以将图片尺寸压缩60%,可以明显提升弱网下,向服务器获取图片的速度。压缩比率在请求图片URL时,在URL中拼接发送给服务端,服务端会返回对应压缩比率的图片。

28bf0080527b36e59706b3a23175ed60.jpeg

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

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

相关文章

【命名空间详解】c++入门

目录 命名空间的定义 1.命名空间的正常定义 2.命名空间还可以嵌套 3. 命名空间可以合并 命名空间的使用 1.加命名空间名称及作用域限定符 2.使用using将命名空间中某个成员引入 3.使用using namespace 命名空间名称 引入 输入&#xff0c;输出 输出 命名空间的定义 …

Java技术学习|Git

学习材料声明 尚硅谷Git入门到精通全套教程&#xff08;涵盖GitHub\Gitee码云\GitLab&#xff09; GIt Git 是一个免费的、开源的分布式版本控制系统&#xff0c;可以快速高效地处理从小型到大型的各种项目。Git 易于学习&#xff0c;占地面积小&#xff0c;性能极快。 它具有…

英伟达大跳水!一夜暴跌10%,市值蒸发2000亿

相信大家已经在各大社交平台上看到了&#xff0c;英伟达一夜蒸发了2000亿美元&#xff01; GPT-3.5研究测试&#xff1a; https://hujiaoai.cn GPT-4研究测试&#xff1a; https://higpt4.cn Claude-3研究测试&#xff08;全面吊打GPT-4&#xff09;&#xff1a; https://hic…

大语言模型隐私防泄漏:差分隐私、参数高效化

大语言模型隐私防泄漏&#xff1a;差分隐私、参数高效化 写在最前面题目6&#xff1a;大语言模型隐私防泄漏Differentially Private Fine-tuning of Language Models其他初步和之前的基线微调模型1微调模型2通过低秩自适应进行微调&#xff08; 实例化元框架1&#xff09; 在隐…

Vue2 —— 学习(九)

目录 一、全局事件总线 &#xff08;一&#xff09;全局总线介绍 关系图 对图中的中间商 x 的要求 1.所有组件都能看到 2.有 $on $off $emit &#xff08;二&#xff09;案例 发送方 student 接收方 二、消息订阅和发布 &#xff08;一&#xff09;介绍 &#xff08…

虚拟机中的打印机,无法打印内容,打印的是白纸或英文和数字,打印不了中文

原因&#xff1a;打印机驱动设置不正确 解决方案&#xff1a; 打开打印机属性 -> 高级 -> 新驱动程序 下一页 -> Windows 更新 耐心等待&#xff0c;时间较长。 选择和打印机型号匹配的驱动&#xff0c;我选择的是&#xff1a; 虽然虚拟机和主机使用的驱动不…

跨境电商指南:防关联浏览器和云主机有什么区别?

跨境电商的卖家分为独立站卖家和平台卖家。前者会自己开设独立站点&#xff0c;比如通过 shopify&#xff1b;后者则是入驻亚马逊或 Tiktok 等平台&#xff0c;开设商铺。其中平台卖家为了扩大收益&#xff0c;往往不止开一个店铺&#xff0c;或者有店铺代运营的供应商&#xf…

皇后之战:揭秘N皇后问题的多维解法与智慧【python 力扣52题】

作者介绍&#xff1a;10年大厂数据\经营分析经验&#xff0c;现任大厂数据部门负责人。 会一些的技术&#xff1a;数据分析、算法、SQL、大数据相关、python 欢迎加入社区&#xff1a;码上找工作 作者专栏每日更新&#xff1a; LeetCode解锁1000题: 打怪升级之旅 python数据分析…

Go之map详解

map的结构 map实现的两个关键数据结构 hmap 定义了map的结构bmap 定义了hmap.buckets中每个bucket的结构 // A header for a Go map. type hmap struct {count int // 元素的个数flags uint8 // 状态标记&#xff0c;标记map当前状态&#xff0c;是否正在写入B …

css层叠性,继承性,优先级

前言 本文概要&#xff1a;讲述css的三大特性&#xff0c;层叠&#xff0c;继承和优先级。 层叠性 描述&#xff1a;我们试想以下这种情况&#xff1a;我们定义了同一个选择器&#xff0c;但是定义的属性不同。属性有相同的也有不同的&#xff0c;那么最后我们这个页面会听谁的…

CSS display属性

目录 概述&#xff1a; 设置display示例&#xff1a; none&#xff1a; block&#xff1a; inline&#xff1a; inline-block &#xff1a; 概述&#xff1a; 在CSS中我们可以使用display属性来控制元素的布局&#xff0c;我们可以通过display来设置元素的类型。 在不设置…

封装个js分页插件

// 分页插件类 class PaginationPlugin {constructor(fetchDataURL, options {}) {this.fetchDataURL fetchDataURL;this.options {containerId: options.containerId || paginationContainer,dataSizeAttr: options.dataSizeAttr || toatalsize, // 修改为实际API返回的数据…

ppt技巧:​如何将两个PPT幻灯片文件合并成一个?

第一种方式&#xff1a;复制粘贴幻灯片 1. 打开第一个PPT幻灯片文件&#xff0c;确保你已经熟悉该文件的内容和布局。 2. 打开第二个PPT幻灯片文件&#xff0c;浏览其中的所有幻灯片&#xff0c;选择你想要合并到第一个文件中的幻灯片。 3. 使用快捷键CtrlC&#xff08;Wind…

虚拟ip地址怎么弄到手机上

在当下的社会&#xff0c;手机已经变得至关重要&#xff0c;它融入了我们的日常生活&#xff0c;无论是上网冲浪、社交互动&#xff0c;还是工作学习&#xff0c;都离不开它。但有时候&#xff0c;由于某些限制&#xff0c;我们可能无法充分享受网络带来的便利。这时&#xff0…

Nginx part2.1

目录 搭建目录网页 为网页设置用户登录 做一个文件目录网页&#xff0c;并进行登陆 示范 搭建目录网页 启动nginx&#xff1a; systemctl start nginx 开机自启动nginx&#xff1a; systemctl enable nginx 启动完服务后&#xff0c;查看自己的nginx的状态&#xff1a;sys…

【JavaWeb】Day47.Mybatis基础操作——删除

Mybatis基础操作 需求 准备数据库表 emp 创建一个新的springboot工程&#xff0c;选择引入对应的起步依赖&#xff08;mybatis、mysql驱动、lombok&#xff09; application.properties中引入数据库连接信息 创建对应的实体类 Emp&#xff08;实体类属性采用驼峰命名&#xf…

【C++提高】常用容器

常用容器 引言&#xff1a;迭代器的使用一、vector容器1. vector基本概念2. vector的迭代器3. vector构造函数4. vector赋值操作5. vector容量和大小6. vector插入和删除7. vector数据存取8. vector互换容器9. vector预留空间 二、deque容器1. deque容器的基本概念2. deque容器…

python免费调用阿里云通义千问(q-wen-max)大模型API

文章目录 通义千问开通免费API Keypython调用阿里云通义千问API 通义千问 通义千问&#xff0c;是基于阿里巴巴达摩院在自然语言处理领域的研究和积累。采用更先进的算法和更优化的模型结构&#xff0c;能够更准确地理解和生成自然语言、代码、表格等文本。 支持更多定制化需…

HarmonyOs开发:导航tabs组件封装与使用

前言 主页的底部导航以及页面顶部的切换导航&#xff0c;无论哪个系统&#xff0c;哪个App&#xff0c;都是最常见的功能之一&#xff0c;虽然说在鸿蒙中有现成的组件tabs可以很快速的实现&#xff0c;但是在使用的时候&#xff0c;依然有几个潜在的问题存在&#xff0c;第一&a…

GRAF: Generative Radiance Fields for 3D-Aware Image Synthesis

GRAF: Generative Radiance Fieldsfor 3D-Aware Image Synthesis&#xff08;基于产生辐射场的三维图像合成&#xff09; 思维导图&#xff1a;https://blog.csdn.net/weixin_53765004/article/details/137944206?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3…