QEMU DirtyLimit特性介绍

news2024/10/7 3:17:05

文章目录

  • 背景
  • 基本原理
    • PML
    • Dirty-Ring
    • Dirty-Limit
  • 具体实现
    • 数据结构
      • vcpu_dirty_rate_stat
      • dirtylimit_state
    • 算法实现
      • 接口逻辑
        • qmp_set_vcpu_dirty_limit
        • qmp_cancel_vcpu_dirty_limit
      • 限制算法
        • 算法框架
        • 理想效果
        • 具体实现
  • 测试验证
    • QEMU
    • Libvirt
  • 一个广子

背景

  • 热迁移实现逻辑中,如果虚机内存负载高,源端不断产生新的内存脏页,热迁移由于不断传输源端新产生的脏页,导致剩余脏页量一直无法达到阈值,迟迟无法收敛。因此热迁移实现中,一个重要的逻辑就是实现脏页收敛算法,使得源端脏页能够尽快达到阈值。常用的收敛算法有auto-converge、xbzrle、compression、multifd等,这些算法各有其优缺点。
  • 其中auto-converge是最有效的收敛算法,其核心思想是降低脏页产生的速率。通过减少虚拟机vCPU运行时间来降低虚机脏页产生速率,使其小于迁移拷贝速率,以满足迁移收敛条件,参考cpu throttle原理浅析。该算法优点是任何虚拟化场景都适用且有效。缺点是在限制虚机脏页产生的同时,也限制了虚机vCPU运行时间,虚机的计算性能在迁移过程中也随之下降。
  • 内核引入dirty-ring后,提供了一种基于dirty-ring实现虚机内存脏页统计的方式,参考Dirty Ring脏页统计。dirty-ring的机制让我们很可以计算每个vCPU的脏页速率,如果再给每个vCPU设置一个速率上限,当vCPU超过上限时通过throttle的方式让其睡眠以达到降低脏页速率的目的。最后周期性计算vCPU脏页速率并对比设置的速率上限,当某个vCPU超过该上限时,就通过睡眠“惩罚”它。这样可以实现将所有vCPU都控制在一个设置的速率上限内。这就是DirtyLimit的核心思想。如果将其应用在热迁移中,可以达到和auto-converge相同的收敛效果,并且热迁过程中读性能还不会下降。DirtyLimit的介绍可以参考天翼云公众号文章: 迁移速度与计算性能兼得!天翼云DirtyLimit技术大显身手

基本原理

PML

  • Dirty-Ring基于Intel PML(page-modification log buffer)实现,我们首先介绍PML工作流程,其框架如下:
    在这里插入图片描述
    Intel VT-x提供的VMCS(virtual machine control structure)中,有三个地方与PML特性相关:
  1. Extended-Page-Table Pointer: EPTP字段中的bits[6],控制当物理CPU访问内存页后,硬件是否将对应的accessed and dirty flags 置位,PML需要开启。
    在这里插入图片描述
  2. VM-execution control fields: 控制区域中的Secondary Processor-Based VM-Execution Controls子字段的bits[17]控制位用于控制是否开启PML,PML需要开启。Control Field for Page-Modification Logging存放一段4K物理内存的地址,这段内存是就是PML的buffer,其内容是vCPU访问的物理内存页地址(GPA), 每条地址64bit,一共512条,因此其大小为512 * 64bit = 4K:
    在这里插入图片描述
    在这里插入图片描述
  3. Guest State area: Guest状态字段中的PML index子字段,用于指示物理CPU将下一次访问的内存地址记录到PML buffer的哪一条,一旦设置好,CPU填写了一条PML entry后,硬件会自动将PML index加1。
    在这里插入图片描述
  • 使能PML的整个流程是,开启EPTP的页表标脏功能,开启VM-execution control fields的PML开关,为每个CPU的PML buffer分配4K内存,将内存地址填入VM-execution control fields的地址字段,最后设置将CPU填写PML buffer的起始位置写入PML index,执行VMLAUNCH指令进入guest模式。

