multipath 内核接口及框架介绍

文章目录

      • 1 云主机使用网络存储 io 流程
      • 2 multipath 介绍

1 云主机使用网络存储 io 流程

对于一个云服务环境,大致会有网络节点,存储节点,计算节点,控制节点,其中虚拟云主机在计算节点工作,而虚拟云主机(qemu 虚机)使用的存储一般通过 ceph,drbd,mulitpch,iscsi 使其在存储节点存储。

(1)计算节点云主机访问本地 /dev/vda,/dev/vdb … 块设备,实际访问的是 dm-mulitpath 管理的计算节点的 /dev/dm-0…1,2,3 设备。

/dev/vda
  -> virtio_blk
    -> virtio_pci
      -> 虚拟主机(qemu vhost)
        ->  本地节点 tcm_vhost
          -> target_core_mod
            -> target_core_iblock/target_core_pscsi
              -> 计算节点 /dev/dm-0

(2)/dev/dm-0 实际由 iscsi 客户端管理的 /dev/sda 和 /dev/sdb 聚合而成,可实现故障路径切换,io 负载均衡等能力,实际 io 数据会通过 iscsi_tcp(如果是 fc 服务,则是走光纤通道)到达存储节点的 /dev/drbd0。如果主路径故障,通路不同则会由 multipath 切换到备用路径(这里是/dev/sdb),等待恢复后切换回主路径(回到/dev/sda)。这里假设走的主路径:

/dev/dm-0
  -> /dev/sda
    -> iscsi_tcp/fc
      -> 到达存储节点 iscsi_target_mod/tcm_fc
        -> target_core_mod
          -> target_core_iblock/target_core_pscsi
            -> /dev/drbd0 (drbd 模块)

(3)存储节点的/dev/drbd0由 drbd 内核模块配置,实现块数据拷贝同步到后端存储设备。后端存储设备为/dev/alcache0,该设备实际是一块高速 SSD 设备(如 nvme),实现数据的高速缓存。
(4)/dev/alcache0由 bcache 配置管理,实际对应后端存储为/dev/rbd0。bcache 实现将 nvme 作为高速缓存将数据同步到 /dev/rbd0 中。
(5)/dev/rbd0 设备由 ceph rbd 管理,该模块提供了分布式存储管理能力,如 io 调度等,通过 rbd,数据 io 会实际到达 HHD 等大容量存储设备。

/dev/rbd0
  -> rbd 模块(ceph 网络协议转发数据)
    -> ceph rbd 管理(分布式存储管理实际物理磁盘)

2 multipath 介绍

可以看到上述路径中在计算节点使用了 mutlipath 聚合路径来实现负载均衡和路径切换等。

用户态工具相关:
启动脚本

/usr/lib/systemd/system/multipathd.service

udev 命令规则

/usr/lib/udev/rules.d/11-dm-mpath.rules
/usr/lib/udev/rules.d/62-multipath.rules

用户工具

/usr/sbin/multipathd        守护进程,监听系统中路径状态的变化,并做相应的处理。
/usr/sbin/mpathpersist      SCSI PR命令工具,主要用于隔离。
/usr/sbin/mpathconf         修改多路径配置
/usr/sbin/kpartx            DeviceMapper虚拟设备创建工具
/usr/sbin/multipath         多路径命令工具

查看 device mapper 映射表

[wings-client@fedora sections]$ sudo dmsetup table
mpatha: 0 41940992 multipath 1 queue_if_no_path 1 alua 1 1 service-time 0 2 2 8:0 1 1 8:16 1 1

[wings-client@fedora sections]$ ll /dev/sd*
brw-rw----. 1 root disk 8,  0 101615:29 /dev/sda
brw-rw----. 1 root disk 8, 16 101615:29 /dev/sdb

查看被映射的多路径设备

[wings-client@fedora sections]$ ll /dev/dm-*
brw-rw----. 1 root disk 253, 0 101615:29 /dev/dm-0
[wings-client@fedora sections]$ 

