webserver相关

先声明,这里内容大部分是从网上摘抄!!!我基本就是把它们整合了一下,原作者链接我以后也会贴上(暂时记不住了)。

大家也可以在下面交流关于该项目的面试的问题和回答。

项目流程(*)

首先是服务器的一个参数初始化操作。

通过构造WebServer这个对象传递参数进行服务器相关参数的设定,主要参数有设置定时器超时时间、设置Epoll触发模式、设置日志系统的开启、日志路径以及异步开关、设置线程池的线程数量。

然后是通过设定的参数对服务器的各个模块进行初始化。主要有日志、线程池、IO复用、HTTP对象、缓冲区、阻塞队列等模块。

日志模块的初始化是先建立一个日志文件,通过一个变量按照天数去划分不同的日志文件。线程池采用RAII手法,在构造时创建线程,析构时唤醒所有睡眠的线程使其退出循环。IO复用是对epoll函数调用的一个封装。HTTP对象主要设置文件存放的相关路径。缓冲区和阻塞队列主要完成指定大小的参数设定。

服务器各个模块初始化完成之后就是主线程里IO复用监听事件的循环。

监听事件有新连接到达、读事件和写事件和异常事件。根据不同的事件进行一个任务处理。

当新连接到达的时候,通过调用accept取出新连接(ET模式下需要循环操作),将新连接的文件描述符用来初始化HTTP连接(套接字地址和文件描述符绑定到一个HTTP对象),完成绑定定时器的初始化,同时添加监听读事件,设置其文件描述符为非阻塞。

当有异常事件发生的时候,关闭该连接同时移除监听事件和定时器。

当触发读事件的时候,调整定时器的定时时间,将读任务放入线程池的任务队列当中去。这个时候线程池对象里的多个线程,处于一个睡眠或者竞争任务并执行的过程,任务加入到任务队列当中去时会发送一个唤醒信号,如果有睡眠的线程则会被唤醒,进入循环里探测任务队列是否为空,取出任务并执行或者队列为空继续睡眠。

线程执行读任务函数主要是完成一个非阻塞读取调用直到读完,将数据缓存在用户缓冲区中,接着执行一个消息解析的操作,根据HTTP解析是否成功的判断来决定重新注册写事件还是读事件。如果解析失败那么重新注册读事件等待下次读取更多数据直到一个完整的HTTP请求。如果是解析成功的话就制作响应报文并且注册写事件,等待内核缓冲区可写触发事件时,将其写入内核缓冲区。

这部分的重点是逻辑处理的过程,也就是HTTP解析和HTTP报文的制作。

解析采用的是状态机和正则表达式,每次都读取以\r\n结尾的一段字符串,通过状态机来判定获取的字符串是属于HTTP请求的哪一部分,再跳转到相应的函数进行解析,如果读取的字符串没有以\r\n结尾则认为此次数据获取不完整,返回解析失败重新注册读事件。如果解析完成之后则根据解析过程中保存的相应信息,制作响应报文,通过集中写将资源文件和响应报文分别发送回客户端。

项目总结

项目难点?

http报文解析,多线程同步

项目瓶颈

线程池的线程数量这一块,线程池线程数量是在构造时就确定的,不能根据机器的核心数量动态调整(需要修改源码去设定),固定的线程数量在核心过少或者过多的机器上都会有性能损失(可以通过使用普通线程和核心线程进行优化)。

解决方法

线程池的线程数量在构造的时候就已经确定下来,当机器核心数量改变时需要通过修改才能改变线程数量去匹配,不符合开闭原则,线程数量这一块可以通过C++17新特性动态获得机器的核心数量std::thread::hardware_concurrency(),来使程序更好的匹配机器实现更好的性能【避免核心数量过少进行频繁的上下文切换以及核心数量过多被闲置】。或者通过普通线程(临时创建用来响应多余的任务)和核心线程(一直存在)来优化【实现较为复杂】

定时器

定时器处理非活动连接

非活跃,是指客户端(这里是浏览器)与服务器端建立连接后,长时间不交换数据,一直占用服务器端的文件描述符,导致连接资源的浪费。

定时事件,是指固定一段时间之后触发某段代码,由该段代码处理一个事件,如从内核事件表删除事件,并关闭文件描述符,释放连接资源。

