本篇汇集网络上各家之长与个人整理的一些PHP面试题,我就不要 Face 的挂原创了 🤡。
希望大家把知道或实战面试的题 评论或私信发我,我们一起加油。
内容原作者看到,请留言原文地址。
求推:因口罩原因,让本来…雪上加霜,我计划换个新环境,各位大佬如有机会,请联系我 (Chon-Wang)。
一、PHP 面试题
1.1 基础篇
PHP 7 新特性
常用模板引擎
Smarty、Blade
面向对象的七大原则
- 单一职责原则
- 开放封闭原则
- 里式替换原则
- 依赖倒置原则
- 接口隔离原则
- 迪米特原则
- 合成/聚合复用原则
什么是面向对象?主要特征是什么?
面向对象是程序的一种设计方式, 它利于提高程序的重用性, 使程序结构更加清晰。
主要特征 :封装、继承、多态。
类、成员方法、成员属性的修饰符有哪些?他们之间的区别是什么?
PHP 支持多继承吗?如何实现多继承?
不支持多继承, 但可以通过
Trait
实现。
PHP的基本变量类型
四种标量类型 :boolean (布尔型)、integer (整型)、float (浮点型, 也称作 double)、string (字符串)
四种复合类型 :array (数组)、object (对象)、callable、iterable
最后是两种特殊类型 :resource(资源)、NULL(NULL)
列举 30 个常用的函数
常用魔术方法及场景
常用魔术常量
常用超全局变量
PHP 错误级别介绍与设置错误级别的方式
- PHP 官方手册有 16 个级别的错误
- 常见错误级别有:
E_ERROR
: 致命的运行错误并阻止脚本执行E_WARNING
: 运行时警告E_PARSE
: 解析错误E_NOTICE
: 注意E_USER_ERROR
: 用户生成的错误消息E_USER_WARNING
: 用户生成的警告E_USER_NOTICE
: 用户生成的注意E_ALL
: 所有的错误、警告、注意
设置错误级别的方式:
- 修改 php.ini 配置文件
- 例:
error_reporting = E_ALL & ~E_NOTICE
, 表示报告除E_NOTICE
之外的所有错误。error_reporting
函数设置
- 例:
error_reporting(E_ERROR | E_WARNING);
PHP 异常处理
<?php # php 使用 try catch 来捕获异常 # 例: try { if ($count > 10) throw new Exception('数量不可超过 10 个'); if ($width > 100) throw new widthException('宽度不可超过 100 米'); if ($height > 150) throw new heightException('高度不可超过 150 米'); } catch (Exception $e){ # 常用异常捕获信息 echo $e->getLine(); echo $e->getCode(); echo $e->getFile(); echo $e->getMessage(); } catch (heightException $e){ echo $e->getMessage(); } catch (widthException $e){ echo $e->getMessage(); }
1.2 进阶篇
PSR 标准规范
设计模式原则与你知道的设计模式有哪些?
PHP 垃圾回收机制(GC)
- 使用 引用计数机制
- 将每个 PHP 变量保存在一个叫
zval 变量容器
中。zval 变量容器
包含 变量的类型、变量值、 is_res、refcountis_ref
用于标识该变量是否为引用集合或变量。refcount
表示指向当前变量的个数。- 默认打开垃圾回收机制, 当发现有存在循环引用的zval时, 就会把其投入到根缓冲区, 当根缓冲区达到配置文件中的指定数量后, 就会进行垃圾回收, 以此解决循环引用导致的内存泄露问题
- 如果引用计数减少到零, 所在变量容器将被清除(free), 不属于垃圾;
- 如果一个zval的引用计数减少后还大于0, 那么它会进入垃圾周期。
- 其次, 在一个垃圾周期中, 通过检查引用计数是否减1, 并且检查哪些变量容器的引用次数是零, 来发现哪部分是垃圾。
PHP 底层原理
PHP代码执行过程:
- 启动 php 及 zend 引擎
- 加载注册拓展模块
- 对代码进行词法/语法分析
- 编译成opcode(opcache)
- 执行 opcode
PHP 的四层体系, 从下至上分为四层:
- Zend 引擎
- Zend 引擎整体用C语言实现,是 PHP 的内核部分,它负责将 PHP 代码翻译(词法、语法解析等一系列编译过程)为可执行的 opcode 操作码,并实现相应的处理方法、基本的数据结构(如 hashtable、OO)、内存分配及管理、提供相应的 API 方法供外部调用。
- 扩展层
- 围绕着 Zend 引擎,Extensions 通过组件化的方式提供各种基础服务,我们常见的各种内置函数(例如变量操作函数、字符串操作函数等)以及标准库等都是通过 Extensions 来实现。
- SAPI(服务器应用程序编程接口)
- SAPI 通过一系列钩子函数,使得 PHP 可以和外围交互数据,这是 PHP 非常优雅和成功的一个设计,通过 SAPI 成功的将 PHP 本身和上层应用解耦隔离,PHP 可以不再考虑如何针对不同应用进行兼容,而应用本身也可以针对自己的特点实现不同的处理方式。
- Application(上层应用)
- 这就是我们平时编写的 PHP 程序,通过不同的 SAPI 方式得到各种各样的应用模式
PHP 运行模式, 各自的原理
这一块的知识,网上一堆,各有各的说法,我借鉴并进行整理,各位大佬请提提意见。
先了解一下 CGI :
- CGI(Common Gateway Interface)全称是“通用网关接口”,是一种让 客户端 与 Web服务器 程序进行通信(数据传输)的协议。
- CGI 用来规范 Web服务器 传输到 解析器(例: php-cgi) 中的数据类型以及数据格式,包括URL、查询字符串、POST数据、HTTP header等。
- 解析器只要符合 CGI 标准,就能作为一个 cgi 程序与 Web 服务器交互。
- 一次请求都要 fork 一个进程, 然后销毁,也就是(fork-and-execute)模式,性能较低。
PHP 运行模式:
FastCGI
- FastCGI(Fast Common Gateway Interface)全称是“快速通用网关接口”,也是一种让 客户端 与 Web服务器 程序进行通信(数据传输)的协议。。
FastCGI
是CGI
模式的升级版, 目的是避免重复解析配置文件和初始执行环境。- 像是一个常驻型
CGI
, 可以一直处理请求不结束该进程。- 多进程,将比
CGI
消耗更多的服务器内存。- 可平滑停止/启动进程。
PHPCGI
- 一个
CGI
程序,是 PHP 实现CGI
的 PHP解析器。- 用于解析请求,返回结果。
- 不可平滑重启。
PHP-FPM
PHP-FPM
为FastCGI
的进程管理器。- 工作原理为:
- Web 服务器启动时,加载启动
PHP-FPM
,PHP-FPM
读取配置文件,初始化运行环境。PHP-FPM
创建一个 Master 主进程和若干个 Worker 进程,负责监听端口,等待接收请求,每个进程内都调用一个PHP-CGI
。- 用户发起请求, Web服务器接收请求并转发给
PHP-FPM
,空闲的 Worker 进程以抢占式的接收该请求。- 监听接收后,
PHPCGI
解析请求,开始执行业务处理代码, 处理完成后,按照 CGI 规定的格式返给 Worker 进程, 然后退出进程, 此时 Worker 进程变成空闲状态等待下次请求。- Worker 进程将结果返给 Web服务器, Web服务器接收返回内容并返回给客户端。
MODULE
apache + php
运行时,默认使用的是module 模式
,它把 php 作为apache
的模块随apache
启动而启动,接收到用户请求时则直接通过调用mod_php 模块
进行处理。
PHP-CLI
PHP-CLI 模式
属于命令行模式- 在终端直接输入
php 文件名.php
就可直接运行代码- 没有超时时间
echo
、var_dump
、phpinfo
等输出会直接打印到控制台中
PHP 数组底层原理
- 底层实现是通过散列表(hash table) + 双向链表(解决hash冲突)
- hashtable:将不同的关键字(key)通过映射函数计算得到散列值(Bucket->h) 从而直接索引到对应的Bucket
- hash表保存当前循环的指针, 所以foreach 比for更快
- Bucket:保存数组元素的key和value, 以及散列值h
- 如何保证有序性
- 散列函数和元素数组(Bucket)中间添加一层大小和存储元素数组相同的映射表。
- 用于存储元素在实际存储数组中的下标
- 元素按照映射表的先后顺序插入实际存储数组中
- 映射表只是原理上的思路, 实际上并不会有实际的映射表, 而是初始化的时候分配Bucket内存的同时, 还会分配相同数量的 uint32_t 大小的空间, 然后将 arData 偏移到存储元素数组的位置。
- 解决hash重复(php使用的链表法):
- 链表法:不同关键字指向同一个单元时, 使用链表保存关键字(遍历链表匹配key)
- 开放寻址法:当关键字指向已经存在数据的单元的时候, 继续寻找其他单元, 直到找到可用单元(占用其他单元位置, 更容易出现hash冲突, 性能下降)
- 基础知识
- 链表:队列、栈、双向链表
- 链表:元素 + 指向下一元素的指针
- 双向链表:指向上一元素的指针 + 元素 + 指向下一元素的指针
PHP 数组遍历为什么能保证有序
- bucket
- 映射表
依赖注入实现方式
- 构造函数依赖注入(如果依赖的类多,就会造成构造函数的形参特别多)
- set 方式注入(如果依赖的类多,那 set 的方法也特别多)
- 采用类似 Laravel 服务容器 实现依赖注入(调用时使用闭包,这样就做到 使用才实例化)
PHP 内存溢出解决
- 增加 PHP 可用内存大小
- 对大数组分批处理或 yield 处理
- 及时销毁大数组或变量
- 根据业务规则,尽可能的少用 静态变量
- 数据库操作完,及时关闭
1.3 对比篇
define() 与 const 区别
- 两者都是定义常量使用
- const 是语言结构, define 是函数
- const 可在类中使用, define 不可以
- const 可以不同命名空间定义相同名称的常量, define 不可以
- const 大小写敏感, define 默认敏感, 可通过第三个参数为 true 设置为不敏感
include 和 require 的区别是什么?
require 是无条件包含, 也就是如果一个流程里加入 require , 无论条件成立与否都会先执行 require , 当文件不存在或者无法打开的时候, 会提示错误, 并且会终止程序执行
include有返回值, 而require没有 (可能因为如此 require 的速度比 include 快), 如果被包含的文件不存在的话, 那么会提示一个错误, 但是程序会继续执行下去
单引号与双引号的区别
- 单引号不解析变量,双引号解析变量
- 单引号只可解析单引号及转义符本身,双引号可解析更多的特殊字符。例:
\n
、\r
、\t
- 解析速度不同,因单引号不考虑变量解析,所以比双引号要快
传值与传引用的区别
- 按值传递 :函数范围内对值的任何改变在函数外部都会被忽略
- 按引用传递 :函数范围内对值的任何改变在函数外部也能反映出这些修改, 因为传引用传的是内存地址。
- 优缺点:按值传递时, php 必须复制值。特别是对于大型的字符串和对象来说, 这将会是一个代价很大的操作。按引用传递则不需要复制值, 对于性能提高很有好处。
cookie 与 session 的区别, 禁用 cookie 后如何传递 session
== 与 === 的区别
- == 要求两侧的值相同,弱类型判断
- === 要求两侧的值与类型都得相同
echo、print、print_r、var_dump 的区别
- print_r 与 var_dump 是函数, echo、print 是语句
echo
用于输出数值变量或字符串,可以逗号分隔输出多个。数组输出 Array, 对象报错。例:echo $a, $b;
print $a;
print_r
可简单输出 字符串、数字、数组、对象, 但 布尔(false)、null 都是打印\n
var_dump
可输出所有字符串、数字、布尔、数组、对象。包括键、值、类型、长度。
isset 与 empty 的区别
isset
检测变量是否设置并且非 NULLempty
检测变量的值是否为 false, PHP 中 0、false、[]、‘0’ 、‘’ 所代表的布尔值都是 false
for 与 foreach 的区别,哪个更快?为什么?
- for 需要预先知道数组的长度, foreach 不需要
- foreach 效果要比 for 高,foreach 直接通过结构体中的 next 指针获取下一个值, 而 for 循环需要根据 key 先进行一次 hash 才得到值。
1.4 实践题
微信实际支付成功, 但回调失败如何处理?
- 临时页面处理:在返回页增加 “支付成功” 与 “遇到问题, 联系客服” 按钮选项。这两个按钮都重新调取微信获取支付结果的接口,成功或失败都跳转一个中间页。
- 定时处理:如没有临时页, 则根据业务情况, 设置合适的回调周期, 周期性的调取 “获取微信支付结果的接口” , 将支付结果更新至数据库。
如何获取客户端 IP 与服务端 IP
# 客户端IP $_SERVER['REMOTE_ADDR'] # 服务端IP $_SERVER['SERVER_ADDR'] # 客户端IP(代理穿透) $_SERVER['HTTP_X_FORWARDED_FOR'] /* * 获取客户端IP地址 * @return string */ function get_client_ip() { if(getenv('HTTP_CLIENT_IP')){ $client_ip = getenv('HTTP_CLIENT_IP'); } elseif(getenv('HTTP_X_FORWARDED_FOR')) { $client_ip = getenv('HTTP_X_FORWARDED_FOR'); } elseif(getenv('REMOTE_ADDR')) { $client_ip = getenv('REMOTE_ADDR'); } else { $client_ip = $_SERVER['REMOTE_ADDR']; } return $client_ip; } /* 获取服务器端IP地址 * @return string */ function get_server_ip() { if (isset($_SERVER)) { if($_SERVER['SERVER_ADDR']) { $server_ip = $_SERVER['SERVER_ADDR']; } else { $server_ip = $_SERVER['LOCAL_ADDR']; } } else { $server_ip = getenv('SERVER_ADDR'); } return $server_ip; }
不使用临时变量交换两个变量的值
list($a, $b) = array($b, $a); # 或 数组下标 $array[0] = $a; $array[1] = $b; $a = $array[1]; $b = $array[0];
通过 $_FILES 获取上传文件类型可能受到黑客伪造, 如何判断用户上传的图像文件类型真实可靠
getimagesize
函数获取的数组结果下标为 2 的值代表文件的类型。1 = GIF, 2 = JPG, 3 = PNG, 4 = SWF, 5 = PSD, 6 = BMP, 7 = TIFF(intel byte order), 8 = TIFF(motorola byte order), 9 = JPC, 10 = JP2, 11 = JPX, 12 = JB2, 13 = SWC, 14 = IFF, 15 = WBMP, 16 = XBM,
短信验证码防刷机制
- 前端时间控制:60 秒后才能再次发送,但刷新页面就会又能发送
- Token 校验:校验通过才发送,这时还可以将 60 秒缓存
- 图形验证码限制
- 次数限制:根据业务场景,例: 同一手机号,24小时内不可超过5条
- 相同返回:例: 30 分钟之内,如果验证码未使用,则返回同一个验证码
- 短信预警机制:例:检测短时发送量,达到预警值,就给管理员发送提醒。
- IP限制
如何实现 session 共享
- 将 session 持久化至数据库
- 将 session 保存 至 Redis、Memcache
如何实现单点登录
PHP 如何解决跨域问题?
# 1. 代理, 由 php 调用 php 接口 # 2. Nginx 反向代理
# 3. 允许所有域名访问 header(“Access-Control-Allow-Origin:*”); header(‘Access-Control-Allow-Methods:POST’);// 表示只允许POST请求 header(‘Access-Control-Allow-Headers:x-requested-with, content-type’); # 4. 允许单个域名访问 header(‘Access-Control-Allow-Origin:http://www.test.cn‘); header(‘Access-Control-Allow-Methods:POST’); //表示只允许POST请求 header(‘Access-Control-Allow-Headers:x-requested-with, content-type’); //请求头的限制
# 5. 允许多个域名访问 $array = ['域名1', '域名2']; public function setheader() { // 获取当前跨域域名 $origin = isset($_SERVER[‘HTTP_ORIGIN’]) ? $_SERVER[‘HTTP_ORIGIN’] : ‘’; if (in_array($origin, $array)) { header(‘Access-Control-Allow-Origin:’ . $origin); # 允许 $array 数组内的域名跨域访问 header(‘Access-Control-Allow-Methods:POST,GET’); # 响应类型 header(‘Access-Control-Allow-Credentials: true’); # 带 cookie 的跨域访问 header(‘Access-Control-Allow-Headers:x-requested-with,Content-Type,X-CSRF-Token’); # 响应头设置 } }
1.5 算法或代码实现题
快速排序
function quick_sort($array) { if (count($array) <= 1) return $array; $array_count = count($array); # 数组数量 $key = $array[0]; # 对比值 $left_arr = array(); # 接收小于对比值的数 $right_arr = array(); # 接收大于对比值的数 for ($i=1; $i<$array_count; $i++){ if ($array[$i] <= $key){ $left_arr[] = $array[$i]; }else{ $right_arr[] = $array[$i]; } } $left_arr = quick_sort($left_arr); $right_arr = quick_sort($right_arr); return array_merge($left_arr, array($key), $right_arr); }
冒泡排序
$list = [2, 4, 1, 7, 9, 3]; $len = count($list); for ($i = $len - 1; $i > 0; $i--) { $flag = 1; for ($j = 0; $j < $i; $j++) { if ($list[$j] > $list[$j + 1]) { $tmp = $list[$j]; $list[$j] = $list[$j + 1]; $list[$j + 1] = $tmp; $flag = 0; } } if($flag) break; } var_dump($list);
二分查找
//二分查找(数组里查找某个元素) function bin_sch($array, $low, $high, $k){ if ($low <= $high){ $mid = intval(($low+$high)/2); if ($array[$mid] == $k){ return $mid; }elseif ($k < $array[$mid]){ return bin_sch($array, $low, $mid-1, $k); }else{ return bin_sch($array, $mid+1, $high, $k); } } return -1; }
顺序查找
function seq_sch($array, $n, $k){ $array[$n] = $k; for($i=0; $i<$n; $i++){ if($array[$i]==$k){ break; } } if ($i<$n){ return $i; }else{ return -1; } }
插入排序
function insertSort($arr) { $count = count($arr); for ($i = 1; $i < $count; $i++) { $tmp = $arr[$i]; for ($j = $i - 1; $j >= 0; $j--) { // 从小到大 【<】 从大到小 【>】 if ($tmp < $arr[$j]) { $arr[$j] = $arr[$j + 1]; $arr[$j + 1] = $tmp; } else { break; } } } return $arr; }
选择排序
function selectSort($arr){ for ($i=1;$i<count($arr);$i++){ $p = $i; for ($j = $i + 1; $j < count($arr);$j++){ if ($arr[$p] > $arr[$j]){ $p = $j; } } if ($p != $i){ $tmp = $arr[$p]; $arr[$i] = $tmp; $arr[$p] = $arr[$i]; } } return $arr; }
字符串反转
<?php function str_rev ($str) { # true 模拟死循环, $i 为长度 for ($i = 0; true; $i++) //true模拟死循环 { if (!isset($str[$i])) break; } $return_str = ''; for ($j = $i - 1; $j >=0 ; $j -- ) { $return_str .= $str[$j]; } return $return_str; } # 或 function str_rev($str,$encoding='utf-8'){ $result = ''; $len = mb_strlen($str); for($i=$len-1; $i>=0; $i--){ $result .= mb_substr($str,$i,1,$encoding); } return $result; }
字符串长度
function strlen($str) { if ($str == '') return 0; $count = 0; while (1){ if ($str[$count] != NULL){ $count++; continue; }else{ break; } } return $count; }
写一个可以从 URL 链接中取出文件的扩展名
function getExt($url) { $arr = parse_url($url);//parse_url解析一个 URL 并返回一个关联数组,包含在 URL 中出现的各种组成部分 //'scheme' => string 'http' (length=4) //'host' => string 'www.sina.com.cn' (length=15) //'path' => string '/abc/de/fg.php' (length=14) //'query' => string 'id=1' (length=4) $file = basename($arr['path']);// basename函数返回路径中的文件名部分 $ext = explode('.', $file); return $ext[count($ext)-1]; }
写一个二维数组排序算法函数
/** * 二维数组排序 * @param $arrays * @param $sort_key * @param $sort_order (SORT_DESC 降序;SORT_ASC 升序) * @param $sort_type (请看官方文档 array_multisort 函数的说明) * @return array|false */ function array_sort($arrays,$sort_key,$sort_order=SORT_DESC,$sort_type=SORT_NUMERIC ){ if(is_array($arrays)){ foreach ($arrays as $array){ if(is_array($array)){ $key_arrays[] = $array[$sort_key]; }else{ return false; } } }else{ return false; } array_multisort($key_arrays,$sort_order,$sort_type,$arrays); return $arrays; }
PHP 遍历文件夹
- 遍历某一个目录下面的文件和文件夹
$dir = __DIR__; if (is_dir($dir)) { $array = scandir($dir, 1); foreach($array as $key => $value) { if ($value == '.' || $value == '..') { unset($array[$key]); continue; } } } else { echo '不是一个目录'; } print_r($array);
- 写出一个函数对文件目录做遍历
function loopDir($dir){ $handle = opendir($dir); while(false !==($file =readdir($handle))){ if($file!='.'&&$file!='..'){ echo $file."<br>"; if(filetype($dir.'/'.$file)=='dir'){ loopDir($dir.'/'.$file); } } } } $dir = '/'; loopDir($dir);
- 遍历某个目录下面的所有文件和文件夹(包含子文件夹的目录和文件也要依次读取出来)
$dir = __DIR__; function my_dir($dir) { $files = array(); if(@$handle = opendir($dir)) { while(($file = readdir($handle)) !== false) { if($file != ".." && $file != ".") { if(is_dir($dir."/".$file)) { $files[$file] = my_dir($dir."/".$file); } else { $files[] = $file; } } } closedir($handle); return $files; } } print_r(my_dir($dir));
写一个函数, 将 “open_door” 转为 “OpenDoor”
function ucstring($string){ return str_replace(' ', '', ucwords(str_replace('_', ' ', $string))); } # 或 function ucstring($string){ $array = explode('_', $string); foreach($array as $key=>$val){ $new_string .= ucwords($val); } return $new_string; }
写一个函数, 将 1234567890 转为 1,234,567,890 逗号隔开
function numFormate($number){ $str = (string) $number; $string = strrev($str); # 先反转 $length = strlen($string); # 获取长度 for($i = 0; $i < $length; $i = $i+3) { $new_string .= substr($string, $i, 3) . ','; } return strrev(rtrim($new_string, ',')); }
取扩展名
function get_ext1($file_name){ return strrchr($file_name, ‘.’); } function get_ext2($file_name){ return substr($file_name,strrpos($file_name, ‘.’)); } function get_ext3($file_name){ return array_pop(explode(‘.’, $file_name)); } function get_ext4($file_name){ $p = pathinfo($file_name); return $p['extension']; } function get_ext5($file_name){ return strrev(substr(strrev($file_name), 0, strpos(strrev($file_name), ‘.’))); } function getExt($url){ $arr = parse_url($url); $file = basename($arr['path']); $ext = explode(“.”,$file); return $ext[1]; }
求两个日期的差数, 例如2022-2-5 ~ 2022-3-6 的日期差数
function get_days($date1, $date2){ $time1 = strtotime($date1); $time2 = strtotime($date2); return ($time2-$time1)/86400; }
PHP 打印出前一天的时间, 格式: 2022年01月01号 12:00:00
echo date("Y年m月d日 H:i:s", strtotime("-1 day"));
写出一个函数,参数为年份和月份,输出结果为指定月的天数
function getDayCount($year, $month) { $date_string = $year . '-' . $month . '-1'; return date('t', strtotime($date_string)); }
获取今天是本月所在的第几周
echo ceil(date('d')/7);
单例模式并实现 mysqli 数据库连接
class Db { private static $instance; public $handle; Private function __construct($host,$username,$password,$dbname) { $this->handle=NULL; $this->getcon($host,$username,$password,$dbname); } public static function getBb() { self::$instance=new Db(); return self::$instance; } private function getcon($host,$username,$password,$dbname) { if($this->handle!=NULL){ return true; } $this->handle=mysqli_connect($host,$username,$password,$dbname); } }
以下表达式运算结果是?
# 案例1 $a = "aabbzz"; $a++; echo $a; # 输出 aabcaa # 案例2 if ('1e3' == '1000') echo 'yes'; # 输出 yes # 因为 1e3 是科学计数法, 1e3 等于 1 乘以10 的 3 次方 # 案例3 $data = ['a','b','c']; foreach($data as $k=>$v){ $v = &$data[$k]; } # 输出数组值为 ['b','c','c'] # 案例4 $a= 0.1; $b = 0.7; if($a+$b == 0.8){ echo true; } else { echo false; } # 输出 空 # echo false 输出空 , echo true 输出 1 # php 浮点计算会转为 二进制, 0.1 + 0.7 = 0.7999999.... # 案例5 $a= 0; $b= 0; if($a = 3>0 || $b = 3>0){ $a++; $b++; } echo $a,$b; # 输出 11 # 算数运算符 > 比较运算符 > 逻辑运算符(!除外) > 赋值
二 、MySQL 面试题
2.1 其他面试题
2.2 基础篇
MySQL 三范式
一条 MySQL 语句执行步骤
Server 层按顺序执行 SQL 的步骤为:
- 客户端请求 -> 连接器(验证用户身份, 给予权限)
- 查询缓存(存在缓存则直接返回, 不存在则执行后续操作)
- 分析器(对 SQL 进行词法分析和语法分析操作)
- 优化器(主要对执行的 SQL 优化选择最优的执行方案方法)
- 执行器(执行时会先看用户是否有执行权限, 有才去使用这个引擎提供的接口)-> 去引擎层获取数据返回(如果开启查询缓存则会缓存查询结果)
分区、分库、分表的理解及注意事项
- 流程
评估容量和分表数量-> 根据业务选定分表key->分表规则(hash、取余、range)->执行->考虑扩容问题- 水平拆分
- 根据字段水平拆分为多个表
- 每个表的结构相同
- 所有分表的合集是全量数量
- 垂直拆分
- 根据字段垂直拆分
- 表结构不一样, 分表的同一个关联行是一条完整的数据
- 扩展表, 热点字段和非热点字段的拆分(列表和详情的拆分)
- 获取数据时, 尽量避免使用join, 而是两次查询结果组合
- 问题
- 跨库join问题
- 全局表:需要关联部分系统表的场景
- 冗余法:常用字段进行冗余
- 组装法:多次查询的结果进行组装
- 跨节点的分页、排序、函数问题
- 事务一致性
- 全局主键id
- 使用uuid -> 会降低聚簇索引效率
- 使用分布式自增id
- 扩容问题
- 升级从库
- 从库升级为主库, 数据一致, 只需要删除冗余数据即可
- 成倍扩容:需要在加一倍从库
- 双写迁移:
- 新数据进行双写, 同时写进新老数据库
- 旧数据复制到新数据库
- 以老数据库为准, 验证数据一致性之后删除冗余数据
如何保证 Redis 和 MySQL 数据一致性
- 采用延时双删策略
- 异步更新缓存(基于订阅 binlog 的同步机制)
2.3 对比篇
关系型数据库 与 非关系型数据库的区别
关系型数据库
- 容易理解, 因为它采用了关系模型来组织数据。
- 可以保持数据的一致性。
- 数据更新的开销比较小。
- 支持复杂查询(带 where 子句的查询)
非关系型数据库
- 无需经过 SQL 层的解析, 读写效率高。
- 基于键值对, 读写性能很高, 易于扩展
- 可以支持多种类型数据的存储, 如图片, 文档等等。
- 扩展(可分为内存性数据库以及文档型数据库, 比如 Redis, MongoDB, HBase 等, 适合场景:数据量大高可用的日志系统/地理位置存储系统)。
char 与 varchar 的区别
- char 是定长字符串, 根据定义的字符长度分配足量的空间。
- varchar 是变长字符串, 比定长字符串节省空间。
- 存储长度: char 最大长度 255 字符, varchar 65535 个字符
- 存储方式: char 英文1个字节, 汉字2个字节;varchar 中英文都为2个字节
- 定长: char 为定长字符串, 不足长度部分用隐藏空格填空, varchar 为变长字符串
- 占用空间: char 会浪费空间, varchar 节省空间
- 查询效率:char 比 varchar 更快, 因为 char 在查询时直接查询查询固定长度。但数据量不同、索引是否命中, 实际结果可能有所偏差。
drop、delete、truncate 的区别
drop
命令用于删除数据表, 删除后此表不存在。drop table 表名;
。delete
命令用于删除表数据, 删除后可恢复。delete from 表名 where 列名=条件值
truncate
命令用于删除所有表数据同时使自增值初始至1。truncate 表名
拖库、洗库、撞库是什么?怎么解决?
- 拖库: 通过对目标网站扫描, 查找漏洞, 例如:进行sql注入, 文件上传在服务器建立后门(webshell)获取root权限, 下载数据库
- 洗库: 通过技术手段和黑链将用户信息变现
- 撞库: 拿到你的用户名或密码,去其他平台尝试登录
- 解决:
- 对数据库密码进行加密
- 每位用户设置单独密码, 并定期更新
- 设置黑白名单
主键 和 唯一索引的区别?
- 主键为一种约束, 唯一索引为一种索引, 本质上就不同。
- 主键在表中只能有一个, 唯一索引可以有多个。
- 主键创建后一定包含唯一性索引, 而唯一索引不一定就是主键。
- 主键不能为null,唯一索引可以为null.
- 主键可以被其它表引用, 唯一索引不能。
- 主键和索引都是键, 主键是逻辑键, 索引为物理键, 即主键不实际存在。
2.4 MySQL 索引篇
索引类型
普通索引、唯一索引、主键索引、复合索引、全文索引
索引最左匹配原则指的是什么?
- 以最左边的为起点任何连续的索引都能匹配上。
聚集索引和非聚集索引的区别
- 聚集索引: 以主键作为B+树索引的键值而构建的B+树索引, 称为 聚集索引。
- 非聚集索引: 以主键以外的列值作为键值构建的B+树索引, 称为 非聚集索引。
- 区别: 聚集索引 叶子节点存储表中的数据, 非聚集索引 叶子节点存储该列对应的主键。
- 非聚集索引 想要查找数据还需要根据叶子结点存储的主键再去 聚集索引 中查找, 这个再根据 聚集索引 查找数据的过程, 称为 回表。
哪些操作会导致索引失效
- in 操作
- or 操作
- 左或左右模糊匹配,
%??
或%??%
- 索引字段进行函数或表达式计算
>、<、between
单条 MySQL 一次能使用多少索引
使用一个索引。如果可以, 建议创建 联合索引。
2.5 存储引擎篇
你知道的常用的存储引擎
MyISAM、InnoDB、CSV、Memory、Archive、Merge、BDB
MyISAM 与 InnoDB 的区别
- MyISAM 不支持事务、InnoDB支持事务
- MyISAM 支持表锁, InnoDB支持行锁与表锁
- MySQL 将表中的行数存在变量中, 需要时直接返回该变量。InnoDB需要全表扫描
- MyISAM 存储文件格式有
*.myd
、*.myi
、*.frm
, InnoDB存储文件有*.ibd
、*.frm
。
InnoDB 四大特性
- 插入缓冲(insert buffer)
- 二次写(double write)
- 自适应哈希索引(ahi)
- 预读(read ahead)
2.6 MySQL 事务篇
事务的四大特性(ACID)
- 原子性
- 一致性
- 隔离性
- 持久性
事务的隔离级别
- 读未提交(Read uncommited)
- 读已提交(Read commit)
- 可重复读(Repeatable read)
- 可串行化(Serializable)
为什么会产生脏读、不可重复读、幻读?如何解决
- 产生脏读:R-UC、RC 的隔离级别中可能会发生 脏读
- 不可重复读: R-UC、RC 的隔离级别中可能会发生 不可重复读
- 幻读:R-UC、RC、RR 的隔离级别中会发生 幻读
- 解决方式: 加锁、根据业务情况设置合理的隔离级别
2.7 MySQL 锁篇
请看我的文章 - MySQL - 全局锁、表级锁、行级锁、元数据锁、自增锁、意向锁、共享锁、独占锁、记录锁、间隙锁、临键锁、死锁
请尽可能介绍数据库有哪些锁?
- 全局读锁
- 表锁(表共享锁、表独占锁、元数据锁、自增锁、意向锁)
- 行锁(行共享锁、行独占锁、间隙锁、临键锁、插入意向锁)
- 死锁
什么是乐观锁与悲观锁?怎么实现?
理解一:
乐观锁:乐观的认为数据不会发生冲突。在提交更新时, 才会对数据进行冲突检测。
悲观锁:优先于自身, 当前事务需要用时, 直接加锁, 其它事务只能等待。
实现乐观锁:加版本标识或时间戳, 先查询出当前版本号, 然后执行更改操作, 然后提交更新时, 再从数据库获取最新版本号, 如果与之前获取版本号相同, 则认为是正常数据, 直接更新, 否则为过期数据, 返回错误。
实现悲观锁: 共享锁或独占锁
理解二:
- 乐观锁:执行更新的时候判断其他进程是否修改了数据, 如果修改了就放弃修改
- 实现机制:
- 版本号机制:数据表加一个version版本号字段, 每次修改+1。
- CAS算法
- 场景:读取频繁场景
- 悲观锁:先取锁再操作-操作数据的时候把数据锁住, 操作完成之后才会释放, 释放锁之前其他进程不能修改数据
- 实现机制
- select * from table where id = 1 for update
- 场景:写入操作频繁的场景
死锁是如何产生的?如何避免与解决死锁?
- 产生的四个必要条件
- 互斥条件
- 请求与保持条件:一次性分配全部资源, 否则一个都不分配
- 非剥夺条件:当进程获得一部分资源等待其他资源的时候释放占有的资源
- 循环等待条件:
理解:一个资源只能被一个进程占用, 进程获取资源资源还能申请新的资源, 并且已经获得的资源不能被剥夺, 同时多个进程相互等待其他进程被占用的资源- 解除死锁
- 终止进程(全部干掉)
- 逐个种植(杀一个看一下有没有解除)
尽量避免死锁:
减少事务当中不必要的 SQL 操作, 这样事务持续的时间缩短, 减少发生死锁的可能性。
事务中, 如果需要修改, 则直接申请使用独占锁, 不要申请共享锁
在业务支持的情况下, 进行相关更改后立即提交事务, 降低发生死锁的可能性。
选择合适事务隔离级别
使用 SHOW ENGINE INNODB STATUS 来查看最近的死锁原因。
启用 innodb_print_all_deadlocks 收集更广泛的死锁信息, 记录在错误日志当中。
当看到死锁原因后, 请调整优化业务逻辑代码, 以避免再次发生死锁。
解决死锁:
终止进程(全部干掉)
逐个终止(杀掉一个检查是否已解除死锁)
超时释放:通过 innodblockwait_timeout 设置等待死锁超时时间, 默认自动回滚小事务的锁
冲突释放:开启死锁检测, 发现会与其他事务产生死锁时, 自动释放当前语句的锁
全局读锁、表读锁、表写锁、行读锁、行写锁的 SQL 语句
- 全局读锁: 加锁:
flush tables with read lock;
、解锁:unlock tables;
- 表锁:
lock tables 表名 read
、lock tables 表名 write
、解锁:unlock tables;
- 行读锁:
select .... lock in share mode
- 行写锁:
.... for update
InnoDB 存储引擎有几种锁的算法?
行级锁、间隙锁、临键锁
锁优化有什么建议?
- 根据业务场景, 设置合适的隔离级别
- 要想用好锁, 首先要创建合适的索引, 从而减少锁的冲突
- 在重要业务需要保持完整性时, 最好直接获取高级别的锁
- 根据业务场景, 尽量减少共享锁的使用
2.8 MySQL 日志篇
MySQL 进阶 - 主从复制
MySQL 有哪些系统日志
- 错误日志
- 慢查询日志
- 通用查询日志
- 元数据日志
- 二进制日志
- 中继日志
- redo log
- undo log
redo log、undo log、bin log 的区别
- redo log:
InnoDB
存储引擎实现, 记录的是物理级别上的页修改操作, 比如页号, 偏移量, 写入的数据, 主要是为了保证数据的可靠性, 主要用于 崩溃恢复。- undo log:记录的是逻辑操作日志, 比如对某一行数据进行了
insert
操作, 那么undo log
就记录一条与之相反的delete
操作。主要用于事务的回滚和一致性非锁定读。- bin log:属于归档日志, 保存的是执行成功的语句。可以实现 主从复制 和 数据恢复。
redo log 刷盘方式(WAL)
每执行一条 DML 语句, 会先把记录写入
redo log buffer(用户空间)
, 再保存到内核空间的缓冲区OS-buffer
中, 后续根据设置的策略, 再一次性将多个操作记录写入磁盘。
- 延迟写延迟刷
- 实时写延迟刷
- 实时写实时刷
binlog 的格式有哪几种?
- Statement(基于 SQL 语句的复制)
- Row(基于行的复制)
- Mixed(混合模式)
什么是脏页和干净页?
- 脏页: 为了操作性能优化, 会把数据先放入内存中, 之后再统一更新至磁盘。当内存数据和磁盘数据内容不一致时, 那这个内存页为脏页;
- 干净页: 内存数据写入磁盘后, 内存页与磁盘数据相同, 则称为干净页。
什么情况下会引发刷脏页操作?
- redo log 写满
- 系统内存不足, 当需要释放内存时, 如果淘汰的是脏页, 则会出发刷脏页操作
- MySQL 服务关闭时会出发刷脏页操作
2.9 SQL 语句实现篇
SQL 优化有哪些方法?
- 52 条 SQL 语句性能优化策略,建议收藏
- 数据表字段类型优化
- 所有字段非 null
- 选择合适的字段类型
- 数据表引擎类型优化
- 数据表索引优化
- 根据业务数据的数量, 对 where 或 order by 的字段, 增加索引
- SQL 语句优化
- 不要使用 select *
- exists 代替 in
- 不在索引列上使用 null 值判断
- 减少在索引列上使用函数或计算
如何优化大分页查询
- 原因
mysql查询分页数据时不是直接跳过offset(100000), 而是取offset + page_size = 100000 + 10 = 100010条数据, 然后放弃其掉前面的100000条数据, 所以效率地下- 优化方案
- 延时关联:使用覆盖索引
- 主键阈值法:主键是自增的情况下, 通过条件推算出符合条件的主键最大值&最小值(使用覆盖索引)
- 记录上一页的结果位置, 避免使用 OFFSET
- 子查询(
select * from test where id>=(select id from test limit 1000000,1)limit 10
)- between, 这种方式 ID 必须自增且没有断点(
select * from test where id between 1000000 and 1000100 limit 100;
)- >= , 这种方式 ID 必须自增且没有断点 (
select * from test where id>=1000000 limit 100
)
explain 分析 SQL 性能问题, 了解各参数含义
随机查询 10 条数据
SELECT * FROM 表名 WHERE id >= (SELECT floor( RAND() * ((SELECT MAX(id) FROM 表名 )-(SELECT MIN(id) FROM 表名)) + (SELECT MIN(id) FROM 表名))) ORDER BY id LIMIT 50;
存储 IP 字段使用什么类型?
多表查询 SQL 语句案例
三、Redis 面试题
3.1 Redis 是什么?
Redis 是非关系型数据库,key-value 型数据库。
3.2 Redis 有哪几种数据结构类型?
String、Hash、List、Set、Zset
3.3 Redis 使用场景有哪些?
保存字符串、计数器、功能开关、队列、排行、关注点赞
3.4 Redis 持久化有哪几种?区别是什么
3.5 缓存的 雪崩、击穿、穿透
- 缓存雪崩:同一时间大量缓存失效, 导致请求直接查询数据库, 数据库内存和CPU压力增加甚至宕机
解决:
- 热点数据永不过期或者分布到不同实例, 降低单机故障问题
- 缓存时间添加随机数, 防止大量缓存同时失效
- 做二级缓存或者双缓存, A为原始缓存 短时效, B为备份缓存 , 长期有效。更新时候双写缓存
- 缓存穿透:缓存和数据库都没有数据, 大量请求下, 所有请求直接击穿到数据库, 导致宕机。
解决:
1. 缓存空值(null)或默认值
2. 业务逻辑前置校验
3. 使用布隆过滤器请求白名单
4. 用户黑名单限制- 缓存击穿:数据库中有数据, 但是缓存突然失效之后发生大量请求导致数据库压力增加甚至打垮宕机
解决:
- 热点数据永不过期
- 互斥锁:获取锁之后不管成功还是失败都要释放锁
3.6 Redis 数据过期和淘汰策略
- 常规过期删除策略
- 定时删除
- 通过定时器在过期的时候立即删除
- 内存释放及时但是消耗更多的CPU, 大并发的时候需要消耗CPU资源影响处理请求的速度
- 内存友好, CPU不友好
- 惰性删除
- 放任键过期不管, 到下次需要去取出的时候检查是否过期并删除
- 可能存在大量过期键, 且不会使用, 导致内存溢出
- 内存不友好, CPU友好
- 定期删除
- 每隔一段时间检查, 删除过期的键
- 删除多少和检查多少有算法决定
- redis采用的 惰性删除 + 定期删除
- 周期性随机测试一些设置了过期时间的键进行检查, 到期则删除
- 每次清理的时间不超过CPU的25%, 达到时间则退出检查
- 定期没有删除到的键, 且以后不会使用的键还是会存在内存中, 所以需要配合淘汰策略
- 淘汰策略(内存不足以写入新数据的时候执行)
- volatile-lru :设置了过期时间且最近使用越少越优先淘汰
- volatile-ttl :设置了过期时间且过期时间越早越优先淘汰
- volatile-random :设置了过期时间中随机删除
- allkeys-lru :所有键中最近使用越少越优先淘汰(修改:2022-08-24 15:40:37)
- allkeys-random :所有键中过期随机淘汰
- no-enviction :不允许淘汰, 内存不足报错
3.7 怎么实现 Redis 高可用
- 主从复制
- 哨兵模式
- Redis 集群
3.8 Redis 与 Memcached 的区别
- 数据结构: Memcached 仅支持 key-value 格式
- 执行速度: Memcached 读写速度高于 Redis
- 复制: Memcached 不支持复制, Redis 支持主从复制
- 线程: Memcached 是多线程, Redis 是单线程
3.9 Redis 分布式锁怎么实现
- 实现:
加锁:setnx
解锁:del
锁超时:expire- 可能出现的问题
- setnx 和expire非原子性问题(加锁之后还没来得及设置超时就挂了)
解决方案:
Redis 2.6.12以上版本为set指令增加了可选参数, 伪代码如下:set(key, 1, 30, NX),这样就可以取代setnx指令- 超时误删其他进程锁。(A进程执行超时, 导致锁释放, 这时候B进程获取锁开始处理请求, 这时候A进程处理完成, 会误删B进程的锁)
解决方案:只能删除自己进程的锁 (lua脚本防止B进程获取过期锁之后误删A进程的锁)- 并发场景, A进程执行超时导致锁释放, 这时候B进程获取到锁。
解决方案:开启守护进程, 给当前进程要过期的锁延时。- 单点实例安全问题
单机宕机之后导致所有客户端无法获取锁
解决:
主从复制, 因为是异步完成的所以无法完全实现解决
四、前端面试题
4.1 基础篇
必须掌握 jQuery 操作 DOM 节点的常用情况
常用 css 设置
JS 数组与对象的操作
跨域请求的解决方案
4.2 网络知识面试题
get 与 post 的区别
- 正常情况已经习惯 get 用于获取数据, post 用于增删改需求
- GET
- 可被缓存
- 多用于读取一个资源,以 ? 分割 url 和请求数据, 参数之间用 & 相连
- 请求的数据,地址栏可明文看到,也可通过监听器检测到
- 传送数据量小,不能大于 2KB, 但每个浏览器定义所不同
- 产生一个 TCP 数据包, 正常情况下 header 和 data 一并发给服务器, 服务器响应 200
- POST
- 基于
<form>
表单提交向服务器发起请求- 请求的数据,不是明文,通过 request body 传递, 但也可通过监听器检测到
- 传送数据量默认不受限制
- 产生两个 TCP 数据包, 正常情况下先发送 header, 服务器响应 100, 再发送 data, 服务器响应 200
http 协议由什么组成?
请求报文组成
请求行:包含请求方法、URI、HTTP版本信息
请求首部字段
请求内容实体
响应报文组成
状态行:包含HTTP版本、状态码、状态码的原因短语
响应首部字段
响应内容实体
http 与 https 的区别
- 端口:http 80; https :443
- http无状态, https是有http + ssl构建的可进行加密传输的协议
- http明文传输, https加密传输
- http更快, 三次握手三个包, https 需要12个包(3个tcp包+9个ssl握手包)
WebSocket 原理
浏览器访问某个网址的详细过程
- 用户访问域名
- 域名DNS解析
- 请求到对应IP服务器和端口
- nginx 监听到对应端口的请求
- nginx 对 url 进行 location 匹配
- 执行匹配 location 下的规则
- nginx 转发请求给 php
- php-fpm 的 master 进程监听到 nginx 请求
- 闲置的worker进程抢占并执行请求
- worker 进程返回执行结果给 nginx
- nginx返回结果给用户
OSI网络协议的七个层级
应用层、表示层、会话层、传输层、网络层、(数据)链路层、物理层
记忆套路:
首字:应表会传(物链网)
第一个字:应用层(出现次数多, 易忆)
前四个正向:应表 - 会传
后三个反向:物联网谐音比网链物更好记
TCP/UDP 的区别
- 都是属于传输层协议
- TCP
- 面向连接, 所以只能一对一
- 面向字节流传输
- 数据可靠, 不丢失
- 全双工通信
- UDP(根据TCP特点反记)
- 无连接, 支持一对一, 一对多, 多对多
- 面向报文传输
- 首部开销小, 数据不一定可靠但是速度更快
- TCP 是事先为所发送的数据开辟出连接好的通道,然后再进行数据发送;而UDP则不为IP提供可靠性、流控或差错恢复功能。一般来说,TCP对应的是可靠性要求高的应用,而UDP对应的则是可靠性要求低、传输经济的应用。
三次握手四次挥手
第一种:
三次握手:
- 首先服务器监听某个端口, 客户端发起请求 携带 syn 数据包 (第一次)
- 服务端接收到这个数据包, 返回 syn/ack 的数据包给客户端 (第二次)
- 最后客户端再次发送一个 ack 的数据包(第三次)。
四次挥手:
- 客户端发送一个FIN(关闭连接), 用来关闭客户端到服务器的数据传送, 此时客户端进入FIN_WAIT_1状态
- 服务器收到FIN后, 发送一个ACK给客户端, 确认序号为收到需要+1(与SYN相同, 一个FIN占用一个序号), 此时服务器进入CLOSE_WAIT状态
- 服务器发送一个FIN, 用来关闭服务器到客户端的数据传送, 服务器进入LAST_ACK状态
- 客户端收到FIN后, 客户端进入TIME_WAIT状态, 接着发送一个ACK给服务器, 确认序号为收到的序号+1, 服务器进入CLOSED状态
第二种:
三次握手:
- 第一次:客户端发送SYN = 1,seq = client_isn
作用:
客户端:无
服务端:确认自己的接收功能和客户端的发送功能- 第二次:服务端发送SYN = 1,seq = server_isn,ACK =client_isn +1
作用:
客户端:确认自己发送和接收都正常, 确认服务端的接收和发送正常
服务端:确认自己的接收正常, 确认服务端的发送正常(这时候服务端还不能确认客户端接收是否正常)- 第三次:客户端发送SYN = 0, ACK = server_isn+1,seq =client_isn+1
作用:双方确认互相的接收和发送正常, 建立连接四次挥手
- 第一次:客户端发送FIN
作用:告诉服务端我没有数据发送了(但是还能接收数据)- 第二次:服务端发送ACK
作用:告诉客户端收到请求了, 可能服务端可能还有数据需要发送, 所以客户端收到进入FIN_WAIT状态, 等服务端数据传输完之后发送FIN- 第三次:服务端发送FIN
作用:服务端告诉客户端我发送完了, 可以关闭连接了。- 第四次:客户端发送ACK
作用:客户端收到FIN之后, 担心服务端不知道要关闭, 所以发送一个ACK, 进入TIME_WAIT, 等待2MSL之后如果没有收到回复, 证明服务端已经关闭了, 这时候客户端也关闭连接。注意:
- 当收到对方的FIN报文时, 仅仅表示对方不再发送数据了但是还能接收数据
- 最后需要等待2MSL是因为网络是不可靠的, 如果服务端没有收到最后一次ACK,服务端会重新放FIN包然后等客户端再次发送ACK包然后关闭(所以客户端最后发送ACK之后不能立即关闭连接)
状态码
100、200、300、400、500 分别是什么意思?
状态码分类
- 1xx:信息, 服务器收到请求, 需要请求者继续操作
- 2xx:成功
- 3xx:重定向
- 4xx:客户端错误
- 5xx:服务端错误常用状态码
200:请求成功
301:永久重定向
302:临时移动
400 bad request:客户端请求语法错误
401 unauthorized:客户端没有权限
403 forbidden:服务器拒绝客户端请求
404 not found:客户端请求资源不存在
500 Internal Server Eerro:服务器内部错误
502 bad gateway:作为网关或者代理工作的服务器尝试执行请求时, 从上游服务器接收到无效的响应
503 Service Unavailable 超载或系统维护
504 Gateway timeout:网关超时
什么是心跳机制?
心跳就是业务层来提供一个连接判断是否存活。
客户端定时发送一个心跳包, 告诉服务器, 服务器定时检测所有客户端。看最后一个心跳包的时间长短。如果过长则主动关闭这个连接。
服务器定时询问所有的客户端。如果没有反馈则关闭连接。
两者的区别:
- 第一种, 对服务和网络压力小, 但需要客户配合。
- 第二种对服务器和网络压力大。
长连接与短连接?区别是什么?
五、安全面试题
5.1 SQL注入是什么?如何预防?
5.2 XSS 与 SCRF 攻击?Web 攻击如何预防?
- sql注入
- XSS攻击
推荐阅读(很详细的案例来分析XSS攻击的不同类型及解决方案):前端安全系列(一):如何防止XSS攻击?- CSRF攻击:
推荐阅读 :前端安全系列(二):如何防止CSRF攻击?- 文件上传漏洞
推荐阅读 :浅析文件上传漏洞- 跨域问题:
推荐阅读 :- jsonp
- cors
- nginx代理
5.3 PHP 文件上传安全性考虑
- 限制文件大小
- 黑名单、白名单文件类型检测
- 统一规则文件名
- 非执行权限存储
- 下载资料限制
六、API 面试题
6.1 接口安全措施, 有哪些实现方法?
- 常用对称加密(AES、DES )(DES、AES、RSA等常用加密算法介绍与比较)
- 常用非对称加密(RSA、DSA)
- 单向加密(MD5、SHA)
- JWT(PHP使用 jwt 验证、PHP中怎样使用JWT进行授权验证?)
- 黑白名单
- IP限流
- https 协议
- 验证码
6.2 降级、限流、熔断实现原理及方式
七、高级面试题
7.1 如何解决并发问题?
- 项目拆分(根据项目需求及模块功能拆分成多个子系统)
- 提升硬件配置
- 应用集群、负载均衡
- 扩容服务器硬件配置
- 提升网络带宽
- 存储方案
- 文件存储服务器
- 图片存储服务器
- 专用搜索服务器
- 缓存
- Nginx 缓存
- 应用缓存: 热点数据、静态配置等
- 降级(自动或人工开关控制)
- 根据粒度范围暂时关闭影响度小的服务(例: 精品推荐、分享等)
- 延迟服务(例:延迟统计各种分数)
- 写降级
- 读降级
- 限流
- 验证码
- IP黑名单
- Nginx 限流 控制速率(limit_req_zone)
- Nginx 限流 控制并发连接数(limit_conn_zone 和 limit_conn)
- 消息队列
- Redis
- RabbitMQ
- Kafka
- MySQL 数据库优化
- SQL 语句优化
- 索引优化
- 数据表结构优化
- 分库分表(结合项目需求)
- 主从复制、读写分离
- 专用搜索服务器
- 前端优化
- 合并 css 和 js
- CDN 加速
- 页面静态化与动静分离
- 后端优化
- 代码优化
7.2 简单秒杀系统并发考虑
- 硬件 - 秒杀系统使用单独服务器
- 集群 - 服务器、Redis、MySQL 集群(如果一台不够, 一台增加到N台, 壕)
- 前端 - 秒杀页面静态化, CDN加速
- 前端 - 控制提交频率、按钮置灰(只能拦住小白)
- Nginx - IP限流
- 后端 - 用户限流, 对用户 uid 计数, 比如 10 秒内只准透过1个请求, 重复请求均返回一个提示页面。
- 队列 - Redis 预先将商品入队
- 队列 - 请求时, Redis 队列控制, 串行处理
- 数据库 - 不需要实时更新数据
- 得全靠上边这些支撑了, 看 Redis 的瓶颈
- 数据库 - 实时更新数据
- 如果是单库, 可使用表锁、排他锁
- 如果支持, 业务规则分化。比如有一万个库存, 每个整点只放 2 千个库存, 一天分 5 个时间点抢。
- 防止直接通过链接访问秒杀页面, 秒杀链接通过父级页面点击按钮获取一个唯一标识, 点击提交时, 没有带唯一标识的请求, 直接返回 “很遗憾, 本次没有抢到”(这个就比较狗了, 哈哈)
- 点击抢购弹出输入验证码(这个功能让人有点不爽, 京东也这样做了。。。吐了)
7.3 单点登录实现原理
7.4 数据结构特性
7.5 说一下你在项目中遇到的难点以及如何解决的?
想一想,每人留言说一条,当然,两条也可以 😘
八、算法面试题
8.1 参考学习资料
8.2 时间复杂度与空间复杂度
时间复杂度
时间复杂度:全程为渐进时间复杂度, 估算对处理器的使用效率(描述算法的效率趋势, 并不是指算法具体使用的时间, 因为不同机器的性能不一致, 只是一种效率计算的通用方法)
表示方法:大O符号表示法
复杂度量级:
- 常数阶O(1)
- 线性阶O(n)
- 平方阶O(n²)
- 立方阶O(n³)
- K次方阶O(n^k)
- 指数阶(2^n)
- 对数阶O(logN)
- 线性对数阶O(nlogN)
时间复制类型:
- 最好时间复杂度
- 最坏时间复杂度
- 平均时间复杂度
- 均摊时间复杂度
空间复杂度
空间复杂度:全程渐进空间复杂度, 估算对计算机内存的使用程度(描述算法占用的存储空间的趋势, 不是实际占用空间, 同上)
九、Linux 面试题
9.1 Linux 常用命令
十、HR 常问问题
十一、转载地址
参考原文太多了, 没有全部记录下来, 只记了一部分。