新时代异步 IO 框架:IO_URING 的原理、用法、业界示例分析

news2024/11/25 10:40:08

文章目录

  • IO_URING
    • 基本介绍
      • 常见 I/O 模型
      • IO_URING
    • 原理
      • 核心结构
      • 工作模式
      • 高级特性
    • 用法
      • API
      • liburing
        • 基本流程
        • Demo
    • 业界示例
      • SeaStar / ScyllaDB
      • CEPH
      • RocksDB
      • ClickHouse

IO_URING

基本介绍

常见 I/O 模型

当前 Linux 的几种 I/O 模型:
在这里插入图片描述

I/O 模型

  • 同步 I/O 是目前应用最广的 I/O 模型,其缺点非常明显:大量内存拷贝、系统调用导致上下文切换频繁;随着设备性能越来越高,这种方式已经无法有效利用设备的全部性能。
  • AIO 的优点就是通过异步方式和 Linux Kernel 交互,减少了对用户态应用程序的阻塞,提高了并发度,但其最大的缺点就是仅支持 Direct I/O,无法有效利用文件系统和 Page Cache。
  • SPDK 通过 Kernal Bypass 的方式实现,其基于 VFIO 在用户态重新实现 NVMe 驱动和协议,无系统调用、无上下文切换、无锁,是目前性能最高的 I/O 模型。但是它仅限于 NVMe,不支持其他磁盘类型,且使用起来非常困难。
    在这里插入图片描述
    各 I/O 模型的对比

为了弥补上述方案的缺陷,Linux Kernel 5.1 版本加入一个特性——IO_URING


IO_URING

IO_URING 的设计目标是提供一个统一、易用、可扩展、功能丰富、高效的网络和磁盘系统接口。其具有以下几个特点:

  • 真正异步:只要设置了合适的 flag,它在系统调用上下文中就只是将请求放入队列, 不会做其他任何额外的事情,保证了应用永远不会阻塞。
  • 支持任何类型的 I/O:cached files、direct-access files 甚至 blocking sockets。无需 poll+read/write 来处理 sockets。 只需提交一个阻塞式读(blocking read),请求完成之后,就会出现在 completion ring。
  • 可拓展、灵活:基于 IO_URING 甚至能重写 Linux 的每个系统调用。
  • 高性能
    • 用户态和内核态共享提交队列(submission queue)和完成队列(completion queue)。
    • 用户态支持 Polling 模式,不依赖硬件的中断,通过调用 IORING_ENTER_GETEVENTS 不断轮询收割完成事件。
    • 内核态支持 Polling 模式,IO 提交和收割可以 offload 给 Kernel,且提交和完成不需要经过系统调用。
    • 可以提前注册用户态内存地址/文件描述符,减小地址映射/引用计数的开销。
    • ……


原理

核心结构

如下图,每个 IO_URING 实例都有两个环形队列(RingBuffer,通过共享内存实现),由用户态和内核态共同管理。

  • 提交队列 submission queue (SQ):用户态线程生产,通过系统调用通知内核消费。
  • 完成队列 completion queue (CQ):内核生产,通知用户态消费。

两个队列都提供了无锁接口(内部通过 barriers 同步),都是单生产者,单消费者。
在这里插入图片描述

IO_URING 核心结构

工作方式

  • 提交:
    • 应用尝试获取一个 SQ Entry,并向 SQ Entry 中填充数据。
    • 提交 SQ Entry 到 SQ 中,更新 SQ tail。
  • 完成
    • 内核从 SQ head 处取出 SQ Entry 消费,并更新 SQ head。
    • 内核为完成的一个或多个请求创建 CQ Entry,更新 CQ tail。
    • 应用消费 CQ Entry,更新 CQ head。(无需切换到内核态)

这里的提交和完成都支持批量处理,如连续提交多个 Entry 到 SQ,或持续消费 CQ。