多路径创建的 dm 设备的主设备号都是 253。
查看设备的 wwid

[wings-client@fedora sections]$ lsscsi -i
[0:0:0:0]    cd/dvd  QEMU     QEMU DVD-ROM     2.5+  /dev/sr0   -
[6:0:0:0]    disk    LIO-ORG  block1           4.0   /dev/sdb   3600140554012e51273044299221f2903
[7:0:0:0]    disk    LIO-ORG  block1           4.0   /dev/sda   3600140554012e51273044299221f2903
[wings-client@fedora sections]$

同一个设备的两条路径,以及被映射的新设备的 wwid 相同
查看当前多路径信息

[wings-client@fedora sections]$ sudo multipath -ll
mpatha (3600140554012e51273044299221f2903) dm-0 LIO-ORG,block1
size=20G features='1 queue_if_no_path' hwhandler='1 alua' wp=rw
`-+- policy='service-time 0' prio=50 status=active
  |- 7:0:0:0 sda 8:0  active ready running
  `- 6:0:0:0 sdb 8:16 active ready running
[wings-client@fedora sections]$ 

多路径软件读取/etc/multipath.conf文件,并根据 wwid 将相同设备聚合成一个虚拟设备。

内核中 dm-multipath 源码实现主要在linux/drivers/md/dm-*中。
dm-multipath 模块属于 md 模块的一个后端部分。
md 是多个设备的内核 RAID 实现,其中 dm 是设备映射器实现,允许将一个设备映射到另一个设备(一个或者多个)上,这可用作创建在/dev/mapper 目录中访问的虚拟设备(映射设备)。该设备的所有 IO 都将映射到其他设备。

RAID 控制器
RAID控制器是一种硬件设备或软件程序,用于管理计算机或存储阵列中的硬盘驱动器(HDD)/固态硬盘(SSD),以便它们能如逻辑部件一样工作,各施其职。RAID控制器的功能既可以由硬件也可以由软件来实现。硬件RAID一般用于处理大量数据的RAID模式。随着处理器的能力的不断增强,软件RAID功能已经成为可能,不过当处理大量数据时CPU仍然会显得力不从心。一般的中高档服务器多使用硬件RAID控制器,但是由于硬件RAID控制器的价格昂贵,导致系统成本大大增加。而随着处理器的性能快速发展,使得软件RAID的解决方法得到人们的重视。在这里,我们使用的是软件RAID。

在Linux系统中目前以MD (Multiple Devices)虚拟块设备的方式实现软件RAID,利用多个底层的块设备虚拟出一个新的虚拟块设备,并且利用条带化(stripping)技术将数据块均匀分布到多个磁盘上来提高虚拟设备的读写性能,利用不同的数据冗余算法来保护用户数据不会因为某个块设备的故障而完全丢失,而且还能在设备被替换后将丢失的数据恢复到新的设备上。

MD (Multiple Devices)框架
Multiple Devices虚拟块设备(利用底层多个块设备虚拟出一个新的虚拟块设备)。目前MD支持linear, multipath, raid0 (stripping), raid1 (mirror), raid4, raid5, raid6, raid10等不同的冗余级别和组成方式,当然也能支持多个RAID阵列的层叠组成raid1+0, raid5+1等类型的阵列。
dm 整体框架大致如下:
在这里插入图片描述
这里主要关心 multipath 部分。
内核选中 DM_MULTIPATH 即可启用 multipath 相关内核支持。
DM_MULTIPATH_QL 和 DM_MULTIPATH_ST 为两种可选的路径选择算法。

dm-mpath.c是多路径驱动的核心,负责初始化相关数据结构,以及向 mapping/target interface 注册 dm 的 target type 回调。
dm-selector.c是负责管理路径选择算法的苦函数。
dm-round-robin.c是必备的路径选择算法:在一条路径上完成指定的 IO 次数后就切换到下一条路径,不断循环。
另外两种是可选算法,见上述 Kconfig:
1)dm-service-time.c算法:根据路径的吞吐量以及完成的字节数选择负载较轻的路径。
2)dm-queue-length.c算法:根据正在处理的 IO 个数较少的路径。