定时器,是指利用结构体或其他形式,将多种定时事件进行封装起来。具体的,这里只涉及一种定时事件,即定期检测非活跃连接,这里将该定时事件与连接资源封装为一个结构体定时器。

定时器容器,是指使用某种容器类数据结构,将上述多个定时器组合起来,便于对定时事件统一管理。具体的,项目中使用升序链表将所有定时器串联组织起来。

具体的,利用alarm函数周期性地触发SIGALRM信号,信号处理函数利用管道通知主循环,主循环接收到该信号后对升序链表上所有定时器进行处理,若该段时间内没有交换数据,则将该连接关闭,释放所占用的资源。

从上面的简要描述中,可以看出定时器处理非活动连接模块,主要分为两部分,其一为定时方法与信号通知流程,其二为定时器及其容器设计与定时任务的处理。

信号通知逻辑

  • 创建管道,其中管道写端写入信号值,管道读端通过I/O复用系统监测读事件
  • 设置信号处理函数SIGALRM(时间到了触发)和SIGTERM(kill会触发,Ctrl+C)
  • 利用I/O复用系统监听管道读端文件描述符的可读事件
  • 信息值传递给主循环,主循环再根据接收到的信号值执行目标信号对应的逻辑代码

常用的定时器结构和它们的差异?

除了小根堆实现之外,还有使用时间轮和基于升序链表实现的定时器结构。

基于升序链表实现的定时器结构按照超时时间作为升序做排序,每个结点都链接到下一个结点,由于链表的有序性以及不支持随机访问的特性,每次插入定时器都需要遍历寻找合适的位置,而且每次定时器调整超时时间时也需要往后遍历寻找合适的位置进行挪动,遍历操作效率较低。优化方式可以通过利用IO复用的超时选项。

不同于采用单条链表实现的定时器每次插入更新进行遍历来寻找合适的位置进行操作,时间轮利用哈希思想,将相差整个计时周期整数倍的定时器散列到不同的时间槽中,减少链表上的定时器数量避免过多的顺序遍历操作。时间轮通过提高时间槽的数量来提高查找效率【使每个时间槽里的链表长度尽可能短】,通过减少计时间隔来提高定时器的精度【使定时时间尽可能准确】,设计时需要考虑这两个因素,时间槽多但是定时器数量少则会造成效率低下,可以通过多级时间轮优化,但是实现起来复杂。

小根堆实现的定时器结构,每次取堆头都是最短超时时间,能够利用IO复用的超时选项,每次的计时间隔动态调整为最短超时时间,确保每次调用IO复用系统调用返回时都至少有一个定时事件的超时发生或者监听事件的到达,有效地减少了多余的计时中断(利用信号中断进行计时)。最主要是确保每次定时器插入、更新、删除都能实现稳定的logn时间复杂度【该时间复杂度是调整堆的代价,定时器的定位利用哈希表实现O1查找】,而不像链表一样依赖于定时器数量的大小以及时间轮需要兼顾时间精度和效率的问题。

日志系统

为什么要异步?和同步的区别是什么?

因为同步日志的,日志写入函数与工作线程串行执行,由于涉及到I/O操作,在单条日志比较大的时候,同步模式会阻塞整个处理流程,服务器所能处理的并发能力将有所下降,尤其是在峰值的时候,写日志可能成为系统的瓶颈。

而异步日志采用生产者-消费者模型,工作线程将所写的日志内容先存入缓冲区,写线程从缓冲区中取出内容,写入日志。并发能力比较高

阻塞队列:Deque(里面放的是字符串,要写的日志信息)、互斥锁、条件变量(生产者消费者)

阻塞体现在生产者消费者模型上(用的是条件变量,没用信号量),当队列里没有日志可写,就阻塞等待,有日志,就唤醒线程进行写日志

数据库连接池用到信号量,线程池和日志阻塞队列用到条件变量,三者都用到互斥锁日志实现分级、分天、分文件(每个日志文件50000行)日志级别:LOG_DEBUG LOG_INFO LOG_WARN LOG_ERROR写日志

日志系统记录的内容及作用?

在开发其他模块的时候主要用来记录模块的信息,通过日志判断模块功能是否正常。

在服务器运行期间主要记录服务器设定的参数、新客户的建立与断开、异常事件的发生(并发数量达到上限、套接字初始化失败等)。便于对服务器运行过程中一些不明情况的分析。

日志系统的实现需要考虑什么?

