酱紫啊啊啊啊啊

酱紫啊啊啊啊啊 查看完整档案

南宁编辑  |  填写毕业院校  |  填写所在公司/组织 google.com 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

酱紫啊啊啊啊啊 赞了文章 · 2020-12-08

PHP面试题

词法结构

题1 php区分大小写吗?

分析:用户定义的类和函数、内置的结构以及关键字例如echo,while,class是不区分大小写的,但是变量和常量区分大小写,例如$user,$User,$USER是3个不同的变量。

表达式和操作符

题1.
<?php

$a = 3;
$b = 5;
if($a = 5 || $b = 7) {
    $a++;
    $b++;
}

echo '$a = ',$a,', $b = ',$b;

分析:该题考察php中操作符的优先级,根据 php手册,逻辑运算符||的优先级要高于赋值运算符=,因此if条件实际的顺序应该是if($a = (5 || $b = 7))$a 的值是bool类型的,由于5true,所以会执行$a++; $b++;,由于$abool类型的,所以$a++的结果仍然是1,用var_dump()打印$a的结果是boolean true$b的结果是6
所以本题最后的结果是$a = 1, $b = 6

题2 不使用比较运算符求两个数中较大者
function max($a, $b) {
    return ($a + $b + abs($a -$b)) / 2;
}
题3 求下面的程序输出什么
echo '1+5='. 1 + 5,PHP_EOL;
echo '1+5='. 5 + 1,PHP_EOL;
echo '5+1='. 5 + 1,PHP_EOL;
echo '5+1='. 1 + 5,PHP_EOL;

分析:此题隐式转换和操作符优先级。.+的优先级是相同的。字符串'1+5='. 1会转换成1,所以第一行会输出运算结果6,以此类推,后面3行输出2610


变量

题1
<?php
 
$a =  array(1);
$b = & $a[0]; 
$c = $a;
$c[0]++;
 
echo $c[0].$a[0];

分析:本题考察变量的引用。在赋值数组的时候,如果=右边的数据存在引用,那边赋值的新数组对应的元素也是引用,所以改变$c[0]的值 也会同时改变$a[0]的值,因此这段代码输出的结果是22
如果没有第2$b = & $a,那么输出的结果是21
如果是$a是普通变量,例如

<?php

$a = 1;
$b = & $a;
$c = $a;
$c++;

echo $c,$a;

输出的结果也是21

题2
<?php

$a = 1; 
$x = &$a; 
$b = $a++; 

$b$x 的值
分析:此题考察变量的引用赋值以及递增运算符++$a, $a++, --$a, $a--
由于 $b = $a++;,因此 $b等于1,同时$a递增1,即此时$a等于2,由于$x$a的引用,即$x$a的别名,指向的是同一个内容,因此$x等于2

题3 以下代码输出什么
$a = 2;
$b = &$a;
unset($a);
echo $b;

分析:此题同样考察变量的引用。在引用赋值之后,$a$b有相同的值但不同的名称,销毁其中任何一个变量,不会影响其值的其他别名。

题4 PHP如何管理内存,它的垃圾收集机制是怎样的

分析:
php使用引用计数写时复制来管理内存。写时复制保证了变量间复制值不浪费内存,引用计数保证了不再需要时将内存释放给操作系统。
当将一个变量的值复制到另一个变量时,php没有为复制值使用更多的内存,相反,它会更新符号表(符号表是一个将变量名映射到内存中变量值所在地址的数组)来说明这两个变量拥有相同的内存块,如果后来修改了任意一个副本,php将分配所学的内存来进行复制,例如:

$username = ['dee', 'lee', 'john];
$users = $username; // 数组不被复制
$user[1] = 'jack';  // 值改变,数组被复制

通过延迟分配和复制,php在很多情形下节省了时间和内存,这就是写时复制。符号表中每一个值都有一个引用计数器,它的数值表示获取那片内存的方式的数目。在上例中,在$username$users初始化赋值后,数组指向了符号表,$username和$user的引用计数器的值为2,即那块内存有两种方式可以获得:通过$username和$users。当$users\[1\]被改变时,php为$users创建了一个新的数组,此时$username和$users的引用计数器的值各位1.
当一个变量离开作用域,例如函数参数和局部变量到达函数末尾时,它的值的引用计数减1。当一个变量在其他内存空间被赋值时,旧值的引用计数减1。当引用计数的值为0时,他的内存被释放,这就是引用计数。

题5 PHP中变量的生命周期

分析:
局部变量:为其所在函数被调用的整个过程。当局部变量所在的函数结构结束时,局部变量的生命周期也随之结束。
全局变量:为其所在“.php”脚本文件被调用的整个过程。当全局变量所在脚本文件结束调用时,全局变量的生命周期结束
静态变量:有时某个定义函数结束后,我们希望该函数内的变量仍然存在,就需要将这个变量声明为静态变量(static)。

题6 请说说你对PHP中引用类型和值类型的理解

分析:
值类型:大部分变量类型,如字符串,整型,浮点型,数组等,赋值时会在内存中创建一个新的变量。
引用类型:引用就是创建变量的别名。赋值之后两个变量拥有相同的变量但是不同的名称。对象的赋值不会创建新的变量。引用的优点是,当大的字符串或数组进行赋值时可以节省资源。

题7 交换两个变量的值,不使用新的变量

分析:
方法① 使用异或运算符

<?php

$a = 4;
$b = 3;
$a = $a ^ $b;
$b = $a ^ $b;
$a = $a ^ $b;

方法②
使用list()函数

<?php

$a = 'memcached';
$b = 'redis';
list($a, $b) = array($b, $a);
题7 用最短的代码找出3个数字中的最大值

分析:可以使用三元运算

<?php

($a = ($a > $b ? $a : $b)) > $c ? $a : $c;

不够短?还可以使用max()函数:

<?php

$max = max(array($a, $b, $c));
题8 以下代码会输出?
$A="Hello"; 
function print_A() {
      $A= "php mysql !!";
      global$A; 
      echo$A;
}
echo$A;
print_A();

分析:HelloHello
第一次执行echo $A; ,$A的值是Hello, 第二次在方法中赋值的$A是局部变量, global $A声明全局变量$A ,则在函数中echo $A由于是全局变量所以值还是Hello 。如果本题在globle $A后执行$A = "php mysql" 结果就是Hellophp mysql!!了。


函数

题1 include, include_once, require, require_once这几个函数有什么区别?

分析:
includerequire这两个关键字都是包含并运行指定文件,不同的是include在引入不存的文件时产生一个警告且脚本还会继续执行,而require则会导致一个致命性错误且脚本停止执行
include_once语句与include类似,唯一的区别是如果该文件中已经被加载成功了,则后续再次加在同一个文件的操作会被忽略,require_oncerequire的区别也是如此。

题2 isset() 、empty()与is_null的区别

分析:
isset()函数检测变量是否设置,如果变量存在并且值不是 NULL 则返回 TRUE,否则返回 FALSE。
empty()函数检查一个变量是否为空,当变量存在,并且是一个非空非零的值时返回 FALSE 否则返回 TRUE
is_null()函数检测变量是否为 NULL,如果变量是 null 则返回 TRUE,否则返回 FALSE。

题3 php中传值与传引用的区别

按值传递:函数范围内对值的任何改变在函数外部都会被忽略
引用传递:函数范围内对值的任何改变在函数外部也能反映出这些修改

题4 用php写出显示客户端IP、服务器端IP的代码和网页地址

客户端IP:$_SERVER["REMOTE_ADDR"]
服务器端IP:$_SERVER["SERVER_ADDR"]
网页地址:$_SERVER["REQUEST_URI"]
当前脚本的执行路径:$_SERVER["SCRIPT_FILENAME"]或者__FILE__
当前脚本的名称:$_SERVER["PHP_SELF"]或者$_SERVER["SERIPT_NAME"]
链接到前一页的URL地址:$_SERVER["HTTP_REFERER"]

题5 echo,print(),print_r(),printf(),sprintf(),var_dump()有什么区别

echo是语言结构,用于输出字符串到页面
print()函数发送一个值(它的参数)给浏览器
print_r()函数可以智能地显示传给它的参数,可以打印字符串和数字,也可以打印数组和对象
printf()函数可以通过替换模版中的值输出字符串
var_dump()print_r()更适合调试,它用更适合阅读的格式显示所有php的值

题6 php中对数组序列化和反序列化的函数,把utf-8转换成gbk的函数

serializeunserializeiconv("utf-8","gbk",$strs)

题7 strlen()与mb_strlen的作用分别是什么?

strlen()无法正确处理中文字符串的占位,对于gb2312得到的是汉字个数的2倍,utf8得到的是汉字个数的3

mb_strlen()就很好的解决了这个问题,它的第二个参数就是设置字符编码的

题7 exit和return有什么区别

分析:
脚本执行到exit语句时,就会停止执行
return语句一般用于某个函数退出返回,或者囧啊本停止执行

字符串

题1 使用5种方式获取一个文件的扩展名

分析:
可以使用php内置的函数、数组函数、字符串函数来实现。
1.内置函数pathinfo(),在返回的数组中键为extension的值就是扩展名

<?php

$filename = 'pic.20160626.jpg';

$pathinfo = pathinfo($filename);
var_dump($pathinfo['extension']); // jpg

2.同样使用内置函数pathinfo(),带上第二个参数PATHINFO_EXTENSION

$ext = pathinfo($filename, PATHINFO_EXTENSION);
var_dump($ext);

3.数组函数explode()+count()

$arr = explode('.', $filename);
var_dump($arr[count($arr) - 1]); // jpg

4.字符串函数strrpos()+substr():strrpos()用于计算指定字符串在目标字符串中最后一次出现的位置

$position = strrpos($filename, '.');
var_dump(substr($filename, $position + 1)); // jpg

5.字符串函数strrchr():strrchr()查找指定字符在字符串中的最后一次出现,并该函数返回字符串的一部分

var_dump(strrchr($filename, '.')); // .jpg
题2 给任意一段URL,取出该URL中包含的扩展名

分析:
类似于上一题。也可以使用内置函数parse_url()+basename()来解析URL:

<?php

$url = 'http://www.sina.cn/index.php?username=dee';

$parse_url = parse_url($url);
$basename = basename($parse_url['path']); // index.php
$arr = explode('.', $basename);
$ext = array_pop($arr); // php
题3 PHP中单引号和双引号有什么区别?哪个速度更快?

分析:
1.用单引号括起来的变量不能被解析
2.在单引号括起来的字符串中只有单引号'和反斜线܀要用反斜线转义;用双引号括起来的变量能够被解析,有双引号、换行符、回车、制表符、大括号、中括号等需要转义。
3.如果包含的字符串中含有变量,使用单引号更快,因为单引号不会解析变量,而双引号解析变量需要时间。

数组

题1 写个函数用来对二维数组排序

解析:
情况一、如果是针对二维数组中每个元素的某个值进行排序,例如

$arr = [
    ['name'=>'dee','age'=>28],
    ['name'=>'emperor', 'age'=>27],
    ['name'=>'Lee', 'age'=>20],
    ['name'=>'Arshavin', 'age'=>33],
    ['name'=>'Totti', 'age'=>40],
    ['name'=>'K6', 'age'=>27],
];

按照age的值从小到大进行排序
解法① 可以使用usort()函数

$arr = [
    ['name'=>'dee','age'=>28],
    ['name'=>'emperor', 'age'=>27],
    ['name'=>'Lee', 'age'=>20],
    ['name'=>'Arshavin', 'age'=>33],
    ['name'=>'Totti', 'age'=>40],
    ['name'=>'K6', 'age'=>27],
];

usort($arr, function($a, $b){
    return $a['age'] >= $b['age'] ? 1 : 0;
});

var_dump($arr);

解法② 使用array_multisort()函数

$arr = [
    ['name'=>'dee','age'=>28],
    ['name'=>'emperor', 'age'=>27],
    ['name'=>'Lee', 'age'=>20],
    ['name'=>'Arshavin', 'age'=>33],
    ['name'=>'Totti', 'age'=>40],
    ['name'=>'K6', 'age'=>27],
];

$age = [];
foreach($arr as $val) {
    $age[] = $val['age'];
}
array_multisort($age, SORT_ASC, $arr);

var_dump($arr);

情况二、如果是针对二维数组中每个元素的某几个值进行排序,例如

$arr = [
    ['name'=>'dee','age'=>28, 'height'=>180],
    ['name'=>'emperor', 'age'=>27, 'height'=>181],
    ['name'=>'Lee', 'age'=>20, 'height'=>170],
    ['name'=>'Arshavin', 'age'=>33, 'height'=>173],
    ['name'=>'Totti', 'age'=>40, 'height'=>183],
    ['name'=>'Jonh', 'age'=>27, 'height'=>170],
];

要根据age从小到到,age相同的按height从大到小排序:

$arr = [
    ['name'=>'dee','age'=>28, 'height'=>180],
    ['name'=>'emperor', 'age'=>27, 'height'=>181],
    ['name'=>'Lee', 'age'=>20, 'height'=>170],
    ['name'=>'Arshavin', 'age'=>33, 'height'=>173],
    ['name'=>'Totti', 'age'=>40, 'height'=>183],
    ['name'=>'Jonh', 'age'=>27, 'height'=>170],
];

$age = $height = [];
foreach($arr as $val) {
    $age[] = $val['age'];
    $height[] = $val['height'];
}
array_multisort($age, SORT_ASC, $height, SORT_DESC, $arr);

var_dump($arr);

情况三、如果是针对多维数组中每个元素的某几个值进行排序,例如

$arr = [
    ['name'=>'dee','info'=>['subinfo'=>['age'=>28, 'height'=>180]]],
    ['name'=>'emperor', 'info'=>['subinfo'=>['age'=>27, 'height'=>181]]],
    ['name'=>'Lee', 'info'=>['subinfo'=>['age'=>20, 'height'=>170]]],
    ['name'=>'Arshavin', 'info'=>['subinfo'=>['age'=>33, 'height'=>173]]],
    ['name'=>'Totti', 'info'=>['subinfo'=>['age'=>40, 'height'=>183]]],
    ['name'=>'Jonh', 'info'=>['subinfo'=>['age'=>27, 'height'=>170]]]
];

要根据age从小到到,age相同的按height从大到小排序:
$arr = [

['name'=>'dee','info'=>['subinfo'=>['age'=>28, 'height'=>180]]],
['name'=>'emperor', 'info'=>['subinfo'=>['age'=>27, 'height'=>181]]],
['name'=>'Lee', 'info'=>['subinfo'=>['age'=>20, 'height'=>170]]],
['name'=>'Arshavin', 'info'=>['subinfo'=>['age'=>33, 'height'=>173]]],
['name'=>'Totti', 'info'=>['subinfo'=>['age'=>40, 'height'=>183]]],
['name'=>'Jonh', 'info'=>['subinfo'=>['age'=>27, 'height'=>170]]]

];

function get_key($arr, $key) {

$weight = [];
if(is_array($arr)) {
    foreach ($arr as $k => $v) {
        if ($key === $k) {
            $weight[] = $v;
        } else {
            $tmp = get_key($v, $key);
            if(! empty($tmp)) {
                $weight[] = $tmp[0];
            }
        }
    }
    return $weight;
}

}

$age = 'age';
$age = get\_key($arr, $age);

$height = 'height';
$height = get\_key($arr, $height);
array_multisort($age, SORT\_ASC, $height, SORT_DESC, $arr);

echo '<pre>';
print_r($arr);

类和对象

题1 接口与抽象类的区别是什么?

抽象类:

抽象类是不能被实例化的类,只能作为其他类的父类来使用,抽象类是通过关键字abstract来声明

抽象类与普通类类似,都包含成员变量和成员方法,两者的区别在于,抽象类中至少包含一个抽象方法

抽象方法没有方法体,该方法天生就是要被子类重写的

抽象方法的格式为:abstract function abstractMethod()

子类继承抽象类使用extends

接口:

接口是通过interface关键字来声明,接口中的成员常量和方法都是public的,方法可以不写关键字public

接口中的方法也是没有方法体的,接口中的方法也是天生要被子类实现的

接口能实现多继承

子类实现接口使用implements

题2 以下代码会输出什么
class Dog {
    public $age;
    public function __construct($age) {
        $this->age = $age;
    }
}

function test($dog) {
    $dog->age = 10;
}

$dog = new Dog(100);
test($dog);
echo $dog->age;

题3 以下代码会输出什么

class Dog {
    public $age;
    public function __construct($age) {
        $this->age = $age;
    }
}

function test($dog) {
    $dog = new Dog(10);
}

$dog = new Dog(100);
test($dog);
echo $dog->age;

题4 以下代码会输出什么

class Dog {
    public $age;
    public function __construct($age) {
        $this->age = $age;
    }
}

function test($dog) {
    $dog = null;
}

$dog = new Dog(100);
test($dog);
echo $dog->age;

题5 以下代码会输出什么

class Dog {
    public $age;
    public function __construct($age) {
        $this->age = $age;
    }
}

function test(&$dog) {
    $dog = new Dog(10);
}

$dog = new Dog(100);
test($dog);
echo $dog->age;

错误和异常


缓存与静态化


时间日期

题1 知道一天的日期,求任意一天叮得日期
题2 用php打印出前一天的时间格式

文件

题1 写一个函数,算出两个文件的相对路径
<?php
function relative_path($a, $b) {
    $a2arr = explode('/', $a);
    $b2arr = explode('/', $b);

    $diff = array_diff($a2arr, $b2arr);

    $count = count($diff);
    $relative = str_repeat('../', $count);
    array_pop($b2arr);

    return $relative.implode('/', array_diff($b2arr, $a2arr));
}
题2 写一个函数,能够遍历一个文件夹下的所有文件和子文件夹
<?php

function tree($dir) {
    static $files;
    if(is_dir($dir)) {
        if($handle = opendir($dir)) {
            while(($file = readdir($handle)) !== false) {
                if(is_dir("$dir/$file")) {
                    if(! in_array($file, array('.', '..'))) {
                        $files[] = "$dir/$file";
                        tree("$dir/$file");
                    }
                } else {
                   $files[] = "$dir/$file";
               }
            }
       }
   }
   return $files;

}

$dir = 'D:practise/test';

题3 删除某目录下的子目录及文件

分析:需要使用到的目录函数包括is_dir()opendir()readdir()rmdir()closedir();需要用到的文件函数包括is_file()unlink(),使用递归算法

function deldir($dir) {
    if(is_dir($dir)) {
        $handle = opendir($dir);
        while (false !== ($file = readdir($handle))) {
            if($file == '.' || $file == '..'){  
                continue;    
               }    
            if(is_dir($dir.'/'.$file)) {
                deldir($dir.'/'.$file);
            } else {
                unlink($dir.'/'.$file);
            }
        }
        closedir($handle);
        rmdir($dir);
    } elseif(is_file($dir)) {
        unlink($dir);
    } else {
        return 'error dir';
    }
    return 'del complete';
}
题4 php中Web上传文件的原理是什么,如何限制上传文件的大小?

分析:
通过form表单使用POST方法上传,可以使用户上传文本和二进制文件
客户端html部分如下

<form action="" method="post" enctype="multipart/form-data">
    <input type="file" name="userfile">
    <input type="submit" value="上传">
</form>

服务端通过$\_FILES接收通过HTTP上传到服务器的文件,上传的内容存储在$_FILES'xx'中,然后再通过move_uploaded_file,将上传的文件移动到新位置:

if(! empty($_FILES)) {
    if(is_uploaded_file($_FILES['userfile']['tmp_name'])) {
        if($_FILES['userfile']['error'] == UPLOAD_ERR_OK) {
            $upload_dir = './upload';
            $tmp_name = $_FILES['userfile']['tmp_name'];
            $name = $_FILES['userfile']['name'];
            if(move_uploaded_file($tmp_name, $upload_dir.'/'.$name)) {
                echo 'success.';
            } else {
                echo 'error';
            }
        }
    }
}

有多个配置可以限制上传文件的大小,在php.ini中:
post_max_size:(php 5.5.12)默认3M
upload_max_filesize:(php 5.5.12)默认64M
max_execution_time:必要的情况下还需要修改该配置,设置了脚本被解析器中止之前允许的最大执行时间,单位秒。(php 5.5.12)默认120
memory_limit:(php 5.5.12)默认128M


Cookie和Session

题1 禁用COOKIE 后 SEESION 还能用吗?

分析:在默认情况下(PHP>=4.3.0),php.inisession.use_only_cookies的值为1,也就是说在不更改任何配置的情况下,禁用了浏览器的 Cookie 功能,是没有办法使用 Session 的,因为默认情况下,Session_ID 保存在 Cookie 中。如果希望在禁用了 Cookie 之后仍然可以使用 Session,至少需要更改php.ini中的以下几个配置:session.use_cookies改为0session.use_only_cookies改为0、·session.use_trans_id·改为1(表示Session_ID通过url的参数进行传递)。使用url传递Session_ID相比使用Cookie传递Session_ID来说非常不安全,一是完全暴露Session_ID,二是容易遭到Session固定攻击。

题2 如何设置session的过期时间

分析:如果没有设定Session的生存周期,保存Session_ID的Cookie是保存在内存中的,关闭浏览器后该ID自动注销。如果客户端没有禁用Cookie,Cookie在启动Session时扮演的是存储Session_ID和Session生命周期的角色,可以手动设置Session的生存期:

$lifetime = 24 * 3600;
set_cookie(session_name(), session_id(), time() + $lifetime, '/');

也可以使用 session_set_cookie_params()函数设置Session的生存期。
Session过期后,PHP会对其进行回收,因此Session并不是随着浏览器的关闭而消失。
如果浏览器禁用了Cookie,那么Session的生命周期会随着浏览器进程的结束而结束,即只要关闭了浏览器,再次请求页面就要重新注册Session。

题3 请介绍Session的原理

分析:由于HTTP协议的无状态特性,协议本身并不支持服务器端保存客户端的状态信息,为了让服务器端和客户端保持联系,引入了Session的概念,用其来保持客户端的状态信息。Session通过一个成为PHPSESSID(可以更改名称)的Cookie和服务器端联系。Session通过Session_ID判断用户。
当使用了session_start()函数,用户第一次访问站点时,PHP会为用户创建一个session ID,这就是该用户的唯一标识,每一个访问的用户都会得到一个自己唯一的session ID。session ID会存放在响应头(Response)里的cookie中,之后发送给客户端,于是客户端就有了该用户在这个站点的session ID。当用户第二次访问该站点时,浏览器会带着本地存放的cookie(里面存有上次得到的session ID)随着请求(Request)一起发送到服务器,服务端接到请求后会检测是否有session ID,如果有就会找到响应的session文件,把其中的信息读取出来;如果没有就重新创建一个Session_ID。

题4 如何注销Session

分析:Session的注销分为4个步骤:
1.开启Session,所有有关Session的操作都需要先开启Session(除非在php.ini中设置session.auto_start为1,默认为0)

session_start();

2.删除所有的session变量,即把session数组清空

$_SESSION = array();

3.如果是基于Cookie的Session,则需要把保存Session_ID的Cookie删除

if(isset($_COOKIE[session_name()])) {
    setcookie(session_name(), '', time() - 1, '/');
}

4.彻底销毁Session

session_destroy();
题5 简述Session的回收机制

分析:如果用户退出网站时没有主动注销帐号,那么Session的回收将是被动的。php.ini中和Session回收有关的配置有:
session.gc_maxlifetime:表示 Session 文件的过期时间,默认为 1440 秒即 24分钟
session.gc_probability:默认值为1
session.gc_divisor:默认值为1000
后面两个配置代表 session_start() 函数每调用 1000 次触发一次 Session 文件的全部扫描,把过期的 Session 文件删除。过期的 Session 文件的判断标准是:文件的修改时间和当前时间相差是否大于 session.gc_maxlifetime。当用户每进行一个操作哪怕是一个刷新页面的动作时,都会修改 Session 文件的修改时间。对于设置分级目录存储的Session,php不会自动回收,需要自己实现回收机制。

题6 session共享问题解决方案

分析:可以将Session入库(普通数据库、内存表),或者使用内存存储系统例如Redis来存储Session,达到服务器间Session的共享。通过session_save_handler()函数实现。

题7 大型网站中Session方面应注意什么

分析:大型网站有很多衡量标准,访问量是其中一个。对于大访问量的网站,如果Session按照默认的设置存储,会影响系统性能。默认情况下,Session由文件的形式保存在指定的目录下,如果同一个目录下文件数超过10000,文件的定位将会非常耗时,可以通过修改php.inisession.save_path将Session文件存储在多级目录下;也可以保存在数据库中;最好的方式是保存在key-value形式的内存数据库中,例如Redis。
另一个方面,大型网站一般有多台服务器,要做好Session的同步。如果是使用数据库或者NoSQL来存储Session,Session的同步会比较容易。如果是文件形式保存Session文件,可以使用NFS或者FastDFS等文件系统存储Session文件。

题8 Session和Cookie的联系和区别

分析:
如果不修改php的配置,则Session是基于Cookie的:Session的唯一标识Session_ID保存在Cookie中,通过Cookie中的Session_ID来维持服务器端和客户端之间的状态。
可以通过设置保存Session_ID的Cookie的过期时间来设置Session的生存周期。
Cookie通过文件或者数据库的形式保存客户端,Session通过文件、数据库等形式保存在服务器端。

题9 服务器端设置了cookie的过期时间,然后修改本地时间小于cookie过期时间,cookie会不会被删除

分析:
会。因为Cookie的过期时间依赖于客户端时钟的标准。

题10 如何设置Cookie的过期时间,关闭浏览器Cookie会消失吗

分析:
通过setcookie()函数的第三个参数expire设置Cookie的过期时间,单位为秒,例如

setcookie('username', 'dee', time() + 24 * 3600, '/');

这段代码表示name为username的Cookie,会在24小时后过期。

如果要删除某个Cookie,同样使用setcookie()函数,例如

setcookie('user', '', time() - 1, '/');

只需要把Cookie的过期时间改为小于当前的一个时间即可。


HTTP协议

题1 HTTP状态码
题2 get请求和post请求的区别

算法

题1 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

分析:可以用递归和递推两种方法实现。
① 递归
如果台阶只有 1 级,则只有 1种跳法,如果台阶有2级,则有2种跳法(一次1级跳2次或一次2级)。把n级台阶时的跳法设为f(n),如果第一次只跳1级,则后面的n-1级的跳法为f(n-1),如果第一次跳2级,则后面n-2级的跳法为f(n-2)。当n > 2时,n级台阶的跳法为f(n) = f(n-1) + f(n-2),这就是一个斐波那契数列的问题

<?php

function fibonacci($n) {
    $result = [0, 1, 2];
    if($n <= 2) {
        return $result[$n]; // $n = 1,1种跳法;$n = 2,2种跳法
    }
    return fibonacci($n - 1) + fibonacci($n - 2);
}

② 递推

<?php

function climbstairs($n) {
    $dp = [1, 1];
    if($n < 2) {
        return 1;
    }
    for($i = 2; $i <= $n; $i++) {
        $dp[2] = $dp[0] + $dp[1];
        $dp[0] = $dp[1];
        $dp[1] = $dp[2];
    }
    return $dp[2];
}
题2 用一段代码实现无限级分类

分析:此题考察递归算法。

<?php

function level_layer($cate, $name = 'child', $pid = 0) {
    $arr = [];
    foreach($cate as $val) {
        if($val['pid'] == $pid) {
            $val[$name] = level_layer($cate, $name, $val['id']);
            $arr[] = $val;
        }
    }
    return $arr;
}

$data = [
    ['id' => 1, 'title' => '北京市', 'pid' => 0],
    ['id' => 2, 'title' => '朝阳区', 'pid' => 1],
    ['id' => 3, 'title' => '海淀区', 'pid' => 1],
    ['id' => 4, 'title' => '江西省', 'pid' => 0],
    ['id' => 5, 'title' => '九江市', 'pid' => 4],
    ['id' => 6, 'title' => '浔阳区', 'pid' => 5],
    ['id' => 7, 'title' => '庐山区', 'pid' => 5],
    ['id' => 8, 'title' => '南昌市', 'pid' => 4],
    ['id' => 9, 'title' => '河北省', 'pid' => 0],
    ['id' => 10, 'title' => '保定市', 'pid' => 9],
    ['id' => 11, 'title' => '石家庄市', 'pid' => 9],
    ['id' => 12, 'title' => '江苏省', 'pid' => 0],
    ['id' => 13, 'title' => '浙江省', 'pid' => 0],
];

echo '<pre>'; print_r(level_layer($data));
题3 一群猴子排成一圈,按 1,2,...,n 依次编号。然后从第 1 只开始数,数到第 m 只,把它踢出圈,从它后面再开始数,再数到第 m 只,在把它踢出去...,如此不停的进行下去,直到最后只剩下一只猴子为止,那只猴子就叫做大王。要求编程模拟此过程,输入 m、n,输出最后那个大王的编号。

分析:这是一个“约瑟夫环”问题,可以使用循环队列、循环链表等多种方法来解决,这里使用循环链表。

<?php

// 猴子类,链表上的每一个元素
class Monkey {
    public $number;
    public $name = null;

    public function __construct($number, $name = null) {
        $this->number = $number;
        $this->name = $name;
    }
}

// 初始化链表,添加元素
function init(&$first, $n) {
    $current = null;
    for($i = 0; $i < $n; $i++) {
        $monkey = new Monkey($i + 1);
        if($i == 0) {
            $first = $monkey;
            $first->next = $monkey; // 只有一个元素时
            $current = $first;
        } else {
            $current->next = $monkey;
            $monkey->next = $first; // $monkey 是变化的
            $current = $current->next;
        }
    }
}

// 显示链表
function showList($first) {
    $current = $first;
    while($current->next != $first) {
        echo '猴子的编号是:',$current->number,'<br />';
        $current = $current->next;
    }
    echo '猴子的编号是:',$current->number,'<br />';
}

// 删除元素
function delElement($first, $m, $start = 1) {
    $tail = $first; // 指向尾部节点的引用,在first节点之前
    while($tail->next != $first) {
        $tail = $tail->next; // 把tail节点设置在first节点之前
    }

    // 从第几个元素开始数
    for($i = 0; $i < $start - 1; $i++) {
        $tail = $tail->next;
        $first = $first->next;
    }

    while($tail != $first) { //当$tail==$first则说明只有最后一个元素
        for($i = 0; $i < $m - 1; $i++) {
            $tail = $tail->next;
            $first = $first->next;
        }
        echo '<br />删除编号 '.$first->number,' 的元素';
        // 跳过被删除的节点(first指向的节点)
        $first = $first->next;
        $tail->next = $first;
    }
    echo '<br />最后剩下的编号是: '.$tail->number;
}

$n = 4; // 元素的个数(猴子的数量)
$m = 3;   // 每次数几个元素
$start = 1; // 从第几个元素开始数

$first = null; // 链表头部节点的引用

init($first, $n);

showList($first);

delElement($first, $m, $start);
题4 写一段php代码实现冒泡排序

解答:

function bubble($array) {
    $count = count($array);
    for($i = 0; $i < $count - 1; $i++) {
        for($j = 0; $j < $count - 1 - $i; $j++) {
            // 递减
            if($array[$j] < $array[$j + 1]) {
                $tmp = $array[$j];
                $array[$j] = $array[$j + 1];
                $array[$j + 1] = $tmp;
            }
        }
    }
    return $array;
}
题5 写一段php代码实现快速排序
/*快速排序*/
function quick_sort($array) {
// 递归到只有一个元素
if(count($array) <= 1) {
    return $array;
}

$key = $array[0];
$left_arr = [];
$right_arr = [];

// 左边数组的元素都比右边数组的元素小
for($i = 1; $i < count($array); $i++) {
    if($array[$i] <= $key) {
        $left_arr[] = $array[$i];
    } else {
        $right_arr[] = $array[$i];
    }
}

// 递归对左右两个数组分别进行排序
$left_arr = quick_sort($left_arr);
$right_arr = quick_sort($right_arr);

// 合并结果
return array_merge($left_arr, array($key), $right_arr);
}

$arr = [10, 21, 1, 3, 89, 27, 75, 45, 91, 200, 451, 37, 2];
var_dump(quick_sort($arr));
题6 写一段php代码实现二分查找
<?php

function binarySearch(array $array, $needle) {
    $start = 0;
    $end = count($array) - 1;

    while($start < $end) {
        $middle = floor(($start + $end) / 2);
        if($array[$middle] == $needle) {
            return $middle;
        }
        if($needle < $array[$middle]) {
            $end = $middle - 1;
        }
        if($needle > $array[$middle]) {
            $start = $middle + 1;
        }
    }
    return false;
}
题7 换硬币问题:如果有100元,可以使用1元、2元、5元、10元4种面额的零钱来兑换,有多少种兑换方法?
<?php

$a = 1; 
$b = 2;
$c = 5;
$d = 10;
$sum = 0;
for ($i = 0; $i <= 100; $i++) {
   for ($j = 0; $j <= 50; $j++) {
      for ($k = 0; $k <= 20; $k++) {
              for($h = 0; $h <= 10; $h++) {
            if ($a * $i + $b * $j + $c * $k + $d * $h == 100) {
              $sum += 1;
              printf("%d个1元,%d个2元,%d个5元,%d个10元<br />", $i, $j, $k, $h);
            }
               }
       }
   }
}
printf("共%d种换法", $sum); 
题8 鸡兔同笼问题

正则表达式

题1 请写一个函数验证电子邮件的格式是否正确

设计模式

题1 请用单态设计模式方法设计类满足如下需求,使用php代码编写类实现在每次对数据库连接的访问中都只能获取唯一的一个数据库连接,具体连接数据库的详细代码忽略,请写出主要逻辑代码。

MySQL

题1 where和having的区别
题2 内连接、左连接、右连接的区别
题3 mysql优化常见的方案
题4 explain的用法
题5 索引的种类和用法
题6 mysql的存储引擎Innodb和MyISAM的区别
题7

一个足球网站有一张新闻表news,字段包括新闻编号id、新闻分类cid、标题title、点击量click,现在需要对 news 表按照 cid (分类id)进行过分组,按照每组新闻的数量进行排序,同时取出每组点击量(click)最多的两篇新闻。
分析:
创建表

CREATE TABLE `news` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '新闻编号',
  `cid` int(11) NOT NULL COMMENT '分类编号 例如 1 世界足球新闻 2 英超新闻 3 西甲新闻',
  `title` varchar(25) NOT NULL COMMENT '新闻标题',
  `click` int(11) NOT NULL DEFAULT '0' COMMENT '点击量',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

表数据

INSERT INTO `news` VALUES ('1', '1', '法国2-0力克德国进决赛', '1888');
INSERT INTO `news` VALUES ('2', '2', '传曼联1亿英镑报价博格巴', '201');
INSERT INTO `news` VALUES ('3', '3', '队报:巴萨接近签法国国脚', '150');
INSERT INTO `news` VALUES ('4', '1', '克罗斯:德国队表现最好的一场', '100');
INSERT INTO `news` VALUES ('5', '1', '决赛对阵:法国vs葡萄牙', '205');
INSERT INTO `news` VALUES ('6', '1', '格列兹曼6场6球仅次普拉蒂尼', '211');
INSERT INTO `news` VALUES ('7', '1', '阿森纳法国双星赛后安慰厄齐尔', '188');
INSERT INTO `news` VALUES ('8', '2', '回声报:利物浦要求艾比交易中加入回购条款', '11');
INSERT INTO `news` VALUES ('9', '3', '普约尔:没有比巴萨更适合梅西的地方', '225');
INSERT INTO `news` VALUES ('10', '3', '皇家贝蒂斯有意狼堡前锋多斯特', '13');
INSERT INTO `news` VALUES ('11', '3', '哈维:我觉得梅西不会想离开巴萨的', '1000');
INSERT INTO `news` VALUES ('12', '2', '传温格已将西迪贝看作重点目标', '1200');

此题的需求是 1.按新闻分类分组 2.按照新闻分类下新闻数量给分好的组排序 3.排好序的分组还需要取出该组分类下点击量最多的两篇新闻
需求1 可以使用group by来进行分组
需求2 可以使用count()+group by,取出每组分类下新闻的数量
以上2个需求可以用一条sql完成

select *, count(*) as num from news group by cid order by num desc;

查询结果
图片描述
可以看到已经按照数量num进行了排序

需求3 可以cid+click进行排序,这个需求可以用一条sql完成,用子查询实现

select a.* from news a 
where (
    select count(*) from news b where a.cid = b.cid and b.click > a.click
) < 2
order by cid, click desc; 

查询结果
图片描述

此时每个cid下点击量最多的两篇文章已经查询出来了,只不过查询的结果没有按照每个分类下新闻数量的多少来排序

只需要最后一步,把两个sql语句进行左(右)连接即可

select * from (
    select a.* from news a 
    where (
        select count(*) from news b where a.cid = b.cid and b.click > a.click
    ) < 2
    order by cid, click desc
) tablea RIGHT JOIN ( 
    select cid, count(*) as num from news group by cid order by num desc
) tableb on tablea.cid = tableb.cid 
order by tableb.num desc, click desc;

查询结果:
图片描述

题8 一张表有id,sortid,title,如何按照sortid进行分组,按照sortid数量进行排序,输出结果。

分析:此题和题6类似。
表结构

CREATE TABLE `test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(25) NOT NULL,
  `sortid` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

创建数据

INSERT INTO `test` VALUES ('1', 'title1', '106');
INSERT INTO `test` VALUES ('2', 'title2', '78');
INSERT INTO `test` VALUES ('3', 'title3', '56');
INSERT INTO `test` VALUES ('4', 'title4', '78');
INSERT INTO `test` VALUES ('5', 'title5', '78');
INSERT INTO `test` VALUES ('6', 'title6', '12');
INSERT INTO `test` VALUES ('7', 'title7', '56');

分三步创建sql

-- step 1
select a.* from test a 
where (
    select count(*) from test b where  b.sortid > a.sortid
) < (select count(*) from test)
order by sortid; 

-- step2
SELECT *, count(*) AS num, GROUP_CONCAT(id) AS ids FROM test GROUP BY sortid ORDER BY num; 

-- final
select * from (
    select a.* from test a 
    where (
        select count(*) from test b where  b.sortid > a.sortid
    ) < (select count(*) from test)
    order by sortid
) tablea LEFT JOIN (
    SELECT *, count(*) AS num, GROUP_CONCAT(id) AS ids FROM test GROUP BY sortid ORDER BY num
) tableb 
on tablea.sortid = tableb.sortid
order by tableb.num asc;

此题没有要求列出每个分组的前N条数据,那就把每个分组下所有的数据都列出来,查询结果如下:
图片描述

题9

有如下表及数据

name

subject

score

张三

数学

90

张三

语文

50

张三

地理

40

李四

语文

55

李四

政治

45

王五

政治

30

要求:查询出2门及2门以上不及格者的平均成绩
解析:
先查出所有人的平均分(AVG(score)),再找出每个人2门及2门以上不及格的数量(SUM(score<60)),最后筛选出数量大于等于2的人的信息(Having num>=2)

SELECT name, AVG(score) AS avg, SUM(score < 60) AS num 
FROM result 
GROUP BY name 
HAVING num >= 2;

查询结果:
图片描述

方法2:

SELECT `name`, AVG(score) AS avg FROM result WHERE `name` in (
        SELECT name FROM result GROUP BY name HAVING SUM(score<60) >= 2 
)
GROUP BY `name`;

查询结果:
图片描述
方法3:复杂很多

SELECT tablea.name, tablea.avg, tableb.num FROM (
    SELECT *,AVG(score) AS avg FROM result GROUP BY name
) tablea RIGHT JOIN (
    SELECT *, COUNT(*) AS num FROM (
        SELECT * FROM result WHERE score < 60
    ) a
    GROUP BY a.name HAVING num >= 2
) tableb on tablea.name = tableb.name;

查询结果:
图片描述

题10

有两张表:
表A

id

num

a

5

b

10

c

15

d

10

表B

id

num

b

5

c

15

d

20

e

99

要求查询出以下效果:

id

sum(num)

a

5

b

15

c

30

d

30

e

99

分析:
先使用UNION ALL将两张表联合起来,再使用SUM()+GROUP BY

SELECT id, SUM(num) FROM (
    SELECT * FROM A 
    UNION ALL
    SELECT * B
) AS tmp
GROUP BY tmp.id
;
题11 优化MySQL数据库的方法

综合/解决方案

题1 对于大流量的网站,可以采用哪些方法来解决访问量问题

安全


NoSQL

题1 为什么不能使用Memcached存储Session
题2 Redis相比Memcached有哪些优势
题3 Redis有哪些优点
题4 如何使用Redis存储Session
题5 Redis如何与MySQL同步

Linux

题1 查看系统负载有哪些命令

Node.js


JavaScript / jQuery


HTML / CSS


SVN / Git

查看原文

赞 2 收藏 1 评论 0

酱紫啊啊啊啊啊 回答了问题 · 2020-04-27

解决码云(gitee.com) 修改个人空间地址后,无法使用ssh

问题已经解决,是码云的问题,我提交了ISSUES,如今问题已经修复

关注 2 回答 2

酱紫啊啊啊啊啊 提出了问题 · 2020-04-20

解决码云(gitee.com) 修改个人空间地址后,无法使用ssh

前几天手溅,在码云设置里面,修改了个人空间地址,
image.png
使用新地址执行 clone

git clone git@gitee.com:xx/xx.git

报错

Handshake: Repository Not Found

测试SSH公钥

ssh -T git@gitee.com

返回

Hi XXX! You've successfully authenticated,

仓库地址都是复制粘贴,并试过多次,多个不同仓库;
在服务器上 clone

HTTPS 方式使用没问题,修改地址已经过去两天了,应该不是什么缓存问题
真是头大

关注 2 回答 2

酱紫啊啊啊啊啊 赞了回答 · 2020-04-03

如何用 docker 中的 php 运行 workerman

workerman有视频教程,挺不错的。网址: https://study.163.com/course/...

关注 4 回答 3

酱紫啊啊啊啊啊 赞了回答 · 2020-04-03

如何用 docker 中的 php 运行 workerman

eee...我才楼主是这样运行的...
docker run -it --rm --name ws -w /usr/src/myapp -v "$PWD":/usr/src/myapp -p 8000:8000 php:7.2-fpm php ws.php start -d

然而你可以试试这样

docker run -it -d --rm --name ws -w /usr/src/myapp -v "$PWD":/usr/src/myapp -p 8000:8000 php:7.2-fpm php ws.php start

关注 4 回答 3

酱紫啊啊啊啊啊 提出了问题 · 2019-08-30

如何用 docker 中的 php 运行 workerman

我的phpdocker里面,

ws.php官方的websocket例子

我尝试用命令

docker run -it --rm --name ws -w /usr/src/myapp -v "$PWD":/usr/src/myapp -p 8000:8000 php:7.2-fpm php ws.php start

启动,服务正常,

但是加了 -d ,会输出 Workerman[ws.php] start in DAEMON mode,但是没有后台启动,这个应该怎么理解,

是否要把 workerman构建成镜像,才能运行?

关注 4 回答 3

酱紫啊啊啊啊啊 赞了回答 · 2019-04-27

webpack2中CopyWebpackPlugin无法复制自动生成的文件

在根目录下加一个static文件夹
图片描述

关注 8 回答 6

酱紫啊啊啊啊啊 收藏了文章 · 2019-01-05

Redis持久化

Redis的持久化

Redis有两种持久化的方式:快照(RDB文件)和追加式文件(AOF文件):

  • RDB持久化方式会在一个特定的间隔保存那个时间点的一个数据快照。
  • AOF持久化方式则会记录每一个服务器收到的写操作。在服务启动时,这些记录的操作会逐条执行从而重建出原来的数据。写操作命令记录的格式跟Redis协议一致,以追加的方式进行保存。
  • Redis的持久化是可以禁用的,就是说你可以让数据的生命周期只存在于服务器的运行时间里。
  • 两种方式的持久化是可以同时存在的,但是当Redis重启时,AOF文件会被优先用于重建数据。

RDB

工作原理

  • Redis调用fork(),产生一个子进程。
  • 子进程把数据写到一个临时的RDB文件。
  • 当子进程写完新的RDB文件后,把旧的RDB文件替换掉。

优点

  • RDB文件是一个很简洁的单文件,它保存了某个时间点的Redis数据,很适合用于做备份。你可以设定一个时间点对RDB文件进行归档,这样就能在需要的时候很轻易的把数据恢复到不同的版本。
  • 基于上面所描述的特性,RDB很适合用于灾备。单文件很方便就能传输到远程的服务器上。
  • RDB的性能很好,需要进行持久化时,主进程会fork一个子进程出来,然后把持久化的工作交给子进程,自己不会有相关的I/O操作。
  • 比起AOF,在数据量比较大的情况下,RDB的启动速度更快。

缺点

  • RDB容易造成数据的丢失。假设每5分钟保存一次快照,如果Redis因为某些原因不能正常工作,那么从上次产生快照到Redis出现问题这段时间的数据就会丢失了。
  • RDB使用fork()产生子进程进行数据的持久化,如果数据比较大的话可能就会花费点时间,造成Redis停止服务几毫秒。如果数据量很大且CPU性能不是很好的时候,停止服务的时间甚至会到1秒。

文件路径和名称

默认Redis会把快照文件存储为当前目录下一个名为dump.rdb的文件。要修改文件的存储路径和名称,可以通过修改配置文件redis.conf实现:

# RDB文件名,默认为dump.rdb。
dbfilename dump.rdb

# 文件存放的目录,AOF文件同样存放在此目录下。默认为当前工作目录。
dir ./

保存点(RDB的启用和禁用)

你可以配置保存点,使Redis如果在每N秒后数据发生了M次改变就保存快照文件。例如下面这个保存点配置表示每60秒,如果数据发生了1000次以上的变动,Redis就会自动保存快照文件:

save 60 1000

保存点可以设置多个,Redis的配置文件就默认设置了3个保存点:

# 格式为:save <seconds> <changes>
# 可以设置多个。
save 900 1 #900秒后至少1个key有变动
save 300 10 #300秒后至少10个key有变动
save 60 10000 #60秒后至少10000个key有变动

如果想禁用快照保存的功能,可以通过注释掉所有"save"配置达到,或者在最后一条"save"配置后添加如下的配置:

save ""

错误处理

默认情况下,如果Redis在后台生成快照的时候失败,那么就会停止接收数据,目的是让用户能知道数据没有持久化成功。但是如果你有其他的方式可以监控到Redis及其持久化的状态,那么可以把这个功能禁止掉。

stop-writes-on-bgsave-error yes

数据压缩

默认Redis会采用LZF对数据进行压缩。如果你想节省点CPU的性能,你可以把压缩功能禁用掉,但是数据集就会比没压缩的时候要打。

rdbcompression yes

数据校验

从版本5的RDB的开始,一个CRC64的校验码会放在文件的末尾。这样更能保证文件的完整性,但是在保存或者加载文件时会损失一定的性能(大概10%)。如果想追求更高的性能,可以把它禁用掉,这样文件在写入校验码时会用0替代,加载的时候看到0就会直接跳过校验。

rdbchecksum yes

手动生成快照

Redis提供了两个命令用于手动生成快照。

SAVE

SAVE命令会使用同步的方式生成RDB快照文件,这意味着在这个过程中会阻塞所有其他客户端的请求。因此不建议在生产环境使用这个命令,除非因为某种原因需要去阻止Redis使用子进程进行后台生成快照(例如调用fork(2)出错)。

BGSAVE

BGSAVE命令使用后台的方式保存RDB文件,调用此命令后,会立刻返回OK返回码。Redis会产生一个子进程进行处理并立刻恢复对客户端的服务。在客户端我们可以使用LASTSAVE命令查看操作是否成功。

127.0.0.1:6379> BGSAVE
Background saving started
127.0.0.1:6379> LASTSAVE
(integer) 1433936394

配置文件里禁用了快照生成功能不影响SAVEBGSAVE命令的效果。

AOF

快照并不是很可靠。如果你的电脑突然宕机了,或者电源断了,又或者不小心杀掉了进程,那么最新的数据就会丢失。而AOF文件则提供了一种更为可靠的持久化方式。每当Redis接受到会修改数据集的命令时,就会把命令追加到AOF文件里,当你重启Redis时,AOF里的命令会被重新执行一次,重建数据。

优点

  • 比RDB可靠。你可以制定不同的fsync策略:不进行fsync、每秒fsync一次和每次查询进行fsync。默认是每秒fsync一次。这意味着你最多丢失一秒钟的数据。
  • AOF日志文件是一个纯追加的文件。就算是遇到突然停电的情况,也不会出现日志的定位或者损坏问题。甚至如果因为某些原因(例如磁盘满了)命令只写了一半到日志文件里,我们也可以用redis-check-aof这个工具很简单的进行修复。
  • 当AOF文件太大时,Redis会自动在后台进行重写。重写很安全,因为重写是在一个新的文件上进行,同时Redis会继续往旧的文件追加数据。新文件上会写入能重建当前数据集的最小操作命令的集合。当新文件重写完,Redis会把新旧文件进行切换,然后开始把数据写到新文件上。
  • AOF把操作命令以简单易懂的格式一条接一条的保存在文件里,很容易导出来用于恢复数据。例如我们不小心用FLUSHALL命令把所有数据刷掉了,只要文件没有被重写,我们可以把服务停掉,把最后那条命令删掉,然后重启服务,这样就能把被刷掉的数据恢复回来。

缺点

  • 在相同的数据集下,AOF文件的大小一般会比RDB文件大。
  • 在某些fsync策略下,AOF的速度会比RDB慢。通常fsync设置为每秒一次就能获得比较高的性能,而在禁止fsync的情况下速度可以达到RDB的水平。
  • 在过去曾经发现一些很罕见的BUG导致使用AOF重建的数据跟原数据不一致的问题。

启用AOF

把配置项appendonly设为yes

appendonly yes

文件路径和名称

# 文件存放目录,与RDB共用。默认为当前工作目录。
dir ./

# 默认文件名为appendonly.aof
appendfilename "appendonly.aof"

可靠性

你可以配置Redis调用fsync的频率,有三个选项:

  • 每当有新命令追加到AOF的时候调用fsync。速度最慢,但是最安全。
  • 每秒fsync一次。速度快(2.4版本跟快照方式速度差不多),安全性不错(最多丢失1秒的数据)。
  • 从不fsync,交由系统去处理。这个方式速度最快,但是安全性一般。

推荐使用每秒fsync一次的方式(默认的方式),因为它速度快,安全性也不错。相关配置如下:

# appendfsync always
appendfsync everysec
# appendfsync no

日志重写

随着写操作的不断增加,AOF文件会越来越大。例如你递增一个计数器100次,那么最终结果就是数据集里的计数器的值为最终的递增结果,但是AOF文件里却会把这100次操作完整的记录下来。而事实上要恢复这个记录,只需要1个命令就行了,也就是说AOF文件里那100条命令其实可以精简为1条。所以Redis支持这样一个功能:在不中断服务的情况下在后台重建AOF文件。

工作原理如下:

  • Redis调用fork(),产生一个子进程。
  • 子进程把新的AOF写到一个临时文件里。
  • 主进程持续把新的变动写到内存里的buffer,同时也会把这些新的变动写到旧的AOF里,这样即使重写失败也能保证数据的安全。
  • 当子进程完成文件的重写后,主进程会获得一个信号,然后把内存里的buffer追加到子进程生成的那个新AOF里。
  • Redis

我们可以通过配置设置日志重写的条件:

# Redis会记住自从上一次重写后AOF文件的大小(如果自Redis启动后还没重写过,则记住启动时使用的AOF文件的大小)。
# 如果当前的文件大小比起记住的那个大小超过指定的百分比,则会触发重写。
# 同时需要设置一个文件大小最小值,只有大于这个值文件才会重写,以防文件很小,但是已经达到百分比的情况。

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

要禁用自动的日志重写功能,我们可以把百分比设置为0:

auto-aof-rewrite-percentage 0

Redis 2.4以上才可以自动进行日志重写,之前的版本需要手动运行BGREWRITEAOF这个命令。

数据损坏修复

如果因为某些原因(例如服务器崩溃)AOF文件损坏了,导致Redis加载不了,可以通过以下方式进行修复:

  • 备份AOF文件。
  • 使用redis-check-aof命令修复原始的AOF文件:

    $ redis-check-aof --fix
    
  • 可以使用diff -u命令看下两个文件的差异。
  • 使用修复过的文件重启Redis服务。

从RDB切换到AOF

这里只说Redis >= 2.2版本的方式:

  • 备份一个最新的dump.rdb的文件,并把备份文件放在一个安全的地方。
  • 运行以下两条命令:

    $ redis-cli config set appendonly yes
    $ redis-cli config set save ""
    
  • 确保数据跟切换前一致。

  • 确保数据正确的写到AOF文件里。

第二条命令是用来禁用RDB的持久化方式,但是这不是必须的,因为你可以同时启用两种持久化方式。

记得对配置文件redis.conf进行编辑启用AOF,因为命令行方式修改配置在重启Redis后就会失效。

备份

建议的备份方法:

  • 创建一个定时任务,每小时和每天创建一个快照,保存在不同的文件夹里。
  • 定时任务运行时,把太旧的文件进行删除。例如只保留48小时的按小时创建的快照和一到两个月的按天创建的快照。
  • 每天确保一次把快照文件传输到数据中心外的地方进行保存,至少不能保存在Redis服务所在的服务器。

参考

http://redis.io/topics/persistence

查看原文

酱紫啊啊啊啊啊 赞了文章 · 2019-01-05

Redis持久化

Redis的持久化

Redis有两种持久化的方式:快照(RDB文件)和追加式文件(AOF文件):

  • RDB持久化方式会在一个特定的间隔保存那个时间点的一个数据快照。
  • AOF持久化方式则会记录每一个服务器收到的写操作。在服务启动时,这些记录的操作会逐条执行从而重建出原来的数据。写操作命令记录的格式跟Redis协议一致,以追加的方式进行保存。
  • Redis的持久化是可以禁用的,就是说你可以让数据的生命周期只存在于服务器的运行时间里。
  • 两种方式的持久化是可以同时存在的,但是当Redis重启时,AOF文件会被优先用于重建数据。

RDB

工作原理

  • Redis调用fork(),产生一个子进程。
  • 子进程把数据写到一个临时的RDB文件。
  • 当子进程写完新的RDB文件后,把旧的RDB文件替换掉。

优点

  • RDB文件是一个很简洁的单文件,它保存了某个时间点的Redis数据,很适合用于做备份。你可以设定一个时间点对RDB文件进行归档,这样就能在需要的时候很轻易的把数据恢复到不同的版本。
  • 基于上面所描述的特性,RDB很适合用于灾备。单文件很方便就能传输到远程的服务器上。
  • RDB的性能很好,需要进行持久化时,主进程会fork一个子进程出来,然后把持久化的工作交给子进程,自己不会有相关的I/O操作。
  • 比起AOF,在数据量比较大的情况下,RDB的启动速度更快。

缺点

  • RDB容易造成数据的丢失。假设每5分钟保存一次快照,如果Redis因为某些原因不能正常工作,那么从上次产生快照到Redis出现问题这段时间的数据就会丢失了。
  • RDB使用fork()产生子进程进行数据的持久化,如果数据比较大的话可能就会花费点时间,造成Redis停止服务几毫秒。如果数据量很大且CPU性能不是很好的时候,停止服务的时间甚至会到1秒。

文件路径和名称

默认Redis会把快照文件存储为当前目录下一个名为dump.rdb的文件。要修改文件的存储路径和名称,可以通过修改配置文件redis.conf实现:

# RDB文件名,默认为dump.rdb。
dbfilename dump.rdb

# 文件存放的目录,AOF文件同样存放在此目录下。默认为当前工作目录。
dir ./

保存点(RDB的启用和禁用)

你可以配置保存点,使Redis如果在每N秒后数据发生了M次改变就保存快照文件。例如下面这个保存点配置表示每60秒,如果数据发生了1000次以上的变动,Redis就会自动保存快照文件:

save 60 1000

保存点可以设置多个,Redis的配置文件就默认设置了3个保存点:

# 格式为:save <seconds> <changes>
# 可以设置多个。
save 900 1 #900秒后至少1个key有变动
save 300 10 #300秒后至少10个key有变动
save 60 10000 #60秒后至少10000个key有变动

如果想禁用快照保存的功能,可以通过注释掉所有"save"配置达到,或者在最后一条"save"配置后添加如下的配置:

save ""

错误处理

默认情况下,如果Redis在后台生成快照的时候失败,那么就会停止接收数据,目的是让用户能知道数据没有持久化成功。但是如果你有其他的方式可以监控到Redis及其持久化的状态,那么可以把这个功能禁止掉。

stop-writes-on-bgsave-error yes

数据压缩

默认Redis会采用LZF对数据进行压缩。如果你想节省点CPU的性能,你可以把压缩功能禁用掉,但是数据集就会比没压缩的时候要打。

rdbcompression yes

数据校验

从版本5的RDB的开始,一个CRC64的校验码会放在文件的末尾。这样更能保证文件的完整性,但是在保存或者加载文件时会损失一定的性能(大概10%)。如果想追求更高的性能,可以把它禁用掉,这样文件在写入校验码时会用0替代,加载的时候看到0就会直接跳过校验。

rdbchecksum yes

手动生成快照

Redis提供了两个命令用于手动生成快照。

SAVE

SAVE命令会使用同步的方式生成RDB快照文件,这意味着在这个过程中会阻塞所有其他客户端的请求。因此不建议在生产环境使用这个命令,除非因为某种原因需要去阻止Redis使用子进程进行后台生成快照(例如调用fork(2)出错)。

BGSAVE

BGSAVE命令使用后台的方式保存RDB文件,调用此命令后,会立刻返回OK返回码。Redis会产生一个子进程进行处理并立刻恢复对客户端的服务。在客户端我们可以使用LASTSAVE命令查看操作是否成功。

127.0.0.1:6379> BGSAVE
Background saving started
127.0.0.1:6379> LASTSAVE
(integer) 1433936394

配置文件里禁用了快照生成功能不影响SAVEBGSAVE命令的效果。

AOF

快照并不是很可靠。如果你的电脑突然宕机了,或者电源断了,又或者不小心杀掉了进程,那么最新的数据就会丢失。而AOF文件则提供了一种更为可靠的持久化方式。每当Redis接受到会修改数据集的命令时,就会把命令追加到AOF文件里,当你重启Redis时,AOF里的命令会被重新执行一次,重建数据。

优点

  • 比RDB可靠。你可以制定不同的fsync策略:不进行fsync、每秒fsync一次和每次查询进行fsync。默认是每秒fsync一次。这意味着你最多丢失一秒钟的数据。
  • AOF日志文件是一个纯追加的文件。就算是遇到突然停电的情况,也不会出现日志的定位或者损坏问题。甚至如果因为某些原因(例如磁盘满了)命令只写了一半到日志文件里,我们也可以用redis-check-aof这个工具很简单的进行修复。
  • 当AOF文件太大时,Redis会自动在后台进行重写。重写很安全,因为重写是在一个新的文件上进行,同时Redis会继续往旧的文件追加数据。新文件上会写入能重建当前数据集的最小操作命令的集合。当新文件重写完,Redis会把新旧文件进行切换,然后开始把数据写到新文件上。
  • AOF把操作命令以简单易懂的格式一条接一条的保存在文件里,很容易导出来用于恢复数据。例如我们不小心用FLUSHALL命令把所有数据刷掉了,只要文件没有被重写,我们可以把服务停掉,把最后那条命令删掉,然后重启服务,这样就能把被刷掉的数据恢复回来。

缺点

  • 在相同的数据集下,AOF文件的大小一般会比RDB文件大。
  • 在某些fsync策略下,AOF的速度会比RDB慢。通常fsync设置为每秒一次就能获得比较高的性能,而在禁止fsync的情况下速度可以达到RDB的水平。
  • 在过去曾经发现一些很罕见的BUG导致使用AOF重建的数据跟原数据不一致的问题。

启用AOF

把配置项appendonly设为yes

appendonly yes

文件路径和名称

# 文件存放目录,与RDB共用。默认为当前工作目录。
dir ./

# 默认文件名为appendonly.aof
appendfilename "appendonly.aof"

可靠性

你可以配置Redis调用fsync的频率,有三个选项:

  • 每当有新命令追加到AOF的时候调用fsync。速度最慢,但是最安全。
  • 每秒fsync一次。速度快(2.4版本跟快照方式速度差不多),安全性不错(最多丢失1秒的数据)。
  • 从不fsync,交由系统去处理。这个方式速度最快,但是安全性一般。

推荐使用每秒fsync一次的方式(默认的方式),因为它速度快,安全性也不错。相关配置如下:

# appendfsync always
appendfsync everysec
# appendfsync no

日志重写

随着写操作的不断增加,AOF文件会越来越大。例如你递增一个计数器100次,那么最终结果就是数据集里的计数器的值为最终的递增结果,但是AOF文件里却会把这100次操作完整的记录下来。而事实上要恢复这个记录,只需要1个命令就行了,也就是说AOF文件里那100条命令其实可以精简为1条。所以Redis支持这样一个功能:在不中断服务的情况下在后台重建AOF文件。

工作原理如下:

  • Redis调用fork(),产生一个子进程。
  • 子进程把新的AOF写到一个临时文件里。
  • 主进程持续把新的变动写到内存里的buffer,同时也会把这些新的变动写到旧的AOF里,这样即使重写失败也能保证数据的安全。
  • 当子进程完成文件的重写后,主进程会获得一个信号,然后把内存里的buffer追加到子进程生成的那个新AOF里。
  • Redis

我们可以通过配置设置日志重写的条件:

# Redis会记住自从上一次重写后AOF文件的大小(如果自Redis启动后还没重写过,则记住启动时使用的AOF文件的大小)。
# 如果当前的文件大小比起记住的那个大小超过指定的百分比,则会触发重写。
# 同时需要设置一个文件大小最小值,只有大于这个值文件才会重写,以防文件很小,但是已经达到百分比的情况。

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

要禁用自动的日志重写功能,我们可以把百分比设置为0:

auto-aof-rewrite-percentage 0

Redis 2.4以上才可以自动进行日志重写,之前的版本需要手动运行BGREWRITEAOF这个命令。

数据损坏修复

如果因为某些原因(例如服务器崩溃)AOF文件损坏了,导致Redis加载不了,可以通过以下方式进行修复:

  • 备份AOF文件。
  • 使用redis-check-aof命令修复原始的AOF文件:

    $ redis-check-aof --fix
    
  • 可以使用diff -u命令看下两个文件的差异。
  • 使用修复过的文件重启Redis服务。

从RDB切换到AOF

这里只说Redis >= 2.2版本的方式:

  • 备份一个最新的dump.rdb的文件,并把备份文件放在一个安全的地方。
  • 运行以下两条命令:

    $ redis-cli config set appendonly yes
    $ redis-cli config set save ""
    
  • 确保数据跟切换前一致。

  • 确保数据正确的写到AOF文件里。

第二条命令是用来禁用RDB的持久化方式,但是这不是必须的,因为你可以同时启用两种持久化方式。

记得对配置文件redis.conf进行编辑启用AOF,因为命令行方式修改配置在重启Redis后就会失效。

备份

建议的备份方法:

  • 创建一个定时任务,每小时和每天创建一个快照,保存在不同的文件夹里。
  • 定时任务运行时,把太旧的文件进行删除。例如只保留48小时的按小时创建的快照和一到两个月的按天创建的快照。
  • 每天确保一次把快照文件传输到数据中心外的地方进行保存,至少不能保存在Redis服务所在的服务器。

参考

http://redis.io/topics/persistence

查看原文

赞 42 收藏 100 评论 4

酱紫啊啊啊啊啊 赞了回答 · 2018-12-03

vsCode 使用git问题

配置本地仓库

git config user.name "your_name"
git config user.email XXXX@gmail.com

PS: HTTPS 方式会让每次输入用户名和密码,解决办法如下

创建 .git-credential 文件 并写入用户信息:用户名和密码

git config  credential.helper store                  //(当前仓库)
git config --global credential.helper store          //(全局仓库,与上方可以只设置一个或两个都设置,push 时会优先第一个,如果第一个没有,会再去找全局配置)

push 代码 这时会让你输入github用户名和密码, 这一步输入的用户名密码会被记住,
下次再push代码时就不用输入用户名密码, 这一步会在用户目录下生成文件.git-credential记录用户名密码的信息。

关注 8 回答 8

认证与成就

  • 获得 33 次点赞
  • 获得 147 枚徽章 获得 7 枚金徽章, 获得 44 枚银徽章, 获得 96 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-04-07
个人主页被 1.9k 人浏览