磁盘可以说是计算机系统最慢的硬件之一,读写速度相差内存 10 倍以上,所以针对优化磁盘的技术非常的多,比如零拷贝、直接 I/O、异步 I/O 等等,这些优化的目的就是为了提高系统的吞吐量,另外操作系统内核中的磁盘高速缓存区,可以有效的减少磁盘的访问次数。本文会分析 I/O 工作方式,以及如何优化传输文件的性能。参考博客如下:

内容提纲

本会从以下几个方面介绍磁盘的IO技术:

  1. DMA之前的IO方式
  2. 直接内存访问——DMA技术。
  3. DMA文件传输存在的问题。
  4. 如何提高文件传输的性能。
  5. 零拷贝实现原理分析。
  6. PageCache有什么用。
  7. 大文件传输用什么方式实现。

DMA之前的IO

在没有DMA技术之前,操作系统的从磁盘读取数据的IO过程如下所示(以read()接口为例):

read(file, tmp_buf, len);
  1. 用户程序需要读取数据,调用read方法,把读取数据的指令交给CPU执行,线程进入阻塞状态。
  2. CPU发出指令给磁盘控制器,告诉磁盘控制器需要读取哪些数据,然后返回;
  3. 磁盘控制器接收到指令后,把指定的数据放入磁盘内部的缓存区,然后用中断的方式通知CPU;
  4. CPU收到中断信号之后,开始一个字节一个字节的把数据读取到PageCache缓存区;
  5. CPU再一个字节一个字节把数据从PageCache缓存区读取到用户缓存区;
  6. 用户程序从内存中读取到数据,可以继续执行后续逻辑。

可以看到,整个数据的传输过程,都要需要CPU亲自参与搬运数据的过程,而且这个过程,CPU是不能做其他事情的。简单的搬运几个字符数据那没问题,但是如果我们用千兆网卡或者硬盘传输大量数据的时候,都用CPU来搬运的话,肯定忙不过来。计算机科学家们发现了事情的严重性后,于是就发明了 DMA 技术,也就是直接内存访问(Direct Memory Access) 技术。

直接内存访问——DMA技术

什么是 DMA 技术?简单理解就是,在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的事务。

那使用 DMA 控制器进行数据传输的过程究竟是什么样的呢?下面我们来具体看看。

read(file, tmp_buf, len);
  1. 用户程序需要读取数据,调用read方法,把读取数据的指令交给CPU执行。
  2. CPU发出指令给DMA,告诉DMA需要读取磁盘的哪些数据,然后返回,线程进入阻塞状态
  3. DMA向磁盘控制器发出IO请求,告诉磁盘控制器需要读取哪些数据,然后返回;
  4. 磁盘控制器收到IO请求之后,把数据读取到磁盘缓存区,当磁盘缓存读取完成之后,中断DMA;
  5. DMA收到磁盘的中断信号,将磁盘缓存区的数据读取到PageCache缓存区,然后中断CPU;
  6. CPU响应DMA中断信号,知道数据读取完成,然后将PageCache缓存区中的数据读取到用户缓存中;
  7. 用户程序从内存中读取到数据,可以继续执行后续逻辑。

可以看到, 整个数据传输的过程,CPU不再参与磁盘数据搬运的工作,而是全程由DMA完成,但是CPU在这个过程中也是必不可少的,因为传输什么数据,从哪里传输到哪里,都需要CPU来告诉DMA控制器。

早期DMA只存在在主板上,如今由于I/O设备越来越多,数据传输的需求也不尽相同,所以每个I/O设备里面都有自己的DMA控制器。

DMA文件传输存在的问题

如果服务端要提供文件传输的功能,我们能想到的最简单的方式是:将磁盘上的文件读取出来,然后通过网络协议发送给客户端。

传统 I/O 的工作方式是,数据读取和写入是从用户空间到内核空间来回复制,而内核空间的数据是通过操作系统层面的 I/O 接口从磁盘读取或写入。

代码通常如下,一般会需要以下两个系统调用,代码很简单,虽然就两行代码,但是这里面发生了不少的事情。

