Redis 篇-深入了解在 Linux 的 Redis 网络模型结构及其流程(阻塞 IO、非阻塞 IO、IO 多路复用、异步 IO、信号驱动 IO)

news2024/11/16 15:39:31

🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍

文章目录

        1.0 用户空间与内核空间概述

        2.0 Redis 网络模型

        2.1 Redis 网络模型 - 阻塞 IO

        2.2 Redis 网络模型 - 非阻塞 IO 

        2.3 Redis 网络模型 - IO 多路复用

        2.3.1 IO 多路复用 - select

        2.3.2 IO 多路复用 - poll

        2.3.3 IO 多路复用 - epoll

        2.3.4 epoll 的 ET 和 LT 模型

        2.3.5 基于 epoll 的服务端流程

        2.4 Redis 网络模型 - 信号驱动 IO

        2.5 Redis 网络模型 - 异步 IO

        3.0 Redis 单线程及多线程网络模型

        3.1 经典面试题:Redis 是单线程还是多线程?

        3.2 经典面试题:为什么 Redis 要选择单线程?

        3.3 Redis 网络模型的结构及具体流程


        1.0 用户空间与内核空间概述

        1)用户空间:

        用户空间是指运行用户应用程序的内存区域。在这一空间中,应用程序可以执行其代码并处理数据,但不允许直接访问内核空间中的资源或数据结构。

        每个用户程序在其独立的地址空间中运行,彼此之间是隔离的。这意味着一个程序不能直接干扰另一个程序的内存或资源。

        2)内核空间:

        内核空间是操作系统内核所占用的内存区域。内核负责管理硬件资源、进程调度、内存管理、文件系统以及网络协议等核心功能。

        内核空间拥有对所有硬件和系统资源的权限,应用程序无法直接访问这一空间。

        2.0 Redis 网络模型

        Linux 系统为了提高 IO 效率,会在用户空间和内核空间都加入缓冲区:

以上是读数据的过程: 

        当用户要从网络中读取数据时,首先在用户空间中执行命令来调用内核空间中的命令,因为用户空间的命令不能直接来调用或者使用硬件资源。此时,需要等待内核空间调用命令来从网卡中获取数据,接着,将从网卡中获取到的数据先是拷贝到内核空间中的缓冲区,最后再从内核空间中的缓冲区数据拷贝到用户空间缓冲区中。

        简单来说:

        1)写数据时:要把用户缓冲数据拷贝到内核缓冲区,然后写入设备。

        2)读数据时:要从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区。

        这一整个过程可以分为两个小过程:

        1)等待数据就绪。

        2)从内核拷贝数据到用户空间。

        因此,通过对以上两个过程不同的处理就演变出不同的方式:阻塞 IO、非阻塞 IO、IO 多路复用、信号驱动 IO、异步 IO 。

        2.1 Redis 网络模型 - 阻塞 IO

        顾名思义,阻塞 IO 就是两个阶段都必须阻塞等待。

        当用户来读取数据时,此时内核还没有准备好数据,那么进程就直接 "硬等",直到内核准备好数据,所以,第一个过程是当前线程阻塞为阻塞状态。由于第一个过程还没获取到数据,还在等待数据,自然而然的,第二个过程也就没有数据拷贝到用户缓冲区中,也就是说,第二个过程同样是阻塞状态。

        2.2 Redis 网络模型 - 非阻塞 IO 

        顾名思义,非阻塞 IO 的 recvfrom 操作会立即返回结果而不是阻塞用户进程。

        可以看到,非阻塞 IO 模型中,用户进程在第一个阶段是非阻塞,第二个阶段是阻塞状态。虽然是非阻塞,但性能并没有得到提高。而且忙等机制导致 CPU 空转,CPU 使用率暴增。 

        2.3 Redis 网络模型 - IO 多路复用

        无论是阻塞 IO 还是非阻塞 IO,用户应用在一阶段都需要调用 recvfrom 来获取数据,差别在于无数据时的处理方案:

        1)如果调用 recvfrom 时,恰好没有数据,阻塞 IO 会使进程阻塞,非阻塞 IO 使 CPU 空转,都不能发挥 CPU 的作用。

        2)如果调用 recvfrom 时,恰好有数据,则用户进程可以直接进入第二阶段,读取并处理数据。

        比如服务端处理客户端 Socket 请求时,在单线程情况下,只能依次处理每一个 Socket,如果正在处理的 Socket 恰好未就绪,线程就会被阻塞,所有其他客户端 Socket 都必须等待,性能自然会很差。

        因此,可以采用 IO 多路复用的方式来解决。

        IO 多路复用方式简单来说,就是通过一个用户进程来监视内核中的多个数据,并在某个数据准备好则进行读写处理。

        那么用户进程如何知道内核中数据是否就绪呢?

        可以通过文件描述符表(File Descriptor):简称 FD,是一个从 0 开始递增的无符号整数,用来关联 Linux 中的一个文件。在 Linux 中,一切皆文件,例如常规文件、视频、硬件设备等,当然也包括网络套接字(Socket)。

        因此 IO 多路复用:是利用单个线程来同时监听多个 FD,并在某个 FD 可读、可写时得到通知,从而避免无效的等待,充分利用 CPU 资源。

        在 IO 多路复用中,获取到就绪的 FD,然后根据 FD 的信息再来调用 recvfrom 命令,此时,内核中的缓冲区一定会有相对应的数据,直接从内核中拷贝回用户缓冲区即可。

        需要注意的是,如果等待数据过程中,没有监听到已就绪的 FD,仍旧是阻塞等待,所以第二个阶段也是处于阻塞状态。不过,概率很小,因为 FD 很多,总有很大可能在短时间内获取到已就绪的 FD。

        在 IO 多路复用中,对于如何监听 FD 的方式、通知的方式又有多种实现,常见的有:select、poll、epoll 三种常见的方式。

        select、poll、epoll 的主要差异:

        1)select 和 poll 只会通知用户进程有 FD 就绪,但不确定具体是哪个 FD,需要用户进程逐个遍历 FD 来确认。

        2)epoll 则会在通知用户进程 FD 就绪的同时,把已就绪的 FD 写入用户空间。

        2.3.1 IO 多路复用 - select

        select 是 Linux 中最早的 IO 多路复用实现方案:

select 的相关源码:

        在源码中,可以看到有 int select() 函数,里面的主要字段:

        1)int nfds:要监视的 fd_set 的最大 FD + 1,也就是集合中有多个 FD,按照顺序从 1 到 1024 存放在集合中,nfds 表示监视的范围从 0 到最大的 FD + 1 之内。

        2)fd_set *readfds:需要监视读事件的 FD 集合。

        3)struct timeval *timeout:监听的超时时间,null 表表示永不超时、0 表示不阻塞等待、大于 0 表示等待的时间。

        fd_set:表示一种类型,实际就是一个整型数组,数组大小是固定为 1024 个比特位。每一个 bit 表示一个 FD,0 表示未就绪,1 表示已就绪。

select 具体监听 FD 的过程:

        在用户进程中创建一个 fd_set 集合,也就是一个 1024  比特大小的整型数组,再收集需要监听的 FD 并且存放在该数组中,接着调用 select() 方法,开始监听:首先将收集好 FD 数组拷贝到内存缓冲区中,接着在内核空间对该数组进行遍历查看是否有相对应的数据,如果一个都没有找到就绪的 FD,则休眠,直到等到的数据已就绪或者超时就会被唤醒,假设 FD = 1 数据就绪了,接着将对应的 FD 设置为 1,其他设置为 0,再拷贝回用户缓冲区中,最后再由用户空间对数组进行遍历,找到就绪的 FD,调用 recvfrom 命令获取数据。

