当你完整看完它的时候,你离面试成功就不远了,当然了,现在PHP有些没落了。。。

在这里插入图片描述

1、php垃圾回收机制

php垃圾回收机制是自动进行垃圾回收的,主要是使用 引用计数的方式
php7的垃圾回收包含两个部分,一个是垃圾收集器,一个是垃圾回收算法。
垃圾收集器,把刚刚提到的,可能是垃圾的元素收集到回收池中 也就是把变量的 zend_refcount>0的变量 放在回收池中。 当回收池的值达到一定额度了,会进行统一遍历处理。进行模拟删除,如果zend_refcount=0那就认为是垃圾,直接删除它。 遍历回收池中的每一个变量,根据每一个变量,再遍历每一个成员,如果成员还有嵌套的话继续遍历。然后把所有成员的 做模拟的 refcount -1。如果此时外部的变量的 引用次数为 0 。那么可以视为垃圾,清楚。如果大于0,那么恢复引用次数,并从垃圾回收池中取出。

2、php静态化的实现

2.1 伪静态

  利用Apache mod_rewrite实现URL重写的方法

2.2 纯静态

 就是生成HTML文件的方式,我们须要开启PHP自带的缓存机制,即ob_start来开启缓存

3、排序算法

3.1 冒泡排序

就是重复地遍历需要排序的数组,每次比较相邻的两个元素,如果它们的顺序不对,就交换它们,直到找出最大的元素为止,然后再重复以上的操作,直到整个数组排序完成

<?php//声明一个要排序的数组$arr=array(1,43,54,62,21,66,32,78,36,76,39);
function bubbleSort ($arr){
      $len = count($arr);
     //该层循环控制 需要冒泡的轮数
     for ($i=1; $i<$len; $i++) {
    //该层循环用来控制每轮 冒出一个数 需要比较的次数
         for ($k=0; $k<$len-$i; $k++) {
              if($arr[$k] > $arr[$k+1]) {
                   $tmp = $arr[$k+1]; // 声明一个临时变量
                   $arr[$k+1] = $arr[$k];
                   $arr[$k] = $tmp;
      }
    }
  }
    return $arr;
}

3.2 选择排序

在一列数字中,选出最小数与第一个位置的数交换。然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。(以下都是升序排列,即从小到大排列);

<?php
function selectionSort(&$arr){
//判断是否为空
if(empty($arr)){
   return $arr;
}
//如果为1直接返回$arr
if(count($arr)==1){
   return $arr;
}
//定义中间变量
$temp = 0;
$count = count($arr);
$len = $count-1;
//双重循环完成,外层控制轮数,内层控制比较次数
for($i=0; $i<$len; $i++)
{
  //先假设最小的值的位置
  $minIndex = $i;         
  for($j= $i+1; $j<$count; $j++)
  {
  	// //$arr[$minIndex] 是当前已知的最小值
     if($arr[$j] < $arr[$minIndex])
     {
     	/**
     	 * 比较,发现更小的,记录下最小值的位置;
     	 * 并且在下次比较时采用已知的最小值进行比较。
     	 */
         $minIndex = $j;
     }
  }
 
 /**
  *已经确定了当前的最小值的位置,保存到$minIndex中。
  *如果发现最小值的位置与当前假设的位置$i不同,则位置互换即可。
  */       
 
   if($i != $minIndex)
   {
 	   //进行交换 
       $temp = $arr[$i];
       $arr[$i] = $arr[$minIndex];
       $arr[$minIndex] = $temp;               
   }
}
return $arr;
}   
 
//调用选择排序函数,实现升序排列数组元素
$arr = array(2, 3, 8, 12, 9, 1);     
selectionSort($arr);
print_r($arr);

3.3 插入排序

选择排序:进行多次选择,每次选出最大元素放入指定位置

4、php常见运行模式

1)CGI(通用网关接口/ Common Gateway Interface)

2)FastCGI(常驻型CGI / Long-Live CGI)lamp

3)CLI(命令行运行 / Command Line Interface)

4)Web模块模式(Apache等Web服务器运行的模式)

5)ISAPI(Internet Server Application Program Interface)

5、常见的设计模式

总体来说设计模式分为三大类:

1、创建型模式共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
2、结构型模式共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
3、行为型模式共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

6、session和cookie的区别

1、存储位置不同

cookie的数据信息存放在客户端浏览器上。
session的数据信息存放在服务器上。

2、存储容量不同

单个cookie保存的数据<=4KB,一个站点最多保存20个Cookie。
对于session来说并没有上限,但出于对服务器端的性能考虑,session内不要存放过多的东西,并且设置session删除机制。

3、cookie对客户端是可见的,别有用心的人可以分析存放在本地的cookie并进行cookie欺骗,所以它是不安全的。

session存储在服务器上,对客户端是透明对,不存在敏感信息泄漏的风险。

4、有效期上不同

开发可以通过设置cookie的属性,达到使cookie长期有效的效果。
session依赖于名为JSESSIONID的cookie,而cookie JSESSIONID的过期时间默认为-1,只需关闭窗口该session就会失效,因而session不能达到长期有效的效果