线程安全性还有效率问题。

首先是线程安全方面,日志系统需要记录多个连接运行的情况,也就是说日志系统被多个线程拥有,这个时候需要考虑线程安全的问题,通过内部关键操作(涉及临界区资源的部分)进行加锁,实现每个线程对日志对象的访问不会产生冲突,避免日志写入的混乱。

另一个方面的话是效率问题,为了实现线程安全人为地加锁,而日志系统又需要进行IO操作,这样会导致线程持锁阻塞其他线程对锁地争用,同时大量地IO读写也会使当前调用日志对象的线程被阻塞在IO操作上,颠倒了事件处理优先级的顺序。效率这一块可以通过采用异步线程加双缓冲区写入的方式优化,调用日志对象的线程只需要完成两次内存拷贝,通过空间换时间的手法【双缓冲区是为了缓解内存读写速度和磁盘读写速度的差异导致部分数据丢失】,将磁盘IO操作交给单独的线程去处理,这样调用日志对象的线程能够保证尽可能地持锁时间短而且不会阻塞在IO操作上。

日志系统的缓冲区满了(内存不足)怎么办?

可以实现缓冲区动态扩容的手法【通过复用已经写入内核的缓冲区空间以及STL底层的扩容机制】,同时在缓冲区大小达到一定程度的时候主动丢弃该部分数据,将缓冲区的大小重新调整,避免因为内存不足而导致进程崩溃。

HTTP

你这个保存状态了吗?如果要保存,你会怎么做?

可以用(cookie和session)

Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了

HTTP连接到来的一整个流程

CGI有使用吗?

没有实现。CGI是通过服务器调用其他可执行程序,通过输出重定向到套接字实现消息的响应。可以用dup函数实现。先关闭标准输出文件描述符STDOUT_FILENO(其值是1) , 然后复制socket文件描述符connfd。 因为dup总是返回系统中最小的可用文件描述符, 所以它的返回值实际上是1, 即之前关闭的标准输出文件描述符的值。 这样一来, 服务器输出到标准输出的内容(这里是“abcd”) 就会直接发送到与客户连接对应的socket上, 因此printf调用的输出将被客户端获得(而不是显示在服务器程序的终端上) 。 这就是CGI服务器的基本工作原理

为什么使用有限状态机?

一个HTTP报文可以拆分为请求行、头部字段、请求体。每个部分之间都通过特殊界限符划分。在我们获得一个数据包(以\r\n结尾的数据包)的时候可以根据状态机的状态变量判断如何处理当前的数据包,并且在执行完相应操作后设置状态变量进行状态转移完成整个报文的解析工作。

POST怎么实现的?

实现了什么状态码?

200、400、404

为什么需要用双缓冲区而不用一个大的缓冲区?

考虑到一个效率问题。双缓冲区由可以动态扩容的缓冲区和生产者消费者模型组成,动态扩容的缓冲区模块本身不是线程安全(每个HTTP对象独占缓冲区,所以默认设计为非线程安全),每次使用都需要外部加锁。每次我们异步线程访问这个缓冲区就需要和当前调用日志对象的线程争用锁,而如果使用双缓冲区,那么可以避免这种行为,这里设计多一层缓冲区供异步线程使用就可以避免这种锁争用的情况。提高效率的同时设计起来也更加清晰明了。

除此之外多一级缓存可以使得异步线程能够处理更多数据,同时动态扩容的缓冲区也不至于过于庞大(见动态扩容的原理),类似于计算机CPU缓存。

HTTP优化方案

个人答案:

  • 内容缓存:将经常用到的内容进行缓存起来,那么客户端就可以直接在内存中获取相应的数据了。
  • 压缩:将文本数据进行压缩,减少带宽
  • SSL加速(SSL Acceleration):***L协议对HTTP协议进行加密,在通道内加密并加速
  • TCP复用:TCP连接复用是将多个客户端的HTTP请求复用到一个服务器端TCP连接上,而HTTP复用则是一个客户端的多个HTTP请求通过一个TCP连接进行处理。前者是负载均衡设备的独特功能;而后者是HTTP 1.1协议所支持的新功能,目前被大多数浏览器所支持。
  • TCP缓冲:通过采用TCP缓冲技术,可以提高服务器端响应时间和处理效率,减少由于通信链路问题给服务器造成的连接负担。

服务器压力测试