dm-mapth.c核心:

/*-----------------------------------------------------------------
 * Module setup
 *---------------------------------------------------------------*/
static struct target_type multipath_target = {
	.name = "multipath",
	.version = {1, 13, 0},
	.features = DM_TARGET_SINGLETON | DM_TARGET_IMMUTABLE |
		    DM_TARGET_PASSES_INTEGRITY,
	.module = THIS_MODULE,
	.ctr = multipath_ctr,
	.dtr = multipath_dtr,
	.clone_and_map_rq = multipath_clone_and_map,
	.release_clone_rq = multipath_release_clone,
	.rq_end_io = multipath_end_io,
	.map = multipath_map_bio,
	.end_io = multipath_end_io_bio,
	.presuspend = multipath_presuspend,
	.postsuspend = multipath_postsuspend,
	.resume = multipath_resume,
	.status = multipath_status,
	.message = multipath_message,
	.prepare_ioctl = multipath_prepare_ioctl,
	.iterate_devices = multipath_iterate_devices,
	.busy = multipath_busy,
};

static int __init dm_multipath_init(void)
{
	int r;

	kmultipathd = alloc_workqueue("kmpathd", WQ_MEM_RECLAIM, 0);
	if (!kmultipathd) {
		DMERR("failed to create workqueue kmpathd");
		r = -ENOMEM;
		goto bad_alloc_kmultipathd;
	}

	/*
	 * A separate workqueue is used to handle the device handlers
	 * to avoid overloading existing workqueue. Overloading the
	 * old workqueue would also create a bottleneck in the
	 * path of the storage hardware device activation.
	 */
	kmpath_handlerd = alloc_ordered_workqueue("kmpath_handlerd",
						  WQ_MEM_RECLAIM);
	if (!kmpath_handlerd) {
		DMERR("failed to create workqueue kmpath_handlerd");
		r = -ENOMEM;
		goto bad_alloc_kmpath_handlerd;
	}

	r = dm_register_target(&multipath_target);
	if (r < 0) {
		DMERR("request-based register failed %d", r);
		r = -EINVAL;
		goto bad_register_target;
	}

	return 0;

bad_register_target:
	destroy_workqueue(kmpath_handlerd);
bad_alloc_kmpath_handlerd:
	destroy_workqueue(kmultipathd);
bad_alloc_kmultipathd:
	return r;
}

在启动阶段dm_multipath_init向 dm 的 mapping/target interface 注册 target type,并且申请两个工作队列,用于异步执行耗时的 IO 操作。
使用的数据结构struct target_type:

struct target_type {
	uint64_t features;
	const char *name;
	struct module *module;
	unsigned version[3];
	dm_ctr_fn ctr;
	dm_dtr_fn dtr;
	dm_map_fn map;
	dm_clone_and_map_request_fn clone_and_map_rq;
	dm_release_clone_request_fn release_clone_rq;
	dm_endio_fn end_io;
	dm_request_endio_fn rq_end_io;
	dm_presuspend_fn presuspend;
	dm_presuspend_undo_fn presuspend_undo;
	dm_postsuspend_fn postsuspend;
	dm_preresume_fn preresume;
	dm_resume_fn resume;
	dm_status_fn status;
	dm_message_fn message;
	dm_prepare_ioctl_fn prepare_ioctl;
#if 1 /* CONFIG_BLK_DEV_ZONED */
	dm_report_zones_fn report_zones;
#endif
	dm_busy_fn busy;
	dm_iterate_devices_fn iterate_devices;
	dm_io_hints_fn io_hints;
	dm_dax_direct_access_fn direct_access;
	dm_dax_copy_iter_fn dax_copy_from_iter;
	dm_dax_copy_iter_fn dax_copy_to_iter;

	/* For internal device-mapper use. */
	struct list_head list;
};

