2023PHP面试总结

# todo

- rpc和restful-api的区别?

  -  1、从本质区别上看,RPC是基于TCP实现的,RestFul是基于HTTP来实现的。

  -  2、从传输速度上来看,因为HTTP封装的数据量更多所以数据传输量更大,所以RPC的传输速度是比RestFul更快的。 

  -  3、因为HTTP协议是各个框架都普遍支持的。在toC情况下,因为不知道情况来源的框架、数据形势是什么样的,所以在网关可以使用RestFul利用http来接受。而在微服务内部的各模块之间因为各协议方案是公司内部自己定的,所以知道各种数据方式,可以使用TCP传输以使各模块之间的数据传输更快。所以可以网关和外界的数据传输使用RestFul,微服务内部的各模块之间使用RPC。

  -  4、RestFul的API的设计上是面向资源的,对于同一资源的获取、传输、修改可以使用GET、POST、PUT来对同一个URL进行区别,而RPC通常把动词直接体现在URL上

- 接口安全的方式?

  - 参数签名

  - 加密方式

# 网络 

## 基础

- ### 键入网址到页面显示,期间发生了什么?

- 解析URL

- 生成HTTP请求

- IP定位

- TCP三次握手

- 服务端接收请求

- (一般指nginx),nginx识别请求是php请求,根据配置文件的路径,将请求转发给fast-cgi进程管理器,然后php-fpm的worker接收到请求,

解析请求,请求初始化,执行脚本,执行后将数据返回给nginx,自己继续等待,nginx再将数据传回客户端

- 服务端返回HTTP响应

- 浏览器接收HTTP响应报文,渲染页面

- TCP四次挥手结束

- ### OSI 参考模型(7层)?

- 应用层,负责给应用程序提供统一的接口;

- 表示层,负责把数据转换成兼容另一个系统能识别的格式;

- 会话层,负责建立、管理和终止表示层实体之间的通信会话;

- 传输层,负责端到端的数据传输;

- 网络层,负责数据的路由、转发、分片;

- 数据链路层,负责数据的封帧和差错检测,以及 MAC 寻址;

- 物理层,负责在物理网络中传输数据帧;

- ### TCP/IP分层模型(4层)?

- 应用层,负责向用户提供一组应用程序,比如 HTTP、DNS、FTP 等;

- 传输层,负责端到端的通信,比如 TCP、UDP 等;

- 网络层,负责网络包的封装、分片、路由、转发,比如 IP、ICMP 等;

- 网络接口层,负责网络包在物理网络中的传输,比如网络包的封帧、 MAC 寻址、差错检测,以及通过网卡传输网络帧等;

### 重点关注

- 应用层

  - http和https

- 传输层

  - TCP和UDP

## HTTP

- HTTP 与 HTTPS 有哪些区别?

- HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。

- HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 **SSL/TLS** 的握手过程,才可进入加密报文传输。

- 两者的默认端口不一样,HTTP 默认端口号是 80,HTTPS 默认端口号是 443。

- HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。

- WebSocket协议,HTTP协议

## TCP

- TCP

- UDP