压测服务器的方式

Webbench是常用的web 性能压力测试工具。

  • 测试处在相同硬件上,不同服务的性能以及不同硬件上同一个服务的运行状况。
  • 展示服务器的两项内容:每秒钟响应请求数和每秒钟传输数据量。

压测基本原理

Webbench 首先 fork 出多个子进程,每个子进程都循环做 web 访问测试。子进程把访问的结果通过pipe 告诉父进程,父进程做最终的统计结果。

压测的瓶颈会在哪里

  • io同步读
  • 存进内存
  • 不同项目可能会有不同的点

怎么验证压测瓶颈的猜测

查看系统参数

  • 内存
  • cpu
  • 文件描述符
  • 磁盘io
  • 看内核态和用户态占用的cpu比率

I/O复用

select,poll,epoll

对于select和poll来说,所有文件描述符都是在用户态被加入其文件描述符集合的,每次调用都需要将整个集合拷贝到内核态;epoll则将整个文件描述符集合维护在内核态,每次添加文件描述符的时候都需要执行一个系统调用。

select使用线性表描述文件描述符集合,文件描述符有上限;poll使用链表来描述;epoll底层通过红黑树来描述,并且维护一个ready list,将事件表中已经就绪的事件添加到这里,在使用epoll_wait调用时,仅观察这个list中有没有数据即可。

select和poll的最大开销来自内核判断是否有文件描述符就绪这一过程:每次执行select或poll调用时,它们会采用遍历的方式,遍历整个文件描述符集合去判断各个文件描述符是否有活动;epoll则不需要去以这种方式检查,当有活动产生时,会自动触发epoll回调函数通知epoll文件描述符,然后内核将这些就绪的文件描述符放到之前提到的ready list中等待epoll_wait调用后被处理。

select和poll都只能工作在相对低效的LT模式下,而epoll同时支持LT和ET模式。

当监测的fd数量较小,且各个fd都很活跃的情况下,建议使用select和poll;当监听的fd数量较多,且单位时间仅部分fd活跃的情况下,使用epoll会明显提升性能。

ET和LT

LT的缺点:在并发量高的时候,epoll_wait返回的就绪队列比较大,遍历比较耗时。因此LT适用于并发量小的情况ET的优点:并发量大的时候,就绪队列要比LT小得多,效率更高

Linux中模拟Proactor模式

1.为什么需要模拟Proactor模式?

(1)真正实现Proactor需要使用到异步IO接口,但是异步IO接口编程相对比较麻烦,而且不容易调试

(2)在Linux下的异步IO是不完整的,aio系列函数是由POSIX定义的异步操作接口,不是真正的操作系统级别支持的,而是在用户空间模拟出来的异步,并且仅仅支持本地文件的异步IO操作,网络编程中的socket是不支持的,这也使得基于Linux的高性能网络程序都是使用Reactor方案

(3)在Windows中实现了一套完整的支持socket的异步编程接口,这套接口是IOCP,是操作系统级别的异步IO,真正意义上的异步IO,因此在Windows中实现高性能网络程序可以使用效率更高的Proactor方案。

2.同步IO模拟Proactor

使用同步 I/O 方式模拟出 Proactor 模式。原理是:主线程执行数据读写操作,读写完成之后,主线程向工作线程通知这一”完成事件“。那么从工作线程的角度来看,它们就直接获得了数据读写的结果(异步)接下来要做的只是对读写的结果进行逻辑处理。使用同步 I/O 模型(以 epoll_wait为例)模拟出的 Proactor 模式的工作流程如下:

  1. 主线程往 epoll 内核事件表中注册 socket 上的读就绪事件。
  2. 主线程调用 epoll_wait 等待 socket 上有数据可读。
  3. 当 socket 上有数据可读时,epoll_wait 通知主线程。主线程从 socket 循环读取数据,直到没有更 多数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列。
  4. 睡眠在请求队列上的某个工作线程被唤醒,它获得请求对象并处理客户请求,然后往 epoll 内核事 件表中注册 socket 上的写就绪事件。
  5. 主线程调用 epoll_wait 等待 socket 可写。
  6. 当 socket 可写时,epoll_wait 通知主线程。主线程往 socket 上写入服务器处理客户请求的结果

省流:读写全部由主线程完成,从工作线程的角度看,他们的模式就是异步的