Dirty-Ring

  • 基于PML buffer,QEMU引入了可以对每个vCPU脏页跟踪的Dirty-Ring机制,基于该机制实现了每个vCPU的脏页速率计算。参考QEMU脏页速率计算原理中的Dirty-Ring一节。

Dirty-Limit

  • 实现脏页速率计算后,我们可以设置DirtyLimit并周期性地计算每个vCPU的脏页速率,对比两者的值,如果脏页速率大于DirtyLimit,便通过让对应vCPU睡眠来惩罚它,让vCPU的脏页速率逐渐下降至设置的值,示意图如下:
    在这里插入图片描述
  1. 首先QEMU获取用户设定的DirtyLimit值,将其保存到内存中,DirtyLimit与vCPU为1对1的关系
  2. 之后启动单独线程周期性计算vCPU的脏页速率DirtyRate并保存到内存中,DirtyRate与vCPU也为1对1的关系
  3. 当KVM将某个vCPU的Dirty Ring的条目填满之后,对应vCPU便会抛出异常,vCPU线程从内核态退出到用户态,此时QEMU在该路径上将其拦截,对比该vCPU的DirtyRate和DirtyLimit的,如果发现DirtyRate大于DirtyLimit,便计算用于“惩罚”vCPU的睡眠时间,然后让对应vCPU线程睡眠。

具体实现

  • dirty-limit的实现在单独的一个文件中:softmmu/dirtylimit.c

数据结构

  • dirty-limit的核心数据结构就是两个全局变量:DirtyRate和DirtyLimit

vcpu_dirty_rate_stat

  • 保存DirtyRate的数据结构
typedef struct VcpuStat {
	/* 保存rates数组中的vCPU个数 */
    int nvcpu; /* number of vcpu */
    /* dirtyrate数组,记录每个vCPU对应的脏页速率 */
    DirtyRateVcpu *rates; /* array of dirty rate for each vcpu */
} VcpuStat;

struct {                         
    VcpuStat stat;     	/* 保存虚机脏页速率 */          
    bool running;    	/* 标记当前是否正在周期性计算脏页速率 */
    QemuThread thread; 	/* 周期性计算脏页速率的线程 */
} *vcpu_dirty_rate_stat

dirtylimit_state

  • 保存DirtyLimit数据结构
typedef struct VcpuDirtyLimitState {
    int cpu_index;		/* vCPU ID*/
    bool enabled;   	/* 标记该vCPU是否使能DirtyLimit*/
    /*
     * Quota dirty page rate, unit is MB/s
     * zero if not enabled.
     */
    uint64_t quota;	 	/* vCPU使能DirtyLimit时用户设置的上限值 */
} VcpuDirtyLimitState;

struct {
    VcpuDirtyLimitState *states;	/* 保存虚机的DirtyLimit */
    /* Max cpus number configured by user */
    int max_cpus;					/* 虚机配置的最大vCPU数 */
    /* Number of vcpu under dirtylimit */
    int limited_nvcpu;				/* 虚机使能DirtyLimit的vCPU数 */
} *dirtylimit_state;

/* protect dirtylimit_state */
static QemuMutex dirtylimit_mutex;

/* dirtylimit thread quit if dirtylimit_quit is true */
static bool dirtylimit_quit;

算法实现

接口逻辑

  • DirtyLimit算法的核心逻辑主要在qmp_set_vcpu_dirty_limitqmp_cancel_vcpu_dirty_limit中实现,我们简单分析其逻辑