7、cookie被禁用后如何与服务端交互

第一种:URL重写(常用),就是把session id直接附加在URL路径的后面。
第二种:表单隐藏字段(现已很少使用)。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。
第三种:将用户信息加密放到http的header部分,每次拿到http的时候,验证获取header的信息
第四种:使用token认证
使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:
● 客户端使用用户名跟密码请求登录
● 服务端收到请求,去验证用户名与密码
● 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
● 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
● 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
● 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
token存储在redis等nosql数据库内,方便快捷
打破误解:浏览器关闭session就消失了?
对session来说,除非程序通知服务器删除一个session,否则服务器会一直保留,程序一般都是在用户做log off的时候发个指令去删除session。
然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个session id就消失了,再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存在硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够打开原来的session.
恰恰是由于关闭浏览器不会导致session被删除,迫使服务器为session设置了一个失效时间,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以以为客户端已经停止了活动,才会把session删除以节省存储空间。

8、什么是jwt

详解地址:https://juejin.cn/post/6844904115080790023
Json Web Token 的简称就是 JWT,通常可以称为 Json 令牌。它是RFC 7519 中定义的用于安全的将信息作为 Json 对象进行传输的一种形式。JWT 中存储的信息是经过数字签名的,因此可以被信任和理解。可以使用 HMAC 算法或使用 RSA/ECDSA 的公用/专用密钥对 JWT 进行签名。
使用 JWT 主要用来下面两点
● 认证(Authorization):这是使用 JWT 最常见的一种情况,一旦用户登录,后面每个请求都会包含 JWT,从而允许用户访问该令牌所允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小。
● 信息交换(Information Exchange):JWT 是能够安全传输信息的一种方式。通过使用公钥/私钥对 JWT 进行签名认证。此外,由于签名是使用 head 和 payload 计算的,因此你还可以验证内容是否遭到篡改。
WT 的格式
下面,我们会探讨一下 JWT 的组成和格式是什么
JWT 主要由三部分组成,每个部分用 . 进行分割,各个部分分别是
● Header
● Payload
● Signature
因此,一个非常简单的 JWT 组成会是下面这样

然后我们分别对不同的部分进行探讨。

Header
Header 是 JWT 的标头,它通常由两部分组成:令牌的类型(即 JWT)和使用的 签名算法,例如 HMAC SHA256 或 RSA。

Payload
Token 的第二部分是 Payload,Payload 中包含一个声明。声明是有关实体(通常是用户)和其他数据的声明。共有三种类型的声明:registered, public 和 private 声明

signature
JWT 的第三部分是一个签证信息,这个签证信息由三部分组成
● header (base64后的)
● payload (base64后的)
● secret
签名用于验证消息在此过程中没有更改,并且对于使用私钥进行签名的令牌,它还可以验证 JWT 的发送者的真实身份

9、跨域问题及处理办法

9.1 产生原因

跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制。
所谓同源是指,域名,协议,端口均相同

9.2 处理办法

9.2.1 设置heard头信息