select 模式存在的问题:

        1)需要将整个 fd_set 从用户空间拷贝到内核空间,select 结束还要再次拷贝回用户空间。

        2)select 无法得知具体哪个 fd 就绪,需要遍历整个 fd_set 。

        3)fd_set 监听的 fd 数量不能超过 1024 。

        2.3.2 IO 多路复用 - poll

        poll 模式对 select 模式做了简单改进,但性能提升不明显,部分关键代码如下:

        poll() 函数的主要字段:

        1)struct pollfd *fds:是一个 pollfd 类型的数组,pollfd 的内部结构封装了要监听的 FD、events 要监听的事件类型、revents 实际发生的事件类型。该数组可以自定义大小,这解决了 select 模式中能最大监听 1024 个 FD 的问题。

        2)nfds_t nfds:数组元素个数。

        3)int timeout:超时时间。

poll 模式监听 FD 的具体流程:

        1)创建 pollfd 数组,向其中添加关注的 fd 信息,数组大小自定义。

        2)调用 poll 函数,将 pollfd 数组拷贝到内核空间,转链表存储,无上限。

        3)内核遍历 fd,判断是否就绪。

        4)数据就绪或超时后,拷贝 pollfd 数组到用户空间,返回就绪 fd 数量 n 。

        5)用户进程判断 n 是否大于 0 。

        6)大于 0 则遍历 pollfd 数组,找到就绪的 fd 。

与 select 对比:

        1)select 模式的 fd_set 大小固定为 1024,而 pollfd 在内核中采用链表,理论上无上限。

        2)监听 FD 越多,每次遍历消耗时间也越久,性能反而会下降。

        2.3.3 IO 多路复用 - epoll

        epoll 模式是对 select 和 poll 的改进,提供了三个函数。

相关源码如下:

        1)int epoll_create():会直接在内核创建 eventpoll 结构体,一颗红黑树,用来记录要监听的 FD,另一个链表,用来记录已就绪的 FD。返回对应的句柄 epfd,用来标记。

        2)int epoll_ctl():该方法主要是将要监听的 FD 添加到内核中的红黑树中。

        主要参数是 epfd,记录 epoll 实例的句柄;op,要执行的类型,包含:ADD、MOD、DEL;fd,要监听的 FD;

        3)int epoll_wait():该方法主要是用来等待接收已就绪的 FD。

        主要参数是 epfd,eventpoll 实例的句柄;*events,用来接收已就绪的 FD;maxevents,数组的最大长度;timeout,超时时间;

epoll 模式监听 FD 的具体流程:

        首先在内核中创建一颗红黑树,用来接收需要监听的 FD 和一个接收已就绪的链表。接着用户进程会将需要监听的 FD 直接添加到红黑树中,并且设置  ep_poll_callback,当 callback 自动触发时,就把对应的已就绪的 FD 从红黑树加入到链表中。此时,list_head 就会通知用户进程来接收链表中已就绪的 FD,将其拷贝到 events 数组中,此时的数组中为已就绪的 FD,可以直接知道已就绪的 FD,不需要遍历。最后就可以根据已就绪的 FD 来调用 recvfrom 命令来获取数据了。

小结:

        1)select 模式存在的三个问题:

        每监听的 FD 最大不超过 1024 。

        每次 select 都需要把所有要监听的 FD 拷贝到内核空间。

        每次都要遍历所有 FD 来判断就绪状态。

        2)poll 模式的问题:

        poll 利用链表解决了 select 中监听 FD 上限的问题,但依然要遍历所有 FD,如果监听较多,性能会下降。

        3)epoll 模式中如何解决这些问题:

        基于 epoll 实例中的红黑树保存要监听的 FD,理论上无上限,而且增删改查效率都非常高,性能不会随监听的 FD 数量增多而下降。

        每个 FD 只需要执行一次 epoll_ctl 添加到红黑树,以后每次 epol_wait 无需传递任何参数,无需重复拷贝 FD 到内核空间。

        内核会将就绪的 FD 直接拷贝到用户空间的指定位置,用户进程无需遍历所有 FD 就能知道就绪的 FD 是谁。

        2.3.4 epoll 的 ET 和 LT 模型

        当 FD 有数据可读时,调用 epoll_wait 就可以得到通知。但是事件通知的模式有两种:

        1)LevelTriggered:简称 LT 。当 FD 有数据可读时,会重复通知多次,直到数据处理完成。是 epoll 的默认模式。