read(file, tmp_buf, len);
write(socket, tmp_buf, len);
  1. 用户程序需要读取数据,调用read方法,把读取数据的指令交给CPU执行,线程进入阻塞状态。
  2. CPU发出指令给磁盘DMA,告诉磁盘DMA需要读取磁盘的哪些数据,然后返回;
  3. 磁盘DMA向磁盘控制器发出IO请求,告诉磁盘控制器需要读取哪些数据,然后返回;
  4. 磁盘控制器收到IO请求之后,把数据读取到磁盘缓存区,当磁盘缓存读取完成之后,中断DMA;
  5. DMA收到磁盘的中断信号,将磁盘缓存区的数据读取到PageCache缓存区,然后中断CPU;
  6. CPU响应DMA中断信号,知道数据读取完成,然后将PageCache缓存区中的数据读取到用户缓存中;
  7. 用户程序从内存中读取到数据,可以继续执行后续写网卡数据操作;
  8. 用户需要向网卡设备写入数据,调用write方法,把写数据指令交给CPU执行,线程进入阻塞;
  9. CPU将用户缓存区的数据写入PageCache缓存区,然后通知网卡DMA写数据;
  10. 网卡DMA将数据从PageCache缓存区复制到网卡,交给网卡处理数据。
  11. 网卡开始处理数据,网卡处理完成数据之后中断网卡DMA;
  12. 网卡DMA处理中断,知道数据处理完成,向CPU发出中断;
  13. CPU响应DMA中断信号,知道数据处理完成,唤醒用户线程;
  14. 用户程序执行后续逻辑。

这个过程比较复杂,其中主要存在以下问题:

  • 发生了4次用户态与内核态的上下文切换,因为发生了两次系统调用,一次是read() ,一次是write(),每次系统调用都得先从用户态切换到内核态,等内核完成任务后,再从内核态切换回用户态。上下文切换到成本并不小,一次切换需要耗时几十纳秒到几微秒,虽然时间看上去很短,但是在高并发的场景下,这类时间容易被累积和放大,从而影响系统的性能。
  • 发生了4次数据拷贝,其中两次是 DMA 的拷贝,另外两次则是通过 CPU 拷贝的,下面说一下这个过程:第一次拷贝,把磁盘上的数据拷贝到操作系统内核的缓冲区里,这个拷贝的过程是通过 DMA 搬运的。第二次拷贝,把内核缓冲区的数据拷贝到用户的缓冲区里,于是我们应用程序就可以使用这部分数据了,这个拷贝到过程是由 CPU 完成的。第三次拷贝,把刚才拷贝到用户的缓冲区里的数据,再拷贝到内核的 socket 的缓冲区里,这个过程依然还是由 CPU 搬运的。第四次拷贝,把内核的 socket 缓冲区里的数据,拷贝到网卡的缓冲区里,这个过程又是由 DMA 搬运的。

我们回过头看这个文件传输的过程,我们只是搬运一份数据,结果却搬运了 4 次,过多的数据拷贝无疑会消耗 CPU 资源,大大降低了系统性能。

这种简单又传统的文件传输方式,存在冗余的上文切换和数据拷贝,在高并发系统里是非常糟糕的,多了很多不必要的开销,会严重影响系统性能。

所以,要想提高文件传输的性能,就需要减少「用户态与内核态的上下文切换」和「内存拷贝」的次数。

如何提高文件传输的性能

减少用户态与内核态的上下文切换的次数

读取磁盘数据的时候,之所以要发生上下文切换,这是因为用户空间没有权限操作磁盘或网卡,内核的权限最高,这些操作设备的过程都需要交由操作系统内核来完成,所以一般要通过内核去完成某些任务的时候,就需要使用操作系统提供的系统调用函数。

而一次系统调用必然会发生 2 次上下文切换:首先从用户态切换到内核态,当内核执行完任务后,再切换回用户态交由进程代码执行。

所以,要想减少上下文切换到次数,就要减少系统调用的次数。

减少数据拷贝的次数

在前面我们知道了,传统的文件传输方式会历经 4 次数据拷贝,而且这里面,「从内核的读缓冲区拷贝到用户的缓冲区里,再从用户的缓冲区里拷贝到 socket 的缓冲区里」,这个过程是没有必要的。

因为文件传输的应用场景中,在用户空间我们并不会对数据「再加工」,所以数据实际上可以不用搬运到用户空间,因此用户的缓冲区是没有必要存在的。

零拷贝实现原理分析

零拷贝技术实现的方式通常有 2 种:

  • mmap + write
  • sendfile

下面就谈一谈,它们是如何减少「上下文切换」和「数据拷贝」的次数。

mmap + write