EPOLLONESHOT事件(保证线程安全)

即使可以使用 ET 模式,一个socket 上的某个事件还是可能被触发多次。这在并发程序中就会引起一个 问题。比如一个线程在读取完某个 socket 上的数据后开始处理这些数据,而在数据的处理过程中该 socket 上又有新数据可读(EPOLLIN 再次被触发),此时另外一个线程被唤醒来读取这些新的数据。于是就出现了两个线程同时操作一个 socket 的局面。一个socket连接在任一时刻都只被一个线程处理,可以使用 epoll 的 EPOLLONESHOT 事件实现。

对于注册了 EPOLLONESHOT 事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次除非我们使用 epoll_ctl 函数重置该文件描述符上注册的 EPOLLONESHOT 事件。这样,当一个线程在处理某个 socket 时,其他线程是不可能有机会操作该 socket 的。但反过来思考,注册了 EPOLLONESHOT 事件的 socket 一旦被某个线程处理完毕, 该线程就应该立即重置这个 socket 上的 EPOLLONESHOT 事件,以确保这个 socket 下一次可读时,其 EPOLLIN 事件能被触发,进而让其他工作线程有机会继续处理这个 socket。

线程池

线程池是由服务器预先创建的一组子线程,线程池中的线程数量应该和 CPU 数量差不多。线程池中的所有子线程都运行着相同的代码。当有新的任务到来时,主线程将通过某种方式选择线程池中的某一个子线程来为之服务。相比与动态的创建子线程,选择一个已经存在的子线程的代价显然要小得多。至于主线程选择哪个子线程来为新任务服务,则有多种方式:

(1)主线程使用某种算法来主动选择子线程。最简单、最常用的算法是随机算法和 Round Robin(轮流选取)算法,但更优秀、更智能的算法将使任务在各个工作线程中更均匀地分配,从而减轻服务器的整体压力。

(2)主线程和所有子线程通过一个共享的工作队列来同步,子线程都睡眠在该工作队列上。当有新的任务到来时,主线程将任务添加到工作队列中。这将唤醒正在等待任务的子线程,不过只有一个子线程将获得新任务的”接管权“,它可以从工作队列中取出任务并执行之,而其他子线程将继续睡眠在工作队列上。

线程数量的直接限制因素是中央处理器(CPU)的处理器(cores)的数量,如果你的CPU是4-cores的

对于CPU密集型的任务(如视频剪辑等消耗CPU计算资源的任务)来说,那线程池中的线程数量最好也设置为4(或者+1防止其他因素造成的线程阻塞)

对于IO密集型的任务,一般要多于CPU的核数,因为线程间竞争的不是CPU的计算资源而是IO,IO的处理一般较慢,多于cores数的线程将为CPU争取更多的任务,不至在线程处理IO的过程造成CPU空闲导致资源浪费。

线程池的特点:

(1)空间换时间,浪费服务器的硬件资源,换取运行效率。(理论可以容纳的子线程远远大于线程池的大小)

(2)池是一组资源的集合,这组资源在服务器启动之初就被完全创建好并初始化,这称为静态资源。

(3)当服务器进入正式运行阶段,开始处理客户请求的时候,如果它需要相关的资源,可以直接从池中 获取,无需动态分配

(4)当服务器处理完一个客户连接后,可以把相关的资源放回池中,无需执行系统调用释放资源

线程池中的工作线程是一直等待吗?

当请求队列中有任务时,工作线程会被唤醒执行任务;当请求队列为空时,工作线程会阻塞等待新的任务到来。这种设计可以有效处理高并发情况下的任务调度。

你的线程池工作线程处理完一个任务后的状态是什么?

这里要分两种情况考虑

(1) 当处理完任务后如果请求队列为空时,则这个线程重新回到阻塞等待的状态

(2) 当处理完任务后如果请求队列不为空时,那么这个线程将处于与其他线程竞争资源的状态,谁获得锁谁就获得了处理事件的资格

如果同时1000个客户端进行访问请求,线程数不多,怎么能及时响应处理每一个呢?

(服务器如何处理高并发的问题 -->对子线程循环调用来解决高并发问题)

通过子线程的run调用函数进行while循环,让每一个线程池中的线程永远都不会终止,他处理完当前任务就去处理下一个,没有任务就一直阻塞在那里等待。这样就能达到服务器高并发的要求