具体流程:

        当内核中的链表中存在已就绪的 FD,那么就会通知多次给用户进程来获取数据到 events 集合中,可能数据量比较大,一次性拷贝不了全部数据,那么就会分多次进行拷贝,在这个阶段,LT 模式会重复多次发送通知给用户来拷贝数据,这个过程中,链表中的数据是不会删除,依旧会在链表中存储,直到数据处理完成。

        2)EdgeTriggered:简称 ET。当 FD 有数据可读时,只会被通知一次,不管数据是否处理完成。

具体流程:

        在内核中链表已存储就绪的 FD 时,就会给用户进程发送一次通知,当用户进程来拷贝数据的时候,链表中的 FD 就会自动删除,不管数据是否处理完毕。

举个例子:

        假设一个客户端 socket 对应的 FD 已经注册到了 epoll 实例中,客户端 socket 发送了 2kb 的数据,服务端调用 epoll_wait,得到的通知说 FD 就绪。服务端从 FD 读取了 1kb 数据,接着再次调用 epoll_wait,形成循环。

        如果采用 ET 模式,每一次调用 epoll_wait 函数服务端都会得到通知来读取剩下的数据。

        如果采用 LT 模式,只有第一次调用 epoll_wait 函数的时候服务端才会得到通知,循环再次调用 epoll_wait 函数时,不再会发送通知给服务端,那么剩下的数据就会丢失。

解决方案:

        1)在第一次拷贝数据完之后,再继续调用添加 FD 的函数,则在红黑树中的已就绪的 FD 又会再次拷贝到链表中,继续下一次通知用户进程。

        2)在第一次拷贝的时候,使用循环,将其数据全部拷贝完毕。


小结:

        ET 模式避免了 LT 模式可能出现的惊群现象。

        ET 模式最好结合非阻塞 IO 读取 FD 数据,相比 LT 会复杂一些。

        2.3.5 基于 epoll 的服务端流程

基于 epoll 模式的 web 服务的基本流程:

        首先调用 epoll_create 函数创建实例,在内核中创建一颗红黑树,接收需要监听的 FD 和一个链表,接收已就绪的 FD。

        接着创建服务端 serverSocket 并将得到的 FD 添加到红黑树中进行监听,调用 epoll_ctl 函数进行添加 FD 到红黑树中,还要给注册 ep_poll_callback,当 FD 就绪时,将就绪的 FD 记录到链表中。

        再接着就是调用 epoll_wait 函数进行等到链表中已就绪的 FD,此时会进行休眠,当超时或者被通知的时候,线程就会被唤醒,如果时超时导致的唤醒,证明目前链表中还没得到已就绪的 FD,那么就需要继续调用 epoll_wait 函数继续等待;如果被通知链表中存在就绪的 FD 被唤醒的时候,就会接着判断事件类型。

        此时,当服务端的 FD 已就绪时,则 serverSocket 接收到 socket 客户端,将对应的 scoket 的 FD 添加到红黑树中,并且注册 ep_poll_callback,对客户端进行监听。

        对客户端进行监听,当监听到了客户端的 FD 状态已就绪了,则说明有请求发送到服务端中,接着就会通知给用户进程,得到对应的 FD,再接着调用 recvfrom 命令来读取内核中的请求数据。

        这就是基于 epoll 模式的 web 服务的基本流程。

        2.4 Redis 网络模型 - 信号驱动 IO

        信号驱动 IO 是与内核建立 SIGIO 的信号关联并设置回调,当内核有 FD 就绪时,会发生 SIGIO 信号通知用户,期间用户应用可以执行其他业务,无需阻塞等待。直到数据就绪,递交 SIGIO 信号,也就是得到通知,告诉用户进程,数据准备好了,可以从内核缓冲区获取了。

        在第一阶段,也就是等待数据是不阻塞的,进程可以执行其他业务,而第二个阶段,拷贝数据是阻塞的。

