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

🔥博客主页: 【小扳_-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 读写步骤中添加多线程进行操作:在读取请求数据、解析数据的步骤、将队列中的结果写入到对应的客户端中,这些步骤都是可以使用多线程进行操作。

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

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

相关文章

WPF LiveChart控件基础属性介绍

WPF LiveChart控件基础属性介绍 在Nuget添加方法如下&#xff1a; 然后在xaml中添加引用&#xff1a; xmlns:lvc"clr-namespace:LiveCharts.Wpf;assemblyLiveCharts.Wpf"调用控件&#xff1a; <lvc:CartesianChart Name"chart" Margin"40"…

Java应用程序的服务器有哪些

1.Tomcat、Jetty 和 JBoss 区别&#xff1f; Apache Tomcat、Jetty 和 JBoss都是用于部署Java应用程序的服务器&#xff0c;它们都支持Servlet、JSP和其他Java EE&#xff08;现在称为Jakarta EE&#xff09;技术。尽管它们有一些相似的功能&#xff0c;但它们之间还是存在一些…

DownUnderCTF web sniffy

题目中给了源码 在index.php中将flag的值赋给了session[flag] session[theme]接收GET传入的theme参数。。。??是PHP中的空合并运算符它的作用是检查左侧的值是否存在且不为null。如果存在&#xff0c;则返回左侧的值&#xff1b;如果不存在&#xff0c;则返回右侧的值。 …

用友U8接口-采购管理(8)

概括 本文的操作需要正确部署U8API主要讲述采购管理接口的使用&#xff0c;以采购订单为例&#xff0c;其他单据接口都是大同小异的&#xff01;许多时候先在ERP做个单&#xff0c;然后仿造ERP单据参数&#xff0c;构造接口JSON参数是不错的做法哦 ERP单据金额计算 在ERP的许…

3DCAT亮相2024中国国际消费电子博览会,引领AI潮流

2024年10月18日-20日&#xff0c;备受瞩目的2024中国国际消费电子博览会&#xff08;以下简称“电博会”&#xff09;在青岛国际会展中心&#xff08;红岛馆&#xff09;盛大开幕。作为消费电子领域的盛会&#xff0c;本次电博会吸引了国内外300多家企业参展&#xff0c;展示了…

android openGL ES详解——缓冲区VBO/VAO/EBO/FBO/离屏渲染

目录 一、缓冲区对象概念 二、分类 三、顶点缓冲区对象VBO 1、概念 2、为什么使用VBO 3、如何使用VBO 生成缓冲区对象 绑定缓冲区对象 输入缓冲区数据 更新缓冲区中的数据 删除缓冲区 4、VBO应用 四、顶点数组对象VAO 1、概念 2、为什么使用VAO 3、如何使用VAO…

Django-中间件(切面编程AOP)

自定义中间件 官网&#xff1a;中间件 | Django 文档 | Django 中间件使用多就在主应用创建&#xff0c;仅限于子应用就在子引用中创建中间件文件.py 之后在settings.py文件中去配置中间件,运行的时候会自动调用中间件 def simple_middleware(get_response):def middleware…

数据结构和算法-动态规划(1)-认识动态规划

认识动态规划 什么是动态规划 Dynamic Programming is a method used in mathematics and computer science to solve complex problems by breaking them down into simpler subproblems. By solving each subproblem only once and storing the results, it avoids redundan…

centos-LAMP搭建与配置(论坛网站)

文章目录 LAMP简介搭建LAMP环境安装apache&#xff08;httpd&#xff09;安装mysql安装PHP安装php-mysql安装phpwind LAMP简介 LAMP是指一组通常一起使用来运行动态网站或者服务器的自由软件名称首字母缩写&#xff1a;Linux操作系统&#xff0c;网页服务器Apache&#xff0c;…

网络文件系统nfs实验1

