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;
#endifdm_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: 算法的参数个数为 02: 该优先组包含两条路径2: 每条路径的参数有两个8:0  1 1: 第一条路径的设备号为 8:0,每进行 1 次 IO 就切换路径,路径的吞吐量权重为 18: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 runningclone_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;
}

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

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

相关文章

LCR 176. 判断是否为平衡二叉树

解题思路&#xff1a; class Solution {public boolean isBalanced(TreeNode root) {return recur(root) ! -1;}private int recur(TreeNode root) {if (root null) return 0;int left recur(root.left);if(left -1) return -1;int right recur(root.right);if(right -1) …

ebay头像如何设置?eBay店铺的头像怎么改?-站斧浏览器

ebay头像如何设置&#xff1f; eBay店铺的头像可以通过以下方式进行设置&#xff1a; 登录eBay账户&#xff1a;店主需要使用自己的eBay账号登录到eBay网站。 进入店铺管理后台&#xff1a;在登录后&#xff0c;店主可以点击页面右上角的用户名或店铺名称&#xff0c;从下拉…

被低估的流量宝地,如何通过Reddit为Shopify店铺引流?

独立站店铺相对于电商平台来说&#xff0c;有一个运营难点那就是需要自主引流。做好引流&#xff0c;你的Shopify店铺也就成功了一半。Reddit作为国外知名的论坛平台&#xff0c;非常适合作为引流的阵地&#xff0c;许多人对这个网站尚不了解&#xff0c;接下来就为大家介绍如何…

HackTheBox - Medium - Linux - Socket

Socket Socket 是一台中等难度的 Linux 机器&#xff0c;其特点是反转 Linux/Windows 桌面应用程序以获取其源代码&#xff0c;从那里发现其 Web 套接字服务中的“SQL”注入。转储数据库会显示一个哈希值&#xff0c;一旦破解&#xff0c;就会产生对该框的“SSH”访问。最后&a…

1295. X的因子链(数论/求1~N的所以质因子)

题目&#xff1a; 1295. X的因子链 - AcWing题库 输入样例&#xff1a; 2 3 4 10 100输出样例&#xff1a; 1 1 1 1 2 1 2 2 4 6 思路&#xff1a; 代码&#xff1a; #include <cstdio> #include <cstring> #include <iostream> #include <algorithm…

Gin 路由注册与请求参数获取

Gin 路由注册与请求参数获取 文章目录 Gin 路由注册与请求参数获取一、Web应用开发的两种模式1.前后端不分离模式2.前后端分离模式 二、RESTful介绍三、API接口3.1 RESTful API设计指南3.2 API与用户的通信协议3.3 RestFul API接口设计规范3.3.1 api接口3.3.2 接口文档&#xf…

viewer插件——预览图片时一直闪烁——问题修复,亲测有效

viewer插件——预览图片时一直闪烁——问题修复&#xff0c;亲测有效 viewer插件的介绍遇到的问题——图片会一直重复加载&#xff0c;造成图片在闪烁的效果解决方法 viewer插件的介绍 之前写过一篇文章&#xff0c;是关于v-viewer图片预览插件——vue2插件集合(elementUi中的…

JAVA 终极面试题

目录标题 一: JAVA 基础1.JDK和JRE有什么区别&#xff1f;2. 面向对象的特征&#xff08;了解&#xff09;3. 和equals的区别是什么&#xff1f;4.两个对象的hashCode()相同,则equals()一定为true&#xff0c;对吗&#xff1f;5.final关键字在java中的作用6.java中的Math.round…

bat批处理文件_命令汇总(1)

文章目录 1、复制文件&#xff1a;2、 移动文件&#xff1a;3、删除文件&#xff1a;4、创建目录&#xff1a;5、删除目录&#xff1a;6、切换目录&#xff1a;7、显示当前目录&#xff1a;8、运行程序&#xff1a;9、显示系统环境变量&#xff1a;10、设置环境变量&#xff1a…