qmp_set_vcpu_dirty_limit
void qmp_set_vcpu_dirty_limit(bool has_cpu_index,	/* qmp是否传入*/
                              int64_t cpu_index,             
                              uint64_t dirty_rate,           
                              Error **errp)                  
{
	/* dirty-limit依赖dirty-ring,首先检查是否配置 */
    if (!kvm_enabled() || !kvm_dirty_ring_enabled()) {
        error_setg(errp, "dirty page limit feature requires KVM with"
                   " accelerator property 'dirty-ring-size' set'");
        return;
    }  
	/* dirty-limit可以不指定vCPU index,这是QEMU会限制所有vCPU,如果指定,检查vCPU index是否超出范围 */
    if (has_cpu_index && !dirtylimit_vcpu_index_valid(cpu_index)) {
        error_setg(errp, "incorrect cpu index specified");
        return;
    }  
	/* 如果当前热迁移正在使用dirtylimit算法,不希望受用户设置的影响,因此热迁移期间不允许使能和取消dirtylimit */
    if (!dirtylimit_is_allowed()) {
        error_setg(errp, "can't set dirty page rate limit while"
                   " migration is running");      
        return;
    }
	/* 如果dirty_rate传入0,表示取消dirty-limit */
    if (!dirty_rate) {
        qmp_cancel_vcpu_dirty_limit(has_cpu_index, cpu_index, errp);
        return;
    }
	/* 为保证qmp_set_vcpu_dirty_limit接口线程安全,保护dirtylimit_state全局变量,加锁 */
    dirtylimit_state_lock();
    
	/* 如果是第一次使能dirty-limit,完成初始化工作
	 * 初始化中主要是初始化全局变量并启动脏页速率计算线程
	 * 周期性计算其vCPU速率并更新到vcpu_dirty_rate_stat
	 * 最后,使能DirtyLimit限制,周期性地为每个超过dirty-limit的vCPU计算睡眠时间 */
    if (!dirtylimit_in_service()) {
        dirtylimit_init();
    }
    
	/* 设置用户配置的dirty-limit,核心工作就是更新全局变量dirtylimit_state */
    if (has_cpu_index) {
        dirtylimit_set_vcpu(cpu_index, dirty_rate, true);
    } else {
        dirtylimit_set_all(dirty_rate, true);
    }

    dirtylimit_state_unlock();
}
qmp_cancel_vcpu_dirty_limit
void qmp_cancel_vcpu_dirty_limit(bool has_cpu_index,
                                 int64_t cpu_index,
                                 Error **errp)
{   
	/* 检查依赖是否满足 */
    if (!kvm_enabled() || !kvm_dirty_ring_enabled()) {
        return;
    }
    /* 检查index是否在范围之内 */
    if (has_cpu_index && !dirtylimit_vcpu_index_valid(cpu_index)) {
        error_setg(errp, "incorrect cpu index specified");
        return;
    }
	/* 检查是否与热迁移冲突 */
    if (!dirtylimit_is_allowed()) {
        error_setg(errp, "can't cancel dirty page rate limit while"
                   " migration is running");
        return;
    }   
    /* 如果当前dirty-limit已经停止,直接返回 */            
    if (!dirtylimit_in_service()) {
        return;
    }
    /* 更新dirtylimit_state全局变量前加锁  */
    dirtylimit_state_lock();

	/* 更新dirtylimit_state */
    if (has_cpu_index) {
        dirtylimit_set_vcpu(cpu_index, 0, false);
    } else {
        dirtylimit_set_all(0, false);
    }
	/* 如果最后一个使能dirty-limit的vCPU被取消,停止脏页速率计算线程,停止DirtyLimit限制逻辑 */
    if (!dirtylimit_state->limited_nvcpu) {
        dirtylimit_cleanup();
    }

    dirtylimit_state_unlock();
}

限制算法