在前面我们知道,read()系统调用的过程中会把内核缓冲区的数据拷贝到用户的缓冲区里,于是为了减少这一步开销,我们可以用 mmap()替换read()系统调用函数。

buf = mmap(file, len);
write(sockfd, buf, len);

mmap() 系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。

具体过程如下:

  1. 应用进程调用了mmap()后,DMA会把磁盘的数据拷贝到内核的缓冲区里。接着,应用进程跟操作系统内核「共享」这个缓冲区;
  2. 应用进程再调用write(),操作系统直接将内核缓冲区的数据拷贝到socket缓冲区中,这一切都发生在内核态,由CPU来搬运数据;
  3. 最后,把内核的socket缓冲区里的数据,拷贝到网卡的缓冲区里,这个过程是由DMA搬运的。

我们可以得知,通过使用mmap()来代替read(), 可以减少一次数据拷贝的过程。

但这还不是最理想的零拷贝,因为仍然需要通过CPU把内核缓冲区的数据拷贝到socket缓冲区里,而且仍然需要4次上下文切换,因为系统调用还是2次。

sendfile

在 Linux 内核版本 2.1 中,提供了一个专门发送文件的系统调用函数 sendfile(),函数形式如下:

#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

它的前两个参数分别是目的端和源端的文件描述符,后面两个参数是源端的偏移量和复制数据的长度,返回值是实际复制数据的长度。

首先,它可以替代前面的 read() 和 write() 这两个系统调用,这样就可以减少一次系统调用,也就减少了 2 次上下文切换的开销。

其次,该系统调用,可以直接把内核缓冲区里的数据拷贝到 socket 缓冲区里,不再拷贝到用户态,这样就只有 2 次上下文切换,和 3 次数据拷贝。如下图:

但是这还不是真正的零拷贝技术,如果网卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技术(和普通的 DMA 有所不同),我们可以进一步减少通过 CPU 把内核缓冲区里的数据拷贝到 socket 缓冲区的过程。

你可以在你的 Linux 系统通过下面这个命令,查看网卡是否支持 scatter-gather 特性:

$ ethtool -k eth0 | grep scatter-gather
scatter-gather: on

于是,从 Linux 内核 2.4 版本开始起,对于支持网卡支持 SG-DMA 技术的情况下, sendfile() 系统调用的过程发生了点变化,具体过程如下:

  1. 通过 DMA 将磁盘上的数据拷贝到内核缓冲区里;
  2. 缓冲区描述符和数据长度传到 socket 缓冲区,这样网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里,此过程不需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中,这样就减少了一次数据拷贝;

所以,这个过程之中,只进行了 2 次数据拷贝,如下图:

这就是所谓的零拷贝(Zero-copy)技术,因为我们没有在内存层面去拷贝数据,也就是说全程没有通过 CPU 来搬运数据,所有的数据都是通过 DMA 来进行传输的。。

零拷贝技术的文件传输方式相比传统文件传输的方式,减少了 2 次上下文切换和数据拷贝次数,只需要 2 次上下文切换和数据拷贝次数,就可以完成文件的传输,而且 2 次的数据拷贝过程,都不需要通过 CPU,2 次都是由 DMA 来搬运。

所以,总体来看,零拷贝技术可以把文件传输的性能提高至少一倍以上。

使用零拷贝技术的项目

事实上,Kafka这个开源项目,就利用了「零拷贝」技术,从而大幅提升了I/O的吞吐率,这也是Kafka在处理海量数据为什么这么快的原因之一。

如果你追溯Kafka文件传输的代码,你会发现,最终它调用了Java NIO库里的transferTo方法:

@Override
public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {
return fileChannel.transferTo(position, count, socketChannel);
}

如果Linux系统支持sendfile()系统调用,那么transferTo()实际上最后就会使用到sendfile()系统调用函数。

曾经有大佬专门写过程序测试过,在同样的硬件条件下,传统文件传输和零拷拷贝文件传输的性能差异,你可以看到下面这张测试数据图,使用了零拷贝能够缩短 65% 的时间,大幅度提升了机器传输数据的吞吐量。

另外,Nginx 也支持零拷贝技术,一般默认是开启零拷贝技术,这样有利于提高文件传输的效率,是否开启零拷贝技术的配置如下:

http {
...
sendfile on
...
}

sendfile 配置的具体意思:

  • 设置为 on 表示,使用零拷贝技术来传输文件:sendfile ,这样只需要 2 次上下文切换,和 2 次数据拷贝。
  • 设置为 off 表示,使用传统的文件传输技术:read + write,这时就需要 4 次上下文切换,和 4 次数据拷贝。