HCIA-Datacom题库(自己整理分类的)——OSPF协议判断

1.路由表中某条路由信息的Proto为OSPF则此路由的优先级一定为10。√ 2.如果网络管理员没有配置骨干区域,则路由器会自动创建骨干区域&#xff1f; 路由表中某条路由信息的Proto为OSPF&#xff0c;则此路由的优先级一定为10。 当两台OSPF路由器形成2-WAY邻居关系时&#xff0…

Android 相机库CameraView源码解析 (四) : 带滤镜预览

1. 前言 这段时间&#xff0c;在使用 natario1/CameraView 来实现带滤镜的预览、拍照、录像功能。 由于CameraView封装的比较到位&#xff0c;在项目前期&#xff0c;的确为我们节省了不少时间。 但随着项目持续深入&#xff0c;对于CameraView的使用进入深水区&#xff0c;逐…

【OpenBMC】的内部README 模板

OpenBMC 本项目的AST2500分支核心代码的机型是ast2500-default&#xff0c;克隆代码后进入编译环境的命令为&#xff1a; source setup ast2500-default 一、源码下载、配置以及编译 重要&#xff1a;请参阅confluence 详细步骤 二、代码使用方法 目前所有自定义修改的代码…

Node.js 文件写入详解:最佳实践与示例

文件写入是 Node.js 中的一项重要任务&#xff0c;它允许你将数据保存到本地文件系统中&#xff0c;供后续使用。这个功能在许多应用中都有广泛的应用&#xff0c;包括数据备份、日志记录、配置文件更新等。在本文&#xff0c;我们将介绍如何在 Node.js 中执行文件写入操作&…

【C#】知识点实践序列之UrlEncode在线URL网址编码、解码

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是2024年第8篇文章&#xff0c;此篇文章是C#知识点实践序列文章&#xff0c; 博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 地址编码大家应该比较经常遇到和使用到&…

CAN数据记录仪在新能源车上的应用

随着新能源汽车的快速发展&#xff0c;对车辆安全和性能的要求也越来越高。在新能源车中&#xff0c;液位传感器是必不可少的零部件之一&#xff0c;用于监测电池液位、冷却液位等关键参数。在测试阶段需要工作人员花费大量时间跟车去获取它的CAN数据&#xff0c;从而分析是否有…

在 Linux 中开启 Flask 项目持续运行

在 Linux 中开启 Flask 项目持续运行 在部署 Flask 项目时&#xff0c;情况往往并不是那么理想。默认情况下&#xff0c;关闭 SSH 终端后&#xff0c;Flask 服务就停止了。这时&#xff0c;您需要找到一种方法在 Linux 服务器上实现持续运行 Flask 项目&#xff0c;并在服务器…

Dependency Dialogue Acts — Annotation Scheme and Case Study [论文解读]

原文链接&#xff1a;https://arxiv.org/pdf/2302.12944.pdf 摘要 在本文中&#xff0c;我们介绍了依存对话行为(Dependency Dialog Act, DDA)&#xff0c;这是一个新颖的框架&#xff0c;旨在捕捉多方对话中说话者意图的结构。DDA结合并适应了现有对话标注框架的特点&#x…

springboot实现OCR

1、引入依赖 <dependency><groupId>net.sourceforge.tess4j</groupId><artifactId>tess4j</artifactId><version>4.5.4</version> </dependency> 2、config Configuration public class TessOcrConfiguration {Beanpublic …

旋转图像(LeetCode 48)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路参考文献 1.问题描述 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在「原地」旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。 示…

SpringBoot连接MySQL并整合MyBatis-Plus

SpringBoot连接MySQL并整合MyBatis-Plus 配置springboot版本目录结构pom.xml文件application.yml数据库表代码Test.javaTestMapper.javaTestMapper.xmlTestService.javaTestServiceImpl.javaTestController.java效果配置 springboot版本 <parent><groupId>org.sp…