算法框架
  • DirtyLimit算法框架示意图如下:
  main   --------------> throttle thread ------------> PREPARE(1) <--------
  thread  \                                                |              |
           \                                               |              |
            \                                              V              |
             -\                                        CALCULATE(2)       |
               \                                           |              |
                \                                          |              |
                 \                                         V              |
                  \                                    SET PENALTY(3) -----
                   -\                                      |
                     \                                     |
                      \                                    V
                       -> virtual CPU thread -------> ACCEPT PENALTY(4)
  • 当qmp_set_vcpu_dirty_limit命令被调用时,QEMU主线程启动了throttle线程,其作用是实现DirtyLimit主要逻辑,主要包含以下阶段:
  1. PREPARE (1) - 准备阶段
    这个阶段主要为二阶段- CALCULATE(2)的计算准备输入,主要准备两个值:虚机当前vCPU脏页速率(dirty page rate),用户配置的虚机vCPU脏页速率上限(dirty page rate limit),其脏页速率通过调用现有的脏页速率计算接口得到,参考QEMU脏页速率计算,脏页速率上限通过用户配置得到。
  2. CALCULATE (2) - 计算阶段
    这个阶段主要判断vCPU的脏页速率是否低于用户配置的脏页速率上限,如果不满足,计算在三阶段-SET PENALTY (3)中用于惩罚虚机vCPU的睡眠时间。
  3. SET PENALTY (3) - 惩罚阶段
    这个阶段是具体让vCPU睡眠的阶段,其主要逻辑是在vCPU因为KVM_EXIT_DIRTY_RING_FULL而异常退出的代码路径上让vCPU睡眠。
  • 通过上面三个阶段的操作。QEMU期望让将vCPU的脏页速率限制在用户配置的阈值范围内。
理想效果
  • 接口实现中我们提到了使能DirtyLimit限制,其核心逻辑就是计算超过DirtyLimit的vCPU的睡眠时间,假设vCPU当前脏页速率current=200MB/s,目标速率quota=40MB/s,速率计算和睡眠时间更新的周期(x_vcpu_dirty_limit_period)为1s,我们举例说明理想的限制算法对速率的限制曲线如下:
    在这里插入图片描述
  • 限制算法的理想行为是:
  1. 如果目标脏页速率与实际脏页速率相差很大,两者相减得到的差值比目标脏页速率一半还多,我们希望惩罚的力度大,让脏页速率在一个速率计算周期内(x_vcpu_dirty_limit_period)线性下降。如图中的第1秒,脏页速率从200MB/s减少到了70MB/s
  2. 如果目标脏页速率与实际脏页速率相差不大,两者相减比max(quota, current)的一半少,我们希望减小惩罚力度,让脏页速率在一个速率计算周期内下降固定值,这样可以避免实际脏页速率在quota值上下大幅的震荡。如图中的第2、3秒,脏页速率在1秒内下降10MB/s。
  3. 如果目标脏页速率与实际脏页速率相差在一个可接受的范围内(默认值25MB/s),保持惩罚力度,让vCPU的睡眠时间不变。如图中的3~9秒。
具体实现
  • 上面是限制算法的理想效果,具体实现时没法完全达到预期,其核心函数是dirtylimit_adjust_throttle,我们进一步分析:
static void dirtylimit_set_throttle(CPUState *cpu,
                                    uint64_t quota,
                                    uint64_t current)
{
    int64_t ring_full_time_us = 0;
    uint64_t sleep_pct = 0;       
    uint64_t throttle_us = 0;
    
	/* 如果当前vCPU的速率已经是0,取消惩罚,直接返回 */
    if (current == 0) {
        cpu->throttle_us_per_full = 0;
        return;
    }
	/* 获取当前虚机vCPU的脏页速率和dirty-ring的size,
	 * 计算理想情况下,vCPU以当前的脏页速率填满一个空的dirty-ring表需要多长时间 */        
    ring_full_time_us = dirtylimit_dirty_ring_full_time(current);
    
    /* 如果目标脏页速率和当前脏页速率相差过大,让vCPU速率线性下降 */   
    if (dirtylimit_need_linear_adjustment(quota, current)) {
        if (quota < current) {   
        	/* 根据目标脏页速率和当前脏页速率,计算一个睡眠时间占填满整个dirty-ring表时间的百分比
        	 * 根据百分比计算vCPU的睡眠时间,期望达到的效果是:
        	 * 当vCPU速率很大时,睡眠的时间会相对较短,反之,睡眠时间会相对长
        	 */   
            sleep_pct = (current - quota) * 100 / current;
            throttle_us =
                ring_full_time_us * sleep_pct / (double)(100 - sleep_pct);
            cpu->throttle_us_per_full += throttle_us;
        } else {
            sleep_pct = (quota - current) * 100 / quota;
            throttle_us =
                ring_full_time_us * sleep_pct / (double)(100 - sleep_pct);
            cpu->throttle_us_per_full -= throttle_us;
        }

        trace_dirtylimit_throttle_pct(cpu->cpu_index,
                                      sleep_pct,
                                      throttle_us);
    } else {
    	/*取一个测试效果最好的经验值作为固定的睡眠时间 */
        if (quota < current) {
            cpu->throttle_us_per_full += ring_full_time_us / 10;
        } else {
            cpu->throttle_us_per_full -= ring_full_time_us / 10;
        }
    }
    /* 为保证vCPU睡眠时间过长导致Guest内核线程softlockup,设置vCPU睡眠时间的上限 */
    cpu->throttle_us_per_full = MIN(cpu->throttle_us_per_full,
        ring_full_time_us * DIRTYLIMIT_THROTTLE_PCT_MAX);

    cpu->throttle_us_per_full = MAX(cpu->throttle_us_per_full, 0);
}
  • 上面分析了睡眠时间计算,最终的数值被保存到了CPUState结构体的throttle_us_per_full字段中,限制算法的最后阶段就是让vCPU睡眠throttle_us_per_full指定的时间,这个逻辑在QEMU处理vCPU线程退出时完成,如下:
kvm_cpu_exec
	/* 执行vCPU线程,陷入内核 */
	run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0);
	/* 从内核exit到用户态空间 */
	switch (run->exit_reason) {
		/* 根据exit_reason做对应处理 */
		case KVM_EXIT_IO:
		......
		/* 如果退出是由于Dirty-Ring满了,做对应的处理
		 * 这里QEMU的主要工作就是清空Dirty-Ring,让内核可以继续填写
		 */
        case KVM_EXIT_DIRTY_RING_FULL: 
            /*
             * We shouldn't continue if the dirty ring of this vcpu is
             * still full.  Got kicked by KVM_RESET_DIRTY_RINGS.
             */
            trace_kvm_dirty_ring_full(cpu->cpu_index);
            qemu_mutex_lock_iothread();    
            /*
             * We throttle vCPU by making it sleep once it exit from kernel
             * due to dirty ring full. In the dirtylimit scenario, reaping
             * all vCPUs after a single vCPU dirty ring get full result in
             * the miss of sleep, so just reap the ring-fulled vCPU.
             */
            if (dirtylimit_in_service()) { 
            	/* 当dirty-limit开启时,仅清空对应vCPU的Dirty-Ring
            	 * 这样可以保证每个vCPU满了之后都会走到该路径,保证其接受惩罚
            	 * 如果某个vCPU满了,但是这里我们把所有vCPU的Dirty-Ring都清空的话
            	 * 就会导致有些脏页速率较大的vCPU永远接收不到惩罚 */
                kvm_dirty_ring_reap(kvm_state, cpu);
            } else {
                kvm_dirty_ring_reap(kvm_state, NULL);
            }
            qemu_mutex_unlock_iothread();  
            /* 调用睡眠函数,实施最终的惩罚 */
            dirtylimit_vcpu_execute(cpu);  
            ret = 0;
            break;
            ......

测试验证

QEMU

  1. 源端
  • 通过下面方式启动虚机:
#!/bin/bash
  
/usr/bin/qemu-system-x86_64 \
    -display none -vga none \
    -name guest=migrate_src,debug-threads=on \
    -monitor stdio \
    -accel kvm,dirty-ring-size=65536 -cpu host \
    -kernel /home/work/fast_qemu/vmlinuz-6.1.19-7.0.0.17.oe2303.x86_64 \
    -initrd /home/work/fast_qemu/initrd-stress.img \
    -append "noapic edd=off printk.time=1 noreplace-smp cgroup_disable=memory pci=noearly console=ttyS0 debug ramsize=4" \
    -chardev file,id=charserial0,path=/var/log/mig_dst_console.log\
    -serial chardev:charserial0 \
    -D /var/log/mig_dst.log \
    -m 4096 -smp 2 \
  1. 目的端
  • 通过下面方式启动虚机,其中initrd-stress.img是QEMU内存压测工具简介中介绍的引导镜像。其中192.168.31.155是目的端IP,9000是目的端QEMU监听迁入内存的端口:
#!/bin/bash
  
/usr/bin/qemu-system-x86_64 \
    -display none -vga none \
    -name guest=migrate_src,debug-threads=on \
    -monitor stdio \
    -accel kvm,dirty-ring-size=65536 -cpu host \
    -kernel /home/work/fast_qemu/vmlinuz-6.1.19-7.0.0.17.oe2303.x86_64 \
    -initrd /home/work/fast_qemu/initrd-stress.img \
    -append "noapic edd=off printk.time=1 noreplace-smp cgroup_disable=memory pci=noearly console=ttyS0 debug ramsize=4" \
    -chardev file,id=charserial0,path=/var/log/mig_dst_console.log\
    -serial chardev:charserial0 \
    -D /var/log/mig_dst.log \
    -m 4096 -smp 2 \
    -incoming tcp:192.168.31.155:9000
  1. 迁移操作
  • 通过以下命令行将虚机从源端迁移到目的端:
QEMU 8.1.50 monitor - type 'help' for more information
(qemu) migrate_set_capability 
auto-converge            background-snapshot      block                    
compress                 dirty-bitmaps            dirty-limit              
events                   late-block-activate      multifd                  
pause-before-switchover  postcopy-blocktime       postcopy-preempt         
postcopy-ram             rdma-pin-all             release-ram              
return-path              switchover-ack           validate-uuid            
x-colo                   x-ignore-shared          xbzrle                   
zero-blocks              zero-copy-send            
(qemu) migrate_set_capability dirty-limit on
(qemu) migrate -d tcp:192.168.31.155:9000
/* 查看迁移使用的capability,可以看到dirty-imit被开启 */
(qemu) info migrate_capabilities
xbzrle: off
rdma-pin-all: off
auto-converge: off
zero-blocks: off
compress: off
events: off
postcopy-ram: off
x-colo: off
release-ram: off
block: off
return-path: off
pause-before-switchover: off
multifd: off
dirty-bitmaps: off
postcopy-blocktime: off
late-block-activate: off
x-ignore-shared: off
validate-uuid: off
background-snapshot: off
zero-copy-send: off
postcopy-preempt: off
switchover-ack: off
dirty-limit: on
/* 查看迁移过程中的实时数据,dirty-limit会在迁移迭代的第三轮开启 */
(qemu) info migrate
globals:
store-global-state: on
only-migratable: off
send-configuration: on
send-section-footer: on
decompress-error-check: on
clear-bitmap-shift: 18
Migration status: active
total time: 3433 ms
expected downtime: 300 ms
setup: 33 ms
transferred ram: 23936 kbytes
throughput: 26.16 mbps
remaining ram: 1438576 kbytes
total ram: 4195080 kbytes
duplicate: 684655 pages
skipped: 0 pages
normal: 4471 pages
normal bytes: 17884 kbytes
dirty sync count: 1
page size: 4 kbytes
multifd bytes: 0 kbytes
pages-per-second: 363313
precopy ram: 23936 kbytes
/* 再次查看迁移信息,dirty-limit有相关信息输出 */
(qemu) info migrate 
globals:
store-global-state: on
only-migratable: off
send-configuration: on
send-section-footer: on
decompress-error-check: on
clear-bitmap-shift: 18
Migration status: active
total time: 372685 ms
expected downtime: 63130 ms
setup: 19 ms
transferred ram: 3598269 kbytes
throughput: 99.79 mbps
remaining ram: 386204 kbytes
total ram: 4195080 kbytes
duplicate: 822808 pages
skipped: 0 pages
normal: 895998 pages
normal bytes: 3583992 kbytes
dirty sync count: 5
page size: 4 kbytes
multifd bytes: 0 kbytes
pages-per-second: 3039
dirty pages rate: 2015 pages
precopy ram: 3598269 kbytes
dirty-limit throttle time: 23272722 us
dirty-limit ring full time: 235078 us

Libvirt

  • TODO

一个广子

  • 本人负责QEMU社区Dirty Limit和Dirty page rate模块的维护,欢迎虚拟化领域的同学提交patch,为社区贡献力量,模块包含以下文件:
Migration dirty limit and dirty page rate
F: system/dirtylimit.c
F: include/sysemu/dirtylimit.h
F: migration/dirtyrate.c
F: migration/dirtyrate.h
F: include/sysemu/dirtyrate.h

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

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

相关文章

---图的遍历和最小生成树

广度优先遍历 --- 针对的是顶点遍历 深度优先遍历 如果给的图不是连通图&#xff1f;以某个点为起点就没有遍历完成。那么怎么保证遍历完剩下的点呢&#xff1f;&#xff1f; 在标记数组当中找没有遍历过的点&#xff0c;在进行遍历 最小生成树 生成树&#xff1a;一个连通…

使用TypeScript和jsdom库实现自动化数据抓取

目录 环境准备 使用TypeScript和jsdom抓取数据 总结 随着网络技术的发展&#xff0c;数据抓取已成为获取信息的重要手段。然而&#xff0c;手动进行数据抓取既耗时又容易出错。因此&#xff0c;本文将介绍如何使用TypeScript和jsdom库实现自动化数据抓取。我们将通过创建一个…

iMazing苹果用户手机备份工具 兼容最新的iOS16操作系统

现在距离苹果秋季新品发布会已过去月余&#xff0c;新iPhone 14系列和新版的iOS 16操作系统也如约与我们见面了&#xff0c;相信大家在9月初抢购的iPhone 14也基本到手了&#xff0c;但随之到来的数据资料备份迁移却是一件令人头大的事情&#xff0c;使用官方提供的iTunes软件卡…

计算机网络,网络(OSI)七层模型,三次握手四次挥手,get与post请求区别,网络IO(BIO\NIO\AIO),TCP与UDP区别

1.OSI模型&#xff1f; 开放式系统互联通信参考模型(Open System Interconnection Reference Model) OSI网络七层模型&#xff1a;应用层、表示层、会话层、传输层、网络层、数据链路层、物理层 TCP/IP协议群简化了OSI七层模型&#xff1a;应用层、传输层、网络层、数据链路…

java-各种成员变量初始化过程-待完善

前置条件 一、本文章讨论的成员变量 public static final String aa "aa";public static final Integer bb 1;public static final Students cc new Students();public static String aa1 "aa";public static Integer bb1 1;public static String bb2…

nodejs+vue中学信息技术线上学习系统-计算机毕业设计

因此&#xff0c;将现代化的计算机技术、网络技术以及多媒体等技术相结合&#xff0c;开发基于互联网的自主学习平台&#xff0c;为学生提供良好的自主学习环境&#xff0c;方便学生能够网上学习&#xff0c;师生通过该平台可以进行课后交流。目 录 摘 要 I ABSTRACT II 目 录 …

ssrf漏洞学习

目录 ssrf漏洞 相关函数 相关协议 file协议 dict协议 gopher协议 ctfshow ssrf web351 web352 web353 web354过滤01 web355五位长度 web356 三位长度 web357 DNS重定向 web358 正则 ssrf漏洞 SSRF&#xff08;Server-Side Request Forgery&#xff0c;服务器端请…

基于SSM的培训学校教学管理平台的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

ASCII_Util.java

package asc_ii;/*** 我写程序&#xff0c;写代码&#xff0c;做项目做产品&#xff0c;更加努力学习做人* 我曾经家里有两只狗&#xff0c;rocket就是那种小型犬吧&#xff0c;两耳朵跑起来飞舞着&#xff0c;我也不记得是不是舞蝶犬* 还有一条中型犬&#xff0c;“豆豆”&…

小程序实现后台数据交互及WXS的使用

一&#xff0c;数据交互准备工作 1.1 后端准备 后端部分代码&#xff0c;可自行创建后端代码 package com.zking.minoa.wxcontroller;import com.zking.minoa.mapper.InfoMapper; import com.zking.minoa.model.Info; import com.zking.minoa.util.ResponseUtil; import org…

FPGA【紫光语法】

寄存器数据类型&#xff1a; reg 默认为 1 bit wide&#xff0c;如果超过 1 bit&#xff0c;则需要 range declaration 设置 reg 的位宽integer 默认位宽为 32 bit&#xff0c;不允许有 range declarationtime 默认位宽为 64 bit&#xff0c;不允许有 range declarat…

黄金现货期货各有各的市场

投资黄金要获得高效的收益&#xff0c;投资者应该选择有一定资金杠杆的保证金品种&#xff0c;比如现货黄金和黄金期货就是这样投资方式&#xff0c;投资者都可以通过它们的杠杆来放大自己的收益&#xff0c;但二者始终存在区别&#xff0c;投资者到底该如何选择呢&#xff1f;…

(2023,DALL-E3,两步微调,标题重建)通过更好的标题改进图像生成

Improving Image Generation with Better Captions 公众号&#xff1a;EDPJ&#xff08;添加 VX&#xff1a;CV_EDPJ 或直接进 Q 交流群&#xff1a;922230617 获取资料&#xff09; 目录 0. 摘要 1. 简介 2. 重建数据集标题 2.1 构建图像标题器 2.1.1 微调标题器 3…

AI虚拟主播频繁亮相,未来会替代真人吗?灰豚AI数字人深度解析!

你可能听说过一些头部主播其实不是真人&#xff0c;而是由人工智能技术生成的虚拟数字人。这些数字人有着逼真的外貌、声音和表情&#xff0c;和真人几乎一模一样&#xff0c;可以在直播平台上和观众进行各种内容的展示和互动。那么&#xff0c;现在来考考你以下哪一个头部主播…

德施曼2023双十一全民换锁季,多款爆品持续引爆全民换购潮

每年双十一&#xff0c;对于各行业的商家来说都是必争之地&#xff0c;在智能锁领域也同样如此。国产高端智能锁品牌德施曼为了迎接此次双十一狂欢盛典&#xff0c;开启了双十一全民换锁季&#xff0c;携旗下多款爆品持续引爆全民换购热潮&#xff01; 德施曼全民换锁季 以旧换…

JOSEF约瑟 JJKY-30Z NK82-III检漏继电器 导轨或面板安装 0.1-50A

系列型号&#xff1a; JY82A检漏继电器 JY82B检漏继电器 JY82-380/660检漏继电器 JY82-IV检漏继电器 JY82-2P检漏继电器 JY82-2/3检漏继电器 JJKY检漏继电器 JD型检漏继电器 JY82-IV;JY82J JY82-II;JY82-III JY82-1P;JY82-2PA;JY82-2PB JJB-380;JJB-380/660 JD-12…

数据结构--线性表回顾

目录 线性表 1.定义 2.线性表的基本操作 3.顺序表的定义 3.1顺序表的实现--静态分配 3.2顺序表的实现--动态分配 4顺序表的插入、删除 4.1插入操作的时间复杂度 4.2顺序表的删除操作-时间复杂度 5 顺序表的查找 5.1按位查找 5.2 动态分配的方式 5.3按位查找的时间…

Vant Weapp的Slider组件自定义button

js部分: <van-slider v-model"value" range drag"priceChange" drag-end"sliderDragEnd" use-button-slot max"1000" min"0" step"10"><view class"custom-button" slot"left-button&…

如何使用LightPicture部署私人图床实现远程访问与图片管理?

文章目录 1.前言2. Lightpicture网站搭建2.1. Lightpicture下载和安装2.2. Lightpicture网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 现在的手机越来越先进&#xff0c;功能也越来越多&#xff0c;而手机…

ENVI IDL:对于GEOTIFF结构体的说明

Tag标签-前言 其中最关键的只有两个标签Tag&#xff0c;一个是MODELPIXELSCALETAG&#xff0c;一个是MODELTIEPOINTTAG。 至于ModelTransformationTag我没用过不了解&#xff0c;但是应该是关于仿射变换相关的&#xff0c;用于将像素坐标与地理/投影坐标进行转换的矩阵。 对于…