服务端&#xff1a; 这个指令是搜索nfs相关的软件包 安装nfs相关的软件包&#xff1a; 列出已安装的nfs-utils软件包中的文件列表&#xff1a; 写配置文件&#xff1a;允许192.168.234.0/24这个网段的客户端能读写这个路径 重新导出所有当前已导出的文件系统&#xff1a; 启动…

DSPy:不需要手写prompt啦,You Only Code Once!

论文地址&#xff1a;https://arxiv.org/abs/2310.03714   项目地址&#xff1a;https://github.com/stanfordnlp/dspy 文章目录 1. 背景2. 签名3. 模块3.1 预测模块3.2 其他内置模块 4. 提词器5. 评估目标6. 代码分析6.1 _prepare_student_and_teacher6.2 _prepare_predicto…

【Docker】- WARNING: Found orphan containers XXX for this project.

报错展示 Creating network "net-10.9.0.0" with the default driver WARNING: Found orphan containers (server-4-10.9.0.8, server-3-10.9.0.7, server-1-10.9.0.5, server-2-10.9.0.6) for this project. If you removed or renamed this service in your compos…

Tomcat隐藏版本号和报错信息

为了避免漏洞扫描的时候造成版本泄露&#xff0c;可以在conf/server.xml配置文件中的<Host>配置项中添加如下配置: <Valve className"org.apache.catalina.valves.ErrorReportValve" showReport"false" showServerInfo"false" /> …

springboot案例

查询全部部门 项目结构 1. controller层 //日志注解,可以直接使用日志对象log.info Slf4j //用于指定将方法返回的对象转换为 JSON 或 XML 格式的响应体 RestController//DeptController.java //日志注解,可以直接使用日志对象log.info Slf4j //用于指定将方法返回的对象转换为…

Face Swap API 的整合与使用手册

Face Swap API 的整合与使用手册 Face Swap API 是一款功能强大的工具&#xff0c;能够通过提供一张源图像和一张目标图像&#xff0c;将目标图像中的人脸巧妙地替换为源图像中对应的位置。 在本手册中&#xff0c;我们将逐步指导您如何整合 Face Swap API&#xff0c;以便您…

Python金色流星雨

系列目录 序号直达链接爱心系列1Python制作一个无法拒绝的表白界面2Python满屏飘字表白代码3Python无限弹窗满屏表白代码4Python李峋同款可写字版跳动的爱心5Python流星雨代码6Python漂浮爱心代码7Python爱心光波代码8Python普通的玫瑰花代码9Python炫酷的玫瑰花代码10Python多…

Linux:编辑器Vim和Makefile

✨✨所属专栏&#xff1a;Linux✨✨ ✨✨作者主页&#xff1a;嶔某✨✨ vim的三种常用模式 分别是命令模式&#xff08;command mode&#xff09;、插入模式&#xff08;Insert mode&#xff09;和底行模式&#xff08;last line mode&#xff09; 各模式的功能区分如下&…

使用C#学习Office文件的处理(pptx docx xlsx)

Office文件 是指PPT 、word、Excel 这些常用工具生成的文件 &#xff0c;例如 pptx docx xlsx。 这些文件的读取和生成有很多很多库 例如 NOPI 、DevExpress、C1、Aspose、Teleric 等等&#xff0c;各有各的优缺点。俺今天不讲这个&#xff0c;俺只是讲讲如何了解Office文件的…

xtu Euler‘s Totient Function+欧拉函数

Eulers Totient Function 样例输入 3 1 6 1 100 1 1000000样例输出 12 3044 303963552392 解题思路&#xff1a; 不管是素数还是合数&#xff0c;初始值都是它本身。 j为素数&#xff0c;f[j]j-1&#xff0c;相当于f[j]j/i*(i-1),ij 埃筛&#xff0c;ji,i为j的…

基于微信小程序实现信阳毛尖茶叶商城系统设计与实现

作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参与学生毕业答辩指导&#xff0c;…