工作模式

  • 中断驱动模式(interrupt driven):默认,通过 io_uring_enter 通知内核 IO 请求的产生以及阻塞等待内核完成请求。
  • 轮询模式(polled):为了提升性能,内核提供了轮询的方式来提交 IO 请求
    • 提交 IO 的轮询(SQPOLL)
      • 当通过 IORING_SETUP_SQPOLL 开启提交队列轮询时,会启动一个内核线程不停的去检查 SQ 是否存在任务,并立即取出任务进行消费。而用户态程序只需要将任务塞进 SQ 提交队列即可,不再需要调用 io_uring_enter。当一段时间(sq_thread_idle 配置)内没有 Poll 到任何请求时,为了避免线程空转,会将其挂起并通过 IORING_SQ_NEED_WAKEUP 标志位更新状态到共享内存中。用户进程可以在每次提交任务时,通过该标志位检查内核 SQ 线程是否运行,如果未运行,则需要通过调用 io_uring_enter,并使用 IORING_SQ_NEED_WAKEUP 参数,来唤醒 SQ 线程。
      • 由于内核和用户态共享内存,所以完成的时候,用户态遍历直接遍历完成队列消费 CQ Entry 即可。在最理想的情况下,IO 提交和收割都不需要使用系统调用。
    • 完成 IO 的轮询(IOPOLL)
      • 在传统的模式下,将 I/O 请求提交给块设备后,进程会进入睡眠状态,当块设备处理完 I/O 请求后则会通过一个中断来唤醒进程,通知 I/O 已完成。IO_URING 中可通过 IORING_SETUP_IOPOLL 开启块设备轮询操作,即提交 I/O 后不直接进入睡眠,而是启动一个内核线程来循环检查 I/O 是否完成。由于不需要被动等待设备通知,因此可以更快获取 I/O 请求的完成状态,这对于延迟非常低以及 IOPS 很高的设备,能够显著提高性能,同时也避免了高频的中断所带来的性能开销,但同时也提高了 CPU 的开销。
      • 仅支持 Direct I/O;仅支持存储设备,且设备/文件系统必须要支持轮询。
  • 内核轮询模式(kernel polled):即同时开启 IORING_SETUP_SQPOLLIORING_SETUP_IOPOLL,内核会同时轮询 SQ 队列和设备驱动队列,无需主动调用 io_uring_enter 来触发。在这种模式下应用无需切换到内核态,无需任何系统调用也能够进行提交和完成,只需要在用户态轮询 CQ 即可。