信号驱动 IO 存在的问题:

        当有大量 IO 操作时,信号较多,SIGIO 处理函数不能及时处理可能导致信号队列溢出而且内核空间与用户空间的频繁信号交互性能也较低。

        2.5 Redis 网络模型 - 异步 IO

        异步 IO 的整个过程都是非阻塞的,用户进程调用完异步 API 后就可以去做其他事情,内核等待数据就绪并拷贝到用户空间后才会递交信号,通知用户进程。

        可以看到,异步 IO 模型中,用户进程在两个阶段都是非阻塞状态。

        3.0 Redis 单线程及多线程网络模型

        3.1 经典面试题:Redis 是单线程还是多线程?

        1)如果对于 Redis 的核心业务部分,也就是命令处理的部分,Redis 就是单线程。

        2)如果对于 Redis 整体来说,那么 Redis 就是多线程。

        在 Redis 版本迭代过程中,在两个重要的时间节点引入了多线程的支持:

        1)Redis v4.0:引入多线程异步处理一些耗时较长的任务,例如异步删除命令 unlink。

        2)Redis v6.0:在核心网络模型中引入多线程,进一步提高对于多核 CPU 的利用率。

        3.2 经典面试题:为什么 Redis 要选择单线程?

        1)抛开持久化不谈,Redis 是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟而不是执行速度,因此多线程并不会带来巨大的性能提升。

        2)多线程会导致过多的上下文切换,带来不必要的开销。

        3)引入多线程会面临线程安全问题,必然要引入线程锁这样的安全手段,实现复杂度增高,而且性能也会大打折扣。

        3.3 Redis 网络模型的结构及具体流程

        1)Redis 网络模型流程:第一个阶段

        创建 serverSocket 服务端,调用 aeEventLoop 在内核中创建红黑树和链表,接着将服务端的 FD 添加到内核的红黑树中进行监听,再接调用 aeApiPoll 函数进行等待数据。一旦监听到服务端的 FD 就绪,就会调用 tcpAccepthandler 连接处理器,简单来说,该处理器就是将连接到服务端的客户端的 FD 添加到内核红黑树中进行监听处理。

        如果是已经添加到红黑树的客户端的 FD 已就绪了,就会将 FD 添加到链表中,且通知用户进程来获取 FD,再接着使用 recvfrom 命令来获取该客户端的请求数据。因此,readQueryFromClient 命令请求处理器的作用是:读取请求数据。

        2)Redis 网络模型流程:第二个阶段

        对于 readQueryFromClient 命令请求处理器的作用是请求数据,那么具体是如何读取的呢?

相关的源代码:

        可以看到里面有三个函数,readQueryFromClient() 调用了 processCommand(),而 processCommand() 调用了 addReply() 。

        该函数具体的任务:

        1)readQueryFromClient():获取当前客户端,客户端中有缓冲区用来读和写。读取请求数据到缓冲区和解析缓冲区字符串,转为 Redis 命令参数存入到数组中。

        2)processCommand():根据命令名称,寻找对应的 command,执行命令,得到结果。

        3)addReply():尝试把结果写到客户端缓存区,如果 buf 写不下,则写到 reply,这是一个链表,容量无上限。再接着将客户端添加到队列中,等待被写出。

具体结构如下:

        在读取客户端发送过来的请求过程中,是 IO 操作。 

        3)Redis 网络模型流程:第三个阶段

        现在结果已经存在到队列中了,等待被读取,也就是输出到对应的客户端,该过程进行 IO 操作。

