PHP 十年程序员 面试宝典
写在前面
休息几个月,感受了下今年求职的环境到底有多糟糕,后面有时间再吐槽当前环境的各种坑吧。。。
顺手记录下自己简单整理的 php 面试知识点,希望对你多有帮助!
仅供参考!!!
Nginx 相关
简介
Nginx 是一个开源的” 高性能代理服务器 (可以处理数千个并发且迅速响应)”,采用异步非阻塞的事件驱动模型实现了高可用(高性能、低消耗、可靠稳定)。常用于 Web 服务器、负载均衡、反向代理以及静态资源缓存等。
重要的配置文件
1.nginx.conf:主要的 Nginx 配置文件,包含全局性的设置,例如进程数、工作模式等。该文件位于 /etc/nginx/ 目录下。
2.sites-available/default:默认的虚拟主机配置文件。该文件位于 /etc/nginx/sites-available/ 目录下。
3.sites-available/:存放了用于配置不同虚拟主机的配置文件,每个文件对应一个虚拟主机。可以在该文件夹下创建新的配置文件以配置更多的虚拟主机。
4.sites-enabled/:存放了启用的虚拟主机的配置文件的符号链接。通常使用 ln -s 命令将 sites-available / 目录下的配置文件链接到 sites-enabled / 目录,从而启用该虚拟主机。
5.conf.d/:该文件夹下存放了其他 Nginx 配置文件的目录,这些配置文件可以包含在主配置文件中。可以在该文件夹下创建新的配置文件以添加其他配置选项。
常用的命令
1. 启动 Nginx:nginx -s start
2. 停止 Nginx: nginx -s stop
3. 重启 Nginx: nginx -s restart
4. 检查 Nginx 配置文件是否正确: nginx -t
5. 打开 Nginx 主进程 PID 文件: nginx -s reopen
6. 关闭 Nginx,并在处理完当前请求后退出: nginx -s quit
7. 重新加载配置文件:nginx -s reload
配置反向代理服务器
打开 Nginx 的配置文件: /etc/nginx/nginx.conf
配置反向代理:在 Nginx 配置文件中找到 http 部分,并配置反向代理:
http {
server {
listen 80;
# 域名
server_name demo.com;
# 代理的后端服务器地址
location / {
proxy_pass http://backend-server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwardeed_for;
}
}
# 后端服务器地址,可以按需添加多个
upstream backend-server {
server backend1.demo.com;
server backend2.demo.com;
}
}
检查配置: sudo nginx -t ,并重启:sudo service nginx restart,反向代理服务器配置成功!
负载均衡配置
解决问题: 在并发或服务繁忙等场景下,实现速度和稳定性(高可用)
原理:通过反向代理来实现的负载均衡
策略 / 算法:
1. 轮询(默认)
2.IP_HASH 算法(保持会话,解决会话问题)
3.Weighted 算法(按权重比例分配服务器,解决差异性的服务器性能问题)
4.URL_HASH 算法(通常用于静态资源代理,配合缓存命中来使用,类似 cdn)
IP/IP 段控制
限制特定 IP 地址或 IP 地址段的访问,使用 Nginx 的 ngx_http_access_module 模块提供的 allow 和 deny 指令
# 注意allow, deny顺序配置
location / {
# 允许访问
allow 192.168.1.0/24;
allow 10.0.0.0/16;
# 拒绝访问
deny all;
}
HTTP 与 HTTPS 的区别以及 nginx 如何支持?
区别:
1. 端口:http 80, https :443
2. 状态:http 无状态,https 是有 http + ssl 身份验证
3. 传输方式:http 明文传输,https 加密传输
4. 连接速度:http 更快(三次握手),https 需要 12 个包(http 的 3 次握手 + 9 个 ssl 握手包)
支持:nginx 直接配置证书即可。
#通过rewrite将所有HTTP请求重定向HTTPS
server {
listen 80;
server_name demo.com;
rewrite ^(.*)$ https://$server_name$1; 。
}
#ssl证书配置有关。
server {
#端口为443。
listen 443 ssl;
#证书绑定的域名。
server_name demo.com;
charset utf-8;
#证书路径配置
ssl_certificate /home/you_path/demo.com.pem;
ssl_certificate_key /home/you_path/demo.com.key;
#ssl算法相关配置
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
# 其他配置
...
}
静态文件缓存与压缩
1. 缓存相关的配置项,proxy_cache_*
2.gzip 压缩的配置项,gzip_*
nginx 日志
1. 配置日志: access_log,error_log
2. 自定义日志格式(可配置变量参考手册),如自定义 mylog 格式:
#时间、客户端IP、请求方法和URL
log_format mylog ‘$time_local $remote_addr $request_method $request_uri’;
#定义日志使用自定义的格式
access_log /var/log/nginx/access.log mylog;
限流与优化
1. 反向代理服务器,可以隐藏真实服务器的 IP 地址,有一定的保护作用。
2.HTTP 限制:使用 limit_req 和 limit_conn,对客户端进行合理的请求限制。
3. 访问限制:使用 GeoIP 和 IP 黑名单等,对恶意 IP 进行拦截或限制。可以基于地理位置、IP 地址段等限制。
4. 负载均衡:通过反向代理功能,将请求分散到多个后端服务器上,从而分摊请求压力。
5. 动静分离:将动态和静态资源分开,静态可用 Nginx 的缓存功能缓存,减轻后端服务器压力。
6. 配置防火墙:结合 Nginx 与防火墙工具(如 iptables),对流量进行控制,只允许合法的,阻止恶意流量。
7. 监控分析:监控(Nginx Stub Status 模块 + 第三方工具),分析(ELK)。问题及时发现处理
PHP 相关
cgi
CGI 是 nginx 和 php 通讯的协议。nginx 服务器在接受请求后,如果是静态请求(图片,文件等无需 php 处理的)则会直接返回给浏览器。如果是一个动态的 php 请求,nginx 就会通过 cgi 协议与 php 通信,将请求数据转换成 php 能理解的信息,php 处理完成也通过 cgi 协议返给 nginx,最后 nginx 再返回给浏览器。
fast-cgi
传统的 cgi 协议在每次连接请求时,会开启一个进程进行处理,处理完毕会关闭该进程。下次请求又重复开启与关闭。频繁的进程启动与关闭,消耗大量的资源和内存。而 fast-cgi 每次处理完请求后,不会 kill 掉这个进程,而是保留进程,使进程可以处理多次请求,不用重新 fork 一个进程,大大提高效率。
php-cgi
php-cgi 是 php 提供给 web serve 的 cgi 协议接口程序,每次请求都会开启一个 php-cgi 进程进行处理,而且开启 php-cgi 会先重载配置,数据结构以及初始化运行环境,如果更新了 php 配置,那么就需要重启 php-cgi 才能生效,例如 phpstudy 就是这种情况。
php-fpm
1.php-fpm 是 php 提供给 web serve 的 fastcgi 协议接口程序,是 php-cgi 的一个管理程序。
2.php-fpm 常驻内存,会开启多个 php-cgi 进程,请求来的时候,php-fpm 将连接分配给一个 php-cgi 子进程处理,处理完毕后 php-cgi 并不会关闭,而是等待处理下一个连接,这也是 fast-cgi 加速的原理。
3.php-fpm 是多进程的,一个 php-cgi 大概消耗 7-25M 内存,需注意连接过多导致内存消耗过大的问题。
4.php-fpm 支持平滑启动,如果更新了 php 配置可使用 /you_path/php-fpm reload 平滑过渡。
php-fpm 优化
PHP-FPM 默认使用静态进程管理模式(启动时创建固定数量的进程)
在高负载环境下,推荐使用动态进程管理模式(动态创建 / 销毁进程以提高性能)
打开配置文件
vim /etc/php-fpm.conf
# 增加进程数
pm.max_children = 50 //pm.max_children表示最大的进程数
pm.start_servers = 20 //pm.start_servers表示启动时的进程数
# 调整进程空闲时间
pm.max_spare_servers = 10 //pm.max_spare_servers表示空闲进程的最大数量
pm.min_spare_servers = 5 //pm.min_spare_servers表示空闲进程的最小数量
# 每个进程允许处理的最大请求次数。到该次数后进程重启,释放资源(大了可能会内存溢出,小了频繁重启)
pm.max_requests = 1000
# 使用动态进程管理模式(static和dynamic)
pm = dynamic
pm.max_children = 50
pm.start_servers = 20
pm.min_spare_servers = 5
pm.max_spare_servers = 10
Nginx 和 php 之间的通信
1.tcp socket ,面向连接的协议,更好的保证通信的正确性和完整性
2.unix socket,不需要网络协议打包拆包,开销小效率高,但高并发时候不稳定,可能返回异常。
网络 7 层协议模型
应用层、表示层、会话层、传输层、网络层、(数据)链路层、物理层。快记:应表会传(物链网)
TCP 和 UDP 的特点和区别
1. 都是属于传输层协议
2.TCP,面向连接,一对一,数据可靠不丢失
3.UDP,无连接,一对多 / 多对多,速度更快但不可靠
TCP 的三次握手和四次挥手
1. 三次握手:
1. 第一次:客户端发送 SYN = 1,seq = client_isn
2. 第二次:服务端发送 SYN = 1,seq = server_isn,ACK =client_isn +1
3. 第三次:客户端发送 SYN = 0, ACK = server_isn+1,seq =client_isn+1
2. 四次挥手
1. 第一次:客户端发送 FIN
2. 第二次:服务端发送 ACK
3. 第三次:服务端发送 FIN
4. 第四次:客户端发送 ACK
HTTP 状态码
- 1xx:请求进行中,服务器收到请求,需要请求者继续操作
- 2xx:成功
- 3xx:重定向
- 4xx:客户端错误
- 5xx:服务端错误
oop 是什么?
面向对象编程是一种计算机编程架构思想,指程序由一个个能够起到子程序作用的单元或对象组成。 面向对象 3 大特性:继承、封装、多态。php 垃圾回收机制
“引用计数方式(is_ref_gc=0 时回收)” + “标记清除方式(解决循环引用时回收)” 来进行垃圾回收。什么是引用传递?
引用传递:函数内对值的任何改变在函数外部也生效(传递地址)
值传递:函数内对值的任何改变在函数外部不生效(复制值) 对大型字符串和对象来说,复制操作代价很大。引用传递会有不错的性能提升(对象默认为引用传递)。设计模式五大原则
单一职责原则:单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。
开放封闭原则:核心理念 “对扩展开放,对修改封闭”。设计时要允许现有代码进行扩展,而不是通过修改现有代码来实现新的功能。这样做可以避免对现有代码造成不必要的破坏和风险,同时也可以提高代码的复用性和可维护性,从而降低开发和维护成本。
接口隔离原则:不要强迫客户使用它们不用的方法,如果强迫用户使用它们不使用的方法,那么这些客户就会面临由于这些不使用的方法的改变所带来的改变。
依赖倒置原则:面向对象而不是面向过程。程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
里氏替换原则:子类可以扩展父类的功能,但不能改变父类原有的功能 。
附:迪米特原则:一个对象应该对其他对象保持最少的了解。
常见的设计模式
单例模式、工厂模式、观察者模式、外观模式、代理模式…
1. 创建型模式共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
2. 结构型模式共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
3. 行为型模式共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
单例模式:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
public static function getInstance()
{
return new static();
}
观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并且有所作为。即出版者 + 订阅者 = 观察者模式。
工厂模式 :调用者和创建者分离,调用者直接向工厂类请求获取对象,减少代码耦合,提高系统的维护性和扩展性。
适配器模式:把对某些相似的类的操作转化为一个统一的 “接口”(比喻的说法)–适配器,或者比喻为一个 “界面”,统一或屏蔽了那些类的细节。适配器模式还构造了一种 “机制”,使 “适配” 的类可以很容易的增减,而不用修改与适配器交互的代码,符合 “减少代码间耦合” 的设计原则。
冒泡排序 / 快速排序
冒泡排序是一种交换排序。对数组进行多轮冒泡,每一轮对数组中的元素两两比较,调整位置,冒出一个最大的数来。
/**
* 冒泡排序
* @param array $arr
*/
function bubbleSort(array $arr) : array
{
$length = count($arr);
// 外层循环,从数组首部开始,每完成一次循环,可确定 $arr[$i] 位置的元素
for ($i = 0; $i < $length; $i++){
// 内层循环,$j 从后往前循环
for ($j = $length - 1; $j > $i; $j--) {
// 若前面的值大于后面的值,则互换位置
if ($arr[$j] < $arr[$j - 1]) {
[$arr[$j], $arr[$j - 1]] = [$arr[$j - 1], $arr[$j]];
}
}
}
return $arr;
}
快速排序:递归算法。先选择数组的第一个元素作为标准,然后把小于或等于它和大于它的数分别放入两个数组中,对这两个数组也进行相同的处理,最后合并这两个数组和第一个元素。
/**
* 快速排序
* @param $arr
*/
function quickSort(&$arr) : void
{
$length = count($arr);
// 若数组为空,则不需要运行
if ($length <= 1) {
return;
}
$middle = $arr[0]; // 选定一个中间值
$left = []; // 接收小于中间值
$right = [];// 接收大于中间值
// 循环比较
for ($i = 1; $i < $length; $i++) {
if ($middle < $arr[$i]) {
$right[] = $arr[$i]; // 大于中间值
} else {
$left[] = $arr[$i]; // 小于或等于中间值
}
}
// 递归排序划分好的左右两边
quickSort($left);
quickSort($right);
$arr = array_merge($left, [$middle], $right);
}
单点登录
单点登录(Single Sign On, SSO)是指一次登录即可访问所有相互信任的应用系统。本质是多个应用系统共享登录状态。
实现方式一:父域 Cookie。
1.Cookie 的作用域由 domain 属性和 path 属性共同决定。所以可以通过把登录状态的 seesion_id 存在主域名域下 (当前域的父域),所有子域名就都可以共享了。
2. 此种实现方式比较简单,但不支持跨主域名。
实现方式二:认证中心。
1. 部署一个认证中心,专门负责处理登录请求的独立的 Web 服务。
2. 用户统一在认证中心进行登录,登录成功后,认证中心记录用户的登录状态,并将 Token 写入 Cookie。(注意这个 Cookie 是认证中心的,应用系统是访问不到的)
3. 应用系统检查当前请求有没有 Token,如果没有,那么就跳转至认证中心。而认证中心通过自动携带的 Cookie 判断是否已经登录。
4. 如果认证中心发现用户尚未登录,则返回登录页面,等待用户登录。如果发现用户已经登录过了,就跳转回目标 URL (目标应用系统),并携带回传一个认证中心的 Token。
5. 应用系统拿到 Token 之后,后端向认证中心确认下 Token 的合法性,防止用户伪造的同时 check 用户登录状态。确认无误后,应用系统记录用户的登录状态,并将 Token 写入 Cookie,访问放行。(此时 Cookie 是当前应用系统的)当用户再次访问当前应用系统时,就会自动带上这个 Token,应用系统验证 Token,就实现了单点登录。
6. 此种实现方式相对复杂,但支持跨域,扩展性好,是单点登录的标准做法。
实现方式三:LocalStorage 跨域。
1. 在这样的场景下,单点登录完全可以在前端实现。前端拿到 Session ID (或 Token )后,除了将它写入自己的 LocalStorage 中之外,还可以通过特殊手段将它写入多个其他域下的 LocalStorage 中。
2. 前端将同一份 Token 写入到了多个域下的 LocalStorage 中,前端每次在向后端发送请求之前,都会主动从 LocalStorage 中读取 Token 并在请求中携带,这样就实现了同一份 Token 被多个域所共享。
3. 此种实现方式完全由前端控制,几乎不需要后端参与,同样支持跨域。但扩展性不好,不利于维护。
php 的堆与栈
1.heap 是堆,stack 是栈;
2.heap 上的空间手动分配 / 释放(程序运行时分配的内存),stack 的空间由操作系统自动分配 / 释放(程序执行时为函数、局部变量等分配的内存空间);
3.zendVM 中的 malloc 函数分配的内存空间即在堆上 (mm_heap)
在 PHP 中,Zend 引擎通过内存分配器来管理堆内存的分配和释放。当 PHP 执行过程中需要分配更多内存时,内存分配器会从堆中分配一块足够大的空间,供变量和数据结构使用。而当某个变量或数据结构不再使用时,内存分配器会将相应的内存空间返回给堆。
php 底层相关理解
通常就是问:符号表(hashTable)、zvalue 结构体、zend vm 的内存分配等
php 的运行模式
CGI 模式(phpstudy、wnmp)
FastCGI 模式(lnmp 最常见的环境)
CLI 模式(php 命令行)
web 模块模式( Apache )
PHP 实现静态化
PHP 的静态化分为:纯静态和伪静态。其中纯静态又分为:局部纯静态和全部纯静态。
PHP 伪静态:利用 Apache mod_rewrite,niginx rewrite 实现 URL 重写的方法;
PHP 纯静态:生成 HTML 文件的方式,须开启 PHP 自带的缓存机制,即 ob_start 来开启缓存。
session 和 cookie
session 和 cookie 是一种会话管理技术,通常把 session_id 存在 cookie 来匹配管理会话。
存储位置:cookie 在客户端,session 在服务器
存储容量:单个 cookie 数据 <=4KB,一个站点最多保存 20 个 Cookie; session 理论没有限制(存重要信息即可)
安全性与其他:cookie 在客户端不安全(占用客户端资源),session 在服务器安全(占用服务器资源)
CSRF 攻击,XSS 攻击
CSRF(Cross-site request forgery)跨站请求伪造,黑客建立一个伪造网站或发送邮箱带了一个正常 URL 链接来让正常用户访问,来让用户通过自己浏览器里的 COOKIE 权限来执行一些非法请求
防范方法有:验证 HTTP Referer 字段、添加 token 并验证;
XSS 攻击
主要将 XSS 代码提交存储在服务器端(数据库,内存,文件系统等),下次请求目标页面时不用再提交 XSS 代码。当目标用户访问该页面获取数据时,XSS 代码会从服务器解析之后加载出来,返回到浏览器做正常的 HTML 和 JS 解析执行,XSS 攻击就发生了。
防范方法:通过过滤是针对非法的 HTML 代码包括单双引号等,使用 htmlspecialchars () 函数
抽象类和接口分别是什么?
抽象类:就是一种特殊的类,不能被实例。可以定义方法,属性。类似于模版,规范后让子类实现详细功能。
接口:主要基于方法 / 对象的规范。可让某个类通过实现多个接口来形成新的类。
抽象类与接口的相同点:
1. 都是用于声明某一种事物,规范名称、参数,形成模块,未有详细的实现细节。
2. 都是通过类来实现相关的细节
3. 语法上,抽象类的抽象方法与接口一样,不能有方法体,即{}符号
4. 都可以用继承,接口继承接口形成新的接口,抽象类继承抽象类形成新的抽象类
抽象类与接口的不同点:
1. 抽象类可以有属性、普通方法、抽象方法,但接口不能有属性、普通方法(可以有常量)
2. 抽象类内未必有方法定义(可以都是普通方法),但接口内一定会有 “方法定义”
3. 抽象类用 abstract 关键字,class 声明为类,接口用 interface 声明
4. 抽象类的抽象方法一定要用 abstract 来声明,而接口不需要
5. 抽象类是用 extends 关键字让子类继承,接口用 implements 实现接口
网站性能优化 / 高并发解决方法
1. 前端优化
减少 HTTP 请求 [将 css,js 等合并]
添加异步请求 (非必须数据先不展示,用户触发某个事件才会异步请求数据)
CDN 加速,建立独立的文件 / 图片服务器 (减少 I/O)
2.web 服务器优化
防盗链处理 (去除恶意请求)
反向代理实现负载均衡
静态资源缓存和 gzip 压缩
流量过滤,ip 限制及黑白名单
3. 应用程序优化
业务代码逻辑检查优化,sql 查询优化
常驻内存 swoole,opcache 缓存技术,连接池技术
4. 数据库优化
读写分离,负载均衡
redis 缓存、数据库缓存策略
分表分区分库,数据拆分
表结构优化,索引优化
防止 sql 注入
验证数据,可以根据相应类型进行严格的验证。比如 int 类型直接同过 intval 进行转换; 参数化绑定,PDO 参数绑定,禁止使用 sql 拼接;mysql 开启严格模式;
8 大魔术常量
1.LINE:文件中 本常量所在行的 行号(即处于第几行)。
2.FELE:本文件的完整路径和文件名。如果被用在 被包含文件中,则返回被包含文件的文件名。本常量总是包含一个绝对路径(如果是符号链接,则是解析后的绝对路径)
3.DIR:本文件所在目录。如果被用在 被包含文件中,则返回被包含文件的所在目录。它等价于 dirname (FILE)。除非是根目录,否则目录名中不包含末尾的斜杠。
4.FUNCTION:函数名称。自 PHP5 起本常量返回函数被定义时的名称(区分大小写)。
5.CLASS:类名称。自 PHP5 起本常量 返回类被定义时的名称(区分大小写,包括其命名空间。如:Foo\Bar)。
6.TRAIT:trait 的名称。自 PHP5.4 起本常量返回 trait 被定义时的名称(区分大小写)。
7.METHOD:类的方法名。返回该方法被定义时的名称(区分大小写)。
8.NAMESPACE:当前命名空间的名称(区分大小写)。本常量是在编译时定义的。
9 个超全局变量
1.$_GLOBALS :储存全局作用域中的变量
2.$_SERVER :获取服务器相关信息
3.$_REQUEST:获取 POST 和 GET 请求的参数
4.$_POST : 获取表单的 POST 请求参数
5.$_GET: 获取表单的 GET 请求参数
6.$_FILES :获取上传文件的的变量
7.$_ENV : 获取服务器端环境变量的数组
8.$_COOKIE:获取浏览器的 cookie
9.$_SESSION : 获取 session
魔术方法
constuct 构建对象的时被调用;
__destruct 明确销毁对象或脚本结束时被调用;
__set 当给不可访问或不存在属性赋值时被调用
__get 读取不可访问或不存在属性时被调用
__isset 对不可访问或不存在的属性调用 isset () 或 empty () 时被调用
__unset 对不可访问或不存在的属性进行 unset 时被调用
__call 调用不可访问或不存在的方法时被调用
__callStatic 调用不可访问或不存在的静态方法时被调用
__sleep 当使用 serialize 时被调用,当你不需要保存大对象的所有数据时很有用
__wakeup 当使用 unserialize 时被调用,可用于做些对象的初始化操作
__clone 进行对象 clone 时被调用,用来调整对象的克隆行为
__toString 当一个类被转换成字符串时被调用
__invoke 当以函数方式调用对象时被调用
__set_state 当调用 var_export () 导出类时,此静态方法被调用。用 set_state 的返回值做为 var_export 的返回值。
__debuginfo 当调用 var_dump () 打印对象时被调用(当你不想打印所有属性)
请求的生命周期
1. 构建请求
2. 查找本地缓存
3. 域名解析
4. 与服务器建立连接(TCP 的三次握手)
5. 发起 HTTP 请求
6. 服务器处理请求
7. 服务器响应 HTTP 请求
8. 客户端解析返回数据
9. 与服务器断开连接(TCP 的四次挥手)
网站打开慢怎么排查
1. 打不开,则 ping 域名,看是否能请求成功。
2. 慢,说明能打开,直接 free/top 命令查看服务器内存和 CPU 使用情况,iftop 等工具查看带宽
3.chrome 的 debug->network 查看响应慢的请求
4. 排查响应慢的接口代码,看 php,mysql,redis 等的日志看错误信息(mysql 的慢查询日志,php-fpm 慢日志,需要配置开启)
什么是 MQ?
mq 是一个消息队列,通常是解决传统的消息传输上管理困难,效率不高的问题。
mq 有三大优点:解耦,异步,削峰
缺点: 增加了中间件,就提高了系统复杂度,增加了维护的成本。比如:要保证消息不丢失 (一致性) 和消息幂等性问题,还要保证 mq 的高可用等
Mysql 相关
数据库三大范式是什么?
第一范式:1NF 是对属性的原子性,要求属性具有原子性,不可再分解;
第二范式:2NF 是对记录的唯一性,要求记录有唯一标识,即实体的唯一性,即不存在部分依赖;
第三范式:3NF 是对字段的冗余性,要求任何字段不能由其他字段派生出来,它要求字段没有冗余,即不存
MyISAM 与 InnoDB 的区别
1.MyISAM 不支持事务与外键,InnoDB 支持。
2.MyISAM 是表锁,InnoDB 行锁 (提高了并发性能)。
3.MyISAM 表是保存成独立文件的形式,在数据转移、备份 MyISAM 更具有优势。
4.AUTO_INCREMENT:MyISAM 可以和其他字段一起建立联合索引,InnoDB 中必须是只有该字段的索引
5.MyISAM 支持 FULLTEXT 类型的全文索引,InnoDB 不支持,但插件 sphinx 支持全文索引 (效果更好)。
6.MyISAM 允许没有任何索引和主键的表存在,InnoDB 没有主键会选择非空唯一索引,或自动生成一个 6 位的 uuid 主键
7.MyISAM 的 SELECT 性能更好,InnoDB 的 INSERT、UPDATE、DELETE 更好(删除全表使用 truncate table)
8. 索引区别:
聚簇索引:索引和数据存储在一个节点
非聚簇索引:索引和数据分开存储,通过索引找到数据实际存储的地址
innodb 使用的聚簇索引,主键索引为聚簇索引(没有主键索引会选择一个非空唯一索引或隐式的创建一个主键索引),辅助索引指向主键索引,然后再找到实际存储地址
myisam 使用非聚簇索引(辅助索引和主键索引一样),都只需要查询一次就能找到数据
聚簇索引的优势和略势:
1. 索引和数据在一起,同一页的数据会被缓存到(buffer)内存中,所以查看同一页数据的时候只需要从内存中取出
2. 数据更新之后之只需要维护主键索引即可,辅助索引不受影响
3. 辅助索引存的是主键索引的值,容易产生回表
4. 使用隐式的 uuid 主键时,数据分布不均匀,导致聚簇索引可能扫全表,效率低下(强制使用自增主键 id )
备注:b-tree(mongodb) 和 b+tree (mysql)的区别:
1.b+tree 的数据全部存储在叶子节点,主节点只存 key, 一次磁盘 IO 能获取到更多的节点
2.b+tree 增加了叶子节点到相邻节点的指针,数据是有序双向链表 ,方便返回查询范围(顺序性)
3.b-tree 的主节点和叶子节点都存储 key 和数据,查找数据不需要找到叶子节点,主节点可以直接返回数据
事务的 ACID 与隔离级别
事务遵循包括原子性在内的 ACID 四大特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability);
原子性 :数据提交工作时,要么保证所有的修改都能够提交,要么就所有的修改全部回滚。
一致性:数据库从一个一致性状态变换到另一个一致性状态。
隔离性: 如果多个事务并发执行,应像各个事务独立执行一样。
持久性:一个成功执行得事务对数据库得作用是持久的,即使数据库发生故障出错,也应该能够恢复
四种隔离级别:
1. 未提交读(read-uncommitted)所有事务都可以看到其他未提交事务的执行结果。很少用于实际应用,因为它的性能也不比其他级别好多少。会产生脏读,不可重复读以及幻读
2. 已提交读(read-committed)这是大多数数据库系统的默认隔离级别(但不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。会产生幻读
3. 可重复读(repeatable-read)这是 MySQL 的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,也会导致幻读 (Phantom Read)。幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的 “幻影” 行。InnoDB 存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
4. 串行化(serializable)这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
MyISAM 表锁
1. 共享读锁(S)之间是兼容的,但共享读锁(S)和排他写锁(X)之间,以及排他写锁之间(X)是互斥的,也就是说读和写是串行的。
2. 在一定条件下,MyISAM 允许查询和插入并发执行,利用这一点来解决应用中对同一表和插入的锁竞争问题。
3.MyISAM 默认锁调度机制是写优先,不一定适合所有应用,可以通过设置 LOW_PRIPORITY_UPDATES 参数,或在 INSERT、UPDATE、DELETE 语句中指定 LOW_PRIORITY 选项来调节读写锁的争用。
4. 由于表锁的锁定粒度大,读写之间又是串行的,因此,如果更新操作较多,MyISAM 表可能会出现严重的锁等待,可以考虑采用 InnoDB 表来减少锁冲突。
InnoDB 表
1.InnoDB 的行锁是基于索引实现的,如果不通过索引访问数据,InnoDB 会使用表锁。
2. 在不同的隔离级别下,InnoDB 的锁机制和一致性读策略不同。
3.MySQL 的恢复和复制对 InnoDB 锁机制和一致性读策略也有较大影响。
4. 注意死锁,锁冲突甚至死锁很难完全避免。
减少锁冲突和死锁
1. 尽量使用较低的隔离级别
2. 精心设计索引,尽量使用索引访问数据,使加锁更精确,从而减少锁冲突的机会。
3. 选择合理的事务大小,小事务发生锁冲突的几率也更小。
4. 给记录集显示加锁时,最好一次性请求足够级别的锁。比如要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁。
5. 不同程序访问一组表时,应尽量约定以相同的顺序访问,这样可以减少死锁的机会。
6. 尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响。
7. 不要申请超过实际需要的锁级别;除非必须,查询时不要显示加锁。
分表 (分库) 的策略
根据业务数据量和业务复杂度来拆分,业务拆分可参考:取余、hash、range 范围; 表结构拆分:垂直、水平。
binlog 日志
通常用与:数据恢复、主从复制
可设置格式: statement 、 row 、 mixed(通常使用 row 或者 mixed )
主从同步
可用于: 实现读写分离 、 数据备份高可用、负载均衡提高性能、版本升级测试
流程:
1. 从节点开启 start slave 命令之后,创建一个 IO 进程连接到主节点
2. 连接成功之后,主节点创建一个 log dump 线程 (主节点会为每一个从节点创一个 log dump 线程)
3. 当 binlog 发生变化时,主节点的 dump log 线程会读取 bin-log 内容并发送给从节点
4. 主节点 dump log 线程读取 bin-log 的内容时会对主节点的 bin-log 加锁,读取完成在发送给从节点之前释放锁
5. 从节点的 IO 线程接收主节点发送的 binlog 内容,并将其写入本地 relay log 文件中
6. 主从节点通过 binlog 文件 + position 偏移量定位主从同步的位置,从节点会保存接收到的 position 偏移量,如果从节点发生宕机重启,自动从 postion 位置发起同步
7. 从节点的 SQL 线程复制读取本地 relay log 的内容,解析成具体的操作并执行,保证主从数据一致性
模式: 异步模式(默认方式) 、 全同步模式 、 半同步模式 (几乎不用)
limit 优化
1. 主键阈值法: 通过条件推算出符合条件的主键最大值 & 最小值来避免
2. 配合 es 来分页
redis 缓存和 mysql 数据一致性
1. 先更新 redis 再更新数据库
2. 先更新数据库,再更新 redis
3. 先删除缓存再更新数据库
问题:以上在并发同时请求时候都有可能出现数据不一致的情况。
解决: 1. 延时双删除 ;2. 串行获取(队列化),如:查询队列 + 更新队列
A 字段分组且 B 字段最大的数据?
# 表结构:name,type,age
select * from users where (type, age) in (select type, max(age) from users group by type)
order by age desc;
# join操作
SELECT u.* FROM users u
JOIN (
SELECT type, Max(age) AS max_age FROM users GROUP BY type
) t
ON u.type = t.type AND u.age = t.max_age
order by age desc;
字段值置换更新
对字段值置换更新,如:字段 status 为 0、1,需把 1 更新为 0,0 更新为 1。
# 数学置换 x = 0 + 1
update `table` set status = 1 - status where 1=1;
# 语法 case when then
UPDATE users
SET type = CASE
WHEN type = 1 THEN 2
WHEN type = 2 THEN 1
ELSE type
END
WHERE 1=1;
Redis 相关
redis 数据结构
常见的数据结构类型有:String、List、Set、 Hash、ZSet
redis 为什么快
Redis 采用的是基于内存的单线程 / 多路 IO 复用模型的 KV 数据库, 由 C 语言编写,官方数据 QPS 达到 100000+。
完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,查找和操作的时间复杂度都是 O (1); Redis 中的数据结构简单,对数据操作也简单; 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或多线程的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,因为没有可能出现死锁而导致的性能消耗; 使用多路 I/O 复用模型,非阻塞 IO;多路 I/O 复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力。在空闲时,会阻塞当前线程,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。 这里 “多路” 指的是多个网络连接,“复用” 指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存的操作不会成为影响 Redis 性能的瓶颈, 所以造就了 Redis 具有很高的吞吐量。
redis 的持久化
RDB 快照(每隔一段时间写入快照的方式)
AOF( 将每一个收到的写命令都通过 write 函数追加到 appendonly.aof 文件)
Redis 内存满了的解决方法
增加内存,内存淘汰策略,搭集群
缓存穿透,雪崩,预热 / 更新
缓存穿透:是指用户查询数据库没有的数据,缓存中也不会有。缓存中找不到,都要去数据库再查一遍,然后返回空(进行了两次无用的查询)。绕过缓存直接查数据库,也是缓存命中率的问题。 解决:拦截查询或空结果缓存
缓存雪崩:同一时间大量缓存失效,从而数据库查询压力暴增导致服务不可用或宕机。 解决:缓存过期策略、做多级缓存、缓存标记等等,总之就是避免同一时间出现大量缓存数据过期;
预热 / 更新:手动或定时对缓存数据进行清理、加载或更新操作。
redis 主从哨兵和集群的区别
一、架构不同
- redis 主从:一主多从;
- redis 集群:多主多从;
二、存储不同 - redis 主从:主节点和从节点都是存储所有数据;
- redis 集群:数据的存储是通过 hash 计算 16384 的槽位,算出要将数据存储的节点,然后进行存储;
三、选举不同 - redis 主从:通过启动 redis 自带的哨兵(sentinel)集群进行选举,也可以是一个哨兵
- 选举流程:先发现主节点 fail 的哨兵,将成为哨兵中的 leader,之后的主节点选举将通过这个 leader 进行故障转移操作,从存活的 slave 中选举新的 master,新的 master 选举同集群的 master 节点选举类似;
- redis 集群:集群可以自己进行选举
- 选举流程:
1. 当主节点挂掉,从节点就会广播该主节点 fail;
2. 延迟时间后进行选举(延迟的时间算法为:延迟时间 + 随机数 + rank*1000,从节点数据越多,rank 越小,因为主从数据复制是异步进行的,所以 所有的从节点的数据可能会不同),延迟的原因是等待主节点 fail 广播到所有存活的主节点,否则主节点会拒绝参加选举;
3. 参加选举的从节点向所有的存活的节点发送 ack 请求,但只有主节点会回复它,并且主节点只会回复第一个到达参加选举的从节点,一半以上的主节点回复,该节点就会成为主节点,广播告诉其他节点该节点成为主节点。
四、节点扩容不同
1.redis 主从:只能扩容从节点,无法对主节点进行扩容;
2.redis 集群:可以扩容整个主从节点,但是扩容后需要进行槽位的分片,否则无法进行数据写入,命令为:其中的 192.168.0.61:8001 为新加入的主从节点;
/usr/local/redis-5.0.3/src/redis-cli -a zhuge --cluster reshard 192.168.0.61:8001
MongoDB 相关
mongodb 是什么?
MongoDB 是由 C++ 语言编写,是一个基于分布式文件存储的开源 NoSQL 数据库 (文档数据库)。 再高负载的情况下,添加更多的节点,可以保证服务器性能。 旨在提供可扩展的高性能数据存储解决方案。mongodb 有哪些特点?
1.MongoDB 是一个面向文档存储的数据库,操作起来比较简单和容易。
2. 可以在 MongoDB 记录中设置任何属性的索引 (如:FirstName=”Sameer”,Address=”8 Gandhi Road”) 实现更快排序。
3. 可以通过本地或者网络创建数据镜像,这使得 MongoDB 有更强的扩展性。
4. 负载的增加(更多的存储空间和更强的处理能力),它可以分布在网络中的其他节点上这就是所谓的分片。
5.MongoDb 支持丰富的查询表达式。查询指令使用 JSON 形式的标记,可轻易查询文档中内嵌的对象及数组。
6.MongoDb 使用 update () 命令可以实现替换完成的文档(数据)或者一些指定的数据字段 。
7.Mongodb 中的 Map/reduce 主要是用来对数据进行批量处理和聚合操作。
8.Map 和 Reduce,Map 函数调用 emit (key,value) 遍历集合中的所有记录,将 key 与 value 传给 Reduce 函数处理。
9.Map 和 Reduce 函数是用 Javascript 编写的,可以通过 db.runCommand 或 mapreduce 命令来执行 MapReduce 操作。
10.GridFS 是 MongoDB 中的一个内置功能,可以用于存放大量小文件。
11.MongoDB 允许在服务端执行脚本, 可以用 Javascript 编写某个函数,直接在服务端执行,也可以把函数的定义存储在服务端,下次直接调用即可。
MySQL 与 MongoDB 之间最基本的差别是什么?
MySQL 和 MongoDB 两者都是免费开源的数据库。MySQL 和 MongoDB 有许多基本差别包括数据的表示 (data representation),查询,关系,事务,schema 的设计和定义,标准化 (normalization),速度和性能。通过比较 MySQL 和 MongoDB,实际上我们是在比较关系型和非关系型数据库 (数据存储结构不同)。
MongoDB 的特点 / 为啥是最好的 Nosql 数据库?
面向文件的、高可用(稳定可靠性能)、易扩展性、丰富的查询语言
MongoDB 的集合 collection
集合就是一组 MongoDB 文档。它相当于关系型数据库中的表概念。集合位于单独的一个数据库中。一个集合内的多个文档可以有多个不同的字段。一般来说,集合中的文档都有着相同或相关的目的。
MongoDB 的文档 Document
文档由一组 key value 组成(json)。是动态模式:同一集合里的文档可能是不同的字段和结构。文档相当于关系型数据库中 table 的一条记录。
什么是 mongod?
mongod 是处理 MongoDB 系统的主要进程。它处理数据请求,管理数据存储,和执行后台管理操作。当我们运行 mongod 命令意味着正在启动 MongoDB 进程,并且在后台运行。
默认端口: “27017”、默认存储路径:”/data/db”
MongoDB 中的副本集
在 MongoDB 中副本集由一组 MongoDB 实例组成,包括一个主节点多个次节点,MongoDB 客户端的所有数据都写入主节点 (Primary),副节点从主节点同步写入数据,以保持所有复制集内存储相同的数据,提高数据可用性。
Elasticsearch 相关
es 简介
一个高扩展、生态化、开源的全文检索引擎,它可以准实时地快速存储、搜索、分析海量的数据。全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置。当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户。
es 查询流程
1. 客户端请求发给某个节点
2. 节点转发给每个分片,查询每个分片上的前 n 条
3. 结果返回给节点,合并整合数据,提取前 n 条
4. 最后返回给请求客户端
ES 为什么比 mysql 快
es 索引存于内存之中,采用倒序索引,结构为” 分词 <=>id” 的方式,能通过分词快速定位文档(分词索引 vs b+tree)