ctr = multipath_ctr
当用户程序加载 DeviceMapper 映射表时,如果其中的映射类型为 multipath,则会根据映射表中的参数调用此函数,
用于构建一个 Target Device:
  申请该实例内存,并进行简单初始化;
  解析参数,并根据参数创建优先级组,解析每个优先级组的路径算法;
  设置一些基本的 DeviceMapper 属性;
一个设备的多路径可以使用不同类型,划分成多个优先组,每个组需要包含一个路径选择器(路径选择算法)以及至少一条路径。

参数组成部分:
  特性参数 硬件参数 优先级组以及路径参数

参数解析实例:
  mpatha: 0 41940992 multipath 1 queue_if_no_path 1 alua 1 1 service-time 0 2 2 8:0 1 1 8:16 1 1
	其中 "1 queue_if_no_path 1 alua 1 1 service-time 0 2 2 8:0 1 1 8:16 1 1" 为传递给驱动的参数。
    1:表示特性参数,后跟特性参数,反之为 0,不跟参数
    1: 表示硬件参数,后跟硬件参数(alua),反之为 0,不跟参数
    1: 表示优先级组个数
    1: 下一个优先级组序号,主要用于解析优先级组时使用
    service-time:当前的优先组使用 service-time 算法
    0: 算法的参数个数为 0
    2: 该优先组包含两条路径
    2: 每条路径的参数有两个
    8:0  1 1: 第一条路径的设备号为 8:0,每进行 1 次 IO 就切换路径,路径的吞吐量权重为 1
    8:16 1 1: 第一条路径的设备号为 8:16,每进行 1 次 IO 就切换路径,路径的吞吐量权重为 1
最终用户态程序看到的结果就是:
mpatha (3600140554012e51273044299221f2903) dm-0 LIO-ORG,block1
size=20G features='1 queue_if_no_path' hwhandler='1 alua' wp=rw
`-+- policy='service-time 0' prio=50 status=active
  |- 7:0:0:0 sda 8:0  active ready running
  `- 6:0:0:0 sdb 8:16 active ready running
    
clone_and_map_rq = multipath_clone_and_map(IO 映射)
判断是否需要切换路径,并获取当前可用路径,把路径信息保存在请求的私有数据中
根据路径信息重定向请求,设置其目的队列
调用路径算法的 start_io 函数。
如果成功则返回 DM_MAPIO_REMAPPED,表明映射成功,通知 dm 框架重新投递请求。

rq_end_io = multipath_end_io
映射完成后,进行 IO 结束时会调用 multipath_end_io 函数进行资源回收,
如果 IO 错误且 pgpath 不为空则使用 fail_path 函数通过 uevent 通知用户层此路径已经失效。
如果设置了 queue_if_no_path 为 0,则会返回 DM_ENDIO_REQUEUE,dm 会把这个请求重新加入队列,
否则直接返回 EIO 错误,最后清除在开始映射时创建的上下文信息,并调用路径选择算法的 end_io 函数。
    
message = multipath_message
多路径驱动除了根据在给定的算法和配置下进行 IO 错误时以外,并不主动探测路径的状态,增加减少路径,
以及状态改变,这些都是由用户态的 multipathd 和 multipath 来控制完成,然后通过 dm 框架提供的接口,
调用 target type 驱动的 message 函数来完成。这里实现了切换优先组,失效以及使能路径等功能。

dm-path-selector.c路径选择算法管理库
使用链表对路径算法进行管理,注册,取消注册,获取,释放等。

/* Register a path selector */
int dm_register_path_selector(struct path_selector_type *type);

/* Unregister a path selector */
int dm_unregister_path_selector(struct path_selector_type *type);

/* Returns a registered path selector type */
struct path_selector_type *dm_get_path_selector(const char *name);

/* Releases a path selector  */
void dm_put_path_selector(struct path_selector_type *pst);

每个路径算法实现下面的结构回调:
/* Information about a path selector type */
struct path_selector_type {
	char *name;
	struct module *module;

	unsigned int table_args;
	unsigned int info_args;

	/*
	 * Constructs a path selector object, takes custom arguments
	 */
	int (*create) (struct path_selector *ps, unsigned argc, char **argv);
	void (*destroy) (struct path_selector *ps);