相关源码如下:

        在等待数据之前,会调用 beforeSleep 函数,监听 socket 的 FD 读事件,并且绑定写处理器,可以把响应写到客户端 socket 。一旦客户端写操作 FD 就绪了,则将队列中的结果写入到客户端中。

Redis 网络模型单线程的最终形态:

        Redis 6.0 版本引入了多线程,目的是为了提高 IO 读写效率。因此解析客户端命令、写响应结果时采用多线程。而核心的命令执行 IO 多路复用模块依然是由主线程执行。

Redis 网络模型多线程的最终形态:

        在单线程的基础上,在 IO 读写步骤中添加多线程进行操作:在读取请求数据、解析数据的步骤、将队列中的结果写入到对应的客户端中,这些步骤都是可以使用多线程进行操作。

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

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

相关文章

vscode【实用插件】Markdown Preview Enhanced 预览 .md 文件

安装 在 vscode 插件市场的搜索 Markdown Preview Enhanced点安装 使用 用 vscode 打开任意 .md 文件右键快捷菜单 最终效果 可打开导航目录

liunxcentos7下 跟目录空间不足docker load镜像报错空间不足

前两天在公司,做jenkins流水线项目,然后把项目放到docker容器里面运行,就在我把镜像打好包的时候正准备往服务器里面导入镜像的时候报错:如图所示 这时发现自己的根目录空间不足。 解决办法:重新加一块磁盘将磁盘挂载…

Qemu开发ARM篇-5、buildroot制作根文件系统并挂载启动

文章目录 1、 buildroot源码获取2、buildroot配置3、buildroot编译4、挂载根文件系统 在上一篇 Qemu开发ARM篇-4、kernel交叉编译运行演示中,我们编译了kernel,并在qemu上进行了运行,但到最后,在挂载根文件系统时候,挂…

基于 Redis 实现滑动窗口的限流

⏳ 限流场景:突发流量,恶意流量,业务本身需要 基于 Redis 实现滑动窗口的限流是一种常见且高效的做法。Redis 是一种内存数据库,具有高性能和支持原子操作的特点,非常适合用来实现限流功能。下面是一个使用 Redis 实现…

谷歌浏览器如何把常用的网址创建快捷方式到电脑桌面?

1、打开想要创建快捷方式的网页之后,点击谷歌浏览器右上角的【三个点】 2、选择【保存并分享】,再选择【创建快捷方式】 3、之后在浏览器上方弹出的框中,重新命名快捷方式。 然后,点击【创建】 4、之后,即可在电…

手机解压软件加密指南:让文件更安全

在数字化时代,文件加密对于保护个人隐私和敏感信息的重要性不言而喻。随着互联网的飞速发展,我们的生活和工作越来越依赖于数字设备和网络。 然而,这也带来了一系列的安全风险,如黑客攻击、数据泄露等。文件加密技术成为了保护我…

mac m1 electron生产环境使用prisma,sqlite

最近在用electron开发一个适合自己的小应用,技术选型中使用prisma和sqlite在进行数据存储,写这篇文章的目的就是用来记录下遇到的一些问题。 开发环境使用prisma 1、开发环境使用prisma非常的简单,只需要按照教程安装prisma,然后…

vue嵌套路由刷新页面空白问题