如果一个客户请求需要占用线程很久的时间,会不会影响接下来的客户请求呢,有什么好的策略呢?

会影响接下来的客户请求,因为线程池内线程的数量是有限的,如果客户请求占用线程时间过久的话会影响到处理请求的效率,当请求处理过慢时会造成后续接受的请求只能在请求队列中等待被处理,从而影响接下来的客户请求

应对策略:我们可以为线程处理请求对象设置处理超时时间, 超过时间先发送信号告知线程处理超时,然后设定一个时间间隔再次检测,若此时这个请求还占用线程则直接将其断开连接。

数据库

登录注册相关

设置一个固定大小的数据库连接池,利用信号量和互斥锁控制放入的连接数量• 登录说一下?数据库登录分为:1.载入数据表 2.提取用户名和密码 3.注册和登录校验 4.页面跳转

1.载入数据表就是把数据库的数据通过通过map容器传到服务器上。

2.当从浏览器上输入用户的用户名和密码后,浏览器会一个post请求报文,服务器通过解析请求报文的消息体,解析出账号密码。

3.根据解析出的账号密码,与map容器中保存账号密码进行对比校验,相符则成功登陆,就将浏览器跳转到对应的界面。注册账号时,同样将输入的账号密码与数据库已经存储的账号名进行对比校验,防止出现相同的账号名。如果不相同就加入数据库。

4.当输入的账号密码与数据库的数据成功匹配,就将浏览器跳转到对应的界面。数据库用单例模式

保存状态了吗?如果要保存,你会怎么做?

(cookie和session)Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了

登录中的用户名和密码你是load到本地,然后使用map匹配的,如果有10亿数据,即使load到本地后hash,也是很耗时的,你要怎么优化?

——这个问题的关键在于大数据量情况下的用户登录验证怎么进行?将所有的用户信息加载到内存中耗时耗利,对于大数据最遍历的方法就是进行hash,利用hash建立多级索引的方式来加快用户验证。具体操作如下:

首先,将10亿的用户信息,利用大致缩小1000倍的hash算法进行hash,这时就获得了100万的hash数据,每一个hash数据代表着一个用户信息块(一级);

而后,再分别对这100万的hash数据再进行hash,例如最终剩下1000个hash数据(二级)。

在这种方式下,服务器只需要保存1000个二级hash数据,当用户请求登录的时候,先对用户信息进行一次hash,找到对应信息块(二级),在读取其对应的一级信息块,最终找到对应的用户数据。

用的mysql啊,redis了解吗?用过吗?

1.mysql和redis的数据库类型mysql是关系型数据库,主要用于存放持久化数据,将数据存储在硬盘中,读取速度较慢。redis是NOSQL,即非关系型数据库,也是缓存数据库,即将数据存储在缓存中,缓存的读取速度快,能够大大的提高运行效率,但是保存时间有限。

2.mysql的运行机制mysql作为持久化存储的关系型数据库,相对薄弱的地方在于每次请求访问数据库时,都存在着I/O操作,如果反复频繁的访问数据库。第一:会在反复链接数据库上花费大量时间,从而导致运行效率过慢;第二:反复的访问数据库也会导致数据库的负载过高,那么此时缓存的概念就衍生了出来。

3.缓存缓存就是数据交换的缓冲区(cache),当浏览器执行请求时,首先会对在缓存中进行查找,如果存在,就获取;否则就访问数据库。缓存的好处就是读取速度快

4.redis数据库redis数据库就是一款缓存数据库,用于存储使用频繁的数据,这样减少访问数据库的次数,提高运行效率。

5.redis和mysql的区别总结(1)类型上从类型上来说,mysql是关系型数据库,redis是缓存数据库(2)作用上mysql用于持久化的存储数据到硬盘,功能强大,但是速度较慢redis用于存储使用较为频繁的数据到缓存中,读取速度快(3)需求上mysql和redis因为需求的不同,一般都是配合使用

全部评论
这是那个tinyWebserver项目吗
点赞 回复 分享
发布于 08-23 20:17 黑龙江
总结的好啊,赞!
点赞 回复 分享
发布于 09-19 15:54 陕西
面试会遇到一个经典问题:“很多人做一样的项目,你怎么脱颖而出”
点赞 回复 分享
发布于 09-19 19:35 四川

相关推荐

19 164 评论
分享
牛客网
牛客企业服务