- TCP和UDP的区别?

  *1. 连接*

  - TCP 是面向连接的传输层协议,传输数据前先要建立连接。

  - UDP 是不需要连接,即刻传输数据。

  *2. 服务对象*

  - TCP 是一对一的两点服务,即一条连接只有两个端点。

  - UDP 支持一对一、一对多、多对多的交互通信

  *3. 可靠性*

  - TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。

  - UDP 是尽最大努力交付,不保证可靠交付数据。

    - 但是我们可以基于 UDP 传输协议实现一个可靠的传输协议,比如 QUIC 协议,具体可以参见这篇文章:[如何基于 UDP 协议实现可靠传输?(opens new window)](https://xiaolincoding.com/network/3_tcp/quic.html)

  *4. 拥塞控制、流量控制*

  - TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。

  - UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。

  *5. 首部开销*

  - TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 `20` 个字节,如果使用了「选项」字段则会变长的。

  - UDP 首部只有 8 个字节,并且是固定不变的,开销较小。

  *6. 传输方式*

  - TCP 是流式传输,没有边界,但保证顺序和可靠。

  - UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。

  *7. 分片不同*

  - TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。

  - UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层。

- 简述TCP三次握手?

- 服务器进程先创建传输控制块TCB,并处于鉴定状态,等待客户端的链接请求

- 客户端创建传输控制块TCB,并想服务器发送链接请求报文段;

- 服务器收到链接请求报文后,如同意建立连接,则发送确认报文段;

- 客户端进程收到服务端的确认报文段后,立即回复确认报文段,并进入已建立连接状态

- 服务器收到确认报文段之后,也进入已建立连接状态;

- 为什么TCP 使用三次握手建立连接?

  

  - 三次握手才可以阻止重复历史连接的初始化(主要原因)

  - 三次握手才可以同步双方的初始序列号

  - 三次握手才可以避免资源浪费

  

- 简述TCP四次挥手

  - 客户端主动调佣关闭连接的函数,于是就会发送FIN报文,这个FIN报文代表客户端不会再发送数据了,进入FIN_WAIT_1状态;

  - 服务端收到FIN报文,然后就马上回复一个ACK确认报文,此时服务端进入CLOSE_WAAIT状态。

  - 服务端发一个FIN包,这个FIN报文代表服务端不会再发送数据,之后处于LAST_ACK状态;

  - 客户端接收到服务端的FIN包,并发送ACK确认包给服务端,此时客户端进入TIME_WAIT状态;

  - 服务端收到ACK确认包后,就进入最后的CLOSE状态;

  - 客户端经过2MSL时间之后,也进入CLOSE状态

- 为什么TCP进行四次挥手断开连接?

  -  服务器收到客户端的 FIN 报文时,内核会马上回一个 ACK 应答报文,**但是服务端应用程序可能还有数据要发送,所以并不能马上发送 FIN 报文,而是将发送 FIN 报文的控制权交给服务端应用程序**: 

    - 如果服务端应用程序有数据要发送的话,就发完数据后,才调用关闭连接的函数;

    - 如果服务端应用程序没有数据要发送的话,可以直接调用关闭连接的函数,

## IP

# 系统

## 进程管理

- 进程,线程,协程

- 线程是进程当中的一条执行流程。可以并发执行。

- 线程,是操作系统能够进行运算调度的最小单位。

- 并行和并发

- 并行:多个程序,交替执行

- 并发:多个程序,同时执行

- 进程间通讯方式

- 管道

- 消息队列

- 共享内存

- 信号量

- 信号

- socket

- 悲观锁,乐观锁

## 网络系统

- I/O多路复用

> https://xiaolincoding.com/os/8_network_system/selete_poll_epoll.html#%E6%9C%80%E5%9F%BA%E6%9C%AC%E7%9A%84-socket-%E6%A8%A1%E5%9E%8B

- 多进程模型

- 上下文切换“包袱”很重

- 多线程模型

- 轻量级

- 因为可以共享部分资源

- 本质:I/O 的多路复用,可以只在一个进程里处理多个文件的 I/O

- select/poll

- select 默认最大值为 1024,只能监听 0~1023 的文件描述符

- select使用bitmap

- poll用动态数组,以链表形式,突破select文件描述符个数限制,还是会收到系统文件描述符限制

- 两者没有太大的本质区别,遍历文件描述符

- select 和 poll 的缺陷在于,当客户端越多,也就是 Socket 集合越大,Socket 集合的遍历和拷贝会带来很大的开销,因此也很难应对 C10K。

- epoll

- epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述符

- epoll 使用事件驱动的机制

- 一致性哈希?

- 一般负载均衡的问题

- 哈希算法有什么问题?

- 有一个很致命的问题,如果节点数量发生了变化,也就是在对系统做扩容或者缩容时,必须迁移改变了映射关系的数据,否则会出现查询不到数据的问题。

- 一致性哈希算法就很好地解决了分布式系统在扩容或者缩容时,发生过多的数据迁移的问题。

- 一致哈希算法也用了取模运算,但与哈希算法不同的是,哈希算法是对节点的数量进行取模运算,而一致哈希算法是对 2^32 进行取模运算,是一个固定的值。

- 哈希环

- 一致性哈希要进行两步哈希:

- 对存储节点进行哈希计算,也就是对存储节点做哈希映射,比如根据节点的 IP 地址进行哈希;

- 当对数据进行存储或访问时,对数据进行哈希映射;

- 一致性哈希算法虽然减少了数据迁移量,但是存在节点分布不均匀的问题。

- 通过虚拟节点提高均衡度?

- 具体做法是,不再将真实节点映射到哈希环上,而是将虚拟节点映射到哈希环上,并将虚拟节点映射到实际节点,所以这里有「两层」映射关系。

- 带虚拟节点的一致性哈希方法不仅适合硬件配置不同的节点的场景,而且适合节点规模会发生变化的场景。

# PHP

- CGI(common gateway interface)、FastCGI、PHP-CGI 和 PHP-FPM 的区别是什么?

- cgi 通用网关接口,是一种协议,单进程模式

- 每次请求都是启动一个cgi进程,加载php.ini,然后加载相关php配置中的扩展,最后动态解析php程序,处理完成后就关闭了。

- fastcgi 是一种常驻进程的cgi模式程序,最后不用关闭,而是静默等待;

- 由fastcgi进程管理器管理,不用每次都去fork,所以大大降低了web服务器的压力,提高了处理效率

- php-fpm:是php-fastcgi的一个进程管理器,是一种多进程管理模型

- 有一个master和多个worker注册,master不处理请求,而是fork出多个worker子进程去处理。

- 一个woker进程只能处理一个请求,处理完一个请求才能处理下一个请求。

- worker的生命周期:等待请求,解析请求,请求初始化,执行脚本,关闭请求,继续等待。

- fastcgi只是通信应用层协议,php-fpm就是实现了fastcgi协议,并嵌入了一个php解释器。

- php-fpm的运行模型?(多进程同步阻塞模式)

- php-fpm是一种master(主)/worker(子)多进程架构模式。

- 当PHP-FPM启动时,会读取配置文件,然后创建一个Master进程和若干个Worker进程(具体是几个Worker进程是由php-fpm.conf中配置的个数决定)。Worker进程是由Master进程fork出来的。

- master进程主要负责CGI及PHP环境初始化、事件监听、Worker进程状态等等,worker进程负责处理php请求。

- master进程负责创建和管理woker进程,同时负责监听listen连接,master进程是多路复用的;woker进程负责accept请求连接,同时处理请求,一个woker进程可以处理多个请求(复用,不需要每次都创建销毁woker进程,而是达到处理一定请求数后销毁重新fork创建worker进程),但一个woker进程一次只能处理一个请求。

- 修改php.ini之后,php-fpm如何完成平滑重启?

- 已经存在的worker没办法,处理完手上的活就结束;

- 新的woker用新的配置

- php-fpm和nginx的通信机制?

- tcp:ip+端口,可以跨服务

- unix domian socket,限制在同一台服务器

- php-fpm有几种工作模式?

- 静态:性能优先, 适合大内存机器

- 动态:均衡优先,适合小内存服务器,2g左右

- 按分配:内存优先,适合微小的内存,2g以下

- php-fpm如何优化?

- 避免程序跑死(hang)

- 在负载较高的服务器上定时重载php-fpm,reload可以平滑重启而不影响生产系统的php脚本运行,每15分钟reload一次,定时任务如下:

- 0-59/15 * * * * /usr/local/php/sbin/php-fpm reload

- 合理增加单个worker进程最大处理请求数,减少内存消耗

- 最大处理请求数是指一个php-fpm的worker进程在处理多少个请求后就终止掉,master进程会重新respawn新的。该配置可以避免php解释器自身或程序引起的memory leaks。默认值是500,可以修改为如下配置:pm.max_requests = 1024

- 开启静态模式,指定数量的php-fpm进程,减少内存消耗

## PHP底层

### php生命周期

读取php脚本文件

词法分析和语法分析

编译和优化

执行php代码

垃圾回收和资源释放

```

modules startup 模块初始化阶段

requerst startup 请求初始化阶段

execute script 执行脚本阶段

requerst shutdown 请求关闭阶段

module shutdown 模块关闭阶段

```

### php的运行模式有几种

- WEB模式

- CLI模式

### PHP数组底层怎么实现的?

> 关键点: hashTable + Linked List

PHP数组底层依赖的散列表数据结构,定义如下(位于Zend/zend_types.h);

数据存储在一个散列表中,通过中间层来保存索引与实际存储在散列表中的位置的映射。

由于哈希函数会存在哈希冲突的可能,因此对冲突的值采用链表来保存。

哈希表的查询效率是O(1),链表查询效率是O(n);因此PHP数据索引速度很快,但是相对比较占用空间。

### PHP内存管理机制与垃圾回收机制

>  参考答案:http://www.cnblogs.com/zk0533/p/5667122.html 

#### php 的内存管理机制是:

预先给出一块空间,用来存储变量,当空间不够时,再申请一块新的空间。

1.  变量名,存在符号表

2. 变量值,存储在内存空间

3. 在删除变量的时候,会将变量值存储的空间释放,而变量名所在的符号表不会减少

#### `php`垃圾回收机制是:(主要是使用了引用计数器)

1. 在5.2版本或之前版本,PHP会根据 引用计数 (`refcount`)值来判断是不是垃圾,如果refcount值为0,PHP会当做垃圾释放掉,这种回收机制有缺陷,对于环状引用的变量无法回收。

2. 在5.3之后版本改进了垃圾回收机制。具体如下:

如果发现一个`zval`容器中的`refcount`在增加,说明不是垃圾; 如果发现一个`zval`容器中的`refcount`在减少,如果减到了0,直接当做垃圾回收; 如果发现一个`zval`容器中的`refcount`在减少,并没有减到0,`PHP`会把该值放到缓冲区,当做有可能是垃圾的怀疑对象; 当缓冲区达到了临界值,`PHP`会自动调用一个方法去遍历每一个值,如果发现是垃圾就清理。

## MVC框架生命周期

用户请求进来,先加载配置文件,框架初始化,然后匹配路由地址 ,寻找对应的controller的文件地址,引入加载文件,实例化controller,

根据路由匹配得到的方法和参数,调用并传参到方法,此处可能需要读取db,model层负责数据的存取,提供封装好的方法给到controller层调用,

controller层得到数据,通过引入view层文件,传递数据到view层,渲染html模板后输出。

- M:对数据表的存取

- V:存放组件、图片、页面模版html文件

- C:获取或改变model数据返回给页面渲染数据

### laravel 生命周期

- public/index入口文件

- 加载项目依赖

- 通过composer包管理器自动生成的类加载程序

- 创建laravel应用实例(或称服务容器)

- 创建APP容器

- 注册应用路径

- 注册项目服务提供者

- 配置中间件和引导程序等

- 接收请求并响应

- 解析内核实例

- 处理Http请求

- 创建请求实例

- 处理请求

- 发送响应

- 终止程序

#### IOC控制反转 + DI 依赖注入 

- IOC是控制反转,由容器来帮助我们进行类的初始化,从而降低对象之间的耦合性

- DI:把相关库绑定的依赖关系注入到容器中

- 门面:相当于给类取别名

- IOC服务容器

- 解决依赖关系

- 通过递归和反射的方式把实例注入

- 绑定

- 反射

##  swoole

- 简单理解:swoole=异步I/O+网络通讯

- 纯C编写,提供了php语言的异步多线程服务

#### swoole底层实现

- epoll

- event poll 

- 协程

# MySQL

## 基础篇

### MySQL的存储引擎有哪些?

- InnoDB

- MyISAM

- Memory

- ...

### 执行一条SQL查询语句,期间发生了什么?

- 连接器:

- 与客户端进行TCP三次握手建立连接

- 校验客户端的用户名和密码

- 读取该用户的权限

- 查询缓存

- key-value的形式存储

- 比较鸡肋,在8.0移除

- 解析SQL:解析器

- 词法分析:关键词,如:select、from

- 语法分析:SQL类型,表名,字段名,where条件等

- 预处理器

- 表和字段是否存在

- 将select * 中的 * 符号,扩展为表上的所有列

- 优化器

- 主要负责将SQL查询语句的执行方案确认下来

- 执行器

- 通过api与存储引擎交互

### MySQL一行记录是怎么存储的?

- 数据存放的文件构成?

- db.opt 默认字符集和字符校验规则

- .frm 表结构

- .idb(独占表空间文件) 表数据

- 表空间文件的结构?

- 段 segment:表空间是由各个段组成的,段是由多个区组成的

- 索引段:存放B+树的非叶子节点的区的集合

- 数据段:存放B+树的叶子节点的区的集合

- 回滚段:存放的是回滚数据的区的集合

- 区 extent:1MB,连续的64个页会被化为一个区

- 页 page:数据是按页为单位来读写的,默认16kb

- 行 row:数据库表中的记录都是按行存放的

## B+Tree树

- 每个节点都是一个数据页,数据页之间是双向索引

- 如何存储数据?

- InnoDB的数据是按【数据页】为单位来读写的,InnoDB数据页的默认大小是16kb

- 数据页包含7个部分

- 文件头:上一个数据页和下一个数据页两个指针,双向链表

- 页头

- 最大、最小记录

- 用户记录

- 空闲空间

- 页目录:相当于章节目录,二分查找

- 文件尾

- 数据页,页目录

- 槽:多条记录

- 数据页:多个槽

- 定位槽,二分查找;遍历槽里面的数据找到对应的记录

## 索引

- 什么是索引?

- 索引就是数据的目录

- 索引的分类?

- 按数据结构:B+tree索引,Hash索引,Full-text索引

- 按物理存储:主键索引,二级索引

- 按字段特性:主键索引,唯一索引,普通索引,前缀索引

- 按字段个数:单列索引,联合索引

- 存储引擎对索引支持?

- InnoDB:B+Tree索引、Full-Text索引

- MyISAM:B+Tree索引、Full-Text索引

- Memory:B+Tree索引、Hash索引

- B+Tree

- 叶子节点和非叶子节点的区别?

- 每个节点里面的数据是按主键顺序存放

- 叶子节点存放数据

- 非叶子节点只存放索引

- 叶子节点包含两个指针,一个指上,一个指下

- 双向链表

- 为什么使用B-Tree树?

- 可以把读取一个节点当做一次磁盘I/O操作

- B+Tree 相比于 B 树和二叉树来说,最大的优势在于查询效率很高,因为即使在数据量很大的情况,查询一个数据的磁盘 I/O 依然维持在 3-4次。

- 主键索引的 B+Tree 和二级索引的 B+Tree 区别?

- 主键索引的 B+Tree 的叶子节点存放的是实际数据,所有完整的用户记录都存放在主键索引的 B+Tree 的叶子节点里;

- 二级索引的 B+Tree 的叶子节点存放的是主键值,而不是实际数据。

- 回表?

- 会先检二级索引中的 B+Tree 的索引值,找到对应的叶子节点,然后获取主键值,然后再通过主键索引中的 B+Tree 树查询到对应的叶子节点,然后获取整行数据。这个过程叫「回表」,也就是说要查两个 B+Tree 才能查到数据。

- 覆盖索引?

- 在二级索引的 B+Tree 就能查询到结果的过程就叫作「覆盖索引」,也就是只需要查一个 B+Tree 就能找到数据。

- 联合索引?

- 最左匹配原则,在 where 子句的顺序并不重要。

- 范围查询的字段可以用到联合索引,但是在范围查询字段的后面的字段无法用到联合索引。

- 遇到(如 >, <)范围查询的时候,就会停止匹配

- where a > 1 and b = 2 不;where a >= 1 and b = 2 可以,主要是其中where a = 1 and b = 1这部分触发了联合索引

- 建立联合索引时,要把区分度大的字段排在前面,这样区分度大的字段越有可能被更多的 SQL 使用到。

- 索引下推? MySQL5.6引入

- 可以在联合索引遍历过程中,对联合索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。

- explain,查询语句的执行计划里,出现了 Extra 为 Using index condition,那么说明使用了索引下推的优化

- 使用索引的优劣?

- 最大的好处就是提高查询速度

- 缺点:

- 占用物理空间,并随数量增加而增大

- 创建和维护索引要耗费时间,并随数量增加而增大

- 降低表的增删改的效率,因为每次增删改索引,B+树为了维护索引有序性,都需要进行动态维护。

- 合理的使用索引?

- 字段有唯一性限制的,比如商品编码,订单号

- 经常用于where查询条件的字段,如果是多个字段可以建立联合索引

- 经常用于group by和order by的字段

- 哪些情况不需要创建索引?

- where条件,group by和order by里用不到的字段

- 字段值存在大量重复数据,如state,sex

- 数据量不大

- 经常更新的字段

- 优化索引?

- 前缀索引

- 覆盖索引

- 主键索引自增

- 防止索引失效

- 索引失效有哪些?

- 使用左或者左右模糊匹配,like %x 或者 like %x%

- 在查询条件中对索引列使用函数,进行表达式计算,索引列类型隐式转换

- 联合索引中未正确使用

- where子句中,or后条件列不是索引列

- explain

- type: all 、range,代表什么意思,谁的效果更好

- 如果是联合索引,type的值是什么?

## 事务

- 事务有哪些特征?ACID

    - 原子性:要不全成功,要不全失败

    - 一致性

    - 隔离性

    - 持久性

- 并行事务会引发什么问题?

    - 脏读:一个事务「读到」了另一个「未提交事务修改过的数据」

    - 不可重复读:在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况

    - 幻读:在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不一样的情况

- 事务的隔离级别有哪些?存在什么问题?

    - 读未提交:脏读,不可重复读,幻读

    - 读已提交:不可重复读,幻读 

    - 可重复读:幻读(不完全), MySQL默认隔离级别

    - 串行化

- InnoDB引擎通过什么技术来保证事务的ACID的呢?

    - 持久性 - redo log(重做日志)

    - 原子性 - undo log(回滚日志)

    - 隔离性 - MVCC(多版本并发控制) 或 锁机制(next-key lock:记录锁+间隙锁)

    - 一致性 - 通过以上三点结合

- MySQL InnoDB针对幻读现象,解决的方案:

    - 快照读(普通select), 通过MVCC

    - 当前读(select ... for update等),通过next-key lock(记录锁+间隙锁)

- 四种隔离级别具体是如何实现?

    - 读未提交:因为可以读到未提交事务修改的数据,所以直接读取最新的数据

    - 串行化:通过加读写锁的方式来避免并行访问

    - 读已提交,可重复读:通过Read View来实现的,区别在于创建Read View的时机不同

    - Read View 相当与一个数据快照

    - 读已提交:每个语句执行前,都会重新生成一个Read View

    - 可重复读:启动事务时,生成一个Read View,然后整个事务都用这个Read View

- 简述MVCC(多版本并发控制)?

    - 记录隐藏列,当前事务版本 trx_id

    - Read View: create_trx_id, m_ids, min_trx_id, max_trx_id

    - 一个事务访问记录时:

    - trx_id < min_trx_id,表示trx_id对应版本的记录是在创建Read View前已经提交的事务生成的,所以该版本的记录对当前事务可见

    - trx_id > max_trx_id,表示这个版本的记录是在创建 Read View 后才启动的事务生成的,所以该版本的记录对当前事务不可见

    - min_trx_id <= trx_id <= max_trx_id

    - 如果记录的 trx_id 在 m_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。

    - 如果记录的 trx_id 不在 m_ids列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。

    - 综上,通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)

