2023年php面试题之一部分收集分享
php面试题2023年收集分享,话不多说直接看题:
php-fpm 与 swoole 的异同
常驻内存
一个请求通过 nginx 转发到 php 来运行,中间是通过 php-fpm 来沟通连接的,通过一种叫 cgi 的协议来通信。
在 php-fpm 还未出现之前,php 一般都是通过 php-cgi 这个 php 官方组件来与 web server 进行通信的,它的特点是每次运行都需要重新加载一次 php.ini 中的配置,并且每有一个请求都会重新启动一个进程来执行,执行完毕后再销毁掉。
这种方式每次都会重新解析 php.ini、重新载入全部扩展,并重新初始化全部数据结构,这无疑是对 cpu 性能的浪费行为,于是就出现了 fast cgi。
fast cgi 的应用体现便是 php-fpm。通过一个 master 进程管理多个 worker 进程的方式,在主进程启动时便将 php.ini 中的配置等信息载入内存,worker 进程创建时会继承 master 进程的数据,并且 worker 进程处理完一个请求后不会销毁,而是继续等待下一个请求。所以只需要一次加载,php.ini 与扩展和初始化数据这部分的性能便被节省出来了。虽然 php.ini 的配置初始化被节省掉了,但是我们平时使用的 laravel 等 php 开发框架中同样有冗长的 ioc 容器初始化,依赖创建的过程,这个问题 php-fpm 就无能为力了。
那么说完了 php-fpm,这些和 swoole 又有什么关系呢?
相同点就在于,swoole 也是常驻内存的,也是一个 master 管理多个 worker 进程,所以也节省掉了多次载入 php.ini 等配置的消耗。
并且,由于 swoole 本身就是一个 PHP 扩展,它的启动是在 php 脚本内开始的,因此可以看做是 php 开发框架的一部分,那么 php 框架初始化的那一系列重复初始化便同样被节省掉了。这便是 swoole 高性能的原因。
swoole 在宣传上写的是为了解决传统 php-fpm 模式并发慢的问题而诞生的,那么就带来一个问题:
php-fpm 模式为什么慢?
php-fpm 模式是以多进程方式来运行的,一个 master 进程创建并管理多个 work 进程。master 进程只负责接收请求和返回响应,剩下的运行工作交给 work 进程来执行。
也就是说每一个请求都对应一个 work 进程,同一时刻,服务器上有多少 work 进程,这台服务器就可以处理多少的并发。
这么一看是不是觉得 php-fpm 的并发能力特别差?假设不考虑服务器配置问题,默认的 400 个进程数同时就只能支持 400 的并发。
实际情况肯定没有这么差,假设很多的脚本只需要 0.001 秒就处理完成了,如果所有的请求都可以快速处理的话,那么我们可以说 1 秒钟的并发数就等于 400*1000=40 万的并发。
这么一看是不是觉得 php-fpm 的性能也没这么差了?
但是,如果你的业务数据量很大,mysql 的查询效率不高,每次请求都需要花费 1 秒钟的时间才能返回响应的话呢?
那么每秒钟的并发数就从 40W 又下降回 400 了。
而 swoole,就是为了解决这个问题所开发出来的一个 php 扩展,它使得每个 worker 进程不会因为 1 秒钟的 io 阻塞而白白让 cpu 浪费 1 秒钟的性能。
swoole 的运行方式
按照刚刚的那个例子来解释的话,swoole 的处理方式就是在一个 worker 进程开始进行 mysql io 查询的时候就将这个请求任务暂时挂起,立马开始执行下一个请求,然后等到第一个请求中的 mysql io 数据返回之后,再切换回第一个请求中继续执行代码返回响应。这样一来,对于 cpu 来说,它一直在执行代码,没有因为请求中 mysql 的 1 秒 io 耗时处于空闲状态。
那么,既然那 1 秒的 io 耗时没有对 cpu 产生影响,那么对于服务器来说,每一秒钟的并发数和之前一样仍然是 40W,只不过由于每个请求还是有 1 秒的耗时,所以单个请求的响应时间依然是 1 秒钟,但是对于 cpu 来说,它每秒处理的请求数量并没有减少,因为对于 cpu 来说一个请求的 io 耗时是 1 秒,1000 个请求的总耗时依旧是 1 秒。
同步与异步
什么是同步
我们平时编写的 php 代码就是同步代码。
php 解释器一行一行的编译运行我们的代码,碰到数据库查询,或者第三方接口调用,或者系统磁盘读写。这些不归 php 当前进程管辖的部分都是 io 操作,它们可能是磁盘 io,可能是网络 io。
而同步的代码一旦碰到这些 io 操作,它们就会停下来等待,等待 mysql 返回查询结果,等待第三方接口返回响应,等待 linxu 文件系统返回磁盘读取结果。
在等待的过程中,cpu 的性能就被浪费掉了。
什么是异步
异步代码就是说代码在运行到一个需要等待的 io 操作时,不在原地傻等,而是继续向下执行其他代码,等到 io 操作有返回结果通知的时候再回过头来执行处理逻辑。
一个简单的例子就是 js 中的 Ajax,下面这个例子当中,js 把 Ajax 请求发送出去就开始执行下一行代码了,所以是先 alert 2,然后等到 Ajax 响应返回再执行回调函数
对于生活中的例子来说,异步就是一个人同时使用洗衣机洗衣服与使用电饭煲做饭,假设洗衣机洗一次衣服要 40 分钟,电饭煲煮饭也需要 40 分钟,那么这两件事都完成需要多长时间呢?
是的,也是 40 分钟(最多多出一些把衣服和米分别放进机器的可以忽略不计的时间),因为人把衣服放进洗衣机就可以去做其他事了,不会守在洗衣机旁傻等,可以去开电饭煲了。而洗衣机洗好衣服以后,会有滴滴声提示人衣服已经洗好了。
回到一开始那个 swoole 并发数的例子,那些需要 1 秒钟来查询 mysql 数据的请求,它们的那 1 秒 io 操作也没有让 cpu 进行傻等,所以对于 cpu 来说,io 操作已经无法影响它的并发数了,因为它始终在工作,并没有浪费等待时间。
解析一下,如果使用异步的方式,那么会有两个比较关键的点:
发起 io 操作,添加回调函数
等任务完成后执行回调函数
异步编程完全没有浪费 cpu 一点性能,那如果所有的 io 耗时操作都用异步操作会怎么样呢?
了解 node.js 的朋友可能经常听见一个词回调地狱。
前端开发中很少会有人在 Ajax 中嵌套 Ajax,但是如果你想通过异步的方式来提升代码的性能,那么不可避免的,只要你的程序中有多个 io 操作,那它们就会向下面这段代码一样变成层层嵌套,很快这段代码就变得不可维护了,甚至是修改的时候都会让人十分头疼。
而 swoole 的出现,就是为了解决同步代码浪费性能的问题,让同步执行的代码变为异步执行,同时使用协程降低异步回调编程时的心智负担。
cpu上下文切换
现在的电脑,一边写代码,一边查文档,一边听音乐都是很常见的,因为 cpu 的核心数很多可以同时做好几件事。但是你在一开始学习 for 循环的时候一定听老师说过,当年的单核 cpu 写循环一定要小心,因为一旦出现死循环了,那么整台电脑都会卡死只能重启了。
cpu 在执行代码的时候是同步的,所以理论上来讲同一时刻只能做一件事,哪怕不进行死循环,按理说之前的老电脑也没办法做到同时写代码与查文档以及听音乐这些事才对,并且就算是现在的四核八核 cpu,那我也是可以同时开十几个网页,同时播放视频的。
让单核 cpu 同时运行多任务的魔法就是上下文切换了,主要的原理就是 cpu 在同时进行玩游戏与播放音乐时,先运行一会游戏,然后马上切到音乐程序上运行一会,不断地在这些应用之间来回切换运行,因为 cpu 的计算速度是远超人脑反应时间的,所以在人类眼中,这些应用就像是在同时运行一样。
那到底什么是上下文呢?
就是程序运行中所需要的数据,包括存储在内存中的,以及 cpu 多级寄存器中的这些数据。
在线程与进程切换的时候,需要把这些数据保存起来,等到它们恢复运行的时候再把数据读取回进程来运行。
本文主要是介绍 swoole 的,swoole 的重点在于异步与协程,为什么要提到上下文切换呢?
因为不论是多进程、多线程还是协程,它们本质上都需要用到上下文切换来实现的。
多进程模式,是由系统来决定每个进程的运行分片时长。
而多线程由于它们一定有一个父级进程,所以每个线程的运行分片时长则是由进程来决定的。
这也是为什么多线程语言的教程里都会提到不是线程开的越多越好的原因,多线程会有线程争抢和系统调度的开销。
同时,由于 cpu 同一段时间内运算速度的总量是固定的,所以线程只需要尽量把 cpu 空闲的算力占满就好,开过多的线程反而会因为增加系统线程调度开销造成业务部分线程性能的下降。
那么协程与多线程多进程又有什么不同呢?
通过实例来对比:
php-cgi 便是多进程的一种体现,每个请求对应一个 cgi 进程,带来的缺点是进程频繁创建销毁的开销以及每次都需要加载 php.ini 配置的性能浪费。
php-fpm 多进程模式的改良,通过 master/worker 的模式,让多进程少了重新加载配置与频繁创建销毁进程的开销。
假设 php 有多线程,省略多次 php.ini 的加载,省略多次开发框架初始化,相应的带来线程调度开销,多线程抢占式模型需要注意数据访问的线程安全,需要给数据加锁,并带来锁争抢与死锁问题。
协程,省略多次 php.ini 加载,省略多次开发框架初始化,由于协程是用户态的线程,所以由代码来控制什么时候进行切换,没有线程调度开销。并且 swoole 以同步的方式编写异步代码,协程的切换由底层调度器自行切换,开发者无需关注线程锁与死锁问题。
swoole 的协程切换是基于 io 来调度的,也就是说它只会在遇到 io 操作的时候才会进行切换,通过节省 io 等待时间来提高服务器性能,因此 swoole 的协程是无法进行并发计算的。不过遇到需要并行计算的场景,swoole 也提供了多进程的运行方式,如果需要多进程协同操作同一个数据,就需要加进程锁了。
事件循环–异步是如何实现的
现在我们已经知道多进程,多线程,协程都是异步的编程方式了,那么异步是怎么实现的呢?
这是一个大问题,先从最基础的看起,基础异步编程就是异步回调模式,也就是在执行任务的同时传入一个回调函数,等到任务执行完毕,回调函数自然而然的就开始运行了。
类似 js 的 Ajax 一样,发起一个 Ajax 请求的时候便是发起了异步任务,同时在 $.ajax 方法的第三个参数传入一个匿名函数,等到后端返回响应以后再继续执行回调函数中的代码。
那么就出现了一个问题,是谁来通知当前进程异步任务已经完成了的呢?
做过 im 通信朋友都知道,两个客户端的对话除了发送消息,最难实现的还是接收消息,因为需要服务端主动做推送。如果不使用 WebSocket 的话,要实现服务端推送就只能使用长连接 + 轮询的方式了。接收消息的那一方客户端需要每隔一段时间就请求一次服务器,看看有没有消息发送给自己。对于异步回调来说,它的实现方式也是有异曲同工之处。
处理异步回调的部分叫做事件循环,可以理解为每个进程有一个死循环,不断的查看当前有没有待执行的任务、已经执行完需要通知的回调。当我们进行异步任务调用的时候,就是向这个循环中投递了一个任务与对应的回调。当任务完成的时候,循环便把任务从监听数组中去除,并执行回调。
下面来看一个简单的事件循环的例子。
可以看到,EventLoop 类中维护了一个 event 数组,用来存储需所有需要监听的事件。在调用 addEventHandler 方法时,则需要将事件的类型、参数,以及回调函数一同传入。
当调用 run 方法时,这个循环就被开启了,可以看到 run 方法中是一个 while 死循环,用来不断的检测是否有已完成的任务。而 while 循环内层的 foreach 则是为了查看所有事件中是否有已完成的单个任务。
而 processTimers 与 processIOEvents 方法则代表了 swoole 中典型的两种事件,io 事件与定时器。由于 linux 系统中万物皆文件的特性,很多看似是网络 io 的功能,其实都要用到文件系统来实现,所以 processIOEvents 方法需要传入 fp 文件指针以及 read 与 write 两种读写事件。例如假设我们投递的是读取事件,那么就调用 fread 函数来读取文件,并把读取到的数据传递给回调函数来执行。这就是一个事件循环的回调过程了。
在理解了时间循环以后,那么事件循环与 swoole 与多进程、多线程、协程之间有什么关系呢?
没错,无论是多进程、多线程还是协程,它们底层都依赖事件循环来实现异步,例如进程与线程之间切换的时候如何通知对应的进程与线程?依赖系统级事件循环。例如协程之间多个协程的切换要如何通知对应的协程?也是依赖事件循环。不过 swoole 为了降低上下文切换带来的消耗,没有依赖系统级事件循环而是自己实现了一套,swoole 的协程上下文切换都是内存读取,避免了 cpu 寄存器、堆栈以及系统内核态与用户态之间的切换,因此切换开销极小。
总结
说了这么多概念,那么 swoole 到底是什么呢?它融合了 php-fpm 的结构模式,优化了单进程的性能浪费,弱化了多线程的调度开销,屏蔽了异步回调的复杂逻辑,是一个常驻内存的高性能 web 扩展。
做一个不严谨的类比,你也可以认为 swoole 是一个语言层面实现的 php-fpm,毕竟 swoole 也支持完全的多进程模式,这种模式下与 php-fpm 的运行方式大同小异。不过由于在语言层面便常驻内存了,所以带来的福利便是在启动 php 脚本的开发框架时,只需要一次载入便保存在内存中了,避免了 php-fpm 每个请求都重新初始化框架的性能浪费。那么同样的由于服务常驻内存了,所以哪怕是在开发过程中,代码相关的改动都需要重启一下 swoole 服务。
而 swoole 的架构,对应下面这张图,便是 master、manager、worker 的结构,在 swoole 服务启动时,master 进程便 fork 出 manager 进程来对 worker 进程进行创建和管理,master 进程自己则通过 reactor 线程来接受与分发请求,master 进程接收到的请求通过 reactor 线程直接发送到 worker 进程中,而 worker 进程负责对请求进行具体的处理。如果开启了协程模式,并且代码也是以协程的方式运行,则一个 worker 可能会一段时间内 (例如 1s) 处理多个请求。因为每个请求遇到 io 等待时,worker 便切换协程直接开始处理下一个请求了,直到 io 任务返回结果,worker 再切换回上一个请求将响应返回给 master 进程。
swoole 对性能的提升带来的代价是编程思维的转变,因为常驻内存了,所以编写业务代码时,对内存变量的使用就需要更加小心,避免造成内存泄露。因为基于异步编程,所以要理解异步的思想,避免写出同步阻塞的代码。
Swoole 是一个 PHP 扩展,它使用 C 语言编写,它提供了一个异步多线程服务器,可以用来构建高性能的网络应用程序,如 Web 服务器,网络爬虫,消息队列,RPC 服务器等。
Swoole 的原理是基于事件驱动和异步编程的,它使用 epoll 或 kqueue 等 Linux 内核特性来实现高性能的网络通信,并且支持异步 MySQL,异步 Redis,数据库连接池,AsyncTask 等功能。
Swoole 的核心是一个事件循环,它会不断地检查网络事件,如果有新的连接请求,它会触发一个回调函数,然后处理连接,如果有数据可读,它会触发另一个回调函数,然后处理数据,如果有数据可写,它会触发另一个回调函数,然后发送数据。
Swoole 还支持定时器,可以定时执行任务,支持异步 MySQL,异步 Redis,数据库连接池,AsyncTask 等功能,可以极大地提高程序的性能。
Swoole 的核心是一个事件循环,它会不断地检查网络事件,如果有新的连接请求,它会触发一个回调函数,然后处理连接,如果有数据可读,它会触发另一个回调函数,然后处理数据,如果有数据可写,它会触发另一个回调函数,然后发送数据
1. 什么是进程、线程、协程?区别?优缺点?
定义:
(1) 进程是系统进行资源分配和调度的独立单位
(2) 线程是进程的实体,是 CPU 调度和分配的基本单位
(3) 协程,又称微线程,自带 CUP 上下文,是比线程更小的执行单元,占用资源小,效率高
区别:
(1) 一个程序至少有一个进程,一个进程至少有一个线程
(2) 线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高
(3) 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大的提高了程序的运行效率
(4) 线程不能够独立执行,必须依存在进程总
优缺点:
进程:
优点:顺序程序的特点:既有封闭性和可再现性
程序的并发执行和资源共享,多道程序设计出现后,实现了程序的并发执行和资源共享,提高了系统的效率和系统的资源利用率
缺点:操作系统调度切换多个线程比切换调度进程在速度上快的多,而且进程间内存无法共享,通讯也比较麻烦。
线程之间由于共享进程内存空间,所以交换数据非常方便,在创建或撤销进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤销线程时的开销
线程:
优点:
它是一种非常” 节俭” 的多任务操作方式。在 Linux 系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种” 昂贵” 的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。当然,在具体的系统上,这个数据可能会有较大的区别;线程间方便的通信机制,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便;使多 CPU 系统更加有效。操作系统会保证当线程数不大于 CPU 数目时,不同的线程运行于不同的 CPU 上;
缺点:调用时,要保存线程状态,频繁调度,需要占用大量的机时
程序设计上容易出错(线程同步问题)
1. 多进程、多线程优缺点
2. 多线程的优点:
无需跨进程边界
程序逻辑和控制方式简单
所有线程可以直接共享内存和变量等
线程方式消耗的总资源比进程方式好
缺点:
每个线程与主程序共用地址空间,受限于 2GB 地址空间
线程之间的同步和加锁控制比较麻烦
一个线程的崩溃可能影响到整个程序的稳定性
到达一定的线程数程度后,即使再增加 cpu 也无法提高性能
线程能够提高的总性能有限,并且线程多了之后,线程本身的调度也是一麻烦事情,需要消耗较多的 CPU
3. 针对于爬虫 应 选择多线程还是多进程?
多进程:密集 CPU 任务,需要充分使用多核 CPU 资源(服务器,大量的并行计算的时候)用多进程
缺点:多个进程之间通信成本高,切换开销大
多线程:密集 I/O 任务(网络 I/O 磁盘 I/O 数据库 I/O)使用多线程合适
缺点:同一个时间切片只能运行一个线程,不能做到高并行,但是可以做到高并发
协程:又称微线程,在单线程上执行多个任务,用函数切换,开销极小,不通过操作系统调度,没有线程。进程的切换开销
多线程请求返回是无序的,哪个线程有数据返回就处理哪个线程,而协程返回的数据是有序的
缺陷:单线程执行,处理密集 CPU 和本地磁盘 IO 的时候,性能较低。处理网络 I/O 性能还是比较高.
总体来看 ,多线程是最佳人选
Swoole/Hyperf 全局变量如何实现
在 Swoole 的持久化应用下,一个 Worker 内的全局变量是 Worker 内共享的,而从协程的介绍我们可以知道同一个 Worker 内还会存在多个协程并存在协程切换,也就意味着一个 Worker 会在一个时间周期内同时处理多个协程(或直接理解为请求)的代码,也就意味着如果使用了全局变量来储存状态可能会被多个协程所使用,也就是说不同的请求之间可能会混淆数据,这里的全局变量指的是 $_GET/$_POST/$_REQUEST/$_SESSION/$_COOKIE/$_SERVER 等 $_开头的变量、global 变量,以及 static 静态属性。
那么当我们需要使用到这些特性时应该怎么办?
对于全局变量,均是跟随着一个 请求 (Request) 而产生的,而 Hyperf 的 请求 (Request)/ 响应 (Response) 是由 hyperf/http-message 通过实现 PSR-7 处理的,故所有的全局变量均可以在 请求 (Request) 对象中得到相关的值;
对于 global 变量和 static 变量,在 PHP-FPM 模式下,本质都是存活于一个请求生命周期内的,而在 Hyperf 内因为是 CLI 应用,会存在 全局周期 和 请求周期 (协程周期) 两种长生命周期。
全局周期,我们只需要创建一个静态变量供全局调用即可,静态变量意味着在服务启动后,任意协程和代码逻辑均共享此静态变量内的数据,也就意味着存放的数据不能是特别服务于某一个请求或某一个协程;
协程周期,由于 Hyperf 会为每个请求自动创建一个协程来处理,那么一个协程周期在此也可以理解为一个请求周期,在协程内,所有的状态数据均应存放于 Hyperf\Context\Context 类中,通过该类的 get、set 来读取和存储任意结构的数据,这个 Context (协程上下文) 类在执行任意协程时读取或存储的数据都是仅限对应的协程的,同时在协程结束时也会自动销毁相关的上下文数据。
携程上下文
由于同一个进程内协程间是内存共享的,但协程的执行 / 切换是非顺序的,也就意味着我们很难掌控当前的协程是哪一个(事实上可以,但通常没人这么干),所以我们需要在发生协程切换时能够同时切换对应的上下文。
在 Hyperf 里实现协程的上下文管理将非常简单,基于 Context 类的 set、get、has、override 静态方法即可完成上下文数据的管理,通过这些方法设置和获取的值,都仅限于当前的协程,在协程结束时,对应的上下文也会自动跟随释放掉,无需手动管理,无需担忧内存泄漏的风险。
事务与 ACID
何为事务呢?书上给予的概念多而难于理解,笔者对事务的理解:一系列操作组成,要么全部成功,要么全部失败。它具备 ACID 四大特性,在并发下,可能存在脏读、幻读、不可重复读的并发问题,于是又引出了四大隔离级别。
01 事务 ACID 特性
MYSQL 作为一个关系型数据库,以最常见的 InnoDB 引擎来说,是如何保证 ACID 的。
(Atomicity) 原子性:一些列操作要么全部成功,要么全部失败
(Isolation) 隔离性:事务的结果只有提交了其他事务才可见
(Consistency) 一致性:数据库总时从一个一致状态变到另一个一致状态 (事务修改前后的数据总体保证一致 转账)
(Durability) 持久性:事务提交后,对数据修改永久的
02 原子性
在聊原子性之前,我得先给大家普及一个东西 ——undo log,这是啥玩意儿呢?如果想要详细了解或则想知道它具体内部咋实现的可以仔细去看书,这里我就简单分享我的理解,知道这些,面试基本够用啦。
undo log,它是一种回滚日志,既可以用来实现隔离性 MVCC,也可以保证原子性。MVCC 待会谈论。实现原子性的关键,是事务回滚时能够撤销所有已经成功执行的 sql 语句。
当事务对数据库进行修改时,InnoDB 会生成对应的 undo log,undo log 会保存事务开始前老版本的数据,当事务发生异常,便会 rollback 回滚到老版本状态。当发生回滚时,InnoDB 会根据 undo log 的内容做相反逻辑操作。
insert 语句,回滚时会执行 delete;
delete 语句,回滚时会执行 insert;
update 语句,回滚时便执行相反的 update,把数据改回来。
总之,MYSQL 的原子性便是由 undo log 来保证,undo log 的作用我做了一下归纳总结:
作用:
undolog 记录事务开始前老版本数据,用于实现回滚,保证原子性,实现 MVCC,会将数据修改前的旧版本保存在 undolog,然后行记录有个隐藏字段回滚指针指向老版本。
03 持久性
在聊持久性之前,我们得先知道 redo log。老规矩,想深入学习理解看书噢,这里只做笔者面试回答分享。
我们以一个生活小案例来理解一下:
redo log,是一种物理日志。它类似于一个卸货的小推车,我们卸货若是每下一件物品就拿着去入库,那岂不是特浪费时间 (效率低、还要找到合适存库位置)。此时,若有一个小推车,我们将货物首先存放在小推车,当推车满了再往库里存,岂不大大增加了效率。
MYSQL 中也用了类似思想,我们再更新数据库时,先将更新操作记录在 redo log 日志,等 redo log 满了或则 MYSQL 空闲了再刷盘。
其实就是 MySQL 里经常说到的 WAL 技术,WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘,也就是先装小推车,等不忙的时候再装库。
总之,MYSQL 的持久性便是由 redo log 来保证,redo log 的作用我做了一下归纳总结:
redo log
物理日志
作用:会记录事务开启后对数据做的修改,crash-safe
特性:空间一定,写完后会循环写,有两个指针 write pos 指向当前记录位置,checkpoint 指向将擦除的位置,redolog 相当于是个取货小车,货物太多时来不及一件一件入库太慢了这样,就先将货物放入小车,等到货物不多或则小车满了或则店里空闲时再将小车货物送到库房。用于 crash-safe,数据库异常断电等情况可用 redo log 恢复。
以下只作了解:
写入流程:先写 redo log buffer,然后 wite 到文件系统的 page cache,此时并没有持久化,然后 fsync 持久化到磁盘
写入策略:
根据 innodb_flush_log_at_trx_commit 参数控制 (我的记忆:innodb 以事务的什么提交方式刷新日志)
0——> 事务提交时只把 redo log 留在 redo log buffer
1——> 将 redo log 直接持久化到磁盘 (所以有个双 “1” 配置,后面会讲)
2——> 只是把 redo log 写到 page cache
04 隔离性
说到隔离性,我们都知道 MYSQL 有四种隔离级别,用来解决存在的并发问题。脏读、幻读、不可重复读。
那么不同隔离级别,隔离性是怎样实现的呢?具体实现原理是怎样的呢?接下来我们就谈谈,看不懂没关系,老规矩,结尾会进行总结滴!
一句话:锁 + MVCC。
锁
1、表锁
lock table table_name read/write
myisam 执行 select 自动加读锁,执行 update/delete/insert 自动加写锁
表加了读锁,不会阻塞其他线程的读操作,阻塞写操作
表加了写锁,读写操作都阻塞
2、行锁
锁的类型
间隙锁 - gap lock:锁定区间范围,防止幻读,左开右开,只在可重复读隔离级别下生效 —|— 为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生
记录锁 - record Lock:锁定行记录,索的索引,索引失效,为表锁
临键锁 - next-key Lock:record lock+gap lock 左开右闭 (解决幻读)
锁的模式
select …. for update
持有写锁,别的不可加读锁,也不可加写锁
select …. lock in share mode
持有读锁,别的可以再加读锁,不可加写锁
共享锁 - 读锁 - S 锁
排他锁 - 写锁 - X 锁
意向锁:读意向锁 + 写意向锁
自增锁
需要的时候加上,并不是马上释放,等事务提交才释放,两阶段锁协议
3、全局锁 —— 全库逻辑备份
4、死锁
两个或多个事务在同一资源上相互占用,并请求加锁时,造成相互等待,无限阻塞
innodb 回滚拥有最少排他行级锁的事务
设置锁等待超时时间
乐观锁与悲观锁
悲观锁用数据库自带锁机制 —— 写多
乐观锁用 version 版本机制或 CAS 算法 —— 读多写少,很少发生冲突情况
SQL如何优化
就是开始我跟他说这个优化是分为几种方式,几种步骤,
第一个是延伸语句,就是你会那个 to sql,或者 var_dump 打印出来这个搜口,获取到这个 SQL 之后,或者说在那个 es 里边获取到这个对应的 solog,找到了这个 SQL 以后。然后对搜索进行一次 explain,然后看它的索引,如果没有的话就去加,但是,你这个索引也要看那个表中的索引的数量和那个表的数据量大小,如果表表的数据量超大,就是可能会导致一个就是你的索引无法命中,这个时候你去主动命中还是不主动命中,这就是由你来选择的,或者是说你退一步加多少,加你去没有办法改了,你就去加一个数据字典,但是这个数据字典还是要去重新跑一遍数据才能加的。
然后另外的一种层面,就是在你已出来之后,你去看你那个表儿,将那个表儿的对应那些字段什么的,这些都给它锁住,最好让它命中于 ID,然后另外一个点就是对表儿进行优化,那表儿优化是什么?表儿优化就是首先第一个字段的类型,
第二个就是它那个,你的这个纳米值的锁死,就是锁的长短,还有你的那个,这个比如说无负号,这从让它从正处开始跑,这种是一个算法儿问题。
然后第三步就是表,就是正儿八经那个表,你说这个表的数据量如果说过于大,然后导致这个 circle 慢,那怎么办?那就是还是那么几种方法,第一种就是做分表,我说别的表分不知道,但是用那个末日引擎去做分表,我这个这个我是了解的做过,然后还有另外一种方式叫逻辑分表,逻辑分表就是通过代码来实现,通过脚本来实现,然后这个根据实际业务,如果是统计类的,你怎么实现都无所谓,然后如果说是是一直在应用于这个,那就正常的末日区分表就好了,然后还有就是对这个对这个。
还有一个就是代码问题,代码问题就很简单,就是看你的折扣,他的你的代码的拼拼装,这个折扣是怎么拼装的,他应用哪几层,或者说你用 ORM,你可不可以不用 orm,或者甚至于说在常规这种,就是屏蔽这个叉 S 这种情况的下,你是不是可以让他使用一个原生直接执行,这样他速度是最快。
搜索优化还有啥吗?
没了,我也不知道还有啥了,分区区区什么区分,那个什么区分,那个什么东西区分那个区分那个那个那个那个分区我没做过,我也不知道对,如果说你还是嫌慢,那加 es,加 es 不就简单了吗,不就快了吗?
因为 es。
读写分离、高可用
binlog 是什么
1、MySQL 的二进制日志 binlog 可以说是 MySQL 最重要的日志,它记录了所有的 DDL 和 DML 语句(除了数据查询语句 select), 以事件形式记录,还包含语句所执行的消耗的时间,MySQL 的二进制日志是事务安全型的。
a、DDL
—-Data Definition Language 数据库定义语言
主要的命令有 create、alter、drop 等,ddl 主要是用在定义或改变表 (table) 的结构,数据类型,表之间的连接和约束等初始工作上,他们大多在建表时候使用。
b、DML
—-Data Manipulation Language 数据操纵语言
主要命令是 slect,update,insert,delete, 就像它的名字一样,这 4 条命令是用来对数据库里的数据进行操作的语言
2、mysqlbinlog 常见的选项有一下几个:
从二进制日志中读取指定等于时间戳或者晚于本地计算机的时间
从二进制日志中读取指定小于时间戳或者等于本地计算机的时间 取值和上述一样
从二进制日志中读取指定 position 事件位置作为开始。
从二进制日志中读取指定 position 事件位置作为事件截至
3、一般来说开启 binlog 日志大概会有 1% 的性能损耗。
4、binlog 日志有两个最重要的使用场景。
a、mysql 主从复制:mysql replication 在 master 端开启 binlog,master 把它的二进制日志传递给 slaves 来达到 master-slave 数据一致的目的。
b、数据恢复:通过 mysqlbinlog 工具来恢复数据。
binlog 日志包括两类文件:
1)、二进制日志索引文件 (文件名后缀为.index) 用于记录所有的二进制文件。
2)、二进制日志文件 (文件名后缀为.00000*) 记录数据库所有的 DDL 和 DML (除了数据查询语句 select) 语句事件
RabbitMQ
使用的设计模式主要有以下几种:
1. 生产者 - 消费者模式:生产者将消息发送到 RabbitMQ,消费者从 RabbitMQ 接收消息。
2. 订阅 - 发布模式:发布者将消息发布到 RabbitMQ,订阅者从 RabbitMQ 订阅消息。
3. 路由模式:发布者将消息发布到 RabbitMQ,消费者从 RabbitMQ 接收消息,并使用路由键进行过滤。
4. 通配符模式:发布者将消息发布到 RabbitMQ,消费者从 RabbitMQ 接收消息,并使用通配符进行过滤。
5. 消息确认模式:消费者从 RabbitMQ 接收消息,并确认消息的接收。
6. 消息持久化模式:RabbitMQ 将消息持久化到磁盘,以便在服务器重启后恢复消息
RabbitMQ 使用的设计模式主要有
生产者 - 消费者模式、发布 - 订阅模式、路由模式和通配符模式。
生产者 - 消费者模式:生产者将消息发送到消息队列,消费者从消息队列中接收消息,这种模式可以实现异步处理,提高系统的吞吐量。RabbitMQ 中使用这种模式可以实现消息的异步处理,提高系统的吞吐量。
发布 - 订阅模式:发布者将消息发布到消息队列,订阅者从消息队列中订阅消息,这种模式可以实现一对多的消息传递,提高系统的可伸缩性。RabbitMQ 中使用这种模式可以实现一对多的消息传递,提高系统的可伸缩性。
路由模式:发布者将消息发布到消息队列,消费者从消息队列中接收消息,这种模式可以实现消息的路由,提高系统的可伸缩性。RabbitMQ 中使用这种模式可以实现消息的路由,提高系统的可伸缩性。
通配符模式:发布者将消息发布到消息队列,消费者从消息队列中接收消息,这种模式可以实现消息的灵活路由,提高系统的可伸缩性。
RabbitMQ 中使用这种模式可以实现消息的灵活路由,提高系统的可伸缩性。
相关文章