高级特性

  • 资源预注册
    • 预注册缓冲区:对于频繁操作的 buffer,可以通过 IORING_REGISTER_BUFFERS 将 buffer 注册到内核中,避免每次 I/O 时都需要调用 get_user_pagesunpin_user_pages 进行虚拟地址到物理 Page 的映射。
    • 预注册文件描述符:Linux 在执行 I/O 操作时为了避免文件描述符被释放或者关闭,在访问文件时通过 fget 增加引用计数,在操作完成后通过 fput 减少,这也带来了大量的性能开销(https://lwn.net/Articles/787473/)。为了优化这一点,支持通过 IORING_REGISTER_FILES 提前将文件描述符注册到内核中,使文件描述符的引用计数始终为 1,避免掉这一部份开销。
  • 链接 I/O 操作:支持通过 IOSQE_IO_LINK 将多个 I/O 操作链接在一起,这些操作会通过一次调用同时提交,并按照链接的顺序进行执行。
  • 快速轮询:kernel5.7 后引入的新特性,通过 IORING_FEAT_FAST_POLL 优化对大量非阻塞文件描述符的轮询操作(用于网络 I/O)。其原理就是维护了一个快速轮询队列(类似 epoll),对于未就绪的描述符不再直接转交给异步线程,而是放入该队列中,内核会定期检查描述符是否就绪,一旦就绪就立即开始进行 I/O 操作。避免了不必要的异步线程创建以及线程阻塞,同时也省去了使用 epoll 的开销。
    • 当开启 IORING_SETUP_SQPOLLIORING_SETUP_IOPOLLIORING_FEAT_FAST_POLL 时,在最理想情况下轮询+读/写都能在用户态完成。


用法

API

接口可参考文档:https://tchaloupka.github.io/during/during.io_uring.html

IO_URING 只提供了三个接口:

/*
作用:用于初始化 io_uring 以及 SQ、CQ
参数:
entries:队列深度
params:params
返回值:io_uring 的描述符,失败时返回 -1 并设置 errno
*/
int io_uring_setup(unsigned entries, struct io_uring_params *params);


/*
作用:用于初始化 io_uring 以及 SQ、CQ
参数:
fd:io_uring 描述符
to_submit:指定了 SQ 中提交的 I/O 数量
min_complete:会等待这个数量的 I/O 事件完成再返回;当使用IOPOLL模式时如果未0,则立即返回当前结果。而非0时如果有完成事件,则立即返回;如果没有则poll指定次数或等到线程时间片结束后返回。
flags:标识符
sig:指向信号掩码的指针。调用io_uring_enter时会将当前信号掩码替换为sig,然后等待完成队列中的事件就绪后,再恢复为原始信号掩码
返回值:io_uring 的描述符,失败时返回 -1 并设置 errno
*/
int io_uring_enter(unsigned int fd, unsigned int to_submit, unsigned int min_complete, unsigned int flags, sigset_t *sig);

/*
作用:注册用于异步 I/O 的文件或用户缓冲区
参数:
fd:io_uring 描述符
opcode:操作码
arg:操作指定的参数
nr_args:参数数量
返回值:成功时返回 0,失败时返回 -1 并设置 errno
*/
int io_uring_register(unsigned int fd, unsigned int opcode, void *arg, unsigned int nr_args);


liburing

为了简化 IO_URING 的使用,避免一些繁琐的底层操作(如 ring buffer 管理、memory barrier、mmap 等),Jens Axboe 还封装了一套易用的高级 API —— liburing。

代码仓库:GitHub - axboe/liburing

API 手册:Manpages of liburing-dev in Debian unstable — Debian Manpages

基本流程
  • 使用 io_uring_queue_init 初始化 io_uring
    • 如果需要指定特殊参数可以使用 io_uring_queue_init_params
  • 注册文件描述符(可选)
    • io_uring_register_files 注册待操作文件描述符,之后操作这些文件时通过索引而不是描述符,减少系统调用,优化开销。
    • io_uring_register_eventfd 注册 eventfd,当 I/O 完成后,内核会往这个 fd 中写入一个值,通知异步 I/O 操作已完成。
  • 通过 io_uring_get_sqe 获取 sqe
  • 通过 io_uring_prep_$option 将 sqe 提交到提交队列中
    • 如果需要设置 user_data,可以通过 io_uring_sqe_set_data 传入。在 I/O 操作完成后可以通过 io_uring_cqe_get_data 从 cqe 中读出。
    • 如果有多个请求,可以使用 io_uring_sqe_set_flags 设置 IOSQE_IO_LINK,将请求链接到一起进行批处理。
  • 通过 io_uring_submit 通知 io_uring 从提交队列中消费 sqe
  • 等待完成队列中的任务就绪
    • 阻塞:io_uring_wait_cqe,阻塞等待有一个 cqe 返回
    • 非阻塞:io_uring_peek_cqe,如果没有就绪的 cqe,则直接报错返回。支持批量操作 io_uring_peek_batch_cqe
  • 当前 cqe 中的数据处理完成后,通过 io_uring_cqe_seen 将其标记成已处理,从完成队列中移除。(如果不处理则会一直保留,被重复消费)
  • 完成所有 I/O 操作后,使用 io_uring_queue_exit 销毁 io_uring


Demo

更多例子参考:

  • liburgin:https://github.com/axboe/liburing/tree/master/examples
  • echo_server:https://github.com/frevib/io_uring-echo-server/blob/master/io_uring_echo_server.c
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "liburing.h"

#define QD	4

int main(int argc, char *argv[])
{
	struct io_uring ring;
	int i, fd, ret, pending, done;
	struct io_uring_sqe *sqe;
	struct io_uring_cqe *cqe;
	struct iovec *iovecs;
	struct stat sb;
	ssize_t fsize;
	off_t offset;
	void *buf;

	if (argc < 2) {
		printf("%s: file\n", argv[0]);
		return 1;
	}

	ret = io_uring_queue_init(QD, &ring, 0);
	if (ret < 0) {
		fprintf(stderr, "queue_init: %s\n", strerror(-ret));
		return 1;
	}

	fd = open(argv[1], O_RDONLY | O_DIRECT);
	if (fd < 0) {
		perror("open");
		return 1;
	}

	if (fstat(fd, &sb) < 0) {
		perror("fstat");
		return 1;
	}

	fsize = 0;
	iovecs = calloc(QD, sizeof(struct iovec));
	for (i = 0; i < QD; i++) {
		if (posix_memalign(&buf, 4096, 4096))
			return 1;
		iovecs[i].iov_base = buf;
		iovecs[i].iov_len = 4096;
		fsize += 4096;
	}

	offset = 0;
	i = 0;
	do {
		sqe = io_uring_get_sqe(&ring);
		if (!sqe)
			break;
		io_uring_prep_readv(sqe, fd, &iovecs[i], 1, offset);
		offset += iovecs[i].iov_len;
		i++;
		if (offset >= sb.st_size)
			break;
	} while (1);

	ret = io_uring_submit(&ring);
	if (ret < 0) {
		fprintf(stderr, "io_uring_submit: %s\n", strerror(-ret));
		return 1;
	} else if (ret != i) {
		fprintf(stderr, "io_uring_submit submitted less %d\n", ret);
		return 1;
	}

	done = 0;
	pending = ret;
	fsize = 0;
	for (i = 0; i < pending; i++) {
		ret = io_uring_wait_cqe(&ring, &cqe);
		if (ret < 0) {
			fprintf(stderr, "io_uring_wait_cqe: %s\n", strerror(-ret));
			return 1;
		}

		done++;
		ret = 0;
		if (cqe->res != 4096 && cqe->res + fsize != sb.st_size) {
			fprintf(stderr, "ret=%d, wanted 4096\n", cqe->res);
			ret = 1;
		}
		fsize += cqe->res;
		io_uring_cqe_seen(&ring, cqe);
		if (ret)
			break;
	}

	printf("Submitted=%d, completed=%d, bytes=%lu\n", pending, done,
						(unsigned long) fsize);
	close(fd);
	io_uring_queue_exit(&ring);
	return 0;
}


业界示例

SeaStar / ScyllaDB

在 SeaStar 中,reactor 是可插拔的组件(epoll、aio、io_uring),其基于 IO_URING 封装了一个新的 reactor_backend_uring,接管了所有网络和磁盘 I/O(buffer/direct)与事件循环。

实现参考:https://github.com/scylladb/scylladb/commit/1247be44b01b3402e8694dd622f8ed8306053c32

  • 网络 I/O:

    • 测试环境:8-core x86

    • 内核版本:linux v5.7

    • 测试参数:wrk -c 128 -t 4

    • 测试数据:https://github.com/scylladb/seastar/pull/1235#discussion_r989364804

AIO:
Running 10s test @ http://localhost:10000/
  4 threads and 128 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.49ms  109.56us   3.81ms   87.41%
    Req/Sec    21.51k   783.10    28.03k    87.84%
  862627 requests in 10.10s, 112.71MB read
Requests/sec:  85407.13
Transfer/sec:     11.16MB

IO_URING:
Running 10s test @ http://localhost:10000/
  4 threads and 128 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.35ms  400.14us  37.64ms   98.36%
    Req/Sec    23.94k     2.36k   26.30k    70.25%
  952788 requests in 10.00s, 124.48MB read
Requests/sec:  95266.43
Transfer/sec:     12.45MB
  • 磁盘 I/O:

    • 测试数据:https://www.scylladb.com/2020/05/05/how-io_uring-and-ebpf-will-revolutionize-programming-in-linux/
      在这里插入图片描述
      ScyllaDB 性能对比


CEPH

CEPH 主要是对其底层的存储引擎 BlueStore 进行改造。其原本架构图如下图所示,CEPH 本身就已经在 BlueFS 与磁盘的交互之间抽象出了一个 BlockDevice 层,且原本就采用了 AIO,只需要简单的修改就 API 就可以切换到 liburing,并且其运用了大部分高级特性(SQPOLL、IOPOLL、REGISTER_FILES)。

实现参考:https://github.com/ceph/ceph/pull/27392/files

在这里插入图片描述

BlueStore 架构

以下是官方给出的测试数据:

内核版本:
  linux v5.1
测试参数:
  rw=randwrite
  iodepth=16
  nr_files=1
  numjobs=1
  size=256m

  bluestore_min_alloc_size = 4096
  bluestore_max_blob_size  = 65536

  bluestore_block_path     = /dev/ram0
  bluestore_block_db_path  = /dev/ram1
  bluestore_block_wal_path = /dev/ram2

使用AIO:
bluestore_iouring=false
   4k  IOPS=25.5k, BW=99.8MiB/s, Lat=0.374ms
   8k  IOPS=21.5k, BW=168MiB/s,  Lat=0.441ms
  16k  IOPS=17.2k, BW=268MiB/s,  Lat=0.544ms
  32k  IOPS=12.3k, BW=383MiB/s,  Lat=0.753ms
  64k  IOPS=8358,  BW=522MiB/s,  Lat=1.083ms
 128k  IOPS=4724,  BW=591MiB/s,  Lat=1.906ms

使用IO_URING:
bluestore_iouring=true
   4k  IOPS=29.2k, BW=114MiB/s,  Lat=0.331ms
   8k  IOPS=30.7k, BW=240MiB/s,  Lat=0.319ms
  16k  IOPS=27.4k, BW=428MiB/s,  Lat=0.368ms
  32k  IOPS=22.7k, BW=709MiB/s,  Lat=0.475ms
  64k  IOPS=15.6k, BW=978MiB/s,  Lat=0.754ms
 128k  IOPS=9572,  BW=1197MiB/s, Lat=1.223ms

IOPS 提升:
Overall IOPS increase is the following:
   4k  +14%
   8k  +42%
  16k  +59%
  32k  +89%
  64k  +85%
 128k  +102%


RocksDB

RocksDB 基于 IO_URING 实现了 PosixRandomAccessFile::MultiRead(),并且也只是使用了最基本的功能。RocksDB 是直接在该接口中构造了一个 uring,一次性将所有读取请求批量填充进去,并循环等待所有请求完成(未让出线程,本质上还是同步读取),如果出现任何异常,则退化至原先的方式(线程池 + pread)。

实现参考:https://github.com/facebook/rocksdb/pull/5881/files

在这里插入图片描述

RocksDB 异步架构

官方给的测试数据如下:
在这里插入图片描述

RocksDB 性能提升

详细数据参考:https://www.slideshare.net/ennael/kernel-recipes-2019-faster-io-through-iouring

除了官方的实现,很多公司都有对 RocksDB 进行 IO_URING 改造,例如 TIKV 用 IO_URING 重构了 wal、sstable 的 write 和 compaction 。
https://openinx.github.io/ppt/io-uring.pdf


ClickHouse

ClickHouse 只是用了使用了最基础的 IO_URING(未用到高级特性,主要是由于 CK 中需要根据文件大小来动态决定是否使用 PageCache,因此无法兼容 IOPOLL),将文件系统的同步读取改造为异步(OLAP 数据库主要开销在读取上)。ClickHouse 本身就已经抽象出了 Reader 来接管 FS 层的读 I/O,因此他们只需要简单封装一个 IOUringReader 并返回一个异步的 Future 即可完成改造。

实现参考:https://github.com/ClickHouse/ClickHouse/pull/36103/files

官方给出了测试数据

  • 测试环境:i7-7700K / 32GB desktop with a 7200rpm WD HDD dis、Linux V5.17
  • 测试语句:select count(ignore(*)) from visits
  • 测试参数: min_bytes_to_use_direct_iolocal_filesystem_read_prefetch
  • 测试结果:
+----------------------+---------------+---------------+-------------+-------------+-----------+
| direct_io / prefetch |     pread     |   io_uring    | improvement | significant | cpu_usage |
+----------------------+---------------+---------------+-------------+-------------+-----------+
| no  / no             | 10.75 ± 0.47s |  9.91 ± 0.49s |       7.75% |         yes |    -1.29% |
| no  / yes            |  8.86 ± 0.23s |  7.85 ± 0.31s |      11.48% |         yes |    -2.86% |
| yes / yes            | 14.41 ± 0.57s | 11.49 ± 0.28s |      20.27% |         yes |    -1.74% |
| yes / no             | 14.37 ± 0.50s | 14.34 ± 0.47s |       0.25% |          no |   -24.52% |
+----------------------+---------------+---------------+-------------+-------------+-----------+

在并发场景下,随着并发度的提升,IO_URING 的优势也越来越大。
在这里插入图片描述

ClickHouse 查询性能对比

在这里插入图片描述

ClickHouse CPU 开销对比

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

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

相关文章

AI:130-基于深度学习的室内导航与定位

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的关键代码,详细讲解供…

python 基础知识点(蓝桥杯python科目个人复习计划42)

今日复习内容&#xff1a;重新学习一下贪心算法 今天做题的时候&#xff0c;想了半天贪心算法&#xff0c;结果没全想出来&#xff0c;所以菜就多练&#xff0c;重新学一下呗。 贪心算法是一种常见的算法范式&#xff0c;通常用于求解最优化过程。在每一步的选择中&#xff0…

python学习24

前言&#xff1a;相信看到这篇文章的小伙伴都或多或少有一些编程基础&#xff0c;懂得一些linux的基本命令了吧&#xff0c;本篇文章将带领大家服务器如何部署一个使用django框架开发的一个网站进行云服务器端的部署。 文章使用到的的工具 Python&#xff1a;一种编程语言&…

光芒绽放:妙用“GLAD原则”打造标准的数据可视化图表

光芒绽放&#xff1a;妙用“GLAD原则”打造标准的数据可视化图表 文章目录 光芒绽放&#xff1a;妙用“GLAD原则”打造标准的数据可视化图表前言一、可视化工具有哪些&#xff1f;二、那如何做出正确可视化图表 &#xff1f;GLAD原则1.G原则2.L原则3.A原则4.D原则 三、总结最后…

AliOS编译三方库

文章目录 1、官网教程2、编译NDK2.1 下载ndk2.2 编译环境准备2.3 安装ndk 3 cmake交叉编译3.1 编译工具链3.2 编译三方库 4 自带编译配置文件的交叉编译 1、官网教程 AliOS开发官网链接&#xff1a;AliOS开发者官网 应用开发下NDK开发有相关NDK开发介绍 2、编译NDK 2.1 下载…

优思学院|六西格玛到底有没有用?

有很多人说&#xff0c;我的企业已经是行业的顶峰&#xff0c;不需要做些什么了&#xff0c;更不需要什么六西格玛。如果你这样想就大错特错了。历史上不乏因自满而错失发展机遇&#xff0c;最终被竞争对手超越的案例。 诺基亚&#xff08;Nokia&#xff09;&#xff0c;曾经的…

102.网游逆向分析与插件开发-网络通信封包解析-解读喊话道具数据包并且利用Net发送

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;解读聊天数据包并且利用Net发送 码云地址&#xff08;游戏窗口化助手 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号&#xff1a;cc6370dc5ca6b0176aafc…

【web | CTF】BUUCTF [BJDCTF2020]Easy MD5

天命&#xff1a;好像也挺实用的题目&#xff0c;也是比较经典吧 天命&#xff1a;把php的MD5漏洞都玩了一遍 第一关&#xff1a;MD5绕过 先声明一下&#xff1a;这题的MD5是php&#xff0c;不是mysql的MD5&#xff0c;把我搞迷糊了 一进来题目啥也没有&#xff0c;那么就要看…

P1498 南蛮图腾题解

题目 给定一个正整数n&#xff0c;参考输出样例&#xff0c;输出图形。 输入输出格式 输入格式 每个数据输入一个正整数n&#xff0c;表示图腾的大小&#xff08;此大小非彼大小&#xff09; 输出格式 这个大小的图腾 输入输出样例 输入样例 2 输出样例 /\/__\/\ /\…

前端秘法基础式(CSS)(第一卷)

一.认识CSS CSS 指的是层叠样式表&#xff08;Cascading Style Sheets&#xff09;&#xff0c;它是一种用于描述网页外观和布局的语法 CSS 可以定义网页中元素的字体、颜色、大小、位置、背景等样式&#xff0c;使网页具有美观的外观和统 一的风格。 通过将 CSS 样式表与 HTML…

成员方法传参机制

一、成员方法传参机制 1、值传递&#xff1a;形参改变不影响实参 2、地址传递&#xff1a;形参改变影响实参

wordpress外贸成品网站模板

首页大图slider轮播&#xff0c;橙色风格的wordpress外贸网站模板 https://www.zhanyes.com/waimao/6250.html 蓝色经典风格的wordpress外贸建站模板 https://www.zhanyes.com/waimao/6263.html

NetMizer 日志管理系统 多处前台RCE漏洞复现

0x01 产品简介 NetMizer是提供集成应用交付和应用安全解决方案以实现业务智能网络的优秀全球供应商,为全球企业和运营商提供确保关键业务应用的全面可用性、高性能和完善的安全性的解决方案。 0x02 漏洞概述 NetMizer 日志管理系统position.php、hostdelay.php、等接口处存在…

【动态规划初识】不同的二叉搜索树

每日一道算法题之不同二叉搜索树个数 一、题目描述二、思路三、C++代码一、题目描述 题目来源:LeetCode 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 C++程序要求输入输出格式如下: 示例1:…

Gartner指引:四大误区大揭秘,打造高效安全管理体系

安全管理体系是一个复杂的生态系统&#xff0c;定义了企业的关键信息、安全原则、资源和活动&#xff08;见图1&#xff09;。企业机构所构建和运行的安全体系往往难以既对员工实用&#xff0c;又能有效管理快速发展的数字风险。因此&#xff0c;首席信息官&#xff08;CIO&…

2024.2.16日总结(小程序开发8)

数据监听器 监听对象属性的变化 数据监听器支持监听对象中单个或多个属性的变化 纯数据字段 什么是纯数据字段 纯数据字段指的是哪些不用于页面渲染的data字段 应用场景:例如有些情况下&#xff0c;某些 data 中的字段既不会展示在界面上&#xff0c;也不会传递给其他组件…

《Go 简易速速上手小册》第1章:Go 语言基础(2024 最新版)

文章目录 1.1 Go 语言的安装与环境配置1.1.1 基础知识讲解案例 Demo&#xff1a;简单的 Go 程序 1.1.2 重点案例&#xff1a;搭建一个 Go Web 服务准备工作步骤 1&#xff1a;创建项目目录步骤 2&#xff1a;编写 Web 服务代码步骤 3&#xff1a;运行你的 Web 服务步骤 4&#…

[word] 手把手教您在word中添加mathtype加载项 #笔记#职场发展

手把手教您在word中添加mathtype加载项 mathtype安装完成后&#xff0c;正常情况下会在word文档中的菜单中自动添加mathtype加载项&#xff0c;但有时也会出现小意外&#xff0c;mathtype并没有加载到word文档中&#xff0c;本教程将教您解决如何手动添加mathtype加载项。 原…

Springboot的it职业生涯规划系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; Springboot的it职业生涯规划系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&a…

【上海游戏业:创新与商业的结合】

上海游戏业&#xff1a;创新与商业的结合 我国游戏产业在近年来蓬勃发展&#xff0c;不同城市都涌现出许多优秀的游戏公司。为全面了解中国游戏业的发展情况及地区特色&#xff0c;本文选择分析游戏业较为发达的上海、广州、北京、深圳、成都、杭州、福建七个城市。这些城市在…