设置 HTTP 头部:通过设置响应头部信息,允许其他域名访问当前域名的资源。
header(“Access-Control-Allow-Origin: http://example.com”);
上述代码将允许来自 http://example.com 域名的请求访问当前域名的资源。如果要允许所有域名,可以将该值设置为 *:
header(“Access-Control-Allow-Origin: *”);

支持预检请求:当进行一些复杂跨域请求时,浏览器会发送一个 OPTIONS 请求,检查是否允许跨域。可以通过下面的代码设置对 OPTIONS 请求的响应:
if ($_SERVER[‘REQUEST_METHOD’] == ‘OPTIONS’) {
header(“Access-Control-Allow-Origin: http://example.com”);
header(“Access-Control-Allow-Methods: POST, GET, OPTIONS”);
header(“Access-Control-Allow-Headers: Content-Type”);
exit;
}
上述代码将允许来自 http://example.com 域名的 POST 和 GET 请求,并且允许携带 Content-Type 头信息。

9.2.2 使用jsonp,前端通过javascript来处理
9.2.3 使用nginx反向代理功能,来进行请求的转发

server {
listen 22222;
server_name localhost;
location / {
add_header Access-Control-Allow-Origin ‘http://localhost:8080’;
proxy_pass http://localhost:59200;
}
}

10、什么是CSRF攻击,如何防护?

即跨站请求伪造)攻击就是一种常见的网络安全问题。
所谓CSRF攻击,就是攻击者盗用了用户的身份,以用户的名义进行非法操作。通俗的讲,就是攻击者利用用户的登录态,在用户毫不知情的情况下,进行一些非法的操作,比如转账、发邮件、发帖子等

防御CSRF攻击:
目前防御 CSRF 攻击主要有三种策略:验证 HTTP Referer 字段;在请求地址中添加 token 并验证;在 HTTP 头中自定义属性并验证。

(1)验证 HTTP Referer 字段
服务器可以通过检查HTTP请求头中的referer和User-Agent确认请求的来源是否合法,若不合法则拒绝请求。
(2)增加随机token字段的验证、验证码验证
(3)在 HTTP 头中自定义属性并验证

11、什么是XSS攻击,如何防护?

其原理是攻击者向有 XSS 漏洞的网站中输入恶意的 HTML/JavaScript 代码,当用户浏览该网站时,这段 HTML/JavaScript 代码会自动执行,从而达到攻击的目的

用strip_tags()函数过滤html标签,htmlspecialchars()函数字符内容转换为html实体

12、什么是sql注入,如何防护?

sql注入攻击是一种常见的攻击方式。它利用应用程序未对用户输入进行过滤或限制,将恶意sql语句插入到应用程序中,从而导致数据库被攻击者控制和窃取敏感数据

防止SQL注入的关键是将字符串进行转义,避免让攻击者构造出非法的或者不符合开发者预期的SQL语句,使用PHP防止SQL注入攻击的主要方法是使用预处理语句和绑定变量。

预处理语句,使用占位符处理
对用户提交的数据进行过滤处理,数据格式及类型验证
addslashes函数的作用是在预定义的字符前面加上反斜杠,这些预定义字符包括

13、restful api的理解

满足多端(PC、APP、pad等) 通用一组api接口的规范
具体的HTTP方法和方法含义如下:
● GET(SELECT):从服务器取出资源(一项或多项)。
● POST(CREATE):在服务器新建一个资源。
● PUT(UPDATE):在服务器更新资源(客户端提供完整资源数据)。
● PATCH(UPDATE):在服务器更新资源(客户端提供需要修改的资源数据)。
● DELETE(DELETE):从服务器删除资源。

REST包含三要求,即资源(Resources)、表现层(Representation)、状态转化(State Transfer)
● 资源:就是一个网络上的实体,一个链接,一个图片,一个视频,任务网络上的东西,都可以确定为一个资源,URI就是它的地址
● 表现层:把资源具体展现中的形式,就叫表现层,比如文本使用txt、html、xml、json格式表现,图片使用jpg、gif、png格式表现
● 状态转化:通过操作,使得客户端和服务端对某个资源在表现层上展现出来的不同状态,叫状态转化,而操作的手段,只能是通过HTTP协议,比如用GET、POST、PUT、DELETE请求状态

总之,RESTful API是通过只使用HTTP协议,对网络进行资源化定义,非常简单直观的描述和定义了资源这个概念,你只需要了解HTTP,就可以很快速的学习和使用,不需要额外的配置和协议。
完整的RESTful API组成和定义
1、协议
一般是指HTTP或HTTPS协议
2、域名
如:https://www.google.com
3、版本号
如:https://www.google.com/v1/
4、路径
如:https://www.google.com/v1/persons
注意:路径一般使用名词,且为复数,表示一种资源
5、操作
如:GET、POST、PUT、PATCH、DELETE
一般是HTTP请求的操作,这个比较好理解,是动词,比如获取数据用GET,提交数据用POST,更新数据用PUT,修改数据用PATCH,删除数据用DELETE
6、过滤
通过一般URL参数,提供数据过滤,如
?KaTeX parse error: Expected 'EOF', got '&' at position 8: limit=3&̲offset=10&$page=1
7、状态码
服务器向客户端状态码和提示信息,常见如
css
复制代码200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - []:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [
]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [
]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
8、返回结果
根据上面的请求操作与URL,返回特定的JSON结果

14、php设计模式六大原则

单一职责原则:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
开放封闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象。
接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
迪米特原则:一个对象应该对其他对象保持最少的了解。
依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

15、网站高并发处理方案

15.1 流量优化 – 过滤阻拦恶意请求

验证码、防盗链、外链、nginx分流、负载均衡

15.2 前端优化

不变动的页面 静态资源化
异步请求
浏览器缓存与压缩、图片资源独立

15.3 服务端优化

页面静态化
数据缓存,redis等
并发处理
队列处理数据,延迟
负载均衡等

15.4 数据库

索引添加
sql语句优化
mysql读写分离、mycat等数据库中间件
负载均衡等

15.5 web服务器优化

nginx反向代理实现负载均衡
分布式部署

16、mysql的存储引擎

myisam和innodb
1.InnoDB是MySQL默认的存储引擎。
2.只有 InnoDB 支持事务,MyISAM不支持事务。
3.MyISAM不支持行级锁和外键, InnoDB支持。
4.InnoDB表的大小更加的大,用MyISAM可省很多的硬盘空间。
5.InnoDB 引擎的索引和文件是存放在一起的,找到索引就可以找到数据,是聚簇式设计。
6.MyISAM 引擎采用的是非聚簇式(即使是主键)设计,索引文件和数据文件不在同一个文件中

17、mysql的索引优化

列越小越快
枚举类型替代varchar
避免null值
固定长度的表比动态的快(避免text等不定长字段)
垂直分表(降低表的复杂度,不常用的字段分离出来单独存储)
合理设置索引
分表,分布式(主从)

mysql 数据库、表、记录
monogdb 数据库 、集合、文档对象

18、索引失效的场景

未遵循最左前缀匹配导致索引失效
类型转换导致索引失效
使用函数导致索引失效
计算导致索引失效
like模糊匹配以通配符开头导致索引失效
索引字段使用is not null导致失效
OR 前后存在非索引的列,索引失效
不等于(!= 或者<>)索引失效

19、mysql的事务特性

原子性:事务是一个不可分割的工作单位,要么同时成功,要么同时失败。例:当两个人发起转账业务时,如果A转账发起,而B因为一些原因不能成功接受,事务最终将不会提交,则A和B的请求最终不会成功。
持久性:一旦事务提交,他对数据库的改变就是永久的。注:只要提交了事务,将会对数据库的数据进行永久性刷新。
隔离性:多个事务之间相互隔离的,互不干扰
一致性:事务执行接收之后,数据库完整性不被破坏

20、mysql事务隔离级别

各种隔离级别和数据库事务并发时存在的问题对应情况如下:
隔离级别 脏读 不可重复读 幻读
Read Uncommitted √ √ √
Read Committed × √ √
Repeatable Read × × √
Serializable × × ×
MySQL 的事务隔离级别一共有四个,分别是读未提交、读已提交、可重复读以及可串行化。
MySQL 的隔离级别的作用就是让事务之间互相隔离,互不影响,这样可以保证事务的一致性。
隔离级别比较:可串行化 > 可重复读 > 读已提交 > 读未提交
隔离级别对性能的影响比较:可串行化 > 可重复读 > 读已提交 > 读未提交
由此看出,隔离级别越高,所需要消耗的 MySQL 性能越大(如事务并发严重性),为了平衡二者,一般建议设置的隔离级别为可重复读,MySQL 默认的隔离级别也是可重复读。

各个级别处理的问题及选择
1、在读未提交隔离级别下,事务 A 可以读取到事务 B 修改过但未提交的数据。
可能发生脏读、不可重复读和幻读问题,一般很少使用此隔离级别。
2、在读已提交隔离级别下,事务 B 只能在事务 A 修改过并且已提交后才能读取到事务 B 修改的数据。
读已提交隔离级别解决了脏读的问题,但可能发生不可重复读和幻读问题,一般很少使用此隔离级别。
3、在可重复读隔离级别下,事务 B 只能在事务 A 修改过数据并提交后,自己也提交事务后,才能读取到事务 B 修改的数据。
可重复读隔离级别解决了脏读和不可重复读的问题,但可能发生幻读问题。
提问:为什么上了写锁(写操作),别的事务还可以读操作?
因为 InnoDB 有 MVCC 机制(多版本并发控制),可以使用快照读,而不会被阻塞。
4、各种问题(脏读、不可重复读、幻读)都不会发生,通过加锁实现(读锁和写锁)。

处理幻读的办法: 使用间隙锁、一致性非锁定读
方案一:间隙锁
间隙锁(Gap Locking)是MySQL中一种用于解决幻读问题的机制。当一个事务执行了一个范围查询操作时,MySQL会对查询范围内的间隙(两个值之间的空白区域)进行锁定,从而防止其他事务在这个范围内插入新的数据。
为了使用间隙锁,你需要在事务中使用SELECT … FOR UPDATE语句,它会在读取数据的同时对查询的范围内的间隙进行锁定。这样一来,其他事务就无法在这个范围内插入新的数据,从而避免了幻读的发生。
方案二:一致性非锁定读
一致性非锁定读(Consistent Nonlocking Reads)是MySQL提供的另一种解决幻读问题的方法。在一致性非锁定读中,事务在读取数据时,不会对数据进行锁定,而是通过一些其他的机制(例如MVCC、可重复读的事务隔离级别等)来保证读取到的数据是一致的。
在事务中,你可以使用SELECT … LOCK IN SHARE MODE语句或者SELECT … READ UNCOMMITTED语句来进行一致性非锁定读。这样一来,事务可以在读取数据的同时,其他事务仍然可以对相同的数据进行插入或修改操作,但是读取到的数据仍然是一致的。

21、mysql隔离级别的实现原理

使用 MySQL 的默认隔离级别(可重复读)来进行说明。

每条记录在更新的时候都会同时记录一条回滚操作(回滚操作日志 undo log)。同一条记录在系统中可以存在多个版本,这就是数据库的多版本并发控制(MVCC)。即通过回滚(rollback 操作),可以回到前一个状态的值。

假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录。

当前值是 4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的 read-view。如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。

同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-view A、B、C 对应的事务是不会冲突的。

提问:回滚操作日志(undo log)什么时候删除?
MySQL 会判断当没有事务需要用到这些回滚日志的时候,回滚日志会被删除。

提问:什么时候不需要了?
当系统里么有比这个回滚日志更早的 read-view 的时候。

22、查看当前事务的隔离级别

命令:SHOW VARIABLES LIKE ‘transaction_isolation’;

mysql> show variables like ‘transaction_isolation’;
±----------------------±-------------+
| Variable_name | Value |
±----------------------±-------------+
| transaction_isolation | SERIALIZABLE |
±----------------------±-------------+
命令:SELECT @@transaction_isolation;

mysql> select @@transaction_isolation;
±------------------------+
| @@transaction_isolation |
±------------------------+
| SERIALIZABLE |
±------------------------+

23、设置当前事务的隔离级别

方式 1:通过 set 命令
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;
其中level有4种值:
level: {
REPEATABLE READ
| READ COMMITTED
| READ UNCOMMITTED
| SERIALIZABLE
}
关键词:GLOBAL
SET GLOBAL TRANSACTION ISOLATION LEVEL level;

  • 只对执行完该语句之后产生的会话起作用
  • 当前已经存在的会话无效
    关键词:SESSION
    SET SESSION TRANSACTION ISOLATION LEVEL level;
  • 对当前会话的所有后续的事务有效
  • 该语句可以在已经开启的事务中间执行,但不会影响当前正在执行的事务
  • 如果在事务之间执行,则对后续的事务有效。
    无关键词
    SET TRANSACTION ISOLATION LEVEL level;
  • 只对当前会话中下一个即将开启的事务有效
  • 下一个事务执行完后,后续事务将恢复到之前的隔离级别
  • 该语句不能在已经开启的事务中间执行,会报错的

方式 2:通过服务启动项命令
可以修改启动参数 transaction-isolation 的值
比方说我们在启动服务器时指定了 --transaction-isolation=READ UNCOMMITTED,那么事务的默认隔离级别就从原来的 REPEATABLE READ 变成了 READ UNCOMMITTED。

24、mysql慢查询日志

查看慢查询SQL是否启用:ON是开启,OFF是关闭。
show variables like ‘log_slow_queries’;
开启慢查询日志
set global log_slow_queries = on;

25、mysql 定位慢查询

1.首先确认是否开启了慢查询
2.设置慢查询的时间限制
3.查询慢查询日志可定位具体的慢sql
4.相关sql查询
5.用Explain分析具体的sql语句
mysql配置

  1. 在my.ini中:
  2. long_query_time=1
  3. log-slow-queries=d:\mysql5\logs\mysqlslow.log
    ● 把超过1秒的记录在慢查询日志中

26、mysql 的 b树索引与b+树索引

● B树的每个节点都存储了key和data,而B+树的data存储在叶子节点上。
B+树非叶子节点仅存储key不存储data,这样一个节点就可以存储更多的key,可以使得B+树相对B树来说更矮(IO次数就是树的高度),所以与磁盘交换的IO操作次数更少。
● B+树所有叶子节点构成一个 有序链表,按主键排序来遍历全部记录,能更好支持范围查找。
由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历,相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。
● B+树所有的查询都要从根节点查找到叶子节点,查询性更稳定;而B树,每个节点都可能查找到数据,需要在叶子节点和内部节点不停的往返移动,所以不稳定。

27、redis的数据类型及使用场景

● Redis主要有5种数据类型,包括String,List,Set,Zset,Hash,满足大部分的使用要求
数据类型 可以存储的值 操作 应用场景
String 字符串、整数或者浮点数 对整个字符串或者字符串的其中一部分执行操作
对整数和浮点数执行自增或者自减操作 做简单的键值对缓存
List 列表 从两端压入或者弹出元素
对单个或者多个元素进行修剪,
只保留一个范围内的元素 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的数据
Set 无序集合 添加、获取、移除单个元素
检查一个元素是否存在于集合中
计算交集、并集、差集
从集合里面随机获取元素 交集、并集、差集的操作,比如交集,可以把两个人的粉丝列表整一个交集
Hash 包含键值对的无序散列表 添加、获取、移除单个键值对
获取所有键值对
检查某个键是否存在 结构化的数据,比如一个对象
ZSet 有序集合 添加、获取、删除元素
根据分值范围或者成员来获取元素
计算一个键的排名 去重但可以排序,如获取排名前几名的用户

28、redis的持久化机制

● Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制:

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

RDB持久化配置
Redis会将数据集的快照dump到dump.rdb文件中。此外,我们也可以通过配置文件来修改Redis服务器dump快照的频率,在打开6379.conf文件之后,我们搜索save,可以看到下面的配置信息:
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。
redis.conf中进行配置

AOF持久化配置
在Redis的配置文件中存在三种同步方式,它们分别是:
appendfsync always #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。
appendfsync no #从不同步。高效但是数据不会被持久化。
redis.conf中进行配置,appendonly yes

29、进程、线程、协程

进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础

线程有时被称为轻量级进程( Lightweight Process, LWP),是程序执行流的最小单元。一个标准的线程由线程ID、当前指令指针(PC)、寄存器集合和堆栈组成。
另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。
线程拥有自己独立的栈和共享的堆,共享堆,不共享栈。线程的切换一般也由操作系统调度。线程具有5种状态:初始化、可运行、运行中、阻塞、销毁
对操作系统而言,线程是最小的执行单元,进程是最小的资源管理单元。无论是进程还是线 程,都是由操作系统所管理的

协程是一种比线程更加轻量级的一种函数。正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。协程不是被操作系统内核所管理的,而是完全由程序所控制的,即在用户态执行。 这样带来的好处是:性能有大幅度的提升,因为不会像线程切换那样消耗资源。
虽然一个线程内的多个协程可以切换但是这多个协程是串行执行的,某个时刻只能有一个线程在运行,没法利用CPU的多核能力。

30、redis速度快的原因

1.redis是基于内存的,内存的读写速度非常快;
2.redis是单线程的,省去了很多上下文切换线程的时间,不需要去考虑锁的原因;
3.redis使用多路复用技术,可以处理并发的连接。非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。

31、linux的5种io模型

阻塞IO模型
非阻塞IO模型
信号驱动IO模型
IO复用模型
异步IO模型

32、redis的过期删除策略

定时过期、惰性过期、定期过期

33、redis的内存淘汰策略

  1. noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键
  2. allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键
  3. volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键
  4. allkeys-random:加入键的时候如果过限,从所有key随机删除
  5. volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐
  6. volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
  7. volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
  8. allkeys-lfu:从所有键中驱逐使用频率最少的键

34、抽象类和接口的区别

抽象类:它是一种特殊的,不能被实例化的类,只能作为其他类的父类使用。使用abstract关键字声明。 接口:它是一种特殊的抽象类,也是一个特殊的类,使用interface声明。
区别:
(1)抽象类的操作通过继承关键字extends实现,而接口的使用是通过implements关键字来实现。
(2)抽象类中有数据成员,可以实现数据的封装,但是接口没有数据成员。
(3)抽象类中可以有构造方法,但是接口没有构造方法。
(4)抽象类的方法可以通过private、protected、public关键字修饰(抽象方法不能是private),而接口中的方法只能使用public关键字修饰。
(5)一个类只能继承于一个抽象类,而一个类可以同时实现多个接口。
(6)抽象类中可以有成员方法的实现代码,而接口中不可以有成员方法的实现代码。

35、php常见的魔术方法有哪些?

__construct() 实例化类时自动调用。
__destruct() 类对象使用结束时自动调用。
__set() 在给未定义的属性赋值的时候调用。
__get() 调用未定义的属性时候调用。
__isset() 使用isset()或empty()函数时候会调用。
__unset() 使用unset()时候会调用。
__sleep() 使用serialize序列化时候调用。
__wakeup() 使用unserialize反序列化的时候调用。
__call() 调用一个不存在的方法的时候调用。
__callStatic()调用一个不存在的静态方法是调用。
__toString() 把对象转换成字符串的时候会调用。比如 echo。
__invoke() 当尝试把对象当方法调用时调用。
__set_state() 当使用var_export()函数时候调用。接受一个数组参数。
__clone() 当使用clone复制一个对象时候调用。

36、_autoload()方法的工作原理是什么?

使用这个魔术函数的基本条件是类文件的文件名要和类的名字保持一致。 当程序执行到实例化某个类的时候,如果在实例化前没有引入这个类文件,那么就自动执行__autoload()函数。 这个函数会根据实例化的类的名称来查找这个类文件的路径,当判断这个类文件路径下确实存在这个类文件后 执行include或者require来载入该类,然后程序继续执行,如果这个路径下不存在该文件时就提示错误。 使用自动载入的魔术函数可以不必要写很多个include或者require函数。

37、redis事务的三个阶段

  1. 事务开始 MULTI
  2. 命令入队
  3. 事务执行 EXEC

38、redis哨兵

sentinel,中文名是哨兵。哨兵是 redis 集群机构中非常重要的一个组件,主要有以下功能:
● 集群监控:负责监控 redis master 和 slave 进程是否正常工作。
● 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
● 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
● 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。

39、redis主从复制的核心原理

● 当启动一个 slave node 的时候,它会发送一个 PSYNC 命令给 master node。
● 如果这是 slave node 初次连接到 master node,那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程,开始生成一份 RDB 快照文件,
● 同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, master 会将这个 RDB 发送给 slave,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,
● 接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。
● slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。

过程原理

  1. 当从库和主库建立MS关系后,会向主数据库发送SYNC命令
  2. 主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来
  3. 当快照完成后,主Redis会将快照文件和所有缓存的写命令发送给从Redis
  4. 从Redis接收到后,会载入快照文件并且执行收到的缓存的命令
  5. 之后,主Redis每当接收到写命令时就会将命令发送从Redis,从而保证数据的一致
    缺点
    ● 所有的slave节点数据的复制和同步都由master节点来处理,会照成master节点压力太大,使用主从从结构来解决

40、redis实现分布式锁?

● Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用setNx命令实现分布式锁。
● 当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 setNx不做任何动作
● SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
● 返回值:设置成功,返回 1 。设置失败,返回 0 。

● 使用setNx完成同步锁的流程及事项如下:
● 使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功
● 为了防止获取锁后程序出现异常,导致其他线程/进程调用setNx命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间释放锁,使用DEL命令将锁数据删除

41、redis惊群现象及处理办法

1.1 方案的由来
Redis的缓存数据库是为快速响应客户端减轻数据库压力的有效手段之一,其中有一种功能是失效缓存,其优点是可以不定期的释放使用频率低的业务空间而增加有限的内存,但对于同步数据库和缓存之间的数据来说需要面临一个问题就是:在并发量比较大的情况下当一个缓存数据失效之后会导致同时有多个并发线程去向后端数据库发起请求去获取同一业务数据,这样如果在一段时间内同时生成了大量的缓存,然后在另外一段时间内又有大量的缓存失效,这样就会导致后端数据库的压力陡增,这种现象就可以称为“缓存过期产生的惊群现象”!

1.2 处理逻辑
缓存内真实失效时间time1
缓存value中存放人为失效时间戳 :time2 ( time2 永远小于time1)
缓存value对应的lock锁(就是一个与value 对应的 另一个key),主要用于判断是第几个线程来读取redis的value
当把数据库的数据写入缓存后,这时有客户端第一次来读取缓存,取当前系统时间:system_time 如果system_time >= time2 则认为默认缓存已过期(如果system_time< time1 则还没真实失效 ),这时再获取value的lock锁,调用redis的incr函数(单线程自增函数)判断是第几个获取锁的线程,当且仅当是第一个线程时返回1,以后都逐渐递增。第一个访问的线程到数据库中获取最新值重新放入缓存并删除lock锁的key,并重新设置时间戳;在删除lock之前所有访问value客户端线程获取lock的value都大于1,这些线程仍然读取redis中的旧值,而不会集中访问数据库。

42、缓存雪崩、缓存击穿、缓存穿透

缓存雪崩处理办法:
发生缓存雪崩有两个原因:
● 大量数据同时过期;
● Redis 故障宕机;
针对大量数据同时过期而引发的缓存雪崩问题,常见的应对方法有下面这几种:
● 均匀设置过期时间;
● 互斥锁;
● 后台更新缓存;

在业务刚上线的时候,我们最好提前把数据缓起来,而不是等待用户访问才来触发缓存构建,这就是所谓的缓存预热

针对 Redis 故障宕机而引发的缓存雪崩问题,常见的应对方法有下面这几种:
● 服务熔断或请求限流机制;
● 构建 Redis 缓存高可靠集群;

应对缓存击穿可以采取前面说到两种方案:
● 互斥锁方案,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
● 不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;

当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。
缓存穿透的发生一般有这两种情况:
● 业务误操作,缓存中的数据和数据库中的数据都被误删除了,所以导致缓存和数据库中都没有数据;
● 黑客恶意攻击,故意大量访问某些读取不存在数据的业务;

应对缓存穿透的方案,常见的方案有三种。
● 第一种方案,非法请求的限制;
● 第二种方案,缓存空值或者默认值;
● 第三种方案,使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在;

43、redis和memcached的区别

对比参数 Redis Memcached
类型 1. 支持内存 2. 非关系型数据库 1. 支持内存 2. 键值对形式 3. 缓存形式
数据存储类型 1. String 2. List 3. Set 4. Hash 5. Sort Set 【俗称ZSet】 1. 文本型 2. 二进制类型
查询【操作】类型 1. 批量操作 2. 事务支持 3. 每个类型不同的CRUD 1.常用的CRUD 2. 少量的其他命令
附加功能 1. 发布/订阅模式 2. 主从分区 3. 序列化支持 4. 脚本支持【Lua脚本】 1. 多线程服务支持
网络IO模型 1. 单线程的多路 IO 复用模型 1. 多线程,非阻塞IO模式
事件库 自封转简易事件库AeEvent 贵族血统的LibEvent事件库
持久化支持 1. RDB 2. AOF 不支持
集群模式 原生支持 cluster 模式,可以实现主从复制,读写分离 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据
内存管理机制 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘 Memcached 的数据则会一直在内存中,Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题。但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。
适用场景 复杂数据结构,有持久化,高可用需求,value存储内容较大 纯key-value,数据量非常大,并发量非常大的业务

44、如何保证 缓存与数据库双写入一致的问题?

读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况
先更新数据库,然后再删除缓存
问题场景 描述 解决
先写缓存,再写数据库,缓存写成功,数据库写失败 缓存写成功,但写数据库失败或者响应延迟,则下次读取(并发读)缓存时,就出现脏读 这个写缓存的方式,本身就是错误的,需要改为先写数据库,把旧缓存置为失效;读取数据的时候,如果缓存不存在,则读取数据库再写缓存
先写数据库,再写缓存,数据库写成功,缓存写失败 写数据库成功,但写缓存失败,则下次读取(并发读)缓存时,则读不到数据 缓存使用时,假如读缓存失败,先读数据库,再回写缓存的方式实现
需要缓存异步刷新 指数据库操作和写缓存不在一个操作步骤中,比如在分布式场景下,无法做到同时写缓存或需要异步刷新(补救措施)时候 确定哪些数据适合此类场景,根据经验值确定合理的数据不一致时间,用户数据刷新的时间间隔

45、redis如何查询指定开头的key值?

使用keys指令可以扫出指定模式的key列表
redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长

46、redis消息队列、延时消息队列

使用list类型保存数据信息,rpush生产消息,lpop消费消息,当lpop没有消息时,可以sleep一段时间,然后再检查有没有信息,如果不想sleep的话,可以使用blpop, 在没有信息的时候,会一直阻塞,直到信息的到来。redis可以通过pub/sub主题订阅模式实现一个生产者,多个消费者,当然也存在一定的缺点,当消费者下线时,生产的消息会丢失。

Redis如何实现延迟消息队列
ZSet 多了一个分值(score)属性,用它来存储时间戳,用多个线程轮询Zset获取到期的任务进行处理,以此来实现延迟消息队列等,步骤如下:

  1. 利用 zadd 向集合中插入元素,以元素的时间戳(超时时间)作为 score
  2. 利用 zrangebyscore 以 0 < score <= 当前时间戳 进行获取需要处理的元素

redis实现限流
第一种:基于Redis的string 自增
比如我们需要在10秒内限定20个请求,那么我们在setnx的时候可以设置过期时间10,当请求的setnx数量达到20时候即达到了限流效果
第二种:基于Redis的数据结构zset
用Redis的list数据结构可以轻而易举的实现该功能
我们可以将请求打造成一个zset数组,当每一次请求进来的时候,value保持唯一,可以用UUID生成,而score可以用当前时间戳表示,因为score我们可以用来计算当前时间戳之内有多少的请求数量。而zset数据结构也提供了range方法让我们可以很轻易的获取到2个时间戳内有多少请求
第三种:基于Redis的令牌桶算法
令牌桶算法提及到输入速率和输出速率,当输出速率大于输入速率,那么就是超出流量限制了。
也就是说我们每访问一次请求的时候,可以从Redis中获取一个令牌,如果拿到令牌了,那就说明没超出限制,而如果拿不到,则结果相反。
依靠上述的思想,我们可以结合Redis的List数据结构很轻易的做到这样的代码,只是简单实现
依靠List的leftPop来获取令牌

47、mysql乐观锁与悲观锁

乐观锁和悲观锁是两种思想,用于解决并发场景下的数据竞争问题。
● 乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。 ---- 版本号机制
● 悲观锁:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。---- for update机制

48、redis主从复制的过程?

在redis主从架构中,master负责接收写请求,写操作成功后返回客户端OK,然后后将数据异步的方式发送给多个slaver进行数据同步,不过从redis 2.8开始,slave node会周期性地确认自己每次复制的数据量。
当启动一个slave node的时候,它会发送一个PSYNC命令给master node。如果slave node是重新连接master node,那么master node仅仅会复制给slave部分缺少的数据; 否则如果是slave node第一次连接master node,那么会触发一次full resynchronization全量复制。
开始full resynchronization的时候,master会启动一个后台线程,开始生成一份RDB快照文件,同时还会将从客户端收到的所有写命令缓存在内存(内存缓冲区)中。RDB文件生成完毕之后,master会将这个RDB发送给slave,slave会先写入本地磁盘,然后再从本地磁盘加载到内存中。然后master会将内存中缓存的写命令发送给slave,slave也会同步这些数据。
另外slave node做复制的时候,是不会block master node的正常工作的,也不会block对自己的查询操作,它会用旧的数据集来提供服务; 但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了。slave node主要用来进行横向扩容,做读写分离,扩容的slave node可以提高读的吞吐量。slave与高可用性有很大的关系。

49、redis主从复制过程中的数据丢失问题如何处理?

数据丢失的问题是不可避免的,但是我们可以尽量减少。 在redis的配置文件里设置参数
复制代码min-slaves-to-write 1
min-slaves-max-lag 10
min-slaves-to-write默认情况下是0,min-slaves-max-lag默认情况下是10。
上面的配置的意思是要求至少有1个slave,数据复制和同步的延迟不能超过10秒。如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么这个时候,master就不会再接收任何请求了。
上面两个配置可以减少异步复制和脑裂导致的数据丢失。

50、mysql主从复制延迟解决方案?

分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。 (即减少binlog同步日志)分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。 (即减少binlog同步日志)
打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s,并行复制还是没意义。
重写代码。插入数据时立马查询可能查不到。比如尽量避免插入后就马上读
基于redis 先缓存一份这样的数据,同时进行MYSQL的数据操作

51、nginx惊群现象及处理办法?

● accept_mutex
● 如果开启了accept_mutex 锁,每个 worker 都会先去抢自旋锁,只有抢占成功了,才把 socket 加入到 epoll 中,accept 请求,然后释放锁。accept_mutex 锁也有负载均衡的作用。
● EPOLLEXCLUSIVE
● EPOLLEXCLUSIVE 是 Linux 4.5+ 内核新添加的一个 epoll 的标识,Nginx 在 1.11.3 之后添加了 NGX_EXCLUSIVE_EVENT。EPOLLEXCLUSIVE 标识会保证一个事件发生时候只有一个线程会被唤醒,以避免多个进程监听下的“惊群”问题。不过任一时候只能有一个工作线程调用 accept,限制了真正并行的吞吐量。
● SO_REUSEPORT
● SO_REUSEPORT 是惊群最好的解决方法,Nginx 在 1.9.1 中加入了这个选项,每个 worker 进程都有自己的 socket,这些 socket 都 bind 同一个端口。当新请求到来时,内核根据四元组信息进行负载均衡,非常高效。

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