	/*
	 * Add an opaque path object, along with some selector specific
	 * path args (eg, path priority).
	 */
	int (*add_path) (struct path_selector *ps, struct dm_path *path,
			 int argc, char **argv, char **error);

	/*
	 * Chooses a path for this io, if no paths are available then
	 * NULL will be returned.
	 */
	struct dm_path *(*select_path) (struct path_selector *ps,
					size_t nr_bytes);

	/*
	 * Notify the selector that a path has failed.
	 */
	void (*fail_path) (struct path_selector *ps, struct dm_path *p);

	/*
	 * Ask selector to reinstate a path.
	 */
	int (*reinstate_path) (struct path_selector *ps, struct dm_path *p);

	/*
	 * Table content based on parameters added in ps_add_path_fn
	 * or path selector status
	 */
	int (*status) (struct path_selector *ps, struct dm_path *path,
		       status_type_t type, char *result, unsigned int maxlen);

	int (*start_io) (struct path_selector *ps, struct dm_path *path,
			 size_t nr_bytes);
	int (*end_io) (struct path_selector *ps, struct dm_path *path,
		       size_t nr_bytes);
};

create:实例化一个选择器
destroy:销毁一个选择器

add_path:向该选择器增加一条路径
select_path:选择进行 IO 的路径

fail_path:告诉选择器该路径失效
reinstate_path:告诉选择器该路径可用

status:返回选择器的状态

start_io:使用者进行 IO 前必须调用该函数(如路径算法记录 IO 次数)
end_io:使用者完成 IO 后必须调用该函数

round-robin路径选择器
每条路径只有一个参数,就是每条路径执行多少次 IO 后切换路径。
添加一条路径:

static int rr_add_path(struct path_selector *ps, struct dm_path *path,
		       int argc, char **argv, char **error)
{
	struct selector *s = ps->context;
	struct path_info *pi;
	unsigned repeat_count = RR_MIN_IO;
	char dummy;
	unsigned long flags;

	if (argc > 1) {
		*error = "round-robin ps: incorrect number of arguments";
		return -EINVAL;
	}

	/* First path argument is number of I/Os before switching path */
	if ((argc == 1) && (sscanf(argv[0], "%u%c", &repeat_count, &dummy) != 1)) {
		*error = "round-robin ps: invalid repeat count";
		return -EINVAL;
	}

	if (repeat_count > 1) {
		DMWARN_LIMIT("repeat_count > 1 is deprecated, using 1 instead");
		repeat_count = 1;
	}

	/* allocate the path */
	pi = kmalloc(sizeof(*pi), GFP_KERNEL);
	if (!pi) {
		*error = "round-robin ps: Error allocating path context";
		return -ENOMEM;
	}

	pi->path = path;
	pi->repeat_count = repeat_count;

	path->pscontext = pi;

	spin_lock_irqsave(&s->lock, flags);
	list_add_tail(&pi->list, &s->valid_paths);
	spin_unlock_irqrestore(&s->lock, flags);

	return 0;
}

路径选择策略:
在每条路径进行repeat_count次 IO 后就切换到下一条路径,不断循环。(目前已经废弃,总是 1 次后就切换)

static struct dm_path *rr_select_path(struct path_selector *ps, size_t nr_bytes)
{
	unsigned long flags;
	struct selector *s = ps->context;
	struct path_info *pi = NULL;

	spin_lock_irqsave(&s->lock, flags);
	if (!list_empty(&s->valid_paths)) {
		pi = list_entry(s->valid_paths.next, struct path_info, list);
		list_move_tail(&pi->list, &s->valid_paths);
	}
	spin_unlock_irqrestore(&s->lock, flags);

	return pi ? pi->path : NULL;
}

service-time 路径选择器
添加一条路径:

static int st_add_path(struct path_selector *ps, struct dm_path *path,
		       int argc, char **argv, char **error)
{
	struct selector *s = ps->context;
	struct path_info *pi;
	unsigned repeat_count = ST_MIN_IO;
	unsigned relative_throughput = 1;
	char dummy;
	unsigned long flags;
...
...

每条路径需要指定它的重复次数repeat_count,以及吞吐量权重值relative_throughput。

路径选择策略:
在 IO 开始与结束时,分别增加和减少该路径正在处理的 IO 字节数:

static int st_start_io(struct path_selector *ps, struct dm_path *path,
		       size_t nr_bytes)
{
	struct path_info *pi = path->pscontext;

	atomic_add(nr_bytes, &pi->in_flight_size);

	return 0;
}

static int st_end_io(struct path_selector *ps, struct dm_path *path,
		     size_t nr_bytes)
{
	struct path_info *pi = path->pscontext;

	atomic_sub(nr_bytes, &pi->in_flight_size);

	return 0;
}

在多个路径中,选择正在处理的数据量与吞吐量比值最小的那条路径
static int st_compare_load(struct path_info *pi1, struct path_info *pi2,
			   size_t incoming)
{
	size_t sz1, sz2, st1, st2;

	sz1 = atomic_read(&pi1->in_flight_size);
	sz2 = atomic_read(&pi2->in_flight_size);

	/*
	 * Case 1: Both have same throughput value. Choose less loaded path.
	 */
	if (pi1->relative_throughput == pi2->relative_throughput)
		return sz1 - sz2;
...
...

static struct dm_path *st_select_path(struct path_selector *ps, size_t nr_bytes)
{
	struct selector *s = ps->context;
	struct path_info *pi = NULL, *best = NULL;
	struct dm_path *ret = NULL;
	unsigned long flags;

	spin_lock_irqsave(&s->lock, flags);
	if (list_empty(&s->valid_paths))
		goto out;

	list_for_each_entry(pi, &s->valid_paths, list)
		if (!best || (st_compare_load(pi, best, nr_bytes) < 0))
			best = pi;

	if (!best)
		goto out;

	/* Move most recently used to least preferred to evenly balance. */
	list_move_tail(&best->list, &s->valid_paths);

	ret = best->path;
out:
	spin_unlock_irqrestore(&s->lock, flags);
	return ret;
}

queue-length 路径选择器:
添加一条路径:
只需要指定repeat_count即可

static int ql_add_path(struct path_selector *ps, struct dm_path *path,
		       int argc, char **argv, char **error)
{
	struct selector *s = ps->context;
	struct path_info *pi;
	unsigned repeat_count = QL_MIN_IO;
	char dummy;
	unsigned long flags;
...
...

路径选择策略:
在 IO 开始和结束时,分别增加和减少正在处理的 IO 个数:

static int ql_start_io(struct path_selector *ps, struct dm_path *path,
		       size_t nr_bytes)
{
	struct path_info *pi = path->pscontext;

	atomic_inc(&pi->qlen);

	return 0;
}

static int ql_end_io(struct path_selector *ps, struct dm_path *path,
		     size_t nr_bytes)
{
	struct path_info *pi = path->pscontext;

	atomic_dec(&pi->qlen);

	return 0;
}

选择正在处理的 IO 个数最少的那条路径
static struct dm_path *ql_select_path(struct path_selector *ps, size_t nr_bytes)
{
	struct selector *s = ps->context;
	struct path_info *pi = NULL, *best = NULL;
	struct dm_path *ret = NULL;
	unsigned long flags;

	spin_lock_irqsave(&s->lock, flags);
	if (list_empty(&s->valid_paths))
		goto out;

	list_for_each_entry(pi, &s->valid_paths, list) {
		if (!best ||
		    (atomic_read(&pi->qlen) < atomic_read(&best->qlen)))
			best = pi;

		if (!atomic_read(&best->qlen))
			break;
	}

	if (!best)
		goto out;

	/* Move most recently used to least preferred to evenly balance. */
	list_move_tail(&best->list, &s->valid_paths);

	ret = best->path;
out:
	spin_unlock_irqrestore(&s->lock, flags);
	return ret;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity SVN更新提交小工具

Unity SVN更新提交小工具 前言使用说明必要前提源码参数说明 感谢 前言 Unity开发时每次都要到文件夹中操作SVN&#xff0c;做了一个小工具能够在Editor中直接操作。 使用说明 必要前提 前提是要安装好SVN&#xff0c;在文件夹右键能够看到安装的SVN 源码 using System…

UE4.27.2 网页串流

1、和Unity串流一样安装Node.js 下载地址https://nodejs.org/ 2、下载安装Epic Games启动程序https://www.unrealengine.com/zh-CN/download 3、安装UE4.7.2 4、这里就不安装像素流送演示&#xff0c;选个别的然后创建工程 5、启用PixelStreaming插件 6、设置额外启动参数&am…

uni-app 前后端调用实例 基于Springboot 详情页实现

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…

OEE如何为制造企业实施ISO50001提供支持

ISO50001是一项旨在帮助企业建立和实施能源管理体系的国际标准&#xff0c;以提高能源效率、降低能源消耗和减少环境影响。而设备OEE&#xff08;设备综合效率&#xff09;作为一个关键的生产效率指标&#xff0c;可以为企业实施ISO50001提供重要的支持。本文将介绍ISO50001能源…

Hive10_窗口函数

窗口函数&#xff08;开窗函数&#xff09; 1 相关函数说明 普通的聚合函数聚合的行集是组,开窗函数聚合的行集是窗口。因此,普通的聚合函数每组(Group by)只返回一个值&#xff0c;而开窗函数则可为窗口中的每行都返回一个值。简单理解&#xff0c;就是对查询的结果多出一列…

计算机网络期末知识点总结

计算机网络概述考点 计算机网络的组成 从组成部分看&#xff1a;一个完整的计算机网络主要由硬件、软件、协议三大部分组成&#xff0c;缺一不可。硬件主要指&#xff1a;主机、通信链路、交换设备和通信设备等&#xff1b;软件主要指&#xff1a;用户使用的各种软件&#xf…

vue使用elementui 的 table且自定义某列表头时,添加的点击事件和自带的筛选功能有类似冒泡行为

element 自带的table 需求&#xff1a;在时间这一列的筛选按钮旁边添加一个批量修改按钮问题&#xff1a;如果不加排序这个属性&#xff0c;那么表格自带的筛选和新加的批量筛选点击事件会冲突&#xff08;冒泡事件&#xff09;解决方法&#xff1a;在该列添加sortable属性&…

自定义maven插件 开发步骤手册

Maven只是一套框架&#xff0c;它的功能基于全部依赖于插件来实现。因此可以通过插件开发来定制Maven。 官方文档 https://maven.apache.org/guides/plugin/guide-java-plugin-development.html 命名要求 Maven 官方的插件命名为&#xff1a;maven-<yourplugin>-plug…

Python计算圆的面积

Python 计算圆的面积 圆的面积公式为 &#xff1a; 公式中 r 为圆的半径。 # 定义一个方法来计算圆的面积 def findArea(r): PI 3.142 return PI * (r*r) # 调用方法 r float( input("请输入圆的半径:") ) print( "圆的面积为 %.3f&qu…

介绍十五种Go语言开发的IDE

当涉及到Go语言开发的IDE时&#xff0c;以下是几种常用的选择&#xff1a; Goland&#xff1a;这是由JetBrains公司开发的一款商业IDE&#xff0c;旨在为Go开发者提供符合人体工程学的开发环境。Goland整合了IntelliJ平台&#xff0c;提供了针对Go语言的编码辅助和工具集成&am…

设计模式_结构型模式_装饰器模式

装饰器模式和代理模式很像。 代理模式是已经知道代理谁了&#xff0c;所以只是对委托类的访问权限进行限制&#xff0c;因此用户只需要访问相应的代理类就可以。装饰器模式并不知道要装饰谁&#xff0c;所以需要传入具体的被装饰对象进行功能的添加 目的&#xff1a; 增加现有…

构建高效外卖配送系统:技术要点与实际代码示例

随着外卖服务需求的不断增长&#xff0c;构建一个智能化、高效的外卖配送系统成为餐饮业务成功的关键。在本文中&#xff0c;我们将重新审视外卖配送系统&#xff0c;着重思考技术架构&#xff0c;并提供一些实际代码示例&#xff0c;以展示系统中一些先进的技术要点。 技术架…

重新认识一下 vue3 应用实例

重新认识一下 vue 应用实例 &#x1f495; 创建应用实例 每个 Vue 应用都是通过 createApp 函数创建一个新的 应用实例 应用实例必须在调用了 .mount() 方法后才会渲染出来。该方法接收一个“容器”参数&#xff0c;可以是一个实际的 DOM 元素或是一个 CSS 选择器字符串 //…

Postman 安装及使用

文章目录 1. 安装 Postman1&#xff09;下载2&#xff09;安装3&#xff09;注册用户4&#xff09;登陆完成 2. 创建和发送请求1&#xff09;发送一个 GET 请求2&#xff09;发送一个 POST 请求 3. 查看响应4. 使用环境变量和变量5. 高级功能和测试6. 导出和分享请求总结 Postm…

VirtualBox + Redhat7.6 +Oracle19C 数据库安装

软件工具&#xff1a; 虚拟化工具&#xff1a;VirtualBox-6.1.26-145957-Win.exe操作系统镜像&#xff1a;rhel-server-7.6-x86_64-dvd.iso远程连接工具&#xff1a;XmanagerPowerSuite-7.0.0004r.exe、SecureCRT 8.5.3数据库版本镜像&#xff1a;LINUX.X64_193000_grid_home.…

网络安全B模块(笔记详解)- 弱口令渗透测试

nmap扫描渗透测试 1.通过BT5对服务器场景Linux进行TCP同步扫描 (使用工具Nmap,使用参数n,使用必须要使用的参数),并将该操作使用命令中必须要使用的参数作为Flag提交; Flag:sS 2.通过BT5对服务器场景Linux进行TCP同步扫描 (使用工具Nmap,使用参数n,使用必须要使用的参数…

【小沐学NLP】Python实现TF-IDF算法(nltk、sklearn、jieba)

文章目录 1、简介1.1 TF1.2 IDF1.3 TF-IDF2.1 TF-IDF(sklearn)2.2 TF-IDF(nltk)2.3 TF-IDF(Jieba)2.4 TF-IDF(python) 结语 1、简介 TF-IDF&#xff08;term frequency–inverse document frequency&#xff09;是一种用于信息检索与数据挖掘的常用加权技术。TF是词频(Term Fr…

从vue小白到高手,从一个内容管理网站开始实战开发第三天,使用Element UI构建页面-登录(一)

上次我们介绍了如何安装Element UI库,这次我们使用Element UI中的组件开始开发我们的页面。 开发之前要先在项目中建立好几个目录,方便我们下面的开发。 一、在项目中创建页面管理目录 1、pages目录(文件夹) 首先在src文件夹下创建一个名为pages的文件夹,该文件夹用来统…

Unity3D UGUI图集打包与动态使用(TexturePacker)

制作图集的好处&#xff1a; 众所周知CPU是用来处理游戏的逻辑运算的&#xff0c;而GPU是用来处理游戏中图像的。在GPU中&#xff0c;我们要绘制一个图像需要提交图片&#xff08;纹理&#xff09;到显存&#xff0c;然后再进行绘制&#xff08;在这个过程中会产生一次DrawCall…

【Emgu.CV教程】第22篇 、色彩处理之ApplyColorMap()伪色彩应用

这篇文章讲的内容比较轻松&#xff0c;技术含量比较低。从我个人的角度讲&#xff0c;ApplyColorMap()函数实现了类似PhotoShop的一些酷炫效果&#xff0c;既把原始彩色图转换为21种风格各异的彩色图像&#xff0c;比如秋天风格、热力图风格等等&#xff0c;但是&#xff0c;在…