- 可重复读级别下,发生幻读的场景?

    - 第一个例子:对于快照读, MVCC 并不能完全避免幻读现象。因为当事务 A 更新了一条事务 B 插入的记录,那么事务 A 前后两次查询的记录条目就不一样了,所以就发生幻读。

    - 第二个例子:对于当前读,如果事务开启后,并没有执行当前读,而是先快照读,然后这期间如果其他事务插入了一条记录,那么事务后续使用当前读进行查询的时候,就会发现两次查询的记录条目就不一样了,所以就发生幻读。

## 锁

- MySQL有哪些锁?

- 全局锁

- 表级锁

- 表锁

- 元数据锁(MDL)

- 意向锁

- AUTO-INC锁

- 行级锁

- Record Lock 记录锁

- Gap Lock 间隙锁

- Next-Key Lock

- 插入意向锁

- 全局锁的作用?

- 全库备份

## 日志

- redo log

- undo log

- binlog

## 分库分表

https://mp.weixin.qq.com/s/-WFBtHtTMtHoGwIm9bL4Uw

垂直分:业务拆分,单表字段拆分

```

垂直分库: 一般来说按照业务和功能的维度进行拆分,将不同业务数据分别放到不同的数据库中,核心理念 专库专用。

按业务类型对数据分离,剥离为多个数据库,像订单、支付、会员、积分相关等表放在对应的订单库、支付库、会员库、积分库。不同业务禁止跨库直连,获取对方业务数据一律通过API接口交互,这也是微服务拆分的一个重要依据。

垂直分表: 针对业务上字段比较多的大表进行的,一般是把业务宽表中比较独立的字段,或者不常用的字段拆分到单独的数据表中,是一种大表拆小表的模式。

```