问题描述 在vue项目开发中遇到这样一个问题,在history模式下通过页面点击路由跳转可以打开页面,但是在当前页面刷新就空白了,如下: 点击路由跳转页面是有的 刷新页面就空白 代码 {path: "/home",name: "home&qu…

TreeMap源码详解

优质博文:IT-BLOG-CN 背景:昨天有人问我,他想将Map中的Key按照顺序进行遍历,我说直接使用keySet方法获取到Set集合,因为它是集成Collection接口,所以包含了sort方法后遍历取value值即可。但当看到TreeMap的…

差旅报销的数智化转型 以分贝通为例

企业差旅报销的数智化转型之所以势在必行,源于传统差旅报销方式在效率、合规性和成本控制等方面存在严重不足。作为服务企业的一体化差旅报销管理平台,分贝通结合数千家合作伙伴的实际案例为企业提供定制化的差旅报销数智化解决方案,帮助企业…

【Python报错已解决】AttributeError: ‘tuple‘ object has no attribute ‘log_softmax‘

🎬 鸽芷咕:个人主页 🔥 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 专栏介绍 在软件开发和日常使用中,BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

CentOS 7 中安装 docker 环境

作者:程序那点事儿 日期:2023/02/15 02:31 官网地址 官网文档 docker三种网络模式 Docker CE 支持 64 位版本 CentOS 7,并且要求内核版本不低于 3.10, CentOS 7 满足最低内核的要求。 Docker 分为 CE 和 EE 两大版本。CE 即社区…

前端开发必备:实用Tool封装工具类方法大全

程序员必备宝典网站https://tmxkj.top/#/ 1.判断空值 /*** 判断是否是空值*/ export function isEmpty(value) {if (typeof value string) {return value.length 0 || value "";} else if (typeof value number) {return value 0;} else if (Array.isArray(va…

TypeScript 设计模式之【抽象工厂模式】

文章目录 抽象工厂模式:一个神奇的玩具制造商主要思想:玩具制造商的秘密这个制造商有什么好处和坏处?如何打造这个神奇的玩具制造商?代码实现案例抽象工厂模式主要优点抽象工厂模式主要缺点抽象工厂模式适用场景总结 抽象工厂模式:一个神奇的玩具制造商 想象一下&#xff0c…

(done) 声音信号处理基础知识(7) (Understanding Time Domain Audio Features)

参考:https://www.youtube.com/watch?vSRrQ_v-OOSg&t1s 时域特征包括: 1.幅度包络 2.均方根能量 3.过零率 振幅包络的定义:一个 frame 里,所有采样点中最大的振幅值 一个形象的关于振幅包络的可视化解释如下:…

基于SpringBoot + Vue的大学生日常消费管理系统设计与实现

文章目录 前言一、详细操作演示视频二、具体实现截图三、技术栈1.前端-Vue.js2.后端-SpringBoot3.数据库-MySQL4.系统架构-B/S 四、系统测试1.系统测试概述2.系统功能测试3.系统测试结论 五、项目代码参考六、数据库代码参考七、项目论文示例结语 前言 💛博主介绍&a…

fiddler抓包07_抓IOS手机请求

课程大纲 前提:电脑和手机连接同一个局域网 (土小帽电脑和手机都连了自己的无线网“tuxiaomao”。) 原理如下: 电脑浏览器抓包时,直接就是本机网络。手机想被电脑Fiddler抓包,就要把Fiddler变成手机和网络…

浅拷贝和深拷贝(Java 与 JavaScript)

一、Java 浅拷贝和深拷贝 在Java中,浅拷贝和深拷贝的主要区别在于对对象的引用和内容的复制方式。 浅拷贝 Java 的类型有基本数据类型和引用类型,基本数据类型是可以由 CPU 直接操作的类型,无论是深拷贝还是浅拷贝,都是会复制出…

ANSYS Workbench晶体结构Voronoi泰森多边形建模

在ANSYS Workbench内建立包含晶格及晶格边界在内的晶体结构模型,可用于模拟多种物理现象及材料行为。晶格模型适用于研究微观尺度下的材料性质,以及它们如何影响宏观性能,如进行金属晶体结构建模及断裂的模拟等。 晶体结构模型可采用CAD Vo…

fastapp-微信开发GPT项目第一课

0. 开发说明 在学习开发本项目之前,必须保证有以下知识储备和环境工具。 技术栈说明python>3.9、pydantic>2.7.1python基础,http协议fastapi>0.111.0web协程异步框架,有web开发基础,异步编程,类型标注[pyth…