admin 管理员组文章数量: 887018
php面试题
1、写出你能想到的所有HTTP返回状态值,并说明用途(比如:返回404表示找不到页面)
# 200:服务器请求成功
# 301:永久重定向,旧网页已被新网页永久替代
# 302:表示临时性重定向
# 400:错误请求
# 401:未授权,没有权限,未登录
# 403:禁止访问
# 404:找不到页面
# 500:系统错误,服务器错误
# 502:Bad Gateway(网关故障)无效响应 代理使用的服务器遇到了上游的无效响应
# 503:Service Unavailable 服务不可用,服务器由于维护或者负载过重未能应答
# 504:Gateway Timeout网关超时(nginx做为反向代理服务器,所连接的应用服务器无响应导致)
2、HTTP中GET,POST和PUT的区别
1、GET在浏览器回退时是无害的(会从缓存中拿结果),而POST会再次提交请求。
2、GET产生的URL地址可以被Bookmark(收藏书签),而POST不可以。
3、GET请求会被浏览器主动cache,而POST不会,除非手动设置。
4、GET请求只能进行url编码,而POST支持多种编码方式。
5、GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
6、GET请求在URL中传送的参数是有长度限制的,2kb,而POST没有,但是根据IIS的配置,传输量也是不同的。
7、对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
8、GET参数通过URL传递,不安全,不能用来传递敏感信息,POST放在Request body中,相对安全
# 总结
POST和GET方式的安全性是相对的,另外也要看是从哪个角度来看的。从数据传输过程方面来看,POST方式是更加
安全的,但是从对服务器数据的操作来看,POST方式的安全性又是比较低的。即使是传输过程用POST来执行,
安全性也是相对的,如果了解HTTP协议漏洞,通过拦截发送的数据包,同样可以修改交互数据,
所以这里的安全不是绝对的。
# 1、PUT和POST
PUT和POST都有更改指定URI的语义.但PUT被定义为idempotent的方法,POST则不是.idempotent的方法:如果一
个方法重复执行多次,产生的效果是一样的,那就是idempotent的。也就是说:
PUT请求:如果两个请求相同,后一个请求会把第一个请求覆盖掉。(所以PUT用来改资源)
Post请求:后一个请求不会把第一个请求覆盖掉。(所以Post用来增资源)
# 2、get和post补充
1、HTTP的底层是TCP/IP。HTTP只是个行为准则,而TCP才是GET和POST怎么实现的基本。GET/POST都是TCP链接。
GET和POST能做的事情是一样一样的。但是请求的数据量太大对浏览器和服务器都是很大负担。所以业界有了不成
文规定,(大多数)浏览器通常都会限制url长度在2K个字节,而(大多数)服务器最多处理64K大小的url。
2、GET产生一个TCP数据包;POST产生两个TCP数据包。对于GET方式的请求,浏览器会把http header和data一
并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100 continue,
浏览器再发送data,服务器响应200 ok(返回数据)。
3、在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,
两次包的TCP在验证数据包完整性上,有非常大的优点。但并不是所有浏览器都会在POST中发送两次包,
Firefox就只发送一次。
3、php中双引号和单引号的区别
双引号解释变量,单引号不解释变量;所以单引号的执行效率比双引号要高
双引号可以解析所有的转义字符,单引号只能解析 \ 和 ' 本身的转义
4、echo、print_r、print、var_dump之间的区别
# echo 和 print 区别:
echo - 可以输出一个或多个字符串
print - 只允许输出一个字符串,返回值总为 1
提示:echo 输出的速度比 print 快, echo 没有返回值,print有返回值1。
# echo和print共同点
都是一个语言结构,使用的时候可以不用加括号,也可以加上括号
# var_dump()和print_r()的区别
共同点:都是函数,两者都可以打印数组,对象之类的复合型变量。
区别:
print_r() 是函数,用于格式化输出数组的结构
print_r() 只能打印一些易于理解的信息
print_r() 在打印数组时,会将把数组的指针移到最后边,使用reset() 可让指针回到开始处。
var_dump() 不但能打印复合类型的数据,还能打印资源类型的变量
var_dump() 输出的信息则比较详细,一般调试时用得多。
var_dump() 判断一个变量的类型和长度,并输出变量的数值
5、require一个不存在的文件时,如何避免抛出异常错误?
使用 try {} catch(Exception $e) 捕获异常
使用 file.exists() 判断文件是否存在,存在引入
6、写一段php demo代码实现单例模式
class Singleton
{
private static $instance;
private function __construct()
{
// Do nothing.
}
//获取实例
public static function getInstance()
{
if (!(self::$instance instanceof self)) {
self::$instance = new self();
}
return self::$instance;
}
//防止克隆
private function __clone()
{
// Do nothing.
}
}
7、请描述一下PHP的自动加载机制
在PHP开发过程中,如果希望从外部引入一个class,通常会使用include和require方法,去把定义这个class
的文件包含进来。这个在小规模开发的时候,没什么大问题。但在大型的开发项目中,这么做会产生大量的
require或者include方法调用,这样不仅降低效率,而且使得代码难以维护,况且require_once的代价很大。
在PHP5之前,各个PHP框架如果要实现类的自动加载,一般都是按照某种约定自己实现一个遍历目录,自动加载
所有符合约定规则的文件的类或函数。 当然,PHP5之前对面向对象的支持并不是太好,类的使用也没有现在频繁。
在PHP5后,当加载PHP类时,如果类所在文件没有被包含进来,或者类名出错,Zend引擎会自动调用__autoload
函数。此函数需要用户自己实现__autoload函数。 在PHP5.1.2版本后,可以使用spl_autoload_register
函数自定义自动加载处理函数。当没有调用此函数,默认情况下会使用SPL自定义的spl_autoload函数。
1、 __autoload示例:
function __autoload($class_name) {
echo '__autload class:', $class_name, '<br />';
}
new Demo();
# 以上的代码在最后会输出:__autload class:Demo。
# 并在此之后报错显示: Fatal error: Class ‘Demo’ not found
# 我们一般使用_autoload自动加载类如下:
function __autoload($class_name) {
require_once ($class_name . “class.php”);
}
$memo= new Demo();
我们可以看出_autoload至少要做三件事情,第一件事是根据类名确定类文件名,第二件事是确定类文件所在的
磁盘路径(在我们的例子是最简单的情况,类与调用它们的PHP程序文件在同一个文件夹下),第三件事是将类从
磁盘文件中加载到系统中。第三步最简单,只需要使用include/require即可。要实现第一步,第二步的功能,
必须在开发时约定类名与磁盘文件的映射方法,只有这样我们才能根据类名找到它对应的磁盘文件。
因此,当有大量的类文件要包含的时候,我们只要确定相应的规则,然后在__autoload()函数中,将类名与实际
的磁盘文件对应起来,就可以实现lazy loading的效果。从这里我们也可以看出__autoload()函数的实现中
最重要的是类名与实际的磁盘文件映射规则的实现。
但现在问题来了,假如在一个系统的实现中,假如需要使用很多其它的类库,这些类库可能是由不同的开发工程师
开发,其类名与实际的磁盘文件的映射规则不尽相同。这时假如要实现类库文件的自动加载,就必须__autoload()
函数中将所有的映射规则全部实现,因此__autoload()函数有可能会非常复杂,甚至无法实现。最后可能会导致
__autoload()函数十分臃肿,这时即便能够实现,也会给将来的维护和系统效率带来很大的负面影响。在这种
情况下,在PHP5引入SPL标准库,一种新的解决方案,即spl_autoload_register()函数。
# 2、spl_autoload_register()函数
# 此函数的功能就是把函数注册至SPL的__autoload函数栈中,并移除系统默认的__autoload()函数。
# 下面的例子可以看出:
function __autoload($class_name) {
echo '__autload class:', $class_name, '<br />';
}
function classLoader($class_name) {
echo 'SPL load class:', $class_name, '<br />';
}
spl_autoload_register('classLoader');
new Test();
//结果:SPL load class:Test
语法:bool spl_autoload_register ( [callback $autoload_function] )
接受两个参数:一个是添加到自动加载栈的函数,另外一个是加载器不能找到这个类时是否抛出异常的标志。
第一个参数是可选的,并且默认指向spl_autoload()函数,这个函数会自动在路径中查找具有小写类名和.php
扩展或者.ini扩展名,或者任何注册到spl_autoload_extensions()函数中的其它扩展名的文件。
class CalssLoader {
public static function loader($classname){
$class_file = strtolower($classname).".php";
if (file_exists($class_file)){
require_once($class_file);
}
}
}
// 方法为静态方法
spl_autoload_register('CalssLoader::loader');
$test = new Test();
一旦调用spl_autoload_register()函数,当调用未定义类时,系统会按顺序调用注册到
spl_autoload_register()函数的所有函数,而不是自动调用__autoload()函数。
如果要避免这种情况,需采用一种更加安全的spl_autoload_register()函数的初始化调用方法:
if(false === spl_autoload_functions()){
if(function_exists('__autoload')){
spl_autoload_registe('__autoload',false);
}
}
spl_autoload_functions()函数会返回已注册函数的一个数组,如果SPL自动加载栈还没有被初始化,它会返回
布尔值false。然后,检查是否有一个名为__autoload()的函数存在,如果存在,可以将它注册为自动加载栈中的
第一个函数,从而保留它的功能。之后,可以继续注册自动加载函数。
还可以调用spl_autoload_register()函数以注册一个回调函数,而不是为函数提供一个字符串名称。如提供一个
如array('class','method')这样的数组,使得可以使用某个对象的方法。
下一步,通过调用spl_autoload_call('className')函数,可以手动调用加载器,而不用尝试去使用那个类。
这个函数可以和函数class_exists('className',false)组合在一起使用以尝试去加载一个类,并且在所有的
自动加载器都不能找到那个类的情况下失败
if(spl_autoload_call('className') && class_exists('className',false)){
} else {
}
SPL自动加载功能是由spl_autoload() ,spl_autoload_register(), spl_autoload_functions()
,spl_autoload_extensions()和spl_autoload_call()函数提供的。
总结:项目过大时,include和require使用过多,php5.2后出现__autoload自动载入,后面出现一个php工程
依赖多个框架,每个框架都有__autoload就会报方法重复定义错误,php5.3之后官方提供
spl_autoload_register('autoload1');,解决了方法重复定义冲突
8、发送POST请求时,application/x-www-form-urlencoded格式和multipart/form-data有什么区别,如果需要发送json格式到后台,发送时Content-Type应该如何设置
application/x-www-form-urlencoded是浏览器默认的编码格式,用于键值对参数,参数之间用&间隔;
multipart/form-data常用于文件等二进制,也可用于键值对参数,最后连接成一串字符传输
设置默认application/x-www-form-urlencoded
9、阐述下你对MVC的理解
Model(模型) - 模型代表一个存取数据的对象。它也可以带有逻辑,在数据变化时更新控制器。
View(视图) - 视图代表模型包含的数据的可视化。
Controller(控制器) - 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。
它使视图与模型分离开。Controller层用来调度View层和Model层,将用户界面和业务逻辑合理的组织在一起,
起粘合剂的效果
# 总结:
优点:分层,结构清晰,耦合性低,大型项目代码的复用性得到极大的提高,开发人员分工明确,
提高了开发的效率,维护方便,降低了维护成本。
缺点:简单的小型项目,使用MVC设计反而会降低开发效率,层和层虽然相互分离,但是之间关联性太强,
没有做到独立的重用
10、什么是面向对象?主要特征是什么?
面向对象是程序的一种设计方式,
把具体的事物和行为抽象成类,对象有方法,属性值,不可变常量
它利于提高程序的重用性,使程序结构更加清晰。
主要特征:封装、继承、多态。
11、SESSION 与 COOKIE的区别是什么,请从协议,产生的原因与作用说明?
# 产生的背景和原理:
HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的
连接。这就意味着服务器无法从连接上跟踪会话。于是需要引入一种机制,COOKIE于是就顺应而生。
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。
客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session
# 区别:(位置,大小,安全)
1、cookie 是存放在浏览器端,不同的浏览器存储的cookie数量和数据的大小都不一致。
大多数情况下单个域名限制最多保存20个cookie,每个cookie保存的数据不能超过4K。
2、session存储在服务端,默认是以文件的形式存储,也可以存储在数据库和redis、memcache等缓存内存中。
3、session是占用的服务器内存,所以内存越大,能存的值就越大,原则上讲无上限,一般用于存储对安全要求
较高的重要数据;
# 补充
1、SESSION存储在服务器端,COOKIE保存在客户端。Session比较安全,cookie用某些手段可以修改,不安全。
Session依赖于cookie进行传递。
2、session为‘会话服务’,在使用时需要开启服务,cookie不需要开启,可以直接用
禁用cookie后,session不能正常使用。Session的缺点:保存在服务器端,每次读取都从服务器进行读取
对服务器有资源消耗。Session保存在服务器端的文件或数据库中,默认保存在文件中,文件路径由php配置
文件的session.save_path指定。Session文件是公有的。
12、isset() 和 empty() 区别
Isset判断变量是否存在,可以传入多个变量,若其中一个变量不存在则返回假
empty判断变量是否为空为假,只可传一个变量,如果为空为假则返回真
$a = 1;
$b = 2;
echo isset($a,$b) . "</br>"; // true
echo isset($a,$c) . "</br>"; // false
echo empty("") . "</br>"; // true
echo empty("0") . "</br>"; // true
echo empty(" ") . "</br>"; // false
echo empty("null") . "</br>"; // false
echo empty(null) . "</br>"; // true
13、请说明 PHP 中传值与传引用的区别。什么时候传值什么时候传引用?
按值传递:函数范围内对值的任何改变在函数外部都会被忽略
按引用传递:函数范围内对值的任何改变在函数外部也能反映出这些修改
优缺点:按值传递时,php必须复制值。特别是对于大型的字符串和对象来说,这将会是一个代价很大的操作。
按引用传递则不需要复制值,对于性能提高很有好处。
14、在PHP中error_reporting这个函数有什么作用?
设置 PHP 的报错级别并返回当前级别。
error_reporting(2047)、error_reporting(E_ALL)、error_reporting(-1);
显示所有错误
15、请用正则表达式(Regular Expression)写一个函数验证电子邮件的格式是否正确。
if(isset($_POST['action']) && $_POST['action']==’submitted’){
$email=$_POST['email'];
if(!preg_match(“/^[0-9a-zA-Z-]+@[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+){1,3}$/”,$email)){
echo “电子邮件检测失败”;
}else{
echo “电子邮件检测成功”;
}
}
16、对于用户输入一串字符串$string,要求string中只能包含大于0的数字和英文逗号,请用正则 表达式验证,对于不符合要求的string返回出错信息
class regx {
public static function check($str) {
if(preg_match("/^([1-9,])+$/",$str)) {
return true;
}
return false;
}
}
$str="12345,6";
if(regx::check($str)) {
echo "suc";
} else {
echo "fail";
}
17、单例模式,创建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);
}
}
18、预定义常量有哪些?
(1). __FILE__ 文件的完整路径和文件名。如果用在被包含文件中,则返回被包含的文件名
(2). __DIR__ 文件所在的目录。如果用在被包括文件中,则返回被包括的文件所在的目录
(3). __LINE__ 文件中的当前行号
(4). __FUNCTION__ 返回该函数被定义时的名字(区分大小写)
(5). __CLASS__ 返回该类被定义时的名字(区分大小写)
(6). __METHOD__ 返回该方法被定义时的名字(区分大小写)
(7):__NAMESPACE__ 当前命名空间的名称(区分大小写)
(8):__TRAIT__ Trait 的名字
20、autoload工作原理
当程序执行到实例化某个类的时候,如果在实例化前没有引入这个类文件,那么就自动执行__autoload()
函数。这个函数会根据实例化的类的名称来查找这个类文件的路径,当判断这个类文件路径下确实存在这个类文件
后就执行include或者require来载入该类,然后程序继续执行,如果这个路径下不存在该文件时,就提示错误。
21、PHP程序使用utf-8编码, 以下程序输出结果是什么?
$str = ’hello你好世界’;
echo strlen($str); //17
echo mb_strlen($str,'gbk') //11
?>
22、你所知道的php数组相关的函数?
array()----创建数组
array_combine(keys,values);----通过合并两个数组来创建一个新数组
range(low,high,step)----创建并返回一个包含指定范围的元素的数组
compact()----创建一个包含变量名和它们的值的数组。
array_chunk(array,size,preserve_key);----将一个数组分割成多个
array_merge(array1,array2,array3...)----把两个或多个数组合并成一个数组
array_slice(array,start,length,preserve)----在数组中根据条件取出一段值
array_diff(array1,array2,array3...)----返回两个数组的差集数组
array_intersect(array1,array2,array3...)----计算数组的交集
array_search(value,array,strict)----在数组中搜索给定的值
array_splice(array,start,length,array)----移除数组的一部分且替代它
array_key_exists(key,array)----判断某个数组中是否存在指定的key
shuffle(array)----把数组中的元素按随机顺序重新排列
array_flip(array)----交换数组中的键和值
array_reverse(array,preserve)----将原数组中的元素顺序翻转,创建新的数组并返回
array_unique(array)----移除数组中重复的值
sort(),rsort(),asort(),arsort(),ksort(),krsort()----对一维排序
array_multisort()对多个数组或多维数组进行排序,关联(string)键名保持不变,但数字键名会被重新索引
23、php读取文件内容的几种方法和函数?
打开文件,然后读取。fopen(filename,mode,include_path,context) fread(file,length)
读取整个文件:
$file = fopen("test.txt","r");
fread($file,filesize("test.txt"));
fclose($file);
打开读取一次完成 file_get_contents(path,include_path,context,start,max_length)
24、以下程序,变量str什么值的情况下输入111?
if( ! $str ) { echo 111; }
在$str值为:0,'0',false,null,"",[],0.0
25、你所知道的PHP的一些技术(smarty等)?
Smarty,jquery,ajax,memcache,div+css,js,mysqli,pdo,svn,thinkphp,brophp,yii
26、冒泡排序
# 降序
function func ( $arr )
{
for ( $i = 0 ; $i < count( $arr ) ; $i ++ ) {
for ( $j = 0 ; $j < count( $arr ) - 1 - $i ; $j ++ ) {
if ( $arr[ $j ] < $arr[ $j + 1 ] ) {
$a = $arr[ $j ];
$arr[ $j ] = $arr[ $j + 1 ];
$arr[ $j + 1 ] = $a;
}
}
}
return $arr;
}
$brr = [ 2 , 5 , 7 , 3 , 8 , 0 ];
print_r( func( $brr ) );
# 升序(把小于号改成大于号即可)
27、你所熟悉的PHP商城系统 有哪些?
Ecshop
28、你所熟悉的PHP开发框架 有哪些?
laravel,thinkphp
29、常用的魔术方法有哪些?举例说明?
__construct() 实例化类时自动调用。
__destruct() 类对象使用结束时自动调用。
__set() 在给未定义的属性赋值的时候调用。
__get() 调用未定义的属性时候调用。
__isset() 使用isset()或empty()函数时候会调用。
__unset() 使用unset()时候会调用。
__sleep() 使用serialize序列化时候调用。
__wakeup() 使用unserialize反序列化的时候调用。
__call() 调用一个不存在的方法的时候调用。
__callStatic() 调用一个不存在的静态方法是调用。
__toString() 把对象转换成字符串的时候会调用。比如 echo。
__invoke() 当尝试把对象当方法调用时调用。
__set_state() 当使用var_export()函数时候调用。接受一个数组参数。
__clone() 当使用clone复制一个对象时候调用。
<?php
class Person{
public $a = 0;
public $arr = ['Moe','Larry','Curly'];
function __construct($name=""){
// unset($this->b); //__unset() 使用unset()时候会调用
//$this->getB; //__get() 调用未定义的属性时候调用。
}
function __sleep(){
echo '__sleep';
return [];
}
public function __wakeup() {
echo "__wakeup";
}
function __unset($argu){
echo 'unset';
}
function __set($name,$val){
echo $name;
}
function __get($name){
echo '__get';
}
function __isset($name){
echo '__isset';
}
function __call($name,$argument){
echo '__call';
}
function __invoke(){
echo 11111;
}
function __toString(){
return "__toString";
}
function __destruct(){
echo '__destruct';
}
function __clone(){
echo '__clone';
}
static function __callStatic($name,$argument){
echo '__callStatic';
}
static function __set_state(){
echo '__set_state';
}
}
$person = new Person("cxh");
// $p(); // __invoke() 当尝试把对象当方法调用时调用
// echo $p; // __toString() 把对象转换成字符串的时候会调用。比如 echo。
// var_dump(isset($p->b)); // __isset() 使用isset()或empty()函数时候会调用。
// $p->test(1); // __call() 调用一个不存在的方法的时候调用
// Person::test(); // __callStatic()调用一个不存在的静态方法是调用
// $ps = clone $p; // __clone() 当使用clone复制一个对象时候调用。
// echo serialize($p); // __sleep() 使用serialize序列化时候调用
// var_dump(unserialize(serialize($person))); // __wakeup() 使用unserialize反序列化的时候调用。
// eval('' . var_export($person, true) . ';'); // __set_state() 当使用var_export()函数时候调用。接受一个数组参数
30、TCP和UDP的的区别
1.TCP基于连接与UDP无连接
2.TCP要求系统资源较多,UDP较少;
3.UDP程序结构较简单
4.字节流模式(TCP)与数据报模式(UDP);
5.TCP保证数据正确性,UDP可能丢包
6.TCP保证数据顺序,UDP不保证
# TCP与UDP区别总结:
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;
UDP尽最大努力交付,即不保 证可靠交付
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的UDP没有拥塞控制,
因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
31、tcp三次握手
第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;
SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即
SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和
服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
# 四次挥手
1)客户端进程发出连接释放报文,并且停止发送数据。此时,客户端进入FIN-WAIT-1(终止等待1)状态。
2)服务器收到连接释放报文,发出确认报文,此时,服务端就进入了CLOSE-WAIT(关闭等待),半关闭状态,
即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,
也就是整个CLOSE-WAIT状态持续的时间。
3)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接
释放报文(在这之前还需要接受服务器发送的最后的数据)。
4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,半关闭状态,等待客户端的确认。
5)客户端收到服务器的连接释放报文后,必须发出确认,当客户端撤销相应的TCB后,才进入CLOSED状态。
6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。
可以看到,服务器结束TCP连接的时间要比客户端早一些。
常见面试题
#【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应的
SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能
先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,
我才能发送FIN报文,因此不能一起发送。故需要四步握手。
#【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,
有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK
回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,
它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一
个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。
所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,
2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,
那么Client推断ACK已经被成功接收,则结束TCP连接。
# 【问题3】为什么不能用两次握手进行连接?
答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方
就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S
发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功
地建了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,
不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立
成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。
这样就形成了死锁。
# 【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到
一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,
服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为
客户端出了故障,接着就关闭连接。
32、数据库索引有几类,分别是什么?什么时候该用索引?
普通索引、主键索引、唯一索引,组合索引,外键索引,全文索引
并非所有的数据库都以相同的方式使用索引,作为通用规则,只有当经常查询列中的数据时才需要在表上创建索引。
33、超级全局变量(预定义变量)有哪些
$GLOBALS:超级全局变量组
$_SERVER:服务相关信息
$_REQUEST:用来获取post或get方式提交的值
$_POST:用来获取post方式提交的值
$_GET:用来获取get方式提交的值
$_COOKIE:用来获取cookie存储的值
$_SESSION:用来获取session存储的值
$_FILES:用来获取上传文件表单的值
$_ENV:
34、Include与require的区别
1、执行的原理不一样:
incluce 在用到时加载,require 在一开始就加载
include() 执行时需要引用的文件每次都要进行读取和评估,require() 执行时需要引用的文件只处理
一次(实际上执行时需要引用的文件内容替换了 require() 语句)
2、报错不一样: include包含失败报warning级别的错误,后续代码继续执行;require报fatal error致命
错误,后续代码不执行
原因:PHP 系统在加载PHP程序时有一个伪编译过程,可使程序运行速度加快。但 incluce 的文档仍为解释
执行。include 的文件中出错了,主程序继续往下执行,require 的文件出错了,主程序也停了
3、效率:如果可能多次执行代码,require函数效率要高于include函数。
4、require() 和 include() 语句是语言结构,不是真正的函数
5、包含的类,函数没有作用域,变量存在作用域,在函数里面包含,那么变量作用域就是函数,反之
下面两个有待求证:
1:include()是有条件包含函数,而require()则是无条件包含函数
2:include有返回值,而require没有
经过我自己的测试,php版本5.2以上:
1 两个都是要判断是否为真才能包含
2 两个都有返回值
35、了解XSS攻击吗? 如何防止 ?
XSS是跨站脚本攻击,首先是利用跨站脚本漏洞以一个特权模式去执行攻击者构造的脚本,然后利用不安全的
Activex控件执行恶意的行为。
使用htmlspecialchars()函数对提交的内容进行过滤,使字符串里面的特殊符号实体化。
36、字符串“open_door” 转换成 “OpenDoor”、”make_by_id” 转换成 ”MakeById”。
function str_explode($str){
$str_arr = explode("_",$str);
$str_implode = implode(" ",$str_arr);
//ucwords($str_implode) make by id将每个单词首字母变大写
$str_implode = implode("",explode(" ",ucwords($str_implode)));
return $str_implode;
}
$strexplode = str_explode("make_by_id");
37、PHP处理字符串的常用函数?(重点看函数的‘参数’和‘返回值’)
1:trim()移除字符串两侧的空白字符和其他字符;
2:substr_replace(string,replacement,start,length)
把指定位置字符串的一部分替换为另一个字符串,截取替换
3:substr_count(string,substring,start,length) 计算子串在字符串中出现的次数;截取计算
4:substr(string,start,length)返回字符串的一部分;
5:strtolower()把字符串转换为小写字母;
6:strtoupper()把字符串转换为大写字母;
7:strstr(string,search,before_search)
搜索字符串在另一字符串中是否存在,如果是,返回该字符串及剩余部分,否则返回 FALSE
8:strrchr()查找字符串在另一个字符串中最后一次出现;
9:strtr(string,from,to) 或者 strtr(string,array)
转换字符串中特定的字符,示例:echo strtr("Hilla Warld","ia","eo"); //Hello world
10:strrev()反转字符串;
11:strlen()返回字符串的长度
12:str_replace(find,replace,string,count)替换字符串中的一些字符(对大小写敏感)
13:print()输出一个或多个字符串;
14:explode()把字符串打散为数组;
15:is_string()检测变量是否是字符串;
16:strip_tags()从一个字符串中去除HTML标签;
17:mb_substr()用来截中文与英文的函数
38、nginx和apache的区别
# 1:处理php文件的方式不一样
apache通过libphp5.so模块处理,nginx通过php-fpm处理
# nginx相对于apache的优点
轻量级,同样是 web 服务,比Apache 占用更少的内存及资源,
高并发,Nginx 处理请求是异步非塞的,而Apache 则是阻塞型的,
在高并发下Nginx 能保持低资源低消耗高性能;
高度模块化的设计
编写模块相对简单;
社区活跃,各种高性能模块出品迅速。
# apache相对于nginx的优点
rewrite更强大,模块多,bug少,稳定
# nginx特点
1、高可靠:稳定性master进程管理调度请求分发到哪一个worker=>worker进程响应请求 master多worker
2、热部署:(1)平滑升级(2)可以快速重载配置
3、高并发:可以同时响应更多的请求事件epoll模型几万
4、响应快:尤其在处理静态文件上,响应速度很快sendfile
5、低消耗:cpu和内存1w个请求内存2-3MB
6、分布式支持:反向代理七层负载均衡,新版本也支持四层负载均衡
总结:需要性能,追求高并发用nginx,只求性能稳定用apache,nginx处理动态请求是弱项,
只适合处理静态网页或反向代理
39、php正则两种模式
# 后向引用
$str = '<b>ccc</b>';
$pattern = '/<b>(.*)<\/b>/';
echo preg_replace($pattern, '\\1', $str);
# 贪婪模式 (?、U 取消贪婪模式)
$str = '<b>ccc</b><b>aaa</b>';
$pattern = '/<b>.*?<\/b>/';
$pattern = '/<b>.*<\/b>/U';
echo preg_replace($pattern, '\\1', $str);
40、抽象类和接口的区别
1、抽象类被子类继承,接口被类实现
2、接口只能做方法声明,抽象类可以做方法声明,也可以做方法实现
3、接口里定义的变量只能是公共的,静态的常量,抽象类的变量是普通变量
4、接口是设计的结果,抽象类是重构的结果
5、抽象类和接口都是用来抽象具体对象的,但是接口的抽象级别更高
6、抽象类可以有具体的方法和属性,接口只能有抽象方法和不可变常量
7、抽象类主要用来抽象类别,接口用来抽象功能
8、都不能实例化,抽象方法必须重载才能使用
41、常见的设计模式
# 工厂模式,单例模式,注册树模式,适配器模式,观察者模式,策略模式
# 单例模式
class Singleton
{
private static $instance;
private function __construct()
{
// Do nothing.
}
//获取实例
public static function getInstance()
{
if (!(self::$instance instanceof self)) {
self::$instance = new self();
}
return self::$instance;
}
//防止克隆
private function __clone()
{
// Do nothing.
}
}
#注册树模式
class Register
{
private static $object;
static function set($alias,$obj)
{
self::$object[$alias] = $obj;
}
static function get($alias)
{
return self::$object[$alias];
}
static function _unset($alias)
{
unset(self::$object[$alias]);
}
}
class Database{
static function test(){
echo 'Database';
}
}
Register::set('db',new Database);
Register::get('db')::test();
# 适配器模式
interface Database
{
function connect();
function query();
}
class Mysql implements Database{
function connect(){
}
function query(){
}
}
class PDOs implements Database{
function connect(){
}
function query(){
}
}
42、三大流行框架优缺点
# ThinkPHP
ThinkPHP(FCS)是一个轻量级的中型框架,是从Java的Struts结构移植过来的中文PHP开发框架。它使用面向对
象的开发结构和MVC模式,并且模拟实现了Struts的标签库,各方面都比较人性化,熟悉J2EE的开发人员相对比较
容易上手,适合php框架初学者。 ThinkPHP的宗旨是简化开发、提高效率、易于扩展,其在对数据库的支持方面
已经包括MySQL、MSSQL、Sqlite、PgSQL、 Oracle,以及PDO的支持。ThinkPHP有着丰富的文档和示例,
框架的兼容性较强,但是其功能有限,因此更适合用于中小项目的开发。
优点:
1.易于上手,有丰富的中文文档;
2.框架的兼容性较强,PHP4和PHP5完全兼容、完全支持UTF8等。
3. 适合用于中小项目的开发
缺点:
1.对Ajax的支持不是很好;
2.目录结构混乱,需要花时间整理;
3.上手容易,但是深入学习较难。
# Yii
Yii 是一个基于组件的高性能php框架,用于开发大型Web应用。Yii采用严格的OOP编写,并有着完善的库引用
以及全面的教程。从 MVC,DAO/ActiveRecord,widgets,caching,等级式RBAC,Web服务,到主题化,
I18N和L10N,Yii提供了 今日Web 2.0应用开发所需要的几乎一切功能。事实上,Yii是最有效率的PHP框架之一
优点:
纯OOP
用于大规模Web应用
模型使用方便
开发速度快,运行速度也快。性能优异且功能丰富
使用命令行工具。
缺点:
对Model层的指导和考虑较少
文档实例较少
英文太多
要求PHP技术精通,OOP编程要熟练!
View并不是理想view,理想中的view可能只是html代码,不会涉及PHP代码。
# CodeIgniter
优点:
Code Igniter推崇“简单就是美”这一原则。没有花哨的设计模式、没有华丽的对象结构,一切都是那么简单。
几行代码就能开始运行,再加几 行代码就可以进行输出。可谓是“大道至简”的典范。 配置简单,全部的配置使用
PHP脚本来配置,执行效率高;具有基本的路由功能,能够进行一定程度的路 由;具有初步的Layout功能,能够
制作一定程度的界面外观;数据库层封装的不错,具有基本的MVC功能. 快速简洁,代码不多,执行性能高,框架
简 单,容易上手,学习成本低,文档详细;自带了很多简单好用的library,框架适合小型应用.
缺点:
本身的实现不太理想。内部结构过于混乱,虽然简单易用,但缺乏扩展能力。 把Model层简单的理解为数据库操作.
框架略显简单,只能够满足小型应用,略微不太能够满足中型应用需要.
评价:
总体来说,拿CodeIgniter来完成简单快速的应用还是值得,同时能够构造一定程度的layout,便于模板的复用,
数据操作层来说封装的不 错,并且CodeIgniter没有使用很多太复杂的设计模式,执行性能和代码可读性上都
不错。至于附加的library 也还不错,简洁高效。
# Laravel 框架
优点:
Laravel 的设计思想是很先进的,非常适合应用各种开发模式TDD, DDD 和BDD,作为一个框
架,它准备好了一切,composer 是个php 的未来,没有composer,PHP 肯定要走向没落。
laravel 最大的特点和优秀之处就是集合了php 比较新的特性,以及各种各样的设计模式,
Ioc 容器,依赖注入等。
缺点:
基于组件式的框架,所以比较臃肿
43、在浏览器输入网址到页面显示,期间发生了哪些过程?
# 1、查询DNS,获取域名对应的IP。
(1)检查本地hosts文件是否有这个网址的映射,如果有,就调用这个IP地址映射,解析完成。
(2)如果没有,则查找本地DNS解析器缓存是否有这个网址的映射,如果有,返回映射,解析完成。
(3)如果没有,则查找填写或分配的首选DNS服务器,称为本地DNS服务器。服务器接收到查询时:
如果要查询的域名包含在本地配置区域资源中,返回解析结果,查询结束,此解析具有权威性。
如果要查询的域名不由本地DNS服务器区域解析,但服务器缓存了此网址的映射关系,返回解析结果,
查询结束,此解析不具有权威性。
(4)如果本地DNS服务器也失效:
如果未采用转发模式(迭代),本地DNS就把请求发至13台根DNS,根DNS服务器收到请求后,会判断这个域名
(如.com)是谁来授权管理,并返回一个负责该顶级域名服务器的IP,本地DNS服务器收到顶级域名服务器IP
信息后,继续向该顶级域名服务器IP发送请求,该服务器如果无法解析,则会找到负责这个域名的下一级DNS
服务器(如http://baidu.com)的IP给本地DNS服务器,循环往复直至查询到映射,将解析结果返回本地
DNS服务器,再由本地DNS服务器返回解析结果,查询完成。如果采用转发模式(递归),则此DNS服务器就
会把请求转发至上一级DNS服务器,如果上一级DNS服务器不能解析,则继续向上请求。最终将解析结果依次
返回本地DNS服务器,本地DNS服务器再返回给客户机,查询完成。
# 2、客户机发送HTTP请求报文:
(1)应用层:客户端发送HTTP请求报文
(2)传输层:切分长数据,并确保可靠性。
(3)网络层:进行路由
(4)数据链路层:传输数据
(5)物理层:物理传输bit
# 3、服务器端经过物理层→数据链路层→网络层→传输层→应用层,解析请求报文,发送HTTP响应报文。
# 4、客户端解析HTTP响应报文
# 5、浏览器开始显示HTML
# 6、浏览器重新发送请求获取图片、CSS、JS的数据。
# 7、如果有AJAX,浏览器发送AJAX请求,及时更新页面。
44、获取客户端ip、服务端ip、浏览器属性
客户端ip:$_SERVER['REMOTE_ADDR'] //代理服务器除外
服务器ip:$_SERVER["SERVER_ADDR"]
浏览器属性:$_SERVER["HTTP_USER_AGENT"]
45、使用php写一个方法,可以获取指定目录下的所有文件和文件夹
function getDirContent($path){
//检查文件是否是目录
if(!is_dir($path)){
return false;
}
//列出该目录中的所有文件和目录
$files = scandir($path);
foreach ($files as $value){
//.代表本目录,..代表上级目录
if($value != '.' && $value != '..'){
$filesPath[] = $value;
}
}
return $filesPath;
}
$filesPath = getDirContent("D:\web\ShiRong\static\images");
var_dump($filesPath);
#包含子目录
function searchDir($path,&$files=[]){
if(is_dir($path)){
$files[] = $path;
$opendir = opendir($path);
while ($file = readdir($opendir)){
if($file != '.' && $file != '..'){
searchDir($path.'/'.$file, $files);
}
}
closedir($opendir);
}else{
$files[] = $path;
}
return $files;
}
function searchDir($path,&$files=[]){
if(is_dir($path)){
$files[] = $path;
$dir = scandir($path);
foreach ($dir as $value) {
if($value != '.' && $value != '..'){
searchDir($path.'/'.$value, $files);
}
}
}else{
$files[] = $path;
}
return $files;
}
# 以上两个searchDir方法二选一
//$dir:文件目录,$files:文件以及目录数组名称
$filenames = searchDir("D:\web\ShiRong\static\images");
foreach ($filenames as $value){
echo $value . '<br/>';
}
46、把html转换为实体的函数是?转义特殊字符的函数是?
# 把html转换为实体:htmlspecialchars()
# 转义特殊字符的函数:addslashes
47、php连接mysql出现乱码的原因有哪些?
1.架设服务器安装MYSQL时的会让你选择一种编码,如果这种编码与你的PHP网页不一致,可能就会造成乱码
2.在PHPMYADMIN或mysql-front等系统 创建数据库时会让你选择一种编码,如果这种编码与你的PHP网页不一致
,也有可能造成PHP页面乱码
3.创建表时会让你选择一种编码,如果这种编码与你的网页编码不一致,也可能造成PHP页面乱码
4.创建表时添加字段是可以选择编码的,如果这种编码与你的网页编码不一致,也可能造成PHP页面乱码
5.用户提交页面的编码与显示数据的页面编码不一致,就肯定会造成PHP页面乱码.
如用户输入资料的页面是big5码, 显示用户输入的页面却是gb2312,这种100%会造成PHP页面乱码
6.PHP页面字符集不正确
48、mysql注入攻击原理,如何预防?
# 注入攻击原理
sql字符串拼接
# 预防
1.开启php的魔术模式,,magic_quotes_gpc = on即可,当一些特殊字符出现在网站前端的时候,就会自动进行
转化,转化成一些其他符号导致sql语句无法执行。
2.网站代码里写入过滤sql特殊字符的代码,对一些特殊字符进行转化,比如单引号,逗号,*,(括号)AND 1=1
反斜杠,select union等查询的sql语句都进行安全过滤,限制这些字符的输入,禁止提交到后端中去。
3.开启网站防火墙,IIS防火墙,apache防火墙,nginx防火墙,都有内置的过滤sql注入的参数,当用户输入参数
get、post、cookies方式提交过来的都会提前检测拦截,也可以向国内专业做网站安全的公司去咨询。
49、php导出excel数据量过大,php excel类效率低,如何优化?
1、切割数据导出
2、优化sql语句
3、建议将php升级到最新的php7版本节省内存,且计算速度要快
4、使用fputcsv()导出csv
5、直接运行sql语句进行导出
50、HTTP与HTTPS有什么区别?
超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供
任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,
因此,HTTP协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。
为了解决HTTP协议的这一缺陷,需要使用另一种协议:安全套接字层超文本传输协议HTTPS,为了数据传输的安全
HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。
# HTTP和HTTPS的基本概念
HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从
WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。
HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,
因此加密的详细内容就需要SSL。
HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站
的真实性。
# HTTP与HTTPS有什么区别?
1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、
身份认证的网络协议,比http协议安全。
# HTTPS的优点
尽管HTTPS并非绝对安全,掌握根证书的机构、掌握加密算法的组织同样可以进行中间人形式的攻击,但HTTPS
仍是现行架构下最安全的解决方案,主要有以下几个好处:
(1)使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;
(2)HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全,可防止
数据在传输过程中不被窃取、改变,确保数据的完整性。
(3)HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。
(4)谷歌曾在2014年8月份调整搜索引擎算法,并称“比起同等HTTP网站,采用HTTPS加密的网站在搜索结果中的
排名将会更高”。
# HTTPS的缺点
虽然说HTTPS有很大的优势,但其相对来说,还是存在不足之处的:
(1)HTTPS协议握手阶段比较费时,会使页面的加载时间延长近50%,增加10%到20%的耗电;
(2)HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗,甚至已有的安全措施也会因此而受到影响;
(3)SSL证书需要钱,功能越强大的证书费用越高,个人网站、小网站没有必要一般不会用。
(4)SSL证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗。
(5)HTTPS协议的加密范围也比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。
最关键的,SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。
51、在PHP中,当前脚本的名称(不包括路径和查询字符串)记录在预定义变量(1)中;而链接到当前页面的的前一页面URL记录在预定义变量(2)中
//当前脚本的名称
echo $_SERVER['PHP_SELF'] . "<br />";
echo $_SERVER['SCRIPT_NAME'] . "<br />";
//链接到当前页面的前一页面的 URL 地址:
echo $_SERVER['HTTP_REFERER'] . "<br />";
//前执行脚本的绝对路径名
echo $_SERVER["SCRIPT_FILENAME"] . "<br />";
//查询(query)的字符串(URL 中第一个问号 ? 之后的内容):id=1&bi=2
echo $_SERVER["QUERY_STRING"] . "<br />";
//当前运行脚本所在的文档根目录
echo $_SERVER["DOCUMENT_ROOT"] . "<br />";
52、如果返回“找不到文件”的提示,则可用 header 函数,其语句为?
header("HTTP/1.0 404 Not Found");
53、数组排序函数
sort() - 对数组进行升序排列
rsort() - 对数组进行降序排列
asort() - 根据关联数组的值,对数组进行升序排列
ksort() - 根据关联数组的键,对数组进行升序排列
arsort() - 根据关联数组的值,对数组进行降序排列
krsort() - 根据关联数组的键,对数组进行降序排列
54、写出一个正则表达式,过虑网页上的所有JS/VBS脚本(即把script标记及其内容都去掉)
$script="以下内容不显示:<SCRIPT language='javascript'>alert('cc');
</SCRIPT>";
echo preg_replace("/<script[^>].*?>.*?<\/script>/si", "替换内容", $script);
# .*:任意字符
# ?:可有可无
# [^>]:非 > 字符,^ 取反的意思
# s:忽略换行
# i:忽略大小写
55、php数组合并有几种区别
# 1、“+”运算符
1:不覆盖,只是追加不存在的键名和对应的值。
2:键名不重新索引。
3:无论是全部数字键名还是混合,都只是追加键名和值,如果键名相同则不进行追加,
即把最先出现的值作为最终结果返回。
# 2、array_merge
1:数字索引,不会覆盖,值合并后,键名会连续方式重新索引
2:字符串键名,则该键名后面的值将覆盖前一个值
# 3、array_merge_recursive
1:数字索引,不会覆盖,值合并后,键名会连续方式重新索引
2:字符串键名,将多个相同键名的值递归组成一个数组
56、php如何实现页面跳转
1、header('location:http://www.baidu.com');
2、echo '<meta http-equiv="refresh" content = "1;url= http://www.baidu">';
3、echo '<script>location.href="http://www.baidu"</script>';
57、了解XSS、CSRF、SQL注入原理与防范
1、xss攻击,跨站脚本攻击是攻击者在网页中嵌入恶意脚本程序,当用户打开浏览器,脚本程序便开始在客户端
的浏览器上执行,一盗取客户端cookie等信息,利用系统对用户的信任。
防御措施:使用htmlspecialchars()函数把预定义的字符转换为 HTML 实体。
2、csrf攻击是跨站请求伪造,攻击者盗用了你的身份,以你的名义向第三方网站发送恶意请求,利用系统对页面
浏览器的信任。
防御措施:
(1):将cookie设置为HttpOnly。如果cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到
cookie信息,这样能有效的防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性。
# 1.PHP 5.2以下支持
header(“Set-Cookie: hidden=value; httpOnly”);
# 2.PHP5.2以上支持
ini_set(“session.cookie_httponly”, 1);
# 3.兼容全部PHP版本
setcookie(“name”,”hello”, NULL, NULL, NULL, NULL, TRUE);
(2):设置token验证身份
(3):通过refer识别验证来源网站是否合法,不属于本网站拒绝请求
3、SQL注入
防御措施:
1):对用户的输入进行过滤处理,再进行操作,如:addslashes()方法或者开启magic_quotes_gpc方法
2):使用Prepared Statement机制的PDO,预处理sql和参数绑定
MYSQLi一样原理
3):对于整数id条件查询,强制转int类型
58、请在表格右侧写出执行每一行后每个变量的值,每行变量的值依赖上一行
$a = 1;
$b = 2;
$c = 3;
$d = 4;
$e = 5;
$b = &$a;
echo '$a=' . $a .',$b=' . $b . ',$c=' . $c . ',$d=' . $d . ',$e=' . $e;
echo PHP_EOL . "</br>";
$b = "30$a";
echo '$a=' . $a .',$b=' . $b . ',$c=' . $c . ',$d=' . $d . ',$e=' . $e;
echo PHP_EOL . "</br>";
$e = $c = ($b++);
echo '$a=' . $a .',$b=' . $b . ',$c=' . $c . ',$d=' . $d . ',$e=' . $e;
echo PHP_EOL . "</br>";
$d = ($c > $b) ? ($a+=2) : (--$b);
echo '$a=' . $a .',$b=' . $b . ',$c=' . $c . ',$d=' . $d . ',$e=' . $e;
echo PHP_EOL . "</br>";
$e = implode(',',array($c,$e));
echo '$a=' . $a .',$b=' . $b . ',$c=' . $c . ',$d=' . $d . ',$e=' . $e;
echo PHP_EOL . "</br>";
59、以下代码输出是什么?为什么?
$a = 10;
$b = 20;
function multiply(){
$a = $a * 10;
return $a;
}
function add(){
static $b = 0;
return $b++;
}
$b++;
multiply();
add();
$b = add();
list($b,$a) = array($a,$b);
var_dump($a,$b);
执行$b++时,$b = 21;
60、php常量与变量的区别
1. 常量前面没有美元符号($)
2. 常量只能用 define() 函数定义,而不能通过赋值语句
3. 常量可以不用理会变量范围的规则而在任何地方定义和访问
4. 常量一旦定义就不能被重新定义或者取消定义
5. 常量的值只能是标量
# php常量与变量的作用域:
常量的作用域:常量无论在那定义,都可以直接调用,没有作用域。
变量的作用域:变量定义在那个范围就在那个范围有效,如函数中定义的变量就只在函数中有效。
61、php静态变量和变量的区别
# 不同点:
1. 普通变量用 $(美元符号) 表示,静态变量用static表示
2. 普通变量:函数执行完自动销毁;静态变量:不会被销毁保留上次值
3. 普通变量全局会多次初始化,静态变量:static全局变量只初使化一次
4. 静态和公共、受保护、私有这3种属性格式没有关系
5. 普通变量:存在堆内存,静态变量:存在方法区中的静态存储区
# 相同点:
作用域都是局部的,函数外声明,函数内不能使用,函数内声明函数外不能使用
62、php如何在页面之间传递变量
# 1:POST传值,也就是form表单提交,$_POST['name']获取
# 2:GET传值,也就是通过a链接传递参数,$_GET['name']获取
# 3:session传值,开启session_start(),$_SESSION['name']获取
# 4:cookie传值,$_COOKIE['name']获取
63、用js和jq获取名称为username的input的值和属性
1:js通过属性获取值和属性
document.getElementsByName("username")[0].value;
document.getElementsByName("username")[0].getattribute("age");
2:jq通过属性获取值和属性
$("input[name='username']").val();
$("input[name='username']").attr("age");
64、常用的php测试框架
1、Selenium
2、PHPUnit
3、Behat
4、Codeception
5、SimpleTest
6、Storyplayer
7、Peridot
8、PHPSpec
65、https的工作原理
# HTTP协议由于是明文传送,所以存在三大风险:
1、被窃听的风险:第三方可以截获并查看你的内容
2、被篡改的危险:第三方可以截获并修改你的内容
3、被冒充的风险:第三方可以伪装成通信方与你通信
# HTTPS通信过程
1、浏览器发起往服务器的443端口发起请求,请求携带了浏览器支持的加密算法和哈希算法。
2、服务器收到请求,选择浏览器支持的加密算法和哈希算法。
3、服务器下将数字证书返回给浏览器,这里的数字证书可以是向某个可靠机构申请的,也可以是自制的。
4、浏览器进入数字证书认证环节,这一部分是浏览器内置的TLS完成的:
4.1 首先浏览器会从内置的证书列表中索引,找到服务器下发证书对应的机构,如果没有找到,此时就会提示用户
该证书是不是由权威机构颁发,是不可信任的。如果查到了对应的机构,则取出该机构颁发的公钥。
4.2 用机构的证书公钥解密得到证书的内容和证书签名,内容包括网站的网址、网站的公钥、证书的有效期等。
浏览器会先验证证书签名的合法性(验证过程类似上面Bob和Susan的通信)。签名通过后,浏览器验证证书
记录的网址是否和当前网址是一致的,不一致会提示用户。如果网址一致会检查证书有效期,证书过期了也会
提示用户。这些都通过认证时,浏览器就可以安全使用证书中的网站公钥了。
4.3 浏览器生成一个随机数R,并使用网站公钥对R进行加密。
5、浏览器将加密的R传送给服务器。
6、服务器用自己的私钥解密得到R。
7、服务器以R为密钥使用了对称加密算法加密网页内容并传输给浏览器。
8、浏览器以R为密钥使用之前约定好的解密算法获取网页内容。
具体参考文章:https://blog.csdn.net/wangtaomtk/article/details/80917081
66、const和define的区别
1. const用于类成员变量定义,一旦定义且不能改变其值,define定义全局常量,在任何地方都可以访问。
2. define不能定义在类中,但是能定义在类的方法里面
const定义在类中,php5.3以上支持类外通过const定义常量,通过类名::变量名来进行访问
例如:
class Test{
const a = 1;
function __construct(){
define("NAME","cxh");
}
}
echo Test::a;
new Test;
echo NAME;
3. const不能在条件语句中定义常量
例如:if(1){ const a = 'java'; } // 错误
4. const采用一个普通的常量名称(静态的标量),define可以采用任何表达式作为名称。
例如:const PHP = 1 << 5; // 错误
define('PHP', 1 << 5); // 正确
5. const 总是大小写敏感,然而define()可以通过第三个参数来定义大小写不敏感的常量。
例如:define("NAME","cxh",true);
echo NAME; // cxh
echo name; // cxh
6. 使用const简单易读,它本身是一个语言结构,而define是一个方法,用const定义在编译时比define快很多
具体参考文章:https://www.php.cn/php-weizijiaocheng-438969.html
67、$GLOBALS和global的区别
1:$GLOBALS 是PHP的一个超级全局变量组,在一个PHP脚本的全部作用域中都可以访问。
global的作用是定义全局变量,但是这个全局变量不是应用于整个网站,而是应用于当前页面,
包括include或require的所有文件
2:$GLOBALS 是一个包含了全部变量的全局组合数组,变量的名字就是数组的键。
global 是一个声明全局变量的关键字
3:global在函数产生一个指向函数外部变量的别名变量,而不是真正的函数外部变量
$GLOBALS[]确确实实调用是外部的变量,函数内外会始终保持一致
4:global在函数外定义,函数内不能使用报Undefined variable错误,而$GLOBALS无论哪个地方都能使用
68、php和jsp之间有哪些区别
# 区别
1、PHP是服务器脚本语言;JSP是服务器端编程技术。
2、jsp使用Java语言,通过JDBC来访问数据库,访问数据库的接口比较统一;
PHP对于不同的数据库采用不同的访问接口,访问数据库的接口不是很统一。
3、Java采用面向对象,PHP采用面向过程。
# 比较
1、php和jsp的语言比较
PHP是一种专为Web开发而设计的,解释执行的服务器脚本语言,它大量地借用C和Perl语言的语法,具有简单容易
上手的特点,所以学过c语言的都可以很快的熟悉php的开发。
JSP是一种服务器端编程技术,有助于创建动态网页。它是以Java语言作为脚本语言,结合HTML语法的;
熟悉JAVA语言和HTML语法的人可以很快上手。
但java不光要需要学习语法,好用熟悉一些核心的类库,了解、掌握面向对象的相关知识。java要比PHP难学,
因而JSP技术要比PHP难掌握。
2、php和jsp的数据库访问比较
jsp使用Java语言,通过JDBC来访问数据库,通过不同的数据库厂商提供的数据库驱动方便地访问数据库。
访问数据库的接口比较统一。
PHP对于不同的数据库采用不同的数据库访问接口,所以数据库访问代码的通用性不强。例如:用Java开发的
web应用从MySQL数据库转到Oracle数据库只需要做很少的修改。而PHP则需要做大量的修改工作。
3、php和jsp的性能比较
1)、JSP是基于Java编程语言,所以对API的支持非常庞大,在Web开发方面支持大量的第三方库。
而PHP对API的访问权限有限,支持的第三方库比较少。
2)、JSP支持对象缓存,而PHP不支持缓存。
3)、JSP是Java类的抽象,因此它可以被垃圾收集;而PHP不支持垃圾收集。
4)、JSP非常擅长维护用户会话,而PHP每次都会破坏用户的会话。
5)、JSP执行需要更多时间,因为它被转换为Servlet,编译和执行;而PHP执行所需的时间比JSP少,随着编码
减少和快速开发和执行,即时反馈和更高的生产力。
4、php和jsp的系统设计架构比较
采用Java的web开发技术,需要使用的是面向对象的系统设计方法,而PHP还是采用面向过程的开发方法。
所以用Java进行开发前期需要做大量的系统分析和设计的工作。
5、 php和jsp的跨平台性比较
Java和PHP都有很好的跨平台的特性。几乎都可以在不作任何修改的情况下运行在Linux或者Windows等不同的
操作系统上。
6、 php和jsp的开发成本比较
PHP最经典的组合就是:PHP + MySQL + Apache。非常适合开发中小型的web应用,开发的速度比较快。
而且所有的软件都是开源免费的,可以减少投入。
JSP在学习周期和开发周期都比较长,且所需的软件不是全都免费的,开发成本比较高。
69、php缓存机制
一,PHP缓存机制详解
我们可以使用PHP自带的缓存机制来完成页面静态化,但是仅靠PHP自身的缓存机制并不能完美的解决页面
静态化,往往需要和其他静态化技术(通常是伪静态技术)结合使用。
output buffer是php自带缓存,可以通过配置php.ini关闭,程序缓存是一直开启状态,没法关闭。程序
缓存中内容没法修改,output buffer中内容可以修改,修改完成后全部发给程序缓存。
当我们设计一个通信协议时,“消息头/消息体”的分割方式是很常用的,消息头告诉对方这个消息是干什么的,
消息体告诉对方 怎么干。HTTP传输的消息也是这样规定的,每一个HTTP包都分为HTTP头和HTTP体两部分,后者是
可选的,而前者是必须的。一个网页对应一个消息,发送消息时候,一般来说,都是先消息头部分,在消息头部分
指明了 消息体部分的长度,然后使用\r\n\r\n来表示消息头部分结束,接下来是消息体部分。如果没有定义消息
头,发送默认的消息头。
由图可知,浏览器向apache发送http请求后,apache根据httpd.conf文件,将请求转发给php处理模块,php处理
模块根据php.ini处理test2.php,如果php.ini关闭output buffer,那么php处理模块将信息头部和信息内容
直接发送给程序缓存,如果php.ini开启output buffer,那么php处理模块将信息头部和信息内容直接发送给
Output buffer,Output buffer接收完后再发送给程序缓存。
通过以下实例学习消息与php缓存,配置php.ini。
(1)php.ini,output_buffering=off,关闭php缓存;
(2)display_errors = on,显示错误;
(3)error_reporting=E_ALL & ~E_NOTICE,表示所有非NOTICE级别的错误日志都打印出来;
之后执行以下代码。
<?php
echo “aaa”;
header(“content-type:text/html;charset=utf-8”);
echo “hello”;
?>
这段程序报警告。PHP处理模块一边处理程序,一边将处理结果发送到程序缓存,处理第1行,将默认消息头以及
aaa作为消息体一部分发送到程序缓存,执行第2行,再次发送消息头,此时程序缓存中有消息头了,且没法修改,
此时报警告。因此可以在程序中将output buffer开启,
<?php
ob_start();
echo “aaa”;
header(“content-type:text/html;charset=utf-8”);
echo “hello”;
?>
执行第1行开启缓存,执行第2行,将默认消息头以及aaa作为消息体一部分发送给output buffer,执行第3行,
修改消息头,执行第4行,将hello发送给output buffer,程序执行完后,output buffer将消息发送给程序缓存
程序缓存输出。
二,下面是一些php自带缓存指令:
ob_start() //开启缓存
ob_clean() //清空缓存
ob_end_clean() //清空缓存,关闭缓存
ob_flush() //刷新缓存(将缓存现有内容输出)
ob_end_flush() //刷新缓存,并关闭缓存
$contents = ob_get_contents() //获得缓存内容
file_put_contents("d:/log.txt",$contents) //将缓存内容打印到文本
三,flush与ob_flush区别
flush()是输出程序缓存指令;
ob_flush()是输出自带缓存指令;
70、垃圾回收机制
# 新的垃圾回收机制
php5.3版本之后引入根缓冲机制,即php启动时默认设置指定zval数量的根缓冲区(默认是10000),当php发现有
存在循环引用的zval时,就会把其投入到根缓冲区,当根缓冲区达到配置文件中的指定数量(默认是10000)后,
就会进行垃圾回收,以此解决循环引用导致的内存泄漏问题
# 确认为垃圾的准则
1、如果引用计数减少到零,所在变量容器将被清除(free),不属于垃圾
2、如果一个zval 的引用计数减少后还大于0,那么它会进入垃圾周期。其次,在一个垃圾周期中,通过检查引用
计数是否减1,并且检查哪些变量容器的引用次数是零,来发现哪部分是垃圾。
# 总结垃圾回收机制:
1、以php的引用计数机制为基础(php5.3以前只有该机制)
2、同时使用根缓冲区机制,当php发现有存在循环引用的zval时,就会把其投入到根缓冲区,当根缓冲区达到配置
文件中的指定数量后,就会进行垃圾回收,以此解决循环引用导致的内存泄漏问题(php5.3开始引入该机制)
php中的变量存储在变量容器zval中,zval中除了存储变量类型和值外,还有is_ref和refcount字段。
refcount表示指向变量的元素个数,is_ref表示变量是否有别名。如果refcount为0时,就回收该变量容器。
如果一个zval的refcount减1之后大于0,它就会进入垃圾缓冲区。当缓冲区达到最大值后,回收算法会循环
遍历zval,判断其是否为垃圾,并进行释放处理。
具体参考文章:https://segmentfault.com/a/1190000018369789
71、用php实现一个双向队列
class DuiLie {
private $array = array();//声明空数组
public function setFirst($item){
return array_unshift($this->array,$item);//头入列
}
public function delFirst(){
return array_shift($this->array);//头出列
}
public function setLast($item){
return array_push($this->array,$item);//尾入列
}
public function delLast(){
return array_pop($this->array,$item);//尾出列
}
public function show(){
var_dump($this->array);//打印数组
}
public function Del(){
unset($this->array);//清空数组
}
}
72、如何在命令下运行php脚本(写出两种方式)
# 第一种方式:先进入php安装目录,执行 php 路径/文件名.php。
php my_script.php php -f "my_script.php"
# 第二种方式:php -r “php脚本”;(不需要加php的开始符和结束符)。
php -r "print_r(get_defined_constants());"
73、中间件原理
# 简介
中间件是一个闭包,而且返回一个闭包。中间件为过滤进入应用的HTTP请求提供了一套便利的机制,可以分为
前置中间件和后置中间件。常用于验证用户是否经过认证,添加响应头(跨域),记录请求日志等。
<?php
// 框架核心应用层
$application = function($name) {
echo "this is a {$name} application\n";
};
// 前置校验中间件
$auth = function($handler) {
return function($name) use ($handler) {
echo "{$name} need a auth middleware\n";
return $handler($name);
};
};
// 前置过滤中间件
$filter = function($handler) {
return function($name) use ($handler) {
echo "{$name} need a filter middleware\n";
return $handler($name);
};
};
// 后置日志中间件
$log = function($handler) {
return function($name) use ($handler) {
$return = $handler($name);
echo "{$name} need a log middleware\n";
return $return;
};
};
// 中间件栈
$stack = [];
// 打包
function pack_middleware($handler, $stack)
{
foreach (array_reverse($stack) as $key => $middleware)
{
$handler = $middleware($handler);
}
return $handler;
}
// 注册中间件
// 这里用的都是全局中间件,实际应用时还可以为指定路由注册局部中间件
$stack['log'] = $log;
$stack['filter'] = $filter;
$stack['auth'] = $auth;
$run = pack_middleware($application, $stack);
$run('Laravle');
输出:
Laravle need a filter middleware
Laravle need a auth middleware
this is a Laravle application
Laravle need a log middleware
中间件的执行顺序是由打包函数(pack_middleware)决定,这里返回的闭包实际上相当于:
$run = $log($filter($auth($application)));
$run('Laravle');
# 编写规范
中间件要满足一定的规范:总是返回一个闭包,闭包中总是传入相同的参数(由主要逻辑决定),闭包总是
返回句柄(handler)的执行结果;
如果中间件的逻辑在返回句柄return $handler($name)前完成,就是前置中间件,否则为后置中间件。
74、PHP7中新的语法特性
# 太空船操作符<=>
太空船操作符用于比较两个表达式
例如:当$a小于,等于,大于$b时,分别返回-1,0,1
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1
# 类型声明(必须放第一行,包括命名空间)
declare(strict_types=1); // strict_types=1开启严格模式
function sumOfInts(int ...$ints):int{
return array_sum($ints);
}
# null合并操作符
$page = $_GET['page'] ?? 0;
# 常量数组
define('ANIMALS',['dog','cat','bird']);
# NameSpace批量导入
use Space\{ClassA,ClassB,ClassC as c};
# throwable接口,php5不能捕获错误,php7可以,两种方式:
try{
undefindfunc();
}catch(Error $e){
var_dump($e);
}
set_exception_handler(function($e){
var_dump($e);
});
undefinefunc();
# Closure::call(),改变$this指向
class Test{
private $num = 1;
}
$f = function (){
return $this->num + 1;
}
echo $f->call(new Test);
# intdiv函数
intdiv(10,3); // 3
# list的方括号写法
$arr = [1,2,3];
$list($a,$b,$c) = $arr;
$a = 1; $b = 2; $c = 3; 相当于 [$a,$b,$c] = $arr;
# 抽象语法树
($a)['b'] = 1;
var_dump($a) // ['b'=>1]
75、TCP/IP协议
TCP/IP 是基于 TCP 和 IP 这两个最初的协议之上的不同的通信协议的大的集合。
# TCP
TCP 用于从应用程序到网络的数据传输控制。
TCP 负责在数据传送之前将它们分割为 IP 包,然后在它们到达的时候将它们重组。
# IP-网际协议
IP 负责计算机之间的通信。
IP 负责在因特网上发送和接收数据包。
# HTTP
HTTP 负责 web 服务器与 web 浏览器之间的通信。
HTTP 用于从 web 客户端(浏览器)向 web 服务器发送请求,并从 web 服务器向 web 客户端返回内容(网页)
默认端口号80
# HTTPS
HTTPS 负责在 web 服务器和 web 浏览器之间的安全通信。
作为有代表性的应用,HTTPS 会用于处理信用卡交易和其他的敏感数据。
# SSL
SSL 协议用于为安全数据传输加密数据。
# SMTP
SMTP 用于电子邮件的传输。
# MIME
MIME 协议使 SMTP 有能力通过 TCP/IP 网络传输多媒体文件,包括声音、视频和二进制数据。
# IMAP
IMAP 用于存储和取回电子邮件。
# POP
POP 用于从电子邮件服务器向个人电脑下载电子邮件。
# FTP
FTP 负责计算机之间的文件传输。
# NTP
NTP 用于在计算机之间同步时间(钟)。
# DHCP
DHCP 用于向网络中的计算机分配动态 IP 地址。
# SNMP
SNMP 用于计算机网络的管理。
# LDAP
LDAP 用于从因特网搜集关于用户和电子邮件地址的信息。
# ICMP
ICMP 负责网络中的错误处理。
# ARP
ARP - 用于通过 IP 来查找基于 IP 地址的计算机网卡的硬件地址。
# RARP
RARP 用于通过 IP 查找基于硬件地址的计算机网卡的 IP 地址。
# BOOTP
BOOTP 用于从网络启动计算机。
# PPTP
PPTP 用于私人网络之间的连接(隧道)。
具体参考文章:https://developer.51cto.com/art/201906/597961.htm
mysql面试题
1、varchar与char有什么区别?
# char:
1、表示定长,长度固定
2、插入的长度小于定义长度时,则用空格填充
3、存取速度快,方便程序的存储与查找,空间换取时间
4、最多能存放的字符个数 255 和编码无关
# varchar:
1、表示变长,即长度可变
2、插入多长就存多长
3、速度较慢,时间换空间
4、最多能存放 65532 个字符
尽量避免使用BLOB和TEXT类型,查询会使用临时表,导致严重的性能开销
2、MySQL查询慢有哪些原因?能否给出你的优化建议?为什么会有死锁?
# 查询速度慢的原因很多,常见如下几种:
1、没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷)
2、I/O吞吐量小,形成了瓶颈效应。
3、没有创建计算列导致查询不优化。
4、内存不足
5、网络速度慢
6、查询出的数据量过大(可以采用多次查询,其他的方法降低数据量)
7、锁或者死锁(这也是查询慢最常见的问题,是程序设计的缺陷)
8、sp_lock,sp_who,活动的用户查看,原因是读写竞争资源。
9、返回了不必要的行和列
10、查询语句不好,没有优化
# 可以通过如下方法来优化查询 :
1、把数据、日志、索引放到不同的I/O设备上,增加读取速度,以前可以将Tempdb应放在RAID0上,SQL2000不在
支持。数据量(尺寸)越大,提高I/O越重要.
2、纵向、横向分割表,减少表的尺寸(sp_spaceuse)
3、升级硬件
4、根据查询条件,建立索引,优化索引、优化访问方式,限制结果集的数据量。注意填充因子要适当(最好是使用
默认值0)。索引应该尽量小,使用字节数小的列建索引好(参照索引的创建),不要对有限的几个值的字段建单
一索引如性别字段
5、提高网速;
6、扩大服务器的内存
# 为什么会有死锁?
在多道程序环境中,多个进程可以竞争有限数量的资源。当一个进程申请资源时,如果这时没有可用资源,那么这个
进程进入等待状态。有时,如果所申请的资源被其他等待进程占有,那么该等待进程有可能再也无法改变状态。
3、Mysql锁机制
# 表锁:当多个查询同一时刻进行数据修改时,就会产生并发控制的问题
共享锁(读锁):共享的,不堵塞,多个用户可以同时读一个资源,互不干扰
排它锁(写锁):排他的,一个写锁会阻塞其它的写锁和读锁,只允许一个人进行写入,
防止其它用户读取正在写入的资源
# 锁粒度:
表锁:系统性能开销最小,会锁定整张表,MyISAM
InnoDB事务中,where name='tom'条件使用的不是索引列,也会导致
where id>=1 and id < 5,条件范围内的更新插入,也会导致
行锁:最大程度支持并发处理,开销大,InnoDB
4、请写出数据类型(int char varchar datetime text)的意思;请问 varchar 和 char有什么区别?
Int 整数 char 定长字符 Varchar 变长字符 Datetime 日期时间型 Text 文本型
# Varchar 与char的区别
char是固定长度的字符类型,分配多少空间,就占用多长空间。
Varchar是可变长度的字符类型,内容有多大就占用多大的空间,能有效节省空间。
由于varchar类型是可变的,所以在数据长度改变的时,服务器要进行额外的操作,所以效率比char类型低。
5、MyISAM 和 InnoDB 的基本区别?索引结构如何实现?
# MyISAM:
1、不支持事务
2、表锁
3、易产生碎片,要经常优化
4、读写速度较快
5、拥有全文索引,压缩,空间函数
6、不支持事务和行级锁,不支持崩溃后的安全恢复
7、表存储在两个文件:MYD和MYI
8、设计简单,某些场景下性能很好
# InnoDB:
1、默认事务型引擎,支持事务
2、支持行级锁
3、支持崩溃后的安全恢复
4、读写速度比MyISAM慢
5、数据存储在共享表空间,可以通过配置分开
6、对主键查询的性能高于其它类型的存储引擎
7、内部做了很多优化,从磁盘读取数据时自动在内存创建hash索引,插入数据自动构建插入缓冲区
8、通过一些机制和工具支持真正的热备份
9、支持外键
alter table test add index (`name`);
其它表引擎:Archive、Blackhole、CSV、Memory
6、SQL注入漏洞产生的原因 ? 如何防止?
SQL注入产生的原因:程序开发过程中不注意规范书写sql语句和对特殊字符进行过滤,导致客户端可以通过全局变量
POST和GET提交一些sql语句正常执行。
防止SQL注入:
1、开启配置文件中的magic_quotes_gpc和magic_quotes_runtime设置
2、执行sql语句时使用addslashes进行sql语句转换
3、Sql语句书写尽量不要省略小引号和单引号
4、过滤掉sql语句中的一些关键字:update、insert、delete、select、*
5、提高数据库表和字段的命名技巧,对一些重要的字段根据程序的特点命名,取不易被猜到的。
6、Php配置文件中设置register_globals为off,关闭全局变量注册
7、写出三种以上MySQL数据库存储引擎的名称(提示:不区分大小写)
MyISAM、InnoDB、BDB(Berkeley DB)、Merge、Memory(Heap)、Example、Federated、Archive、
CSV、Blackhole、MaxDB 等等十几个引擎
8、在mysql执行一条sql,期间发生了哪些过程?
1、创建连接
2、查询缓存
3、分析器:分析sql语句,语法是否错误
4、优化器:优化sql语句,选择效率高的方案
5、执行器:执行sql语句,验证是否有读/写权限,没有返回权限错误,
有就打开表调用指定的存储引擎获取执行结果,返回客户端
9、普通索引和唯一索引检索过程中有哪些区别?
# MySQL的查询操作
普通索引:查找到第一个满足条件的记录后,继续向后遍历,直到第一个不满足条件的记录
唯一索引:由于索引定义了唯一性,查找到第一个满足条件的记录后,直接停止继续检索
普通索引会多检索一次,几乎没有影响。因为InnoDB的数据是按照数据页为单位进行读写的,需要读取数据时,
并不是直接从磁盘读取记录,而是先把数据页读到内存,再去数据页中检索
# MySQL的更新操作
普通索引:将数据页从磁盘读入内存,更新数据页
唯一索引:将数据页从磁盘读入内存,判断是否唯一,再更新数据页
由于MySQL中有个changebuffer的机制,会导致普通索引和唯一索引在更新上有一定的区别。
changebuffer的作用是为了降低IO操作,避免系统负载过高。changebuffer将数据写入数据页的过程,叫做merge。
如果需要更新的数据页在内存中时,会直接更新数据页;如果数据不在内存中,会先将更新操作记入changebuffer,
当下一次读取数据页时,顺带merge到数据页中,changebuffer也有定期merge策略。数据库正常关闭的过程中,
也会触发merge。
对于唯一索引,更新前需要判断数据是否唯一(不能和表中数据重复),如果数据页在内存中,就可以直接判断并且
更新,如果不在内存中,就需要去磁盘中读出来,判断一下是否唯一,是的话就更新。changebuffer是用不到的。
即使数据页不在内存中,还是要读出来。
changebuffer用的是bufferpool里的内存,因此不能无限增大。changebuffer的大小,可以通过参数
innodb_change_buffer_max_size来动态设置。这个参数设置为50的时候,表示changebuffer的大小最多只能
占用bufferpool的50%。
10、存储过程和触发器
# 存储过程
1、为以后的使用而保存的一条或多条Mysql语句的集合
2、存储过程就是有业务逻辑和流程的集合
3、可以在存储过程中创建表,更新数据,删除等等
使用场景:
1、通过把处理封装在容易使用的单元中,简化复杂的操作
2、保证数据的一致性
3、简化对变动的管理
# 触发器
提供给程序员和数据分析员来保证数据完整性的一种方法,它是与表事件相关的特殊存储过程
使用场景:
1、通过数据库中的相关表实现级联更改
2、实时监控某张表中的某个字段的更改而需要做出相应的处理
3、生成某些业务编号
滥用会造成数据库及应用程序的维护困难
11、简单描述Mysql中,索引,主键,唯一索引,联合索引的区别,对数据库的性能有什么影响
# 索引:
类似于书籍的目录,要想找到一本书的某个特定主题,需要先查找书的目录,定位对应的页码
存储引擎使用类似的方式进行数据查询,先去索引当中找到对应值,然后根据匹配的索引找到对应的数据行
# 索引对性能的影响
1、大大减少服务器需要扫描的数据量
2、帮助服务器避免排序和临时表
3、将随机IO变成顺序IO
4、大大提高查询速度,降低写的速度,占用磁盘
# 索引类型
1、普通索引:最基本的索引,没有任何约束限制
2、唯一索引:与普通的索引类似,但是具有唯一性
3、主键索引:特殊的唯一索引,不允许有空值
4、组合索引:将多个列组合在一起创建索引,可以覆盖多个列(遵循最左原则)
5、外键索引:只要InnoDB类型的表才可以使用,保证数据的一致性,完整性和实现级联操作
6、全文索引:只能用于MyISAM,并且只能对英文进行检索
# 主键和唯一索引的区别
1、一个表只能有一个主键索引,可以有多个唯一索引
2、主键索引一定是唯一索引,唯一索引不是主键索引
3、主键可以与外键构成参照完整性约束,防止数据不一致
# Mysql索引的创建原则
1、最适合索引的列是出现在where子句中的列,或连接字句中的列而不是出现在select关键字后的列
2、索引列的基数越大,索引的效果越好
3、对字符串索引,应该制定一个前缀长度,可以节省大量的索引空间
4、根据情况创建复合索引,复合索引可以提高查询速度
5、避免创建过多索引,索引会额外占用磁盘空间,降低写操作效率
6、主键尽可能选择较短的数据类型,可以有效减少索引的磁盘占用,提高查询效率
# Mysql索引的注意事项
1、复合索引遵循前缀原则(最左原则)
2、like查询,%不能在前,可以使用全文索引('%字符串%'会导致索引失效,'字符串%'索引有效)
3、column is null 可以使用索引
4、如果msyql估计使用索引比全表扫描更慢,会放弃使用索引
5、如果or前的条件中的列有索引,后面没有,索引都不会被用到
6、列类型是字符串,查询时一定要给值加引号,否则索引失效
12、请简述项目中优化SQL语句执行效率的方法,从哪些方面,SQL语句性能如何分析?
# 1、分析SQL查询慢的方法
记录慢查询日志,分析查询日志,不要打开慢查询日志进行分析,这样比较浪费时间和精力,
可以使用pt-query-digest工具进行分析
# 2、使用show profiles,进入mysql命令界面操作
set profiling=1;开启,服务器上执行的所有语句会检测消耗的时间,存到临时表中
show profile block io,cpu for query 1;
文章参考:https://www.cnblogs.com/itlaoge/archive/2021/01/01/14219568.html
# 3、使用show status
show status会返回一些计数器,show global status查看服务器级别的所有计数
有时根据这些计数,可以猜测出哪些操作代价较高或者消耗时间多
# 4、使用show processlist
观察是否有大量线程处于不正常的状态或者特征
# 5、使用explain或desc
分析单条SQL语句 explain select * from a\G;
# SQL语句优化的一些方法:
1、优化查询过程中的数据访问
# 问题原因:
1)访问数据太多导致查询性能下降
2)确定应用程序是否在检索大量超过需要的数据,可能是太多行或列
3)确认MySQL服务器是否在分析大量不必要的数据行
# 避免使用如下SQL语句:
1)、查询不需要的记录,使用limit解决
2)、多表关联返回全部列,指定A.id,A.name,B.age
3)、总是取出全部列,select * 会让优化器无法完成索引覆盖扫描的优化
4)、重复查询相同的数据,可以缓存数据,下次直接读取缓存
# 是否在扫描额外的记录:
使用explain来进行分析,如果发现查询需要扫描大量的数据但只返回少数的行,可以通过如下技巧去优化:
1)、使用索引覆盖扫描,把所有用的列都放到索引中,这样存储引擎不需要回表获取对应行就可以返回结果
2)、改变数据库和表的结构,修改数据表范式,空间换时间
3)、重写SQL语句,让优化器可以以更优的方式执行查询
2、优化长难的查询语句
MySQL内部每秒能扫描内存中百万行数据,相比之下,响应数据给客户端就要慢的多
使用尽可能少的查询是好的,但是有时将一个大的查询分解为多个查询是很有必要的
# 1)切分查询
将一个大的查询分为多个小的相同的查询
一次性删除1000万的数据要比一次删除一万,暂停一会的方案更加损耗服务器开销
# 2)分解关联查询
可以将一条关联语句分解成多条SQL来执行
让缓存的效率更高
执行单个查询可以减少锁的竞争
在应用层做关联可以更容易对数据库进行拆分
查询效率会有大幅提升
较少冗余记录的查询
3、优化特定类型的查询语句
优化count()查询
count(*)中的*会忽略所有的列,直接统计所有列数,因此不要使用count(列名)
MyISAM中,没有任何where条件的count(*)非常快
当有where条件,MyISAM的count统计不一定比其他表引擎快
1、可以使用explain查询近似值,用近似值替代count(*)
2、增加汇总表
3、使用缓存
4、优化关联查询
确定ON或者USING字句的列上有索引
确保group by和order by中只有一个表中的列,这样MySQL才有可能使用索引
5、优化子查询,尽可能使用关联查询来替代
6、优化group by和distinct
这两种查询均可使用索引优化,是最有效的优化方法
关联查询中,使用标识列进行分组的效率更高
如果不需要order by,进行group by时使用order by null,MySQL不会再进行文件排序
with rollup超级聚合,可以挪到应用程序处理
7、优化limit分页
limit偏移量大的时候,查询效率较低
可以记录上次查询的记录最大id,下次查询时直接根据id来查询
8、优化union查询
union all的效率高于union
13、简述MySQL分表操作和分区的工作原理,分别说说分区和分表的使用场景和各自优缺点?
# 考点:分区表的原理,分库分表的原理,延伸:MySQL的复制原理和负载均衡
# 分区表的原理:
1、对用户而言,分区表是一个独立的逻辑表,但是底层Mysql将其分成了多个物理子表,这对用户来说是透明的
,每一个分区表都会使用一个独立的表文件
2、创建表时使用partition by子句定义每个分区存放的数据,执行查询时,优化器会根据分区定义过滤那些
没有我们需要数据的分区,这样查询只需要查询所需数据在的分区即可
3、分区的主要目的是将数据按照一个较粗的粒度分在不同的表中,这样可以将相关的数据存放在一起,
而且如果想一次性删除整个分区的数据也很方便
# 使用场景
1、表非常大,无法全部存在内存,或者只在表的最后有热点数据,其他都是历史数据,活跃数据和历史数据分开
2、分区表的数据更易维护,可以对独立的分区进行独立的操作
3、分区表的数据可以分布在不同的机器上,从而高效使用资源
4、可以使用分区表来避免某些特殊的瓶颈
5、可以备份和恢复独立的分区
# 限制
1、一个表最多只能有1024个分区
2、5.1版本中,分区表表达式必须是整数,5.5可以使用列分区
3、分区字段中如果有主键和唯一索引列,那么主键列和唯一列都必须包含进来
4、分区表中无法使用外键约束
5、需要对现有表的结构进行修改
6、所有分区都必须使用相同的存储引擎
7、分区函数中可以使用的函数和表达式会有一些限制
8、某些存储引擎不支持分区
9、对于MyISAM的分区表,不能使用load index into cache
10、对于MyISAM表,使用分区表时需要打开更多的文件描述符
# 分库分表的原理
通过一些hash算法或者工具实现将一张数据表垂直或者水平进行物理切分
# 适用场景
1、单表记录条数达到百万到千万级别时
2、解决表锁的问题
# 分表方式
水平分割:表很大,分割后可以降低在查询时需要读的数据和索引的页数,同时也降低了索引的层数,
提高查询速度
使用场景:
1、表中的数据本身就有独立性,例如表中分别记录各个地区的数据或者不同时期的数据,
特别是有些数据常用,有些不常用
2、需要把数据存放在多个介质上
缺点
1、给应用增加复杂度,通常查询时需要多个表名,查询所有数据都需union
2、在许多数据库应用中,这种复杂性会超过它带来的优点,查询时会增加读一个索引层的磁盘次数
垂直分表:把主键和一些列放在一个表,然后把主键和另外的列放在另一个表中
使用场景:
1、如果一个表中某些列常用,而另外一些列不常用
2、可以使数据行变小,一个数据页能存储更多数据,查询时减少I/O次数
缺点:
管理冗余列,查询所有数据需要join操作
# 分表缺点
有些分表的策略基于应用层的逻辑算法,一旦逻辑算法改变,整个分表逻辑都会改变,扩展性较差
对于应用层来说,逻辑算法无疑增加开发成本
# MySQL主从复制工作原理
1、在主库上把数据更改记录到二进制日志
2、从库将主库的日志复制到自己的中继日志
3、从库读取中继日志中的事件,将其重放到从库数据中
# 解决的问题:
1、数据分布:随意停止或开始复制,并在不同地理位置分布数据备份
2、负载均衡:降低单个服务器的压力
3、高可用和故障切换:帮助应用程序避免单点失败
4、升级测试,可以使用高版本的MySQL作为从库
14、现有一统计网站独立访客的需求,流量百万以上,如以 IP 为标识,可以查看当天实时或者指定某天的 IP 数(需要去重),才用 MySQL 来实现,那么:
# 1、你会如何设计表和索引?(文字、sql 均可,方案尽可能高效)
IP 地址转换为整形存储,按日期分表,以 (IP, 日期) 作为联合索引
# 2、数据如何入库,当天实时和某天数据该如何查询?(写出 sql 语句)
redis 队列缓存 -> MySQL 批量入库
select count(ip) from visit_log_{yyyymm} where date = {yyyy-mm-dd};
15、事务四种隔离级别
对于同时运行的多个事务,当这些事务访问数据库中相同的数据时,如果没有采取必要的隔离机制,
就会导致各种并发问题:
# 脏读:
对于两个事务T1, T2, T1读取了已经被T2更新但还没有被提交的字段。
之后,若T2回滚, T1读取的内容就是临时且无效的。
# 不可重复读:
对于两个事务T1, T2, T1读取了一个字段,然后T2更新了该字段。之后, T1再次读取同一个字段,值就不同了。
# 幻读:
对于两个事务T1,T2,T1从一个表中读取了-个字段,然后T2在该表中插入了一些新的行。
之后,如果T1再次读取同一一个表就会多出几行。
数据库事务的隔离性:数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。
一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,
**隔离级别越高,数据一致性就越好, 但并发性越弱。**
# 四种隔离级别:
# 1、READ UNCOMMITTED(读未提交数据):
允许事务读取未被其他事物提交的变更,脏读、不可重复读和幻读的问题都会出现
# 2、READ COMMITED(读已提交数据):
只允许事务读取已经被其它事务提交的变更,可以避免脏读,但不可重复读和幻读问题仍然可能出现
# 3、REPEATABLE READ(可重复读):
确保事务可以多次从一个字段中读取相同的值在这个事务持续期间,禁止其他事物
对这个字段进行更新可以避免脏读和不可重复读,但幻读的问题仍然存在
# 4、SERIALIZABLE(串行化):
确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入,更新和删除操作,
所有并发问题都可以避免,但性能十分低下.
Oracle支持3种事务隔离级别:1,2,4,默认,2
Mysql支持四种事务隔离级别:默认3
16、mysql中有一个事务频繁出现事务锁等待,结合你的经验,请阐述你分析此问题的方法和步骤?
# 问题描述
1. 在一个事务中同时包括了SELECT,UPDATE语句
2. SELECT和UPDATE涉及到的数据为同一张表中的同一记录
3. 在并发情况下就会触发数据库锁等待和死锁的情况
# 共享锁(S锁)
SELECT 语句时对查询行加的锁类型为共享锁。
共享锁的特性为:不允许其他事务对该记录加排他锁,但是允许加共享锁。
保持时间:可重复度级别中共享锁会保持到事务结束。
# 排他锁(X锁)
MYSQL的默认隔离级别(可重复度)中,UPDATE,INSERT和DELETE语句对操作语句加的锁类型为排他锁
排他锁的特性为:不允许其他事务对该记录加共享锁和排他锁
保持时间:会保持到事务结束。
# 更新锁(U锁)
UPDATE语句在更新前需要对整表上更新锁,在找到记录后对需要操作记录上排他锁并释放更新锁
更新锁特性为:用来预定要对此页施加排他锁,它允许其他事务加共享锁,但不允许再施加更新锁或排他锁
保持时间:会保持到事务结束。
# 分析
在MYSQL中使用的默认隔离接级别为可重复读,那么SELECT语句在执行时会给记录增加共享锁,UPDATE语句会给
数据上更新锁和排他锁,INSERT语句会给数据上排他锁(数据为新增,在事务结束之前是对其他事务不可见的,
可忽略)。
这样我们可以很清晰的看出,假设当前并行执行两个事务A和B。
1. 当A开始执行事务中的SELECT语句时,会给该条记录加共享锁,这样事务B不能够对该记录加排他锁,
需要等待A事务提交
2. 当A开始执行事务中的UPDATE语句时,首先会给该页记录加更新锁,这样事务B不能够对该页记录加排他锁,
需要等待A事务提交;同时在定位记录后给记录增加排他锁,这样事务B不能对该记录加共享锁和排他锁,
需要等待A事务提交。
3. 由于A事务执行时间过长,在整个执行事务中还存在调用其他服务,查询其他表的操作。导致事务提交时间过长
B事务一直等待,导致B事务锁等待时间过长。引起文章标题中的问题。
# 解决方案
方案一:缩小事务范围,只把DML语句(UPDATE,INSERT,DELETE)包裹在事务中
方案二:尽可能减少事务中包含的DML语句。提高事务的执行时间。
17、mysql的InnoDB和Myisam引擎在使用select count(*)语句时哪个效率更高,为什么?
在 MyISAM 存储引擎中,把表的总行数存储在磁盘上,
当执行 select count(*) from t 时,直接返回总数据。
在 InnoDB 存储引擎中,跟 MyISAM 不一样,没有将总行数存储在磁盘上,
当执行 select count(*) from t 时,会先把数据读出来,一行一行的累加,最后返回总数量。
原因:跟事务的特性有关,由于多版本并发控制(MVCC)的原因,导致行数不确定
优化方法:加一个普通索引,统计会走索引,索引的本质是数据结构,提高效率
18、InnoDB引擎支持行锁,那什么情况下会产生行锁,什么情况下变成表锁?
产生行锁:当通过索引检索的数据,并进行对应操作时,产生行锁
产生表锁:当不通过索引或索引使用不当失效时,检索的数据操作时,产生表锁
InnoDB事务中,where name='tom'条件使用的不是索引列,也会导致
where id>=1,也会导致
where id>=1 and id < 5,条件范围内的更新插入,只会锁定几条
19、MySQL优化
1:避免使用 select * 你需要什么信息,就查询什么信息,查询的多了,查询的速度肯定就会慢
2:当你只需要查询出一条数据的时候,要使用 limit 1 比如你要查询数据中是否有男生,只要查询一条含有
男生的记录就行了,后面不需要再查了,使用Limit 1 可以在找到一条数据后停止搜索
3:建立高性能的索引,索引不是随便加的也不是索引越多越好,更不是所有索引对查询都有效
4:建数据库表时,给字段设置固定合适的大小. 字段不能设置的太大,设置太大就造成浪费,会使查询速度变慢
5:要尽量使用not null
6:EXPLAIN 你的 SELECT 查询使用EXPLAIN,可以帮助你更了解MySQL是如何处理你的sql语句的,
你可以查看到sql的执行计划,这样你就能更好的去了解你的sql语句的不足,然后优化语句.
7:在Join表的时候,被用来Join的字段,应该是相同的类型的,且字段应该是被建过索引的,这样,MySQL内部会
启动为你优化Join的SQL语句的机制。
8:如果你有一个字段,比如“性别”,“国家”,“民族”, “省份”,“状态”或“部门”,这些字段的取值是有限
而且固定的,那么,应该使用 ENUM 而不是 VARCHAR。
因为在MySQL中,ENUM类型被当作数值型数据来处理,而数值型数据被处理起来的速度要比文本类型快得多。
这样,我们又可以提高数据库的性能。
9:垂直分割将常用和有关系的字段放在相同的表中,把一张表的数据分成几张表
这样可以降低表的复杂度和字段的数目,从而达到优化的目的
10:优化where查询
1): 避免在where子句中对字段进行表达式操作
比如:select列from 表where age*2=36;建议改成select列from表where age=36/2;
2): 应尽量避免在 where 子句中使用 !=或<> 操作符,否则将引擎放弃使用索引而进行全表扫描。
3): 应尽量避免在 where 子句中对字段进行 null 值 判断
4):应尽量避免在 where 子句中使用 or 来连接条件
11:不建议使用%前缀模糊查询,这种查询会导致索引失效而进行全表扫描
例如LIKE “%name”或者LIKE “%name%这两种都是不建议的.但是可以使用LIKE “name%”。
对于LIKE “%name%,可以使用全文索引的形式
12:要慎用in和 not in
例如:select id from t where num in(1,2,3)
建议改成 select id from t where num between 1 and 3
对于连续的数值,能用 between 就不要用 in 了
13:理解in和exists, not in和not exists的区别
很多时候用 exists 代替 in 是一个好的选择:如查询语句使用了not in那么内外表都进行全表扫描,没用
到索引,而not exists子查询依然能用到表上索引,所以无论哪个表大,用not exists都比not in要快。
select num from a where num in(select num from b)
建议改成: select num from a where exists(select 1 from b where num=a.num)
区分in和exists主要是造成了驱动顺序的改变(这是性能变化的关键),如果是exists,那么以外层表为
驱动表,先被访问,如果是IN,那么先执行子查询。所以IN适合于外表大而内表小的情况;EXISTS适合于
外表小而内表大的情况。
关于not in和not exists,推荐使用not exists,不仅仅是效率问题,not in可能存在逻辑问题
14:理解select Count (*)和Select Count(1)以及Select Count(column)区别
一般情况下,Select Count (*)和Select Count(1)两着返回结果是一样的
假如表沒有主键(Primary key), 那么count(1)比count(*)快,
如果有主键的話,那主键作为count的条件时候count(主键)最快
如果你的表只有一个字段的话那count(*)就是最快的
count(*) 跟 count(1) 的结果一样,都包括对NULL的统计,而count(column) 是不包括NULL的统计
收藏文章:https://www.cnblogs.com/bypp/p/7755307.html
20、mysql外连接、内连接与自连接的区别
# 1、内连接( inner join )
两个表中都有对应的数据存在的记录,才会返回
select a.runoob_id, b.runoob_count from runoob_tbl a inner join tcount_tbl b
ON a.runoob_author = b.runoob_author;
# 2、自然连接( natural join )
自联结顾名思义就是把一张表假设为两张一样的表,然后在做“多表查询”
select e.empName,b.empName from t_employee e
left join t_employee b on e.bossId = b.id;
select b.* from shopping as a,shopping as b
where a.name='惠惠' and a.price<b.price order by b.id;
# 3、外连接
(1)左外连接(left outer join / left join):
获取左表所有记录,右表没有对应数据,每个字段以null填充
select a.runoob_id, b.runoob_count from runoob_tbl a left outer
join tcount_tbl b ON a.runoob_author = b.runoob_author;
(2)右外连接(right outer join / right join):
获取右表所有记录,左表没有对应数据,每个字段以null填充
select a.runoob_id, b.runoob_count from runoob_tbl a right outer
join tcount_tbl b on a.runoob_author = b.runoob_author;
(3)全外连接(full join):mysql 暂不支持,可以用union模拟实现:
返回左表和右表中的所有行,当某行在另一个表中没有匹配行时,则另一个表的列用null填充
select e.empName,d.deptName from t_employee e
left join t_dept d on e.dept = d.id
union
select e.empName,d.deptName from t_employee e
right join t_dept d on e.dept = d.id;
# 注意:内连接不写连接条件会出现笛卡尔积的结果,应该避免这种情况,而外连接不写连接条件会报错
21、说说mysql主从同步怎么做的吧?
1:master提交完事务后,写入binlog
2:slave连接到master,获取binlog
3:master创建dump线程,推送binglog到slave
4:slave启动一个IO线程读取同步过来的master的binlog,记录到relay log中继日志中
5:slave再开启一个sql线程读取relay log事件并在slave执行,完成同步
6:slave记录自己的binglog
由于mysql默认的复制方式是异步的,主库把日志发送给从库后不关心从库是否已经处理,这样会产生一个问题
就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了。由此产生两个概念。
# 全同步复制
主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话
性能会受到严重影响。
# 半同步复制
和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回ACK确认给主库,主库收到至少一个
从库的确认就认为写操作完成。
22、索引的优缺点
1:设置了合适的索引之后,数据库利用各种快速定位技术,能够大大加快查询速度,这是创建索引的最主要的原因
2:当表很大或查询涉及到多个表时,使用索引可以成千上万倍地提高查询速度。
3:可以降低数据库的IO成本,并且索引还可以降低数据库的排序成本。
4:通过创建唯一性索引,可以保证数据表中每一行数据的唯一性。
5:可以加快表与表之间的连接。
6:在使用分组和排序时,可大大减少分组和排序的时间:
#优点:可以减少I/O次数,加快检索速度;根据索引分组和排序,可以加快分组和排序速度
#缺点:创建和维护索引都要消耗时间,对数据进行增删改的时候也要动态维护索引,降低效率
23、索引的使用场景
使用where关键字的时候,可以为某些字段建立索引,提高查询速度
使用order by进行排序的时候,数据量很大会使用外部排序,效率很低,此时如果对相应字段建立索引,
能够提高效率
如果要查询的字段都建立了索引,那么存储引擎会查询索引表,提高效率,这也被称为索引覆盖
23、避免死锁方法
1:为表添加合理的索引。不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大
2:在同一个事务中,尽可能一次锁定所有需要的资源,减少产生死锁的概率
3:对于非常容易产生死锁的部分,可以尝试使用表级锁,通过表级锁减少产生死锁的概率
4:降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,
可以避免掉很多因为gap锁造成的死锁。
5:以固定的顺序访问表和行。比如两个更新数据的事务,事务A 更新数据的顺序 为1,2;
事务B更新数据的顺序为2,1。这样更可能会造成死锁。
24、MySQL中各种锁
# 表级锁和行级锁
表级锁:锁定整张表,开销小,加锁快,不会出现死锁,并发度低
行级锁:锁定某一行或某几行数据或者是间隙,开销小,加锁慢,会出现死锁,并发度高
表锁由MySQL Server实现,行锁由存储引擎实现,InnoDB支持行锁和表锁,默认使用行锁,
MyISAM和MEMORY都使用表锁
# 读写锁与意向锁
略
# 记录锁、间隙锁、Next-key锁
InnoDB实现了三种行锁的算法:记录锁(Record Lock)、间隙锁(Gap Lock)、Next-key锁(Next-key Lock)。
只有在RR隔离级别下才有间隙锁和Next-key锁
记录锁:记录锁是对索引的锁定,锁定某个索引后,会阻止其他事务对该索引的插入、删除和更新操作。如果没有
设置索引,InnoDB会自动创建隐藏的聚簇索引
间隙锁:间隙锁是一种加在两个索引之间的锁,或者加在第一个索引之前或最后一个索引之后的间隙。使用间隙锁
可以防止其他事务在这个范围内插入或修改记录,间隙可以包含单个或多个记录,甚至没有记录,它能保证两次
读取这个范围内的记录不会改变,从而不会出现幻读现象。间隙锁和间隙锁是互不冲突的,间隙锁的唯一作用就是
防止其他事务的插入,所以间隙X锁和间隙S锁没有任何区别
Next-key Lock锁:由记录锁和间隙锁组成,它指的是锁定某个索引以及该索引之前的间隙
# 插入意向锁
插入意向锁(Insert Intention Lock):是一种特殊的间隙锁,在insert的时候表示插入的意向。插入意向锁
不影响其他事务加其他任何锁,但是插入意向锁会与间隙锁或Next-key锁冲突
# 自增锁
自增锁(Auto-inc Lock)是一种特殊的表级别锁,专门针对事务插入AUTO_INCREMENT类型的列。事务往表中插入
记录时,所有其他事务的插入必须等待,以便第一个事务插入的行是连续的主键值
# 乐观锁悲观锁
乐观锁:不加锁,假定不会出现冲突直接进行操作,一般使用版本号和CAS操作来实现。乐观锁适用于多读场景,
写操作比较少的情况
悲观锁:加锁,假定会出现冲突,操作时会进行加锁,一般使用封锁机制来实现。悲观锁适用于多写场景,
写操作比较多的情况
25、什么是表分区?
根据一定规则,将数据库中的一张表分解成多个更小的,容易管理的部分。从逻辑上看,只有一张表,
但是底层却是由多个物理分区组成。
26、表分区与分表的区别
分表:指的是通过一定规则,将一张表分解成多张不同的表。比如将用户订单记录根据时间成多个表。
分表与分区的区别在于:分区从逻辑上来讲只有一张表,而分表则是将一张表分解成多张表。
27、表分区有什么好处?
(1):与单个磁盘或文件系统分区相比,可以存储更多的数据。
(2):对于那些已经失去保存意义的数据,通常可以通过删除与那些数据有关的分区,很容易地删除那些数据。
相反地,在某些情况下,添加新数据的过程又可以通过为那些新数据专门增加一个新的分区,来很方便地实现。
(3):一些查询可以得到极大的优化,这主要是借助于满足一个给定WHERE语句的数据可以只保存在一个或多个
分区内,这样在查找时就不用查找其他剩余的分区。因为分区可以在创建了分区表后进行修改,所以在第一次配置
分区方案时还不曾这么做时,可以重新组织数据,来提高那些常用查询的效率。
(4):涉及到例如SUM()和COUNT()这样聚合函数的查询,可以很容易地进行并行处理。这种查询的一个简单例子
如 “SELECT salesperson_id, COUNT (orders) as order_total FROM sales GROUP BY salesperson_id”
通过“并行”,这意味着该查询可以在每个分区上同时进行,最终结果只需通过总计所有分区得到的结果。
5):通过跨多个磁盘来分散数据查询,来获得更大的查询吞吐量。
28、分区表的限制因素
(1):一个表最多只能有1024个分区。
(2):MySQL5.1中,分区表达式必须是整数,或者返回整数的表达式。
在MySQL5.5中提供了非整数表达式分区的支持。
(3):如果分区字段中有主键或者唯一索引的列,那么多有主键列和唯一索引列都必须包含进来。
即:分区字段要么不包含主键或者索引列,要么包含全部主键和索引列。
(4):分区表中无法使用外键约束。
(5):MySQL的分区适用于一个表的所有数据和索引,不能只对表数据分区而不对索引分区,也不能只对索引分区
而不对表分区,也不能只对表的一部分数据分区。
29、MySQL支持的分区类型有哪些?
(1)、RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区。
(2)、LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择
(3)、HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的
列值进行计算。这个函数可以包含MySQL 中有效的、产生非负整数值的任何表达式。
(4)、KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的
哈希函数。必须有一列或多列包含整数值。
说明:在MySQL5.1版本中,RANGE,LIST,HASH分区要求分区键必须是INT类型,或者通过表达式返回INT类型。
但KEY分区的时候,可以使用其他类型的列(BLOB,TEXT类型除外)作为分区键。
30、事务是什么,以及事务四个特性
# 一.什么是事务
事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消
也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。
事务的结束有两种,当事务中的所以步骤全部成功执行时,事务提交。如果其中一个步骤失败,
将发生回滚操作,撤消撤消之前到事务开始时的所以操作。
# 二.事务的 ACID
事务具有四个特征:原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )和
持续性( Durability )。这四个特性简称为 ACID 特性。
1 、原子性
事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做
2 、一致性
事 务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的
结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未
完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是
不一致的状态。
3 、隔离性
一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个
事务之间不能互相干扰。
4 、持续性
也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不
应该对其执行结果有任何影响。
31、悲观锁和乐观锁
# 悲观锁:
就是对于数据的处理持悲观态度,总认为会发生并发冲突,获取和修改数据时,别人会修改数据。
所以在整个数据处理过程中,需要将数据锁定
# 示例代码如下:
set autocommit = 0;
select * from goods where id = 1 for update;
update goods set num = 0 where id = 1;
commit;
# 乐观锁:
就是对数据的处理持乐观态度,乐观的认为数据一般情况下不会发生冲突,只有提交数据更新时,才会对数据
是否冲突进行检测
如果发现冲突了,则返回错误信息给用户,让用户自已决定如何操作。
乐观锁的实现不依靠数据库提供的锁机制,需要我们自已实现,实现方式一般是记录数据版本,一种是通过
版本号,一种是通过时间戳。
给表加一个版本号或时间戳的字段,读取数据时,将版本号一同读出,数据更新时,将版本号加1。
当我们提交数据更新时,判断当前的版本号与第一次读取出来的版本号是否相等。如果相等,则予以更新,
否则认为数据过期,拒绝更新,让用户重新操作
# 示例代码如下:
set autocommit = 0;
1:select nums, version from goods where id = 1;
2:判断库存是否大于购买数量,不大于回滚
3:update goods set nums = nums - 1, version = version + 1
where id = 1 and version = 0 and nums >= 1;
4:判断更新操作是否成功执行,如果成功,则提交,否则就回滚
32、什么情况下导致索引失效?
1、like查询,%不能在前,可以使用全文索引('%字符串%'会导致索引失效,'字符串%'索引有效)
2、or语句前后没有同时使用索引
3、组合索引,没有使用第一列索引,索引失效
4、列类型是字符串,查询时一定要给值加引号,比如:name=1,类型不匹配,应该写成name='1'
5、在索引字段上使用not in,<>,!=,等范围操作符是永远不会用到索引的,例如:id > 5
6、表达式计算,例如:select * from stu where status+1=1;
7、正则表达式,例如:select * from stu where sname regexp '^后盾人'
8、列的重复数据较多
9、is null,is not null 有可能,分情况
B-tree索引 is null不会走,is not null会走,位图索引 is null,is not null 都会走
联合索引 is not null 只要在建立的索引列(不分先后)都会走, in null时 必须要和建立索引第一列一起
使用当建立索引第一位置条件是is null 时,其他建立索引的列可以是is null(但必须在所有列 都满足
is null的时候),或者=一个值; 当建立索引的第一位置是=一个值时,其他索引列可以是任何情况(包括
is null =一个值),以上两种情况索引都会走。其他情况不会走
33、大量数据进行插入时,如何处理?
1、存储过程
delimiter $$ #声明存储过程的结束符号为$$
create procedure auto_insert1()
BEGIN
declare i int default 1;
while(i<3000000)do
insert into s1 values(i,'bob','male',concat('bob',i,'@163.com'));
set i=i+1;
end while;
END$$ #$$结束
delimiter ; #重新声明分号为结束符号
// 查看存储过程
show create procedure auto_insert1\G
// 调用存储过程
call auto_insert1();
省事,但是效率很低,需要进行优化
优化文章参考:https://blog.csdn.net/qq_36182135/article/details/84854619
2、一条sql语句插入多条记录
insert into s1 values(1,'bob','male','bob@163.com'),
(2,'tony','tony','bob@163.com');
合并后日志量(MySQL的binlog和innodb的事务日志)减少了,降低日志刷盘的数据量和频率,
从而提高效率。通过合并SQL语句,同时也能减少SQL语句解析的次数,减少网络传输的IO
3、在事务中插入处理
START TRANSACTION;
insert into s1 values(1,'bob','male','bob@163.com');
insert into s1 values(1,'bob','male','bob@163.com');
COMMIT;
INSERT操作时,mysql内部会建立一个事务,在事务内才进行真正插入处理操作。通过使用事务可以
减少创建事务的消耗,所有插入都在执行后才进行提交操作。
4、数据有序插入
insert into s1 values(2,'bob','male','bob@163.com');
insert into s1 values(1,'bob','male','bob@163.com');
insert into s1 values(3,'bob','male','bob@163.com');
由于数据库插入时,需要维护索引数据,无序的记录会增大维护索引的成本。我们可以参照innodb使用
的B+Tree 索引,如果每次插入记录都在索引的最后面,索引的定位效率很高,并且对索引调整较小;如果
插入的记录在索引中间,需要B+tree进行分裂合并等处理,会消耗比较多计算资源,并且插入记录的索引定
位效率会下降,数据量较大时会有频繁的磁盘操作。
34、数据库存储优化
# 1、禁用索引
对于使用索引的表,插入记录时,MySQL会对插入的记录建立索引。如果插入大量数据,建立索引会降低插入数据
速度。为了解决这个问题,可以在批量插入数据之前禁用索引,数据插入完成后再开启索引。
禁用索引的语句: ALTER TABLE table_name DISABLE KEYS 开启索引语句:
ALTER TABLE table_name ENABLE KEYS
MyISAM对于空表批量插入数据,则不需要进行操作,因为MyISAM引擎的表是在导入数据后才建立索引。
# 2、禁用唯一性检查
唯一性校验会降低插入记录的速度,可以在插入记录之前禁用唯一性检查,插入数据完成后再开启。
禁用唯一性检查的语句:SET UNIQUE_CHECKS = 0; 开启唯一性检查的语句:SET UNIQUE_CHECKS = 1;
# 3、禁用外键检查
插入数据之前执行禁止对外键的检查,数据插入完成后再恢复,可以提供插入速度。
禁用:SET foreign_key_checks = 0; 开启:SET foreign_key_checks = 1;
# 4、批量插入数据
插入数据时,可以使用一条INSERT语句插入一条数据,也可以插入多条数据。
# 5、禁止自动提交
插入数据之前执行禁止事务的自动提交,数据插入完成后再恢复,可以提高插入速度。
禁用:SET autocommit = 0; 开启:SET autocommit = 1;
35、Mysql性能优化
1:硬件和操作系统层面的优化
从硬件层面来说,影响MySQL性能因素主要是CPU,可用内存大小,磁盘读写速度,网络带宽
从操作系统层面来说,应用文件句柄数,操作系统的网络配置,都会影响MySQL的性能
这部分的优化一般是由DBA或者运维工程师去完成,在硬件基础资源的优化中,我们重点应该关注的是,
服务本身所承载的体量,然后提出合理的指标要求,避免出现资源浪费的现象
2:架构设计层面的优化
MySQL是一个磁盘IO访问非常频繁的关系型数据库,在高并发和高性能的场景中,MySQl数据库必然承受
巨大的并发压力,在此时呢我们优化的方式注意可以分为几个部分:
1)搭建Mysql主从集群:单个Mysql服务容易导致单点故障,一旦服务宕机,将会导致依赖Mysql数据库
的应用全部无法响应,主从集群或主主集群,都可以保证服务的高可用性
2)读写分离设计:在读多写少的场景中,用过读写分离的方案,可以去避免读写冲突导致的性能问题
3)引入分库分表的机制:通过分库可以降低单个服务器节省的IO压力,通过分表的方式,可以降低单表
数据量,从而提升sql查询效率
4)针对热点数据:引入更为高效的分布式数据库,比如:redis,MongoDB等。可以很好的缓解mysql
的访问压力,同时还能提升数据的检索性能
3:MySQL程序配置优化
1)Mysql是一个经过互联网大厂,检验过的生产级别的成熟数据库,对于数据库本身的数据优化,
一般可以通过Mysql配置文件myf来完成,比如说:mysql5.7版本默认最大连接数是151个,
可以在myf修改
2)binlog日志默认是不开启,也可以在myf中修改开启
3)缓存池Bufferpool默认大小配置等
这些配置一般是和用户的安装环境以及使用场景有关系,因此官方只提供一个默认配置,具体的情况还是
得使用者根据实际情况去修改,关于配置项的修改需要关注两个层面,第一个是配置的作用域,它可以
分为会话级别和全局范围,第二个是是否支持热加载,因此针对这两个点,需要注意的是全局参数的设定
对于已经存在的会话是无法生效的,会话参数的设定随着会话的销毁而失效。第三个是全局类的统一配置
建议配置在默认的配置文件中,否则重启服务会导致配置失效
4:SQL执行优化
1)慢SQL的定位和排查:通过慢查询日志和慢查询日志工具分析,得到有问题的SQL列表
2)执行计划分析:使用关键字explain,查看当前sql的执行计划,重点关注type,key,rows,filterd等 字段,定位SQL执行慢的根本原因,再去进行优化
3)使用show profile工具:是mysql提供的可以用来分析当前会话中SQL语句资源消耗情况工具,可以
用于SQL调优的测量,在当前会话中默认情况下show profile是关闭状态,打开之后会保存最近15次
的运行结果,针对运行慢的SQL通过profile工具进行详细分析,可以得到SQL执行过程中所有资源的
开销情况,如IO开销,CPU开销,内存开销等等
SQL优化的规则:
1:SQL的查询一定要基于索引来进行数据扫描
2:避免索引列上使用函数或者运算符,会导致索引失效
3:where字句中like %号放在右边
4:使用索引扫描,联合索引中从左往右,命中越多越好
5:尽可能使用SQL语句用到的索引完成排序,避免使用文件排序的方式
6:查询有效的列信息即可,少用 * 代替列信息
7:永远用小结果集驱动大结果集
redis面试题
1、谈谈你对消息队列的理解?除了redis,有用过哪些?
#谈谈你对消息队列的理解?
是一种应用间的通信方式,消息发送后可以立即返回,有消息系统来确保信息的可靠专递,消息发布者只管
把消息发布到MQ中而不管谁来取,消息使用者只管从MQ中取消息而不管谁发布的,这样发布者和使用者都不用
知道对方的存在
# 除了redis,有用过哪些?
RabbitMQ
2、使用redis有哪些好处?
(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
(2) 支持丰富数据类型,支持string,list,set,sorted set,hash
(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
3、redis相比memcached有哪些优势?
(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
(2) redis的速度比memcached快很多
(3) redis可以持久化其数据
4、redis常见性能问题和解决方案:
(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…
这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,
可以立刻启用Slave1做Master,其他不变。
5、MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据
相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略:
voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
6、Memcache与Redis的区别都有哪些?
1)、存储方式
Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。
Redis有部份存在硬盘上,这样能保证数据的持久性。
2)、数据支持类型
Memcache对数据类型支持相对简单。
Redis有复杂的数据类型。
3)value大小
redis最大可以达到1GB,而memcache只有1MB
7、Redis 常见的性能问题都有哪些?如何解决?
1):Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常
大的,会间断性暂停服务,所以Master最好不要写内存快照。
2):Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断
增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和
AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,
策略为每秒同步一次。
3):Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,
导致服务load过高,出现短暂服务暂停现象。
4):Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内
8、redis 最适合的场景
Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,
跟传统意义上的持久化有比较大的差别,那么可能大家就会有疑问,似乎Redis更像一个加强版的Memcached,
那么何时使用Memcached,何时使用Redis呢?
如果简单地比较Redis与Memcached的区别,大多数都会得到以下观点:
1 、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
2 、Redis支持数据的备份,即master-slave模式的数据备份。
3 、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
(1)会话缓存(Session Cache)
最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)
的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,
大部分人都会不高兴的,现在,他们还会这样吗?
幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的
商业平台Magento也提供Redis的插件。
(2)全页缓存(FPC)
除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有
磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。再次以Magento为
例,Magento提供一个插件来使用Redis作为全页缓存后端。此外,对WordPress的用户来说,Pantheon有一个
非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。
(3)队列
Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列
平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。
(4)排行榜/计数器
Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得
我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合
中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:
当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:
ZRANGE user_scores 0 10 WITHSCORESAgora Games就是一个很好的例子,用Ruby实现的,它的排行榜
就是使用Redis来存储数据的。
9、Redis速度快的原因
1、开发语言:Redis就是用C语言开发的,所以执行会比较快
2、纯内存访问:
Redis将所有数据放在内存中,非数据同步正常工作中,是不需要从磁盘读取数据的,0次IO。
内存响应时间大约为100纳秒,这是Redis速度快的重要基础
3、单线程
1)单线程简化算法的实现,并发的数据结构实现不但困难且测试也麻烦。
2)单线程避免了线程切换以及加锁释放锁带来的消耗,对于服务端开发来说,锁和线程切换通常是性能杀手。
当然了,单线程也会有它的缺点,也是Redis的噩梦:阻塞。如果执行一个命令过长,那么会造成其他命
令的阻塞,对于Redis是十分致命的,所以Redis是面向快速执行场景的数据库。
除了Redis之外,Node.js也是单线程,Nginx也是单线程,但他们都是服务器高性能的典范
4、非阻塞多路I/O复用机制
I/O 多路复用模型是利用select、poll、epoll可以同时监察多个流的 I/O 事件的能力,在空闲的时候,
会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流
(epoll是只轮询那些真正发出了事件的流),依次顺序的处理就绪的流,这种做法就避免了大量的无用操作
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高
效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的
操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。
10、redis持久化机制
Redis 的数据全部在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证 Redis 的数据
不会因为故障而丢失,这种机制就是 Redis 的持久化机制。
# Redis 的持久化机制有两种:
1、RDB快照:某个时间点的一次全量数据备份,是二进制文件,在存储上非常紧凑
2、AOF 日志:连续的增量备份,日志记录的是内存数据修改的指令记录文本,恢复时间会无比漫长
# RDB的优缺点
# 优点
RDB文件小,非常适合定时备份,用于灾难恢复
Redis加载RDB文件的速度比AOF快很多,因为RDB文件中直接存储的
时内存数据,而AOF文件中存储的是一条条命令,需要重演命令。
# 缺点
RDB无法做到实时持久化,若在两次bgsave间宕机,则会丢失区间(分钟级)的增量数据,不适用于实时性要求较
高的场景,RDB的cow机制中,fork子进程属于重量级操作,并且会阻塞redis主进程,存在老版本的Redis不兼容
新版本RDB格式文件的问题
# AOF的优缺点
# 优点
AOF只是追加写日志文件,对服务器性能影响较小,速度比RDB要快,消耗的内存较少
# 缺点
1、AOF方式生成的日志文件太大,需要不断AOF重写,进行瘦身。
2、即使经过AOF重写瘦身,由于文件是文本文件,文件体积较大(相比于RDB的二进制文件)。
3、AOF重演命令式的恢复数据,速度显然比RDB要慢。
具体持久化机制参考:https://zhuanlan.zhihu.com/p/77646963
11、缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题
# 缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间
(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求
都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,
造成整个系统崩溃。
# 解决办法:
大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行
读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开。
# 二、缓存穿透
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,
每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,
这也是经常提的缓存命中率问题。
解决办法;
最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这
个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然
把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样
第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。
5TB的硬盘上放满了数据,请写一个算法将这些数据进行排重。如果这些数据是一些32bit大小的数据该如何解决?
如果是64bit的呢?
对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。
Bitmap: 典型的就是哈希表
缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、
时间来完成了。
# 布隆过滤器(推荐)
就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。
它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。
Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入
几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的
Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。
Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。
# 缓存穿透与缓存击穿的区别
缓存击穿:是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬
间,持续的大并发就穿破缓存,直接请求数据。
解决方案;在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,
访问结束再删除该短期key。
增:给一个我公司处理的案例:背景双机拿token,token在存一份到redis,保证系统在token过期时都只有一个
线程去获取token;线上环境有两台机器,故使用分布式锁实现。
# 三、缓存预热
缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,缓存预热就是系统上线后,将
相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!
用户直接查询事先被预热的缓存数据!
解决思路:
1、直接写个缓存刷新页面,上线时手工操作下;
2、数据量不大,可以在项目启动的时候自动进行加载;
3、定时刷新缓存;
# 四、缓存更新
除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行
自定义的缓存淘汰,常见的策略有两种:
(1)定时去清理过期的缓存;
(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存
失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。
# 五、缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务
还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
以参考日志级别设置预案:
(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
(2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时
可以根据情况自动降级或者人工降级;
(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,
可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给
用户。
12、热点数据和冷数据是什么?
# 热点数据,缓存才有价值
对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的
数据,看情况考虑使用缓存
对于上面两个例子,寿星列表、导航信息都存在一个特点,就是信息修改频率不高,读取通常非常高的场景。
对于热点数据,比如我们的某IM产品,生日祝福模块,当天的寿星列表,缓存以后可能读取数十万次。再举个例子,
某导航产品,我们将导航信息,缓存以后可能读取数百万次。
**数据更新前至少读取两次,**缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太
大价值了。
那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?有!比如,这个读取接口对数据库的压力很大,但是
又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助手产品的,点赞数,收藏数,
分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力。
13、redis的数据类型,以及每种数据类型的使用场景
# 1、String
这个其实没啥好说的,最常规的set/get操作,value可以是String也可以是数字。一般做一些复杂的计数
功能的缓存。
# 2、hash
这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,就是用这
种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session
的效果。
# 3、list
使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于
redis的分页功能,性能极佳,用户体验好。本人还用一个场景,很合适—取行情信息。就也是个生产者和消费
者的场景。LIST可以很好的完成排队,先进先出的原则。
# 4、set
因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重?因为
我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共
服务,太麻烦了。
另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
# 5、sorted set
sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP
N操作。
14、Redis 为什么是单线程的?
官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或
者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程
会有很多麻烦!)Redis利用队列技术将并发访问变为串行访问
1)绝大部分请求是纯粹的内存操作(非常快速)2)采用单线程,避免了不必要的上下文切换和竞争条件
2)非阻塞IO优点:
1.速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
2. 支持丰富数据类型,支持string,list,set,sorted set,hash
3.支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
4.丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除如何解决redis的并发竞争key问题
15、Redis 集群方案应该怎么做?都有哪些方案?
1.twemproxy,大概概念是,它类似于一个代理方式,使用时在本需要连接 redis 的地方改为连接twemproxy,
它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体redis,将结果再返回twemproxy
。缺点: twemproxy 自身单端口实例的压力,使用一致性 hash 后,对 redis 节点数量改变时候的计算值的
改变,数据无法自动移动到新的节点。
2.codis,目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在 节点数量改变情况下,旧节
点数据可恢复到新 hash 节点
3.redis cluster3.0 自带的集群,特点在于他的分布式算法不是一致性 hash,而是 hash 槽的概念,以及自
身支持节点设置从节点。具体看官方文档介绍。
16、有没有尝试进行多机redis 的部署?如何保证数据一致的?
主从复制,读写分离
一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将
数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数
据库,而一个从数据库只能有一个主数据库。
17、对于大量的请求怎么样处理
redis是一个单线程程序,也就说同一时刻它只能处理一个客户端请求;
redis是通过IO多路复用(select,epoll, kqueue,依据不同的平台,采取不同的实现)来处理多个客户端
请求的
18、为什么Redis的操作是原子性的,怎么保证原子性的?
对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。
Redis的操作之所以是原子性的,是因为Redis是单线程的。
Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性。
# 多个命令在并发中也是原子性的吗?
不一定, 将get和set改成单命令操作,incr 。使用Redis的事务,或者使用Redis+Lua==的方式实现.
19、Redis事务
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
Redis会将一个事务中的所有命令序列化,然后按顺序执行。
1.redis 不支持回滚“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以
保持简单且快速。
2.如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
3.如果在一个事务中出现运行错误,那么正确的命令会被执行。
注:redis的discard只是结束本次事务,正确命令造成的影响仍然存在.
1)MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,
这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
2)EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打
断时,返回空值 nil 。
3)通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
4)WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有
一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
20、rabbitmq和redis用作消息队列的区别
# RabbitMQ
RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件的一种,最初起源于金融系统,用于在分布式系统中存储
转发消息,在易用性、扩展性、高可用性等方面表现不俗。消息中间件主要用于组件之间的解耦,消息的发送者无需
知道消息使用者的存在,反之亦然。
# Redis
是一个Key-Value的NoSQL数据库,开发维护很活跃,虽然它是一个Key-Value数据库存储系统,但它本身支持MQ
功能,所以完全可以当做一个轻量级的队列服务来使用。
将redis发布订阅模式用做消息队列和rabbitmq的区别:
# 可靠性
redis :没有相应的机制保证消息的可靠消费,如果发布者发布一条消息,而没有对应的订阅者的话,这条消息将
丢失,不会存在内存中;
rabbitmq:具有消息消费确认机制,如果发布一条消息,还没有消费者消费该队列,那么这条消息将一直存放在队
列中,直到有消费者消费了该条消息,以此可以保证消息的可靠消费;
# 实时性
redis:实时性高,redis作为高效的缓存服务器,所有数据都存在在服务器中,所以它具有更高的实时性
# 消费者负载均衡
rabbitmq队列可以被多个消费者同时监控消费,但是每一条消息只能被消费一次,由于rabbitmq的消费确认机制
,因此它能够根据消费者的消费能力而调整它的负载;
redis发布订阅模式,一个队列可以被多个消费者同时订阅,当有消息到达时,会将该消息依次发送给每个订阅者;
# 持久性
redis:redis的持久化是针对于整个redis缓存的内容,它有RDB和AOF两种持久化方式(redis持久化方式,
后续更新),可以将整个redis实例持久化到磁盘,以此来做数据备份,防止异常情况下导致数据丢失。
rabbitmq:队列,消息都可以选择性持久化,持久化粒度更小,更灵活;
# 队列监控
rabbitmq实现了后台监控平台,可以在该平台上看到所有创建的队列的详细情况,良好的后台管理平台可以方面
我们更好的使用;
redis没有所谓的监控平台。
# 总结
redis: 轻量级,低延迟,高并发,低可靠性;
rabbitmq:重量级,高可靠,异步,不保证实时;
rabbitmq:是一个专门的AMQP协议队列,他的优势就在于提供可靠的队列服务,并且可做到异步
redis:主要是用于缓存的,redis的发布订阅模块,可用于实现及时性,且可靠性低的功能。
21、实现分布式锁
一、分布式锁的作用:
redis写入时不带锁定功能,为防止多个进程同时进行一个操作,出现意想不到的结果,so...对缓存进行插入更新
操作时自定义加锁功能。
二、Redis的NX后缀命令
Redis有一系列的命令,其特点是以NX结尾,NX的意思可以理解为 NOT EXISTS(不存在),SETNX命令
(SET IF NOT EXISTS) 可以理解为如果不存在则插入,Redis分布式锁的实现主要就是使用SETNX命令。
三、实现原理
在进程请求执行操作前进行判断,加锁是否成功,加锁成功允许执行下步操作;
如果不成功,则判断锁的值(时间戳)是否大于当前时间,如果大于当前时间,则获取锁失败不允许执行下步操作;
如果锁的值(时间戳)小于当前时间,并且GETSET命令获取到的锁的旧值依然小于当前时间,
则获取锁成功允许执行下步操作;
如果锁的值(时间戳)小于当前时间,并且GETSET命令获取到的锁的旧值大于当前时间,
则获取锁失败不允许执行下步操作;
四、$redis->setnx() 设置锁
$expire = 10;//有效期10秒
$key = 'lock';//key
$value = time() + $expire;//锁的值 = Unix时间戳 + 锁的有效期
$lock = $redis->setnx($key, $value);
//判断是否上锁成功,成功则执行下步操作
if(!empty($lock))
{
//下步操作...
}
如果返回 1 ,则表示当前进程获得锁,并获得了当前插入/更新缓存的操作权限。
如果返回 0,表示锁已被其他进程获取,这是进程可以返回结果或者等待当前锁失效再请求。
五、解决死锁
如果只用SETNX命令设置锁的话,如果当持有锁的进程崩溃或删除锁失败时,
其他进程将无法获取到锁,问题就大了。
解决方法是在获取锁失败的同时获取锁的值,并将值与当前时间进行对比,如果值小于当前时间说明锁以过期失效,
进程可运用Redis的DEL命令删除该锁。
$expire = 10;//有效期10秒
$key = 'lock';//key
$value = time() + $expire;//锁的值 = Unix时间戳 + 锁的有效期
$status = true;
while($status)
{
$lock = $redis->setnx($key, $value);
if(empty($lock))
{
$value = $redis->get($key);
if($value < time())
{
$redis->del($key);
}
}else{
$status = false;
//下步操作....
}
}
但是,简单粗暴的用DEL命令删除锁再SETNX命令上锁也会出现问题。比如,进程1获得锁后崩溃或删除锁失败,
这时进程2检测到锁存在当已过期,用DEL命令删除锁并用SETNX命令设置锁,进程3也检测到锁过期,也用DEL命令
删除锁也用SETNX命令设置了锁,这时进程2和进程3同时获得了锁。问题大了!
为了解决这个问题,这里用到了Redis的GETSET命令,GETSET命令在给锁设置新值的同时返回锁的旧值,
这里利用了GETSET命令同时获取和赋值的特性,在此期间其他进程无法修改锁的值。
例如:
进程1获得锁后操作超时/崩溃/删除锁失败,
进程2检测到锁已存在,但获取锁的值对比当前时间发现锁已过期,
进程2通过GETSET命令重新给锁赋予新的值,并获取到的锁的旧值,再次对比锁的旧值与当前时间,
如果锁的旧值依然小于当前时间的话,这时进程2就可以忽略进程1余留下的废锁进行下步操作了。
进程2完成下步操作后返回前应该删除锁,但在删除锁时可以先检测锁是否还未过期,未过期才做删除操作
已过期的就没必要在去删除锁了,因为很有可能其他进程检测到锁过期时已经去获取锁了。
这里要说明的是,如果有其他进程在进程2之前获取到锁,那么进程2将获取锁失败,但是进程2在用GETSET
获取锁的旧值时也赋予了锁新的值,改写了其他进程赋予锁的超时值。看到这大家可能会有疑问了,进程2没获取到
锁怎么能改变锁的值呢?是的,进程2改变了锁的原有值,但这一点小小的时间误差带来的影响是可以忽略。
以下是Redis实现分布式锁的完整PHP代码:
<?php
/**
* 实现Redis分布锁
*/
$key = 'test'; //要更新信息的缓存KEY
$lockKey = 'lock:'.$key; //设置锁KEY
$lockExpire = 10; //设置锁的有效期为10秒
//获取缓存信息
$result = $redis->get($key);
//判断缓存中是否有数据
if(empty($result))
{
$status = TRUE;
while ($status)
{
//设置锁值为当前时间戳 + 有效期
$lockValue = time() + $lockExpire;
/**
* 创建锁
* 试图以$lockKey为key创建一个缓存,value值为当前时间戳
* 由于setnx()函数只有在不存在当前key的缓存时才会创建成功
* 所以,用此函数就可以判断当前执行的操作是否已经有其他进程在执行了
* @var [type]
*/
$lock = $redis->setnx($lockKey, $lockValue);
/**
* 满足两个条件中的一个即可进行操作
* 1、上面一步创建锁成功;
* 2、 1)判断锁的值(时间戳)是否小于当前时间 $redis->get()
* 2)同时给锁设置新值成功 $redis->getset()
*/
if(!empty($lock) || ($redis->get($lockKey) < time()
&& $redis->getSet($lockKey, $lockValue) < time() ))
{
//给锁设置生存时间
$redis->expire($lockKey, $lockExpire);
//******************************
//此处执行插入、更新缓存操作...
//******************************
//以上程序走完删除锁
//检测锁是否过期,过期锁没必要删除
if($redis->ttl($lockKey))
$redis->del($lockKey);
$status = FALSE;
}else{
/**
* 如果存在有效锁这里做相应处理
* 等待当前操作完成再执行此次请求
* 直接返回
*/
sleep(2);//等待2秒后再尝试执行操作
}
}
}
linux面试题
1、Linux 下建立压缩包,解压缩包的命令
# Tar.gz:
打包: tar -zcvf file.tar.gz file.txt
解压: tar -zxvf file.tar.gz
# Bz2:
打包: bzip2 [-k] 文件
解压: bunzip2 [-k] 文件
# Gzip(只对文件,不保留原文件)
打包: gzip file1.txt
解压: gunzip file1.txt.gz
# Zip: -r 对目录
打包: zip file1.zip file1.txt
解压: unzip file1.zip
2、如何查看linux服务器的当前负载?linux服务器上如何查看php相关的进程有哪些?如何杀死一个指定进程?
# 查看负载
1)使用top命令查看负载,在top下按“1”查看CPU核心数量,shift+"c"按cpu使用率大小排序,shif+"p"
按内存使用率高低排序;
2)使用iostat -x 命令来监控io的输入输出是否过大
# 查看相关进程
ps -aux | grep php 或者 ps -ef | grep php
# 杀死进程
kill 进程id
3、有一个文本1.txt,每行的结构是“姓名 手机号 地址”(用空格分隔,每行手机号可能重复),请用linux相关命令/工具输出去重后的手机号
cut -f2 -d" " 1.txt | uniq -c
# cut -f2 -d" ":以空格" "分割,显示第二字段
# uniq -c 显示该行重复出现的次数,此处相当于去重
4、如何使用shell脚本统计网站前一天出现40x错误的url总数
awk '{print $9}' access.log | grep -E '404' | wc -l
# awk '{print $9}':逐行读取,默认以空格分隔,打印第9列
# grep -E '404':以正则形式搜索匹配
# wc -l:统计数量
参考文章:https://blog.csdn.net/enjoyphp/article/details/100024220
swoole面试题
1、什么是 swoole?
swoole是PHP的异步、并行、高性能网络通信引擎,使用纯c语言编写,提供了PHP语言的异步多线程服务器,
异步TCP/UDP网络客户端,异步MySQL,异步Redis,数据库连接池,AsyncTask异步任务,消息队列,毫秒定时器,
异步文件读写,异步DNS查询。swoole内置了Http/WebSocket服务器端/客户端、Http2.0服务器端。
2、为什么要使用Swoole?
常驻内存,避免重复加载带来的性能损耗,提升海量性能。
协程异步,提高对IO密集型场景并发处理能力(如:微信开发、支付、登录等)。
方便地开发Http、WebSocket、TCP、UDP等应用,可以与硬件通信。
使PHP高性能微服务架构成为现实。
3、什么是常驻内存?
目前传统PHP框架,在处理每个请求之前,都要做—遍加载框架文件、配置的操作。
这可能已经成为性能问题的一大原因,而使用Swoole则没有这个问题,一次加载多次使用,这就是常驻内存。
4、swoole默认有哪几个进程
# Master进程
第一层,Master进程,这个是swoole的主进程,这个进程是用于处理swoole的核心事件驱动的,那么在这个进程
当中可以看到它拥有一个MainReactor[线程]以及若干个Reactor[线程],swoole所有对于事件的监听都会在这些
线程中实现,比如来自客户端的连接,信号处理等。
# 管理进程Manager
Swoole想要实现最好的性能必须创建出多个工作进程帮助处理任务,但Worker进程就必须fork操作,但是fork操作
是不安全的,如果没有管理会出现很多的僵尸进程,进而影响服务器性能,同时worker进程被误杀或者由于程序的
原因会异常退出,为了保证服务的稳定性,需要重新创建worker进程。
# Worker进程
worker 进程属于swoole的主逻辑进程,用户处理客户端的一系列请求,接受由Reactor线程投递的请求数据包,
并执行PHP回调函数处理数据生成响应数据并发给Reactor线程,由Reactor线程发送给TCP客户端可以是异步
非阻塞模式,也可以是同步阻塞模式
# Task进程
taskWorker进程这一进城是swoole提供的异步工作进程,这些进程主要用于处理一些耗时较长的同步任务,
在worker进程当中投递过来。
5、协程 线程 进程区别
# 进程
是计算机中程序关于某数据集合上的一次动态执行过程,是系统进行资源分配和调度的基本单位,是操作系统
机构的基础。有自己独立的内存,地址空间,数据栈等等。进程一般由程序、数据集、进程控制块三部分组成。
狭义定义:进程是正在运行的程序的实例
# 线程
线程是CPU调度的最小单位
线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器
寄存器集合和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。
线程没有自己的系统资源。
线程较之进程,其优势在于一个快,不管是创建新的线程还是终止一个线程;不管是线程间的切换还是线程间
共享数据或通信,其速度与进程相比都有较大的优势。线程的出现是为了降低上下文切换的消耗,提高系统的
并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。
# 进程与线程的区别
每个进程都有一个进程控制块和用户地址空间,每个线程都有一个独立的栈和独立的控制块,
都有自己一个独立执行上下文。
线程在执行过程中与进程有一些不同。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。
但是线程不能够独立执行,必须依存在于进程之中,由进程提供多个线程执行控制。 从逻辑角度来看,多线程的
意义在于一个进程中,有多个执行部分可以同时执行。 此时,进程本身不是基本运行单位,而是线程的容器。
(1):进程是资源的分配和调度的最小单位,而线程是CPU调度的最小单位
(2):同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文)。
一个线程只能属于一个进程,而一个进程可以有多个线程,一个进程至少包括一个线程
(3):进程的创建调用fork或者vfork,而线程的创建调用pthread_create,进程结束后它拥有的所有线程都将
销毁,而线程的结束不会影响同个进程中的其他线程的结束
(4):线程是轻量级的进程,它的创建和销毁所需要的时间比进程小很多,CPU分给线程,
即真正在CPU上运行的是线程。
(5):线程中执行时一般都要进行同步和互斥,因为他们共享同一进程的所有资源
(6):线程有自己的私有属性TCB,线程id,寄存器、硬件上下文,而进程也有自己的私有属性进程控制块PCB,
这些私有属性是不被共享的,用来标示一个进程或一个线程的标志
(7):进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、
堆栈段和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据;
(8):CPU切换一个线程比切换进程花费小;
(9):创建一个线程比进程开销小;
(10):线程占用的资源要⽐进程少很多。
(11):线程之间通信更方便,同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的
方式(IPC)进行;(但多线程程序处理好同步与互斥是个难点)
(12):多进程程序更安全,生命力更强,一个进程死掉不会对另一个进程造成影响(源于有独立的地址空间),
多线程程序更不易维护,一个线程死掉,整个进程就死掉了(因为共享地址空间);
(13):进程对资源保护要求高,开销大,效率相对较低,线程资源保护要求不高,但开销小,效率高,可频繁切换;
# 协程
协程是一种用户态的轻量级线程,由程序自己实现的调度的线程,协程可以理解就是一种用户空间线程,
协程(Coroutine)也叫用户态线程,其通过协作而不是抢占来进行切换。相对于进程或者线程,协程所有的操作
都可以在用户态完成,创建和切换的消耗更低。协程是进程的补充,或者是互补关系。
要理解什么是“用户态的线程”,必然就要先理解什么是“内核态的线程”。 内核态的线程是由操作系统来
进行调度的,在切换线程上下文时,要先保存上一个线程的上下文,然后执行下一个线程,当条件满足时,切换
回上一个线程,并恢复上下文。 协程也是如此,只不过,用户态的线程不是由操作系统来调度的,而是由
程序员来调度的,是在用户态的在用户态完成创建,切换和销毁
作用:提高cpu使用率,避免在线程阻塞的时候大量的线程上下文切换
# 总结:
进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程也由操作系统调度。
协程和线程一样共享堆,不共享栈,协程由程序员在代码里调度。
6、心跳
# 1:心跳是什么
心跳是判断一个事物生还是死的一个标准,在swoole里,心跳是指用来判断一个连接是正常还是断开的
# 2:为什么要心跳?
如果我们要关闭某个连接,我们可以在业务层对fd发起关闭连接的操作,以swoole为例:
$server->close($fd);
正常情况下,都会走完整个四次挥手,(swoole会有onClose回调),系统回收fd,以待分配给其他的连接。
那系统为什么要回收fd,因为fd资源是有限的,所以必需重复利用。
但在某些情况下,如突然拔掉网线或蓝翔演习挖断光缆,服务端并不能感知到这个连接的异常,但实际上是这个
连接已经失效了,如果没有一个回收机制,这类连接将用光所有的fd,导致系统不再能接受新的连接请求,
所以就有了心跳机制。
# 3:什么是心跳机制?
心跳机制就是业务层来提供一个连接是否存活的一个方法,让系统能判定一个连接是否失效。
一般有两种实现方式:
1:客户端定时发送一个心跳包,告诉服务器我还活着,服务器定时检测所有客户端列表,看他们最后一个
心跳包的时间是否过长,如果过长,则认为已无心跳,判定为死连接,主动关闭这个连接。
2:服务器定时询问所有的客户端,你们还活着么?如果活着,给我个回馈,没得到回馈的客户端,格杀勿论。
# 4:两种心跳方案有什么区别?
第一种方案,对服务器和网络的压力更小,而且更具有灵活性,但需要客户端配合定时发送心跳包。
第二种方案,对服务器和网络压力更大,不建议使用。
# 5:心跳在swoole里的实现
swoole采用的是第一种方案
swoole会在主进程独立起一个心跳线程,通过定时轮询所有的连接,来判断连接的生死,
所以swoole的心跳不会堵塞任何业务逻辑。
那怎么判断连接的生死了?swoole在connection结构体中有 time_t last_time 字段,用来存放最后一次
收包的时间戳,进而通过与这个时间对比来判定是否存活
于是,swoole有两个配置:
heartbeat_check_interval: 服务器定时检测在线列表的时间
heartbeat_idle_time: 连接最大的空闲时间 (如果最后一个心跳包的时间与当前时间之差超过
这个值,则认为该连接失效)
补充
1、系统层面也提供心跳机制,只不过粒度相对比较粗,而且时间稍长,没有应用层灵活
2、swoole还提供ping的功能,通过配置ping值,swoole内核可以判断只是一个心跳包,
而不会,也没必要把数据包转发应用层(onReceive)。
7、fd是什么?
fd学名是文件描述符,在unix的哲学就是一切皆文件中,这个fd就是系统层暴露给业务层的用来表示一个五元组
网络连接的标识。你可以简单的理解为一个索引,通过对这个fd的操作,系统层可以找到相应的连接而且进行的
一系列操作,如发送数据到网瞳,进行连接关闭等等。
8、为什么onReceive收到的数据这么大
客户端发送的多次请求,服务端是可以一次性接收的。并不是客户端发送一次,服务端接收一次
9、工作流程
当客户端请求发送到master,会被 main reactor 接收,
将读写操作的监听注册到对应的Reactor线程中,并通知Worker进程处理Onconnect(接收到连接的回调)
客户端的数据会通知对应的Reactor发送给Worker进程处理。
如果Worker投递任务 将数据通过管道发送给Task。Task处理完成后会发送给Worker,
Worker会通知Reactor发送数据给客户端。
当Worker出现异常关闭,Manager会重新创建一个Worker进程,保证Worker数量是固定的。
10、协程的适用场景
高并发服务,如秒杀系统、高性能API接口、RPc服务器,使用协程模式,服务的容错率会大大增加,
某些接口出现故障时,不会导致整个服务崩溃。
10、协程的适用场景
高并发服务,如秒杀系统、高性能API接口、RPc服务器,使用协程模式,服务的容错率会大大增加,
某些接口出现故障时,不会导致整个服务崩溃。
11、内存泄漏如何避免
# 原因(全局变量):global声明的变量,static声明的对象属性或者函数内的静态变量和超全局变量
swoole提供了max_request机制,可以配置max_request和task_max_request这两个参数来避免内存溢出
不必担心worker进程退出后,没“人”处理业务逻辑了,因为我们还有Manager进程,Worker进程退出后Manager
进程会重新拉起一个新的Worker进程。
task_max_request针对task进程,含义同max_request
# max_request参数对server有下面几种限制条件。
1:max_request只能用于同步阻塞、无状态的请求响应式服务器程序
2:纯异步的Server不应当设置max_request
3:使用Base模式时max_request是无效的
4:其中Base模式是swoole运行模式的一种,我们主要介绍多进程模式。
# 总结:
常驻内存减少了不小开销,swoole不错
应尽量避免使用全局变量,不用最好,没啥用
max_request可以解决php的内存溢出问题,但是主要还是要养成释放内存的习惯,因为max_request也有限制场景
12、php-fpm和swoole有什么区别
# 区别
1、“PHP-FPM”只适用于HTTPServer,而swoole不仅用于HTTPServer,还可建立TCP连接;
2、“PHP-FPM”通过FastCGI协议监听Nginx传输请求,而swoole通过Reactor监听事件变化。
# php-fpm和swoole有什么区别
一.PHP-FPM
早期版本的 PHP 并没有内置的 WEB 服务器,而是提供了 SAPI(Server API)给第三方做对接。现在非常流行的
php-fpm 就是通过 FastCGI 协议来处理 PHP 与第三方 WEB 服务器之间的通信。
比如 Nginx + php-fpm 的组合,这种方式运行的 fpm 是 Master/Worker 模式,启动一个 Master 进程监听
来自 Nginx 的请求,再 fork 多个 Worker 进程处理请求。每个 Worker 进程只能处理一个请求,单一进程的
生命周期大体如下:
1.初始化模块。
2.初始化请求。此处请求是请求 PHP 执行代码的意思,并非 HTTP 的请求。
3.执行 PHP 脚本。
4.结束请求。
5.关闭模块。
Swoole 采用的也是 Master/Worker 模式,不同的是 Master 进程有多个 Reactor 线程,Master 只是一个事
件发生器,负责监听 Socket 句柄的事件变化。Worker 以多进程的方式运行,接收来自 Reactor 线程的请求,
并执行回调函数(PHP 编写的)。启动 Master 进程的流程大致是:
1.初始化模块。
2.初始化请求。因为 swoole 需要通过 cli 的方式运行,所以初始化请求时,不会初始化 PHP 的全局变量,
如 $_SERVER, $_POST, $_GET 等。
3.执行 PHP 脚本。包括词法、语法分析,变量、函数、类的初始化等,Master 进入监听状态,并不会结束进程。
Swoole 加速的原理
由 Reactor(epoll 的 IO 复用方式)负责监听 Socket 句柄的事件变化,解决高并发问题。
通过内存常驻的方式节省 PHP 代码初始化的时间,在使用笨重的框架时,用 swoole 加速效果是非常明显的。
二.对比不同
PHP-FPM
Master 主进程 / Worker 多进程模式。
启动 Master,通过 FastCGI 协议监听来自 Nginx 传输的请求。
每个 Worker 进程只对应一个连接,用于执行完整的 PHP 代码。
PHP 代码执行完毕,占用的内存会全部销毁,下一次请求需要重新再进行初始化等各种繁琐的操作。
只用于 HTTP Server。
Swoole
Master 主进程(由多个 Reactor 线程组成)/ Worker 多进程(或多线程)模式
启动 Master,初始化 PHP 代码,由 Reactor 监听 Socket 句柄的事件变化。
Reactor 主线程负责子多线程的均衡问题,Manager 进程管理 Worker 多进程,包括 TaskWorker 的进程。
每个 Worker 接受来自 Reactor 的请求,只需要执行回调函数部分的 PHP 代码。
只在 Master 启动时执行一遍 PHP 初始化代码,Master 进入监听状态,并不会结束进程。
不仅可以用于 HTTP Server,还可以建立 TCP 连接、WebSocket 连接。
docker面试题
1、docker的优势
1、更快速的交付和部署
Docker在整个开发周期都可以完美的辅助你实现快速交付。Docker允许开发者在装有应用和服务本地容器做开
发。可以直接集成到可持续开发流程中。
例如:开发者可以使用一个标准的镜像来构建一套开发容器,开发完成之后,运维人员可以直接使用这个容器来
部署代码。 Docker 可以快速创建容器,快速迭代应用程序,并让整个过程全程可见,使团队中的其他成员更容
易理解应用程序是如何创建和工作的。 Docker 容器很轻很快!容器的启动时间是秒级的,大量地节约开发、
测试、部署的时间。
2、高效的部署和扩容
Docker 容器几乎可以在任意的平台上运行,包括物理机、虚拟机、公有云、私有云、个人电脑、服务器等。
这种兼容性可以让用户把一个应用程序从一个平台直接迁移到另外一个。
Docker的兼容性和轻量特性可以很轻松的实现负载的动态管理。你可以快速扩容或方便的下线的你的应用和服
务,这种速度趋近实时。
3、更高的资源利用率
Docker 对系统资源的利用率很高,一台主机上可以同时运行数千个 Docker 容器。容器除了运行其中应用外,
基本不消耗额外的系统资源,使得应用的性能很高,同时系统的开销尽量小。传统虚拟机方式运行 10 个不同的
应用就要起 10 个虚拟机,而Docker 只需要启动 10 个隔离的应用即可。
4、更简单的管理
使用 Docker,只需要小小的修改,就可以替代以往大量的更新工作。所有的修改都以增量的方式被分发和更新
,从而实现自动化并且高效的管理。
2、Docker的七种用途
1、简化配置
这是Docker初始目的,虚拟机VM最大的好处是基于你的应用配置能够无缝运行在任何平台上。Docker提供同样
类似VM的能力,但是没有任何副作用,它能让你将环境和配置放入代码然后部署,同样的Docker配置能够在各
种环境中使用,这实际是将应用环境和底层环境实现了解耦。
2、代码管道化管理
能够对代码以流式pipeline管道化进行管理,从开发者的机器到生产环境机器这个流程中都能有效管理。因为在
这个流程中会有各种不同的环境,每个都可能有微小的区别,Docker提供了跨越这些异构环境以一致性的微环
境,从开发到部署实现流畅发布。
3、开发人员的生产化
在一个开发环境,我们希望我们的开发环境能更加接近于生产环境,我们会让每个服务运行在自己的VM中,这样
能模拟生产环境,比如有时我们并不总是需要跨越网络连接,这样我们可以将多个Docker装载一系列服务运行在
单机上最大程度模拟生产分布式部署的环境。
4、应用隔离
有很多理由你需要在一台机器上运行多个应用,这就需要将原来铁板一块monolithic的应用切分为很多微服务。
实现应用之间的解耦,将多个应用服务部署在多个Docker中能轻松达到这个目的。
5、服务合并
使用Docker也能合并多个服务以降低费用,不多的操作系统内存占用,跨实例共享多个空闲的内存,这些技术
Docker能以更加紧密资源提供更有效的服务合并。
6、多租户
Docker能够作为云计算的多租户容器,使用Docker能容易为每个租户创建运行应该多个实例,这得益其灵活的
快速环境以及有效diff命令。
7、快速部署
Docker通过创建流程的容器,不必重新启动操作系统,几秒内能关闭,你可以在数据中心创建或销毁资源,不用
担心额外消耗。典型的数据中心利用率是30%,通过更积极的资源分配,以低成本方式对一个新的实例实现一个
更聚合的资源分配,我们很容易超过这个利用率,大大提高数据中心的利用效率。
3、docker解决的问题及原理
# docker理念
解决了运行环境和配置问题软件容器,方便做持续集成并有助于整体发布的容器虚拟化技术
# 三大要素:镜像,容器,仓库
# 解决的问题
Docker的出现一定是因为目前的后端在开发和运维阶段确实需要一种虚拟化技术解决开发环境和生产环境环境
一致的问题,通过 Docker我们可以将程序运行的环境也纳入到版本控制中,排除因为环境造成不同运行结果
的可能
# 原理
docker是一个Client-Server结构的系统,docker守护进程运行在主机上,然后通过socket连接从客户端
访问,守护进程从客户端接受命令并管理运行在主机上的容器,容器:是一个运行时环境
Docker 核心技术与实现原理:http://dockone.io/article/2941
# 为什么docker比vm快
1)docker有着比虚拟机更少的抽象层,由于docker不需要Hypervisor实现硬件资源虚拟化,
运行在docker容器上的程序直接使用的都是实际物理机的硬件资源。因此在cpu、内存利用率上docker
在效率上有明显优势
2)docker利用的是宿主机的内核,而不需要Guest OS。因此,当新建一个容器时,docker不需要和虚拟机
一样重新加载一个操作IT内核。避免引寻,加载操作系统内核返个比较费时费资源的过程,这个过程是分钟
级别的,而docker由于直接利用宿主机的操作系统,则省略了这个过程,因此只需要几秒钟
rabbitmq常见面试题
1、使用RabbitMQ有什么好处?
1.解耦,系统A在代码中直接调用系统B和系统C的代码,如果将来D系统接入,系统A还需要修改代码,过于麻烦!
2.异步,将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度
3.削峰,并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常
2、RabbitMQ 中的 broker 是指什么?cluster 又是指什么?
broker 是指一个或多个 erlang node 的逻辑分组,且 node 上运行着 RabbitMQ 应用程序。
cluster 是在 broker 的基础之上,增加了 node 之间共享元数据的约束。
3、RabbitMQ 概念里的 channel、exchange 和 queue 是逻辑概念,还是对应着进程实体?分别起什么作用?
queue 具有自己的 erlang 进程;exchange 内部实现为保存 binding 关系的查找表;
channel 是实际进行路由工作的实体,即负责按照 routing_key 将 message 投递给 queue 。
由 AMQP 协议描述可知,channel 是真实 TCP 连接之上的虚拟连接,所有 AMQP 命令都是通过channel发送
的,且每一个 channel 有唯一的 ID。一个 channel 只能被单独一个操作系统线程使用,故投递到
特定 channel 上的 message 是有顺序的。但一个操作系统线程上允许使用多个 channel 。
4、vhost 是什么?起什么作用?
vhost 可以理解为虚拟 broker ,即 mini-RabbitMQ server。
其内部均含有独立的 queue、exchange 和 binding 等,但最最重要的是,其拥有独立的权限系统,可以做到
vhost 范围的用户控制。当然,从 RabbitMQ 的全局角度,vhost 可以作为不同权限隔离的手段
(一个典型的例子就是不同的应用可以跑在不同的 vhost 中)。
5、消息基于什么传输?
由于TCP连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ使用信道的方式来
传输数据。信道是建立在真实的TCP连接内的虚拟连接,且每条TCP连接上的信道数量没有限制。
6、消息如何分发?
若该队列至少有一个消费者订阅,消息将以循环(round-robin)的方式发送给消费者。每条消息只会分发给一个
订阅的消费者(前提是消费者能够正常处理消息并进行确认)。
7、消息怎么路由?
从概念上来说,消息路由必须有三部分:交换器、路由、绑定。生产者把消息发布到交换器上;绑定决定了消息如何
从路由器路由到特定的队列;消息最终到达队列,并被消费者接收。
1、消息发布到交换器时,消息将拥有一个路由键(routing key),在消息创建时设定。
2、通过队列路由键,可以把队列绑定到交换器上。
3、消息到达交换器后,RabbitMQ会将消息的路由键与队列的路由键进行匹配(针对不同的交换器有不同的路由
规则)如果能够匹配到队列,则消息会投递到相应队列中;如果不能匹配到任何队列,消息将进入 “黑洞”。
常用的交换器主要分为一下三种:
direct:如果路由键完全匹配,消息就被投递到相应的队列
fanout:如果交换器收到消息,将会广播到所有绑定的队列上
topic:可以使来自不同源头的消息能够到达同一个队列。 使用topic交换器时,可以使用通配符,比如:
“*” 匹配特定位置的任意文本, “.” 把路由键分为了几部分,“#” 匹配所有规则等。特别注意:
发往topic交换器的消息不能随意的设置选择键(routing_key),
必须是由"."隔开的一系列的标识符组成。
8、什么是元数据?元数据分为哪些类型?包括哪些内容?与 cluster 相关的元数据有哪些?元数据是如何保存的?元数据在 cluster 中是如何分布的?
在非 cluster 模式下,元数据主要分为 Queue 元数据(queue 名字和属性等)、Exchange 元数据
(exchange 名字、类型和属性等)、Binding 元数据(存放路由关系的查找表)、Vhost 元数据(vhost
范围内针对前三者的名字空间约束和安全属性设置)。在 cluster 模式下,还包括 cluster 中 node 位置
信息和 node 关系信息。元数据按照 erlang node 的类型确定是仅保存于 RAM 中,还是同时保存在
RAM 和 disk 上。元数据在 cluster 中是全 node 分布的。
下图所示为 queue 的元数据在单 node 和 cluster 两种模式下的分布图。
9、在单 node 系统和多 node 构成的 cluster 系统中声明 queue、exchange ,以及进行 binding 会有什么不同?
答:当你在单 node 上声明 queue 时,只要该 node 上相关元数据进行了变更,你就会得到
Queue.Declare-ok 回应;而在 cluster 上声明 queue ,则要求 cluster 上的全部 node 都要进行
元数据成功更新,才会得到 Queue.Declare-ok 回应。另外,若 node 类型为 RAM node 则变更的数据
仅保存在内存中,若类型为 disk node 则还要变更保存在磁盘上的数据。
死信队列&死信交换器:DLX 全称(Dead-Letter-Exchange),称之为死信交换器,当消息变成一个死信之后,
如果这个消息所在的队列存在x-dead-letter-exchange参数,那么它会被发送到x-dead-letter-exchange
对应值的交换器上,这个交换器就称之为死信交换器,与这个死信交换器绑定的队列就是死信队列。
10、如何确保消息正确地发送至RabbitMQ?
RabbitMQ使用发送方确认模式,确保消息正确地发送到RabbitMQ。发送方确认模式:将信道设置成confirm模式
(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。一旦消息被投递到目的队列后,
或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一ID)。
如果RabbitMQ发生内部错误从而导致消息丢失,会发送一条nack(not acknowledged,未确认)消息。
发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者
应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。
11、如何确保消息接收方消费了消息?
接收方消息确认机制:消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。
只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。这里并没有用到超时机制,RabbitMQ仅
通过Consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ给了Consumer
足够长的时间来处理消息。
# 下面罗列几种特殊情况:
如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ会认为消息没有被分发,然后重新分发给
下一个订阅的消费者。(可能存在消息重复消费的隐患,需要根据bizId去重)
如果消费者接收到消息却没有确认消息,连接也未断开,则RabbitMQ认为该消费者繁忙,将不会给该消费者分发
更多的消息。
12、如何避免消息重复投递或重复消费?
在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,作为去重和幂等的依据(消息投递失
败并重传),避免重复的消息进入队列;在消息消费时,要求消息体中必须要有一个bizId(对于同一业务全局唯一
如支付ID、订单ID、帖子ID等)作为去重和幂等的依据,避免同一条消息被重复消费。
这个问题针对业务场景来答分以下几点:
1.比如,你拿到这个消息做数据库的insert操作。那就容易了,给这个消息做一个唯一主键,那么就算出现重复
消费的情况,就会导致主键冲突,避免数据库出现脏数据。
2.再比如,你拿到这个消息做redis的set的操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,
set操作本来就算幂等操作。
3.如果上面两种情况还不行,上大招。准备一个第三方介质,来做消费记录。以redis为例,给消息分配一个
全局id,只要消费过该消息,将<id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中
查询有没消费记录即可。
13、如何解决丢数据的问题?
启用手动确认模式可以解决这个问题
1、自动确认模式,消费者挂掉,待ack的消息回归到队列中。消费者抛出异常,消息会不断的被重发,直到处理
成功。不会丢失消息,即便服务挂掉,没有处理完成的消息会重回队列,但是异常会让消息不断重试。
2、手动确认模式,如果消费者来不及处理就死掉时,没有响应ack时会重复发送一条信息给其他消费者;如果监听
程序处理异常了,且未对异常进行捕获,会一直重复接收消息,然后一直抛异常;如果对异常进行了捕获,但是
没有在finally里ack,也会一直重复发送消息(重试机制)。
3、不确认模式,acknowledge="none" 不使用确认机制,只要消息发送完成会立即在队列移除,无论客户端
异常还是断开,只要发送完就移除,不会重发。
14、死信队列和延迟队列的使用
# 死信消息:
1、消息被拒绝(Basic.Reject或Basic.Nack)并且设置 requeue 参数的值为 false
2、消息过期了
3、队列达到最大的长度
# 过期消息:
在 rabbitmq 中存在2种方可设置消息的过期时间,第一种通过对队列进行设置,这种设置后,该队列中所有
的消息都存在相同的过期时间,第二种通过对消息本身进行设置,那么每条消息的过期时间都不一样。如果
同时使用这2种方法,那么以过期时间小的那个数值为准。当消息达到过期时间还没有被消费,那么那个消息
就成为了一个 死信 消息。
队列设置:在队列申明的时候使用 x-message-ttl 参数,单位为 毫秒
单个消息设置:是设置消息属性的 expiration 参数的值,单位为 毫秒
延时队列:在rabbitmq中不存在延时队列,但是我们可以通过设置消息的过期时间和死信队列来模拟出延时队列。
消费者监听死信交换器绑定的队列,而不要监听消息发送的队列。
有了以上的基础知识,我们完成以下需求:
需求:用户在系统中创建一个订单,如果超过时间用户没有进行支付,那么自动取消订单。
分析:
1、上面这个情况,我们就适合使用延时队列来实现,那么延时队列如何创建
2、延时队列可以由 过期消息+死信队列 来时间
3、过期消息通过队列中设置 x-message-ttl 参数实现
4、死信队列通过在队列申明时,给队列设置 x-dead-letter-exchange 参数,然后另外申明一个队列绑定
x-dead-letter-exchange对应的交换器。
15、使用了消息队列会有什么缺点?
1.系统可用性降低:你想啊,本来其他系统只要运行好好的,那你的系统就是正常的。
现在你非要加个消息队列进去,那消息队列挂了,你的系统不是呵呵了。因此,系统可用性降低
2.系统复杂性增加:要多考虑很多方面的问题,比如一致性问题、如何保证消息不被重复消费,如何保证保证消息
可靠传输。因此,需要考虑的东西更多,系统复杂性增大。
16、工作模式
1、简单模式:一个生产者,一个消费者
2、Work queues工作队列模式:一个生产者,多个消费者,每个消费者获取到的消息唯一。
2、Pub/Sub订阅模式:一个生产者发送的消息会被多个消费者获取。
3、 Routing路由模式:发送消息到交换机并且要指定路由key ,消费者将队列绑定到交换机时需要指定路由key
4、Topics通配符模式:将路由键和某模式进行匹配,此时队列需要绑定在一个模式上,“#”匹配一个词或多个词,
“*”只匹配一个词。
17、AMQP协议
1.什么是AMQP协议
即高级消息队列协议,规范客户端与消息中间件服务器之间的通信,并能相互操作。
2.AMQP协议的作用
降低应用程序之间的耦合度,这样不同应用之间的集成的难度将变得更小,并开发出更有用的应用程序 。
3.AMQP协议的模型
包含三个成员:Exchange,Message Queue,Binding
Exchange:转换成员,是接收应用程序发过来的信息,但通过Binding这个路由规则,
路由到对应的Message Queue中
Message Queue:这个是存储实际队列的内容
Binding:定义Exchange与Message Queue这间绑定的路由规则
其他面试题
1、WebSocket 是一种网络通信协议?
初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来
什么好处?
答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。
举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务
器主动向客户端推送信息。
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":
每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。
轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,工程师们一直在思考,
有没有更好的方法。WebSocket 就是这样发明的。
WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等
对话,属于服务器推送技术的一种。
# 特点包括:
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不
容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
具体参考:http://www.ruanyifeng.com/blog/2017/05/websocket.html
2、高并发解决方案案例
流量优化:防盗链处理
前端优化:减少HTTP请求,合并css或js,添加异步请求,启用浏览器缓存和文件压缩,CDN加速,
建立独立图片服务器,
服务端优化:页面静态化,并发处理,队列处理
数据库优化:数据库缓存,分库分表,分区操作,读写分离,负载均衡
web服务器优化:负载均衡,nginx反向代理,7,4层LVS软件
具体参考:https://www.cnblogs.com/cn-sbo/p/10853469.html
3、JWT token 认证
# JWT的构成
1、头部(header)
{
"alg": "HS256", //签名算法(HMAC、SHA256、RSA)
"typ": "JWT" //令牌的类型(即JWT)
}
2、载荷(payload, 类似于飞机上承载的物品)
{
"sub": "1", // 签发人
"iss": "http://localhost:8000/auth/login", // 主题
"iat": 1451888119, // 受众
"exp": 1454516119, // 过期时间
"nbf": 1451888119, // 生效时间,在此之前是无效的
"jti": "37c107e4609ddbcc9c096ea5ee76c667", // 签发时间
"aud": "dev" // 编号
}
3、签名(Signature)
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
# 优点
因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以
使用。
因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
它不需要在服务端保存会话信息, 所以它易于应用的扩展
# JWT有什么好处?
1、支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息
通过HTTP头传输.
2、无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有
登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.
4、更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的
服务端只要提供API即可.
5、去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,
你可以进行Token生成调用即可.
6、更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持
的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。
7、CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。
8、性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析
要费时得多.
9、不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理.
10、基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET,
Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft)
# 安全相关
不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
保护好secret私钥,该私钥非常重要。
如果可以,请使用https协议
具体查看:https://www.jianshu.com/p/576dbf44b2ae
https://learnku.com/articles/10885/full-use-of-jwt
4、斐波那契数几种算法
// 普通递归
function func1($n = 1)
{
if($n < 1){
return 0;
}
if ($n < 3) {
return 1;
}
// 递归计算前两位
return func1($n - 1) + fib($n - 2);
}
// 递归优化
function func2($n = 1, $a = 1, $b = 1)
{
if ($n > 2) {
// 存储前一位,优化递归计算
return func2($n - 1, $a + $b, $a);
}
return $a;
}
// 循环
function func3($n){
if($n < 1){
return 0;
}
$arr = [];
for($i = 0; $i <= $n; $i++){
if($i < 2){
$arr[] = $i;
}else{
$arr[] = $arr[$i - 1] + $arr[$i - 2];
}
}
return $arr[$n];
}
function func4($n = 1)
{
if ($n <= 0) {
return 0;
}
if ($n < 3) {
return 1;
}
$a = 0;
$b = 1;
// 循环计算
for ($i = 2; $i < $n; $i++) {
$b = $a + $b;
$a = $b - $a;
}
return $b;
}
// 公式法
function func5($n = 1)
{
// 黄金分割比
$radio = (1 + sqrt(5)) / 2;
// 斐波那契序列和黄金分割比之间的关系计算
$num = intval(round(pow($radio, $n) / sqrt(5)));
return $num;
}
5、什么是nginx?
nginx是一个高性能的HTTP和反向代理web服务器,同时提供了IMAP/POP3/SMTP服务
正向代理:代理的是客户端
反向代理:代理的是服务器
6、nginx负载均衡的几种模式
# 轮询分派
upstream loop{
server 127.0.0.1:8080
server 127.0.0.1:7080
server 127.0.0.1:6305
}
按照默认轮询的方式进行负载,
假设后端server down掉,能自己剔除。
缺点:可靠性低,负载不均衡,机器性能可能不一致
# 权重分派
upstream loopweight{
server 127.0.0.1:8080 weight = 5;
server 127.0.0.2:7080 weight = 5;
server 127.0.0.3:6305 weight = 10;
}
考虑1和2的机器配置低,或者1和2的性能不如3的时候
这样将3的权重设置大一些,更多的请求会被分配到3上。
为1和2分担更多的请求。
# IP哈希
upstream iphash{
ip_hash;
server 127.0.0.1:8080;
server 127.0.0.2:7080;
server 127.0.0.3:6305;
}
这里的IP说的是客户端的出口IP,这样经过 des_server_ip = hash(ip)
相应的ip在没有down掉的情况下,肯定会hash到固定的ip上。
# URL哈希
upstream urlhash{
server 127.0.0.1:8080;
server 127.0.0.2:7080;
server 127.0.0.3:6305;
hash $request_uri;
hash_method crc32;
}
按照URI进行哈希,固定的URI Hash到固定的server上。
# 性能,相应时间分派
upstream iphash{
server 127.0.0.1:8080;
server 127.0.0.2:7080;
server 127.0.0.3:6305;
fair;
}
性能,相应时间分派:
按后端服务器的响应时间来分配请求。响应时间短的优先分配。
down And backup
upstream iphash{
server 127.0.0.1:8080 down; #当前server不参与负载
server 127.0.0.2:7080;
server 127.0.0.3:6305 backup; #非backup的机器(参与负载的机器),down掉或者忙的时候,
请求backup机器。备用机
fair;
}
# 应用
listen 80;
server_name www.domain.com
location ~^ /api{
proxy_pass http://loopweight #选择一种你喜欢的负载策略
}
版权声明:本文标题:人生最好的php,mysql,linux,redis,docker等相关技术经典面试题,新手收藏学习,持续更新中。。。 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1725921003h893005.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论