当然,要使用 sendfile,Linux 内核版本必须要 2.1 以上的版本。

PageCache 有什么作用?

回顾前面说道文件传输过程,其中第一步都是先需要先把磁盘文件数据拷贝「内核缓冲区」里,这个「内核缓冲区」实际上是磁盘高速缓存(PageCache)。

由于零拷贝使用了 PageCache 技术,可以使得零拷贝进一步提升了性能,我们接下来看看 PageCache 是如何做到这一点的。

读写磁盘相比读写内存的速度慢太多了,所以我们应该想办法把「读写磁盘」替换成「读写内存」。于是,我们会通过 DMA 把磁盘里的数据搬运到内存里,这样就可以用读内存替换读磁盘。

但是,内存空间远比磁盘要小,内存注定只能拷贝磁盘里的一小部分数据。

那问题来了,选择哪些磁盘数据拷贝到内存呢?

我们都知道程序运行的时候,具有「局部性」,所以通常,刚被访问的数据在短时间内再次被访问的概率很高,于是我们可以用 PageCache 来缓存最近被访问的数据,当空间不足时淘汰最久未被访问的缓存。

所以,读磁盘数据的时候,优先在 PageCache 找,如果数据存在则可以直接返回;如果没有,则从磁盘中读取,然后缓存 PageCache 中。

还有一点,读取磁盘数据的时候,需要找到数据所在的位置,但是对于机械磁盘来说,就是通过磁头旋转到数据所在的扇区,再开始「顺序」读取数据,但是旋转磁头这个物理动作是非常耗时的,为了降低它的影响,PageCache 使用了「预读功能」。

比如,假设 read 方法每次只会读 32 KB 的字节,虽然 read 刚开始只会读 0 ~ 32 KB 的字节,但内核会把其后面的 32~64 KB 也读取到 PageCache,这样后面读取 32~64 KB 的成本就很低,如果在 32~64 KB 淘汰出 PageCache 前,进程读取到它了,收益就非常大。

所以,PageCache 的优点主要是两个:

  • 缓存最近被访问的数据;
  • 预读功能;

这两个做法,将大大提高读写磁盘的性能。

但是,在传输大文件(GB 级别的文件)的时候,PageCache 会不起作用,那就白白浪费 DMA 多做的一次数据拷贝,造成性能的降低,即使使用了 PageCache 的零拷贝也会损失性能

这是因为如果你有很多 GB 级别文件需要传输,每当用户访问这些大文件的时候,内核就会把它们载入 PageCache 中,于是 PageCache 空间很快被这些大文件占满。

另外,由于文件太大,可能某些部分的文件数据被再次访问的概率比较低,这样就会带来 2 个问题:

  • PageCache 由于长时间被大文件占据,其他「热点」的小文件可能就无法充分使用到 PageCache,于是这样磁盘读写的性能就会下降了;
  • PageCache 中的大文件数据,由于没有享受到缓存带来的好处,但却耗费DMA多拷贝到PageCache一次;

    所以,针对大文件的传输,不应该使用PageCache,也就是说不应该使用零拷贝技术,因为可能由于PageCache被大文件占据,而导致「热点」小文件无法利用到PageCache,这样在高并发的环境下,会带来严重的性能问题。

大文件传输用什么方式实现

绕开 PageCache 的 I/O 叫直接 I/O,使用 PageCache 的 I/O 则叫缓存 I/O。通常,对于磁盘,异步 I/O 只支持直接 I/O。

前面也提到,大文件的传输不应该使用 PageCache,因为可能由于 PageCache 被大文件占据,而导致「热点」小文件无法利用到 PageCache。

于是,在高并发的场景下,针对大文件的传输的方式,应该使用「异步 I/O + 直接 I/O」来替代零拷贝技术。

直接 I/O 应用场景常见的两种:

应用程序已经实现了磁盘数据的缓存,那么可以不需要 PageCache 再次缓存,减少额外的性能损耗。在 MySQL 数据库中,可以通过参数设置开启直接 I/O,默认是不开启;

传输大文件的时候,由于大文件难以命中 PageCache 缓存,而且会占满 PageCache 导致「热点」文件无法充分利用缓存,从而增大了性能开销,因此,这时应该使用直接 I/O。

