# 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多路复用
- 多进程模型
- 上下文切换“包袱”很重
- 多线程模型
- 轻量级
- 因为可以共享部分资源
- 本质: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的组成?