水平分: 上边垂直分库、垂直分表后还是会存在单库、表数据量过大的问题 

```

水平分库: 是把同一个表按一定规则拆分到不同的数据库中,每个库可以位于不同的服务器上,以此实现水平扩展,是一种常见的提升数据库性能的方式。

例如:db_orde_1、db_order_2两个数据库内有完全相同的t_order表,我们在访问某一笔订单时可以通过对订单的订单编号取模的方式 订单编号 mod 2 (数据库实例数) ,指定该订单应该在哪个数据库中操作。

水平分表: 是在同一个数据库内,把一张大数据量的表按一定规则,切分成多个结构完全相同表,而每个表只存原表的一部分数据。

例如:一张t_order订单表有900万数据,经过水平拆分出来三个表,t_order_1、t_order_2、t_order_3,每张表存有数据300万,以此类推。

```

分库分表常见问题和解决方案

https://juejin.cn/post/7303012148488388619

# Redis

## 基础

### 什么是Redis?

- 是一种基于内存的数据库,对数据的读写操作都是在内存中完成的,因此读写速度非常快

- 常用于:缓存,消息队列,分布式缓存等

###  Redis单线程的为什么快?(主要考察一个I/O多路复用 和 单线程不加锁)

- 完全基于内存,绝大分请求是纯粹的内存操作,非常快速,数据在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