另外,由于直接 I/O 绕过了 PageCache,就无法享受内核的这两点的优化:

内核的 I/O 调度算法会缓存尽可能多的 I/O 请求在 PageCache 中,最后「合并」成一个更大的 I/O 请求再发给磁盘,这样做是为了减少磁盘的寻址操作;

内核也会「预读」后续的 I/O 请求放在 PageCache 中,一样是为了减少对磁盘的操作;

于是,传输大文件的时候,使用「异步 I/O + 直接 I/O」了,就可以无阻塞地读取文件了。

所以,传输文件的时候,我们要根据文件的大小来使用不同的方式:

传输大文件的时候,使用「异步 I/O + 直接 I/O」;

传输小文件的时候,则使用「零拷贝技术」;

在 nginx 中,我们可以用如下配置,来根据文件的大小来使用不同的方式:

location /video/ {
sendfile on;
aio on;
directio 1024m;
}

当文件大小大于directio值后,使用「异步I/O+直接I/O」,否则使用「零拷贝技术」。

总结

早期 I/O 操作,内存与磁盘的数据传输的工作都是由 CPU 完成的,而此时 CPU 不能执行其他任务,会特别浪费 CPU 资源。

于是,为了解决这一问题,DMA 技术就出现了,每个 I/O 设备都有自己的 DMA 控制器,通过这个 DMA 控制器,CPU 只需要告诉 DMA 控制器,我们要传输什么数据,从哪里来,到哪里去,就可以放心离开了。后续的实际数据传输工作,都会由 DMA 控制器来完成,CPU 不需要参与数据传输的工作。

传统 IO 的工作方式,从硬盘读取数据,然后再通过网卡向外发送,我们需要进行 4 上下文切换,和 4 次数据拷贝,其中 2 次数据拷贝发生在内存里的缓冲区和对应的硬件设备之间,这个是由 DMA 完成,另外 2 次则发生在内核态和用户态之间,这个数据搬移工作是由 CPU 完成的。

为了提高文件传输的性能,于是就出现了零拷贝技术,它通过一次系统调用(sendfile 方法)合并了磁盘读取与网络发送两个操作,降低了上下文切换次数。另外,拷贝数据都是发生在内核中的,天然就降低了数据拷贝的次数。

Kafka 和 Nginx 都有实现零拷贝技术,这将大大提高文件传输的性能。

零拷贝技术是基于 PageCache 的,PageCache 会缓存最近访问的数据,提升了访问缓存数据的性能,同时,为了解决机械硬盘寻址慢的问题,它还协助 I/O 调度算法实现了 IO 合并与预读,这也是顺序读比随机读性能好的原因。这些优势,进一步提升了零拷贝的性能。

需要注意的是,零拷贝技术是不允许进程对文件内容作进一步的加工的,比如压缩数据再发送。

另外,当传输大文件时,不能使用零拷贝,因为可能由于 PageCache 被大文件占据,而导致「热点」小文件无法利用到 PageCache,并且大文件的缓存命中率不高,这时就需要使用「异步 IO + 直接 IO 」的方式。

在 Nginx 里,可以通过配置,设定一个文件大小阈值,针对大文件使用异步 IO 和直接 IO,而对小文件使用零拷贝。

欢迎关注御狐神的微信公众号



参考文档

原来 8 张图,就可以搞懂「零拷贝」了

linux dma拷贝数据到用户态,图解:零拷贝Zero-Copy技术大揭秘

内核态与用户态、系统调用与库函数、文件IO与标准IO、缓冲区等概念介绍

本文最先发布至微信公众号,版权所有,禁止转载!