- 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;

- 采用单线程,避免不要的上线文切换和竞争条件,也不存在多进程和多进程导致的切换而消耗CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

- 使用多路I/O复用模型

  

### Redis 的数据类型

- string 字符串

- hash  哈希

- List 列表

- Set 集合

- Zset 有序集合

- Bitmaps 位图

- HyperLogLog 基数统计

- GEO 地理信息

- Stream 流

- ### Redis 和Memcached 区别?

 

- 共同点

    - 都是基于内存的数据库,一般都是用来当做缓存使用

        - 都有过期策略

        - 性能都很高

    - 区别

    - Redis支持的数据类型更丰富,而Memcached只能支持最简单的key-value形式

    - Redis支持数据的永久化

        - Redis原生支持集群模式

       - Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持

  

- ### 五种常见数据类型内部实现?

- String  

- SDS 简单动态字符串

- List

- 3.2版本后,只由 quicklist 实现,替代了下面的两种结构

- 3.2版本前,双向链表 或 压缩列表

- Hash

- 7.0中,listpack

- 压缩列表或哈希表

- Set

哈希表 或 整数集合

- Zset

- 7.0中,listpack数据结构

- 压缩列表 或 跳表

- 使用场景

- String:缓存对象、常规计数、分布式锁、共享 session 信息等。