操作系统IO之零拷贝技术的更多相关文章

  1. Linux 中的零拷贝技术,第 2 部分

    技术实现 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.第一部分主要介绍了一些零拷贝技术的相关背景知识,简要概 ...

  2. [转帖]Linux 中的零拷贝技术,第 2 部分

    Linux 中的零拷贝技术,第 2 部分 https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy2/index.html   Linux 中 ...

  3. Linux零拷贝技术 直接 io

    Linux零拷贝技术 .https://kknews.cc/code/2yeazxe.html   https://zhuanlan.zhihu.com/p/76640160 https://clou ...

  4. Linux 零拷贝技术

    简介 零拷贝(zero-copy)技术可以减少数据拷贝和共享总线操作的次数,消除通信数据在存储器之间不必要的中间拷贝过程,有效地提高通信效率,是设计高速接口通道.实现高速服务器和路由器的关键技术之一. ...

  5. IO和零拷贝

    I/O介绍 I/O主要为:网络IO(本质是socket文件读取).磁盘IO 每次IO,都要经由两个阶段: 第一步:将数据从文件先加载至内核内存空间(缓冲区),等待数据准备完成,时间较长 第二步:将数据 ...

  6. 【Netty技术专题】「原理分析系列」Netty强大特性之ByteBuf零拷贝技术原理分析

    零拷贝Zero-Copy 我们先来看下它的定义: "Zero-copy" describes computer operations in which the CPU does n ...

  7. Linux 中的零拷贝技术,第 1 部分

    概述 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.本文是本系列文章的第一部分,主要是介绍一些零拷贝技术的相关 ...

  8. Linux中的零拷贝技术

    转载:https://www.jianshu.com/p/fad3339e3448 引文## 在写一个服务端程序时(Web Server或者文件服务器),文件下载是一个基本功能.这时候服务端的任务是: ...

  9. Java基础-零拷贝技术应用案例

    Java基础-零拷贝技术应用案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 零拷贝技术在Hadoop生态圈中很多组件得到应用,典型的比如kafka组件,它就很成功的应用了零拷贝 ...

随机推荐

  1. 剖析虚幻渲染体系(08)- Shader体系

    目录 8.1 本篇概述 8.2 Shader基础 8.2.1 FShader 8.2.2 Shader Parameter 8.2.3 Uniform Buffer 8.2.4 Vertex Fact ...

  2. 【硬核】MMU是如何完成地址翻译的

    目录 1. 什么是虚拟内存? 2. 虚拟内存的作用 3. 虚拟内存与物理内存 3.1 CPU存取数据 3.2 物理地址常用术语 3.3 虚拟地址常用术语 3.4 页表常用术语 3.5 页命中/缺页 4 ...

  3. kivy里rgb,rgba颜色相关知识,以及支持十六进制颜色值方法

    rgb,rgba在kivy里的格式为如下: rgb:1,1,1---->每个参数为0-1的数值(如0.1),也就是说第一位如果是0.1意思是10%的红色,由r代表,g代表绿色,b代表蓝色 rgb ...

  4. noip模拟测试16

    这次考试,难度还是不小的,先说一下考试过程,首先看一遍题,觉得开题顺序1 3 2, 然后我就先打了第一题,我当时可能是受到之前做题的限制了,觉得他只能每次走一 格,也就是一个单位长度,但是实际上,他甚 ...

  5. 三年Android开发快手、美团、支付宝连挂,怒刷1549页面试题字节上岸

    刚开始面试的时候我真的是处处碰壁,面一家挂一家,面完之后怀疑自我,是不是自己真的太菜了找不到工作.工作本身就是双向选择,一家不行再换一家,总有合适的,千万不要因为别人的一句话就全盘否定自己,一定要自信 ...

  6. Docker入门第一章

    Docker为什么出现? 一款产品:开发到上线 两套环境!应用环境,应用配置! 可能在开发环境你的产品是正常的,但是到了线上环境你的产品可能就会出现各种原因,或许是因为环境的不同,也可能是因为配置的不 ...

  7. 基于SpringBoot的药店管理系统java药房管理系统(源码+数据库文件+文档)

    注意:该项目只展示部分功能,如需了解,评论区咨询即可. 1.开发环境 开发语言:Java 后台框架:SpringBoot 前端技术:HTML+CSS+JavaScript+Bootstrap+jQue ...

  8. 解决vscode+python不提示numpy函数的问题

    前言 使用vscode编写numpy代码时,对于numpy.array()等方法总是无法提示.查找了很多博客后,大部分都是修改配置和安装多种vscode插件,经过尝试后方法对于我来说无效.最后在调试p ...

  9. VBA·Function的基础使用

    阅文时长 | 0.27分钟 字数统计 | 440字符 主要内容 | 1.引言&背景 2.基本结构 3.Demo示例 4.声明与参考资料 『VBA·Function的基础使用』 编写人 | SC ...

  10. [数据库系列之MySQL] Mysql整体架构浅析一

    一.引言 平时我们在做Java系统时,一般情况下都会连接到一个MySQL数据库上去,执行各种增删改查的语句.大部分的Java工程师对MySQL的了解和掌握程度,大致就停留在这么一个阶段:对MySQL可 ...