- Hash:缓存对象、购物车等。

- List:消息队列(但是有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。

- Set:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。

- Zset:排序场景,比如排行榜、电话和姓名排序等。

- ### Redis的持久化

- AOF日志:每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里

- RDB日志:将某一时刻的内存数据,以二进制的方式写入磁盘

- 混合持久化方式:继承了AOF和RDB的优点,前半部分是RDB日志后半部分是AOF日志

## Redis集群

- 主从复制

- 哨兵模式

- 切片集群模式

### 主从复制

-  数据修改只在主服务器上进行 

-  然后将最新的数据同步给从服务器,这样就使得主从服务器的数据是一致的。 

- 异步(无法实现强一致性保证)

#### 如何应对主从数据不一致?

### 哨兵模式

> Redis Sentinel

>

> 主从服务时,当Redis的主服务器出现故障宕机时?手动进行恢复?

 可以监控主从服务器,并且提供**主从节点故障转移的功能。** 

- Redis过期删除与内存淘汰

- 惰性删除

- 定期删除

- Redis缓存设计

- 如何避免缓存雪崩?

- 大量缓存同时失效,全部请求都直接访问数据库,导致数据库的压力骤增

- 解决?

- 将缓存失效时间随机打散

- 设置缓存不过期:通过后台服务来更新数据

- 缓存击穿?

- 热点数据过期,大量请求访问该热点数据,直接访问到数据库

- 解决?

- 互斥锁方案:保证同一时间只有一个业务线程请求缓存

- 不给热点数据设置过期时间

- 缓存穿透?

- 既不在缓存中,也不在数据库中,一直访问数据库

- 解决?

- 非法请求限制

- 设置控制或者默认值

- 使用布隆过滤器快速判断数据是否存在

- 缓存策略,动态缓存热点数据?

- 总体思路:通过数据最新访问时间来做排名,并过滤掉不常访问的数据,只留下经常访问的数据。

- 一致性问题,缓存和数据库

- 先更新数据库,还是信先删除缓存?

- 理论上分析,先更新数据库,再删除缓存也是会出现数据不一致性的问题,但是在实际中,这个问题出现的概率并不高。

- 因为缓存的写入通常要远远快于数据库的写入

- 所以,「先更新数据库 + 再删除缓存」的方案,是可以保证数据一致性的

- 删除失败的方案:延迟双删

- 缓存数据,加上 过期时间

- 如何保证更新数据库后,同时保证删除缓存也能成功?

- 重试机制:如消息队列重试缓存的删除

- 订阅 MySQL binlog 再操作缓存

# 其他待整理

## 设计模式:

- 设计原则,solid:单一职责,开发封闭,里氏替换,接口隔离,依赖反转

- 常见设计模式:单例,工厂,原型,策略,责任链,解释器,组合,装饰器,适配器,外观,代理,桥接等

> 一般会问知道哪些设计模式?然后会选其中一两个设计模式来问原理?

#### 单例模式

实现:

- 三私一公

- 私 `__coustruct / __clone / __wakeup `

- 公 `public static getInstance() {}` 通过此方法获取实例

场景:

- 一般适用于比较耗费资源的类实例化,如:操作数据库,操作Redis等

#### 观察者

> 在业务开发过程中多个功能会相互依赖

>

> 如果我们想在一个对象发生变化后通知和它有相关的类

>

> 比如说你做了某件事后希望可以使用邮件和短信发送通知

#### 策略

> 提供不同的算法实现,供其选择

## Elasticsearch

- 结构: 索引 文档 字段

- 索引结构迁移: 别名

## 消息中间件

### RabbitMQ

AMQP协议 tcp连接 信道(channel) 长连接

组成:生产者》交换机-》路由key-》Binding key-》queue-》消费者

#### 简述rabbitMQ交换机类型?

- Fnout扇形:忽略 binding,发送到全部绑定的队列

- Direct直连模式: 一对一,点对点

- Topic模糊匹配: 一对多

- header:根据headers的数据,判断是否有符合条件的,有就分发;性能差,基本不用

#### rabbitMQ可以直连队列吗?

- 可以

- 但是丧失灵活性

#### 简述rabbitMQ的持久化机制?

- 交换机持久化:

- 队列持久化:

- 消息持久化:

```

append的方式写文件,会根据大小自动生成新的文件,rabbitmq启动时会创建两个进程,一个复杂持久化消息的存储,一个负责非持久化消息的存储(内存不够时)

消息存储时会在ets表中记录消息在文件中的映射以及相关信息(包括ID,偏移量,有效数据,左边文件,右边文件),消息读取时根据该信息到文件中读取,同时更新信息

消息删除时只从ets删除,变为垃圾数据,当垃圾数据超出比例(默认50%),并且文件数达到3个,触发垃圾回收,锁定左右两个文件,整理左边文件有效数据,将右边文件有效数据写入左边数据,更新文件信息,删除右边,完成合并。当一个文件的有用数据等于0时,删除该文件。

写入文件前先写入buffer缓冲区,如果buffer已满,则写入文件(此时只是操作系统的页存)

每隔25ms刷一次磁盘,不管buffer满没满,都将buffer和页存中的数据落盘

每次消息写入后,如果没有后续写入请求,则直接刷盘

```

#### 简述rabbitMQ的事务消息机制?

- 分为 **生产端** 和 **消费端** 的事务

- 生产端:数据预先存入事务消息队列

- 消费端:类似手动ack机制,当commit时才能删除

#### rabbitmq如何保证消息的可靠性传输?

- 使用事务消息

- 使用消息确认机制

- 持久化

```

发送方确认:

- channel设置为confirm模式,则每条消息都会被分配一个唯一ID

- 消息投递成功,信道会发送ack给生产者,包含ID,回调confirmCallBack接口

- 如果发生错误导致消息丢失,发送nack给生产者,回调ReturnCallback接口

- ack和nack只有一个触发,且只有一次,异步触发,可以继续发送消息

接收方确认:

- 声明队列时,指定noack=false,broker会等待消费者手动返回ack,才会删除消息,否则立刻删除

- broker的ack没有超时机制,只会判断链接是否断开,如果端口,消息会被重新发送

```

#### rabbitmq的死信队列、延迟队列原理

- 死信队列:死信消息,设置死信交换机,死信队列接收死信队列

- 延迟队列:死信队列 + TTL

#### rabbitmq的镜像队列原理

> 集群为基础

#### 如何保证消息不被重复消费?

> 幂等性:一个数据或者一次请求,重复多次,确保对应的数据不会改变的,不能出错

思路:

- 如果是写redis,使用set,天然的幂等性

- 生产者发送消息的时候带上一个全局的id,消费者拿到消息后,先根据这个id去redis里面查一下,之前没有消费过,就处理,并且写入这个id到redis,如果消费了,就不处理

- 基于数据库的唯一键

### 消息队列的优缺点,使用场景

优点:

- 解耦,降低系统之间的依赖

- 异步处理,不需要同步等待

- 削峰填谷,将流量从高峰期引到低谷期进行处理

缺点

- 增加了系统的复杂度,幂等、重复消费、消息丢失等问题的带入

- 系统可用性降低,mq的故障会影响系统可用性

- 一致性,消费端可能失败

场景:

- 日志采集,如:ELK

- 发布订阅等

### kafka

#### rabbitmq、kafka、rocketmq等对比

- rabbitmq:erlang语言开发,性能好,高并发,支持多种语言,社区、文档方面有优势,单机吞吐量在万级

- kafka:高性能,高可用,生成环境有大规模使用场景,单机容量有限(超过64个分区响应明显变长),社区更新慢,单机吞吐量在百万级

- rocketmq:java实现,设计参考了kafka,高可用,高可靠,社区活跃度一般,支持语言较少,单机吞吐量在十万级

# 架构

## 中台

- 什么是中台:共享业务;

- DDD的组成?