14
本教程是本人基于360大牛:全面解读PHP面试的课程所做的笔记,加上我个人对一些其他知识的补充,汇集了一些其他教程汇集。众人智慧结晶,个人总结所得
转载请注明出处PHP面试复习知识点(超级全面,由浅入深)

联系我

如果你还是不明白的话,或者本文章内资源下载不了,可以联系我,或者是加我的QQ群(群内有资源提供下载),大家可以多多交流:

qqqq

PHP基础知识

false的七种情况

  1. 整型0
  2. 浮点0.0
  3. 布尔false
  4. 空字符串'',""
  5. 字符串'0'
  6. 空数组[]
  7. NULL
字符串和数字用等于模糊判断时,字符串会被先转换成数字再进行对比

示例代码eq.php

<?php
$arr = [
    "'0e123' == ''" => '0e123' == '',
    "'0e123' == '0'" => '0e123' == '0',
    "'0e123' == 0" => '0e123' == 0,
    "'0e123' == 'e'" => '0e123' == 'e',
    "'0e123' == '000'" => '0e123' == '000',
    "'1e123' == '1'" => '1a000e123' == '1',
    "'1e123' == 1" => '1a000e123' == 1,
    "'000e123' == '0'" => '000e123' == '0',
    "'e123' == 0" => 'a123' == 0,
    "'e123' == '0'" => 'a123' == '0',
    "null == 0" => null == 0,
    "null == '0'" => null == '0',
    "null == false" => null == false,
    "[] == 0" => [] == 0,
    "[] == '0'" => [] == '0',
    "[] == false" => [] == false,
    "false == 0" => false == 0,
    "false == '0'" => false == '0',
];
foreach ($arr as $key => $value) {
    echo '( ' . $key . ' ) => ' . ($value === true ? 'true' : 'false') . PHP_EOL;
}

输出结果

( '0e123' == '' ) => false
( '0e123' == '0' ) => true
( '0e123' == 0 ) => true
( '0e123' == 'e' ) => false
( '0e123' == '000' ) => true
( '1e123' == '1' ) => false
( '1e123' == 1 ) => true
( '000e123' == '0' ) => true
( 'e123' == 0 ) => true
( 'e123' == '0' ) => false
( null == 0 ) => true
( null == '0' ) => false
( null == false ) => true
( [] == 0 ) => false
( [] == '0' ) => false
( [] == false ) => true
( false == 0 ) => true
( false == '0' ) => true

超全局数组

  1. $GLOBALS,包含下面8个超全局数组的值
  2. $_GET
  3. $_POST
  4. $_REQUERT,包含$_GET,$_POST,$_COOKIE
  5. $_SEESION
  6. $_COOKIE
  7. $_SERVER
$_SERVER['SERVER_ADDR'] //服务器地址
$_SERVER['SERVER_NAME'] //服务名称
$_SERVER['REQUEST_TIME'] //请求时间
$_SERVER['QUERY_STRING'] //请求地址中问号后的内容
$_SERVER['HTTP_REFERER'] //上次请求地址
$_SERVER['HTTP_USER_AGENT'] //浏览器信息
$_SERVER['REMOTE_ARRR'] //客户端请求ip
$_SERVER['REQUEST_URI'] // 请求中脚本名称
$_SERVER['PATH_INFO'] // 请求中路径
  1. $_FIELS
  2. $_ENV

null的三种情况

  1. 直接赋值NULL
  2. 未定义变量
  3. unset销毁后的变量

常量

一定定义,不可删除和修改

  1. const 更快,是语言结构,可定义类常量
  2. define 是函数

预定义常量

  1. FILE 文件所在路径+文件名
  2. LINE 所在代码行
  3. DIR 所在文件夹路径
  4. FUNCTION 方法名
  5. CLASS 类名
  6. TRAIT TRAIT的名称
  7. METHOD 类名+方法名
  8. NAMESPACE 命名空间名

引用变量

用不同名字访问同一个变量内容,用『&』符号表示

抽象类和接口

抽象类(abstract):

  1. 定义为抽象的类不能被实例化.
  2. 任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。
  3. 被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现。
  4. 继承一个抽象类的时候,子类必须定义父类中的所有抽象方法;另外,这些方法的访问控制必须和父类中一样(或者更为宽松)。
  5. 例如某个抽象方法被声明为受保护的,那么子类中实现的方法就应该声明为受保护的或者公有的,而不能定义为私有的。此外方法的调用方式必须匹配,即类型和所需参数数量必须一致。例如,子类定义了一个可选参数,而父类抽象方法的声明里没有,则两者的声明并无冲突。
  6. 这也适用于 PHP 5.4 起的构造函数。在 PHP 5.4 之前的构造函数声明可以不一样的.

示例代码abstract_class.php

<?php
abstract class Animal {
    protected $name = 'animal';

    abstract public function call();

    abstract public function run();

    public function getName() {
        return $this->name;
    }
}

class Cat extends Animal {
    protected $name = 'cat';

    public function call() {
        echo $this->getName() . ': 喵喵叫~' . PHP_EOL;
    }

    public function run() {
        echo $this->getName() . '在跑~' . PHP_EOL;
    }
}

class Dog extends Animal {
    protected $name = 'dog';

    public function call() {
        echo $this->getName() . ': 汪汪叫~' . PHP_EOL;
    }

    public function run() {
        echo $this->getName() . '在跑~' . PHP_EOL;
    }
}

$cat = new Cat;
$cat->call();
$cat->run();

$dog = new Dog;
$dog->call();
$dog->run();

运行结果:

cat: 喵喵叫~
cat在跑~
dog: 汪汪叫~
dog在跑~

接口(interface):

  1. 使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。
  2. 接口是通过 interface 关键字来定义的,就像定义一个标准的类一样,但其中定义所有的方法都是空的。
  3. 接口中定义的所有方法都必须是公有,这是接口的特性。
  4. 要实现一个接口,使用 implements 操作符。类中必须实现接口中定义的所有方法,否则会报一个致命错误。类可以实现多个接口,用逗号来分隔多个接口的名称。
  5. 实现多个接口时,接口中的方法不能有重名。
  6. 接口也可以继承,通过使用extends操作符.
  7. 类要实现接口,必须使用和接口中所定义的方法完全一致的方式。否则会导致致命错误.

示例代码interface_class.php

<?php
interface Animal {
    public function call();
    public function run();
}

class HasName {
    protected $name = 'name';

    public function getName() {
        return $this->name;
    }
}

class Cat extends HasName implements Animal {
    protected $name = 'cat';

    public function call() {
        echo $this->getName() . ': 喵喵叫~' . PHP_EOL;
    }

    public function run() {
        echo $this->getName() . '在跑~' . PHP_EOL;
    }
}

class Dog  extends HasName implements Animal {
    protected $name = 'dog';

    public function call() {
        echo $this->getName() . ': 汪汪叫~' . PHP_EOL;
    }

    public function run() {
        echo $this->getName() . '在跑~' . PHP_EOL;
    }
}

输出结果

cat: 喵喵叫~
cat在跑~
dog: 汪汪叫~
dog在跑~

区别:

  1. 对接口的继承使用implements,抽象类使用extends
  2. 接口中不可以声明变量,但可以声明类常量.抽象类中可以声明各种变量
  3. 接口没有构造函数,抽象类可以有
  4. 接口中的方法默认为public,抽象类中的方法可以用public,protected,private修饰
  5. 一个类可以继承多个接口,但只能继承一个抽象类
  6. 接口的定义用interface,抽象类定义用abstract

运算符优先级

优先级由高到低排序
  1. ==递增/递减==
  2. 非(!)
  3. ==算术运算符==
  4. ==大小比较==
  5. (不)相等比较
  6. 引用
  7. 位运算符(^)
  8. 位运算符(|)
  9. ==逻辑与==
  10. ==逻辑或==
  11. ==三目==
  12. ==赋值==
  13. and
  14. oxr
  15. or

浮点数值得精度丢失问题

原因:因为计算机存储是二进制,准换进制时会有精度丢失

解决方案:先将浮点字符串化,再进行整数获取,输出可通过print

$f = 0.57;
$f = $f * 100;
// 输入可通过print
printf('%d', $f);

// 用于存储或二次计算,先将浮点字符串化,再进行整数获取
$f = strval($f);
var_dump($f);
echo floor($f);
echo intval($f);
echo (int)($f);

switch 只能判断整型、浮点、字符

变量类型

  1. 普通变量
  2. 全局变量,通过global定义,可以在局部域调用全局变量,可通过$GLOBAL['XXX']读取变量的值
  3. 静态变量,通过static定义,仅在局部域中存在,执行函数离开作用域,其值也不会消失

ip处理函数

  1. ip2long()
  2. long2ip()

示例代码ip.php

<?php
$ip = '192.168.1.1';
echo $ip. ' => ' . ip2long($ip) . ' => ' . long2ip(ip2long($ip));

// 输出
// 192.168.1.1 => 3232235777 => 192.168.1.1

时间日期处理函数

  1. date()
  2. strtotime()
  3. mktime()
  4. time()
  5. microtime()
  6. date_default_timezone_set()

打印处理

  1. print() 仅输出单个变量
  2. printf() 按格式输出
  3. print_r() 格式化输出
  4. echo 输出多个变量
  5. sprintf() 按格式返回
  6. var_dump() 格式化输出,并输出变量类型
  7. var_export() 将格式化输出,加true可返回,返回内容可直接做变量使用

示例代码print.php

<?php
print('aaaaaa');
echo PHP_EOL;
printf("%s:%2.2f", 'sada', 111.222);
echo PHP_EOL;
print_r([1, 2]);
echo PHP_EOL;
echo 11;
echo PHP_EOL;
echo sprintf("%s:%2.2f", 'sada', 111.222);
echo PHP_EOL;
var_dump([1, 2]);
echo PHP_EOL;
var_export([1, 2]);
echo PHP_EOL;
echo var_export([1, 2], true);
echo PHP_EOL;

输出结果

aaaaaa
sada:111.22
Array
(
    [0] => 1
    [1] => 2
)

11
sada:111.22
array(2) {
  [0]=>
  int(1)
  [1]=>
  int(2)
}

array (
  0 => 1,
  1 => 2,
)
array (
  0 => 1,
  1 => 2,
)

序列化

  1. serialize()
  2. unserialize()

示例代码serialize.php

<?php
$a = 'klinson';
echo $a . ' =>' . serialize($a) . ' => ' . unserialize(serialize($a));

输出结果

klinson => s:7:"klinson"; => klinson

字符串处理

  1. implode(),join()
  2. explode()
  3. strrev() 反转字符
  4. trim(),ltrim(),rtrim()
  5. strstr() 获取第一次出现指定字符串的字符串
  6. number_format() 数字格式化为金钱格式

示例代码string.php

<?php
$str = 'klinson';
$array = explode('n', $str);
var_dump($array, join('n', $array), implode('n', $array));
echo 'strrev(\'klinson\') => ' . strrev($str) . PHP_EOL;
$str = ' klinson ';
var_dump(trim($str), ltrim($str), rtrim($str));
echo 'strstr(\'klinson\', \'n\') => '.strstr($str, 'n') . PHP_EOL;
echo 'number_format(100000000000) => ' . number_format(100000000000) . PHP_EOL;

输出结果

array(3) {
  [0]=>
  string(3) "kli"
  [1]=>
  string(2) "so"
  [2]=>
  string(0) ""
}
string(7) "klinson"
string(7) "klinson"
strrev('klinson') => nosnilk
string(7) "klinson"
string(8) "klinson "
string(8) " klinson"
strstr('klinson', 'n') => nson 
number_format(100000000000) => 100,000,000,000

数组处理

  1. array_keys()
  2. array_values()
  3. array_diff()
  4. array_merge()
  5. array_shift()
  6. array_unshift()
  7. array_pop()
  8. array_push()
  9. sort(), rsort() 对数组升降序排序
  10. asort(),arsort() 对数组键值升降序排序,并保持索引关系
  11. ksort(),krsort() 对数组键名升降序排序

文件操作

fopen() 打开文件并指定模式

  1. r/r+ 只读打开/读写打开,指针在文件开头
  2. w/w+ 只写打开/读写打开,文件存在会清空,不存在会创建
  3. a/a+ 写入追加写入/读写的追加写入,指针在文件末尾
  4. x/x+ 写入/读写打开,指针开头,文件存在返回false,不存在就直接创建
  5. b 二进制打开

写入

  1. fwrite()
  2. fputs()

读取

  1. fread() 获取指定长度字符
  2. fgets() 获取一行字符
  3. fgetc() 获取一个字符

关闭 fopen()

文件大小 filesize()

文件复制 copy()

文件删除 unlink()

文件类型 filetype()

重命名或移动 rename()

文件属性

  1. file_exist()
  2. is_readable()
  3. is_writable()
  4. is_executable()
  5. filectime() 创建时间
  6. fileatime() 访问时间
  7. filemtime() 更新时间

其他不需要fopen()打开的函数

  1. file_get_contents()
  2. file_put_contents()

其他

  1. file()整个文件内容按行读取到一个数组里
  2. readfile()整个文件读取出来,并输出

远程访问

php.ini中打开allow_url_fopen配置,http协议只能使用只读,ftp协议,只能只读或只写

目录操作

名称相关

  1. basename() 文件基础名称
  2. dirname() 文件夹名称
  3. pathinfo() 文件信息数组

示例代码fileinfo.php

<?php
$filepath = __FILE__;
echo '__FILE__ => ' . $filepath . PHP_EOL;
echo 'basename(__FILE__) => ' . basename($filepath) . PHP_EOL;
echo 'dirname(__FILE__) => ' . dirname($filepath) . PHP_EOL;
echo  'pathinfo(__FILE__) => ' . PHP_EOL;
var_dump(pathinfo($filepath));

输出结果

__FILE__ => /home/wwwroot/default/public/index0.php
basename(__FILE__) => index0.php
dirname(__FILE__) => /home/wwwroot/default/public
pathinfo(__FILE__) => 
array(4) {
  ["dirname"]=>
  string(28) "/home/wwwroot/default/public"
  ["basename"]=>
  string(10) "index0.php"
  ["extension"]=>
  string(3) "php"
  ["filename"]=>
  string(6) "index0"
}

目录读取

  1. opendir()
  2. readdir()
  3. closedir()
  4. rewinddir() 重置句柄
  5. disk_free_space()
  6. disk_total_space()

目录删除 rmdir()

目录必须为空

目录创建 mkdir()

重命名或移动 rename()

设计模式

  1. 工厂模式

    工厂模式可以将对象的生产从直接new 一个对象,改成通过调用一个工厂方法生产。这样的封装,代码若需修改new的对象时,不需修改多处new语句,只需更改生产对象方法

    示例代码factory.php

    <?php
    /*** Interface people 人类
     */
    interface  people
    {
        public function  say();
    }
    
    /*** Class man 继承people的男人类
     */
    class man implements people
    {
        // 实现people的say方法
        function say()
        {
            echo '我是男人-hi' . PHP_EOL;
        }
    }
    
    /* Class women 继承people的女人类
     */
    class women implements people
    {
        // 实现people的say方法
        function say()
        {
            echo '我是女人-hi' . PHP_EOL;
        }
    }
    
    /* Interface createPeople 创建人物类
     */
    interface  createPeople
    {
        public function create();
    
    }
    
    /* Class FactoryMan 继承createPeople的工厂类-用于实例化男人类
     */
    class FactoryMan implements createPeople
    {
        // 创建男人对象(实例化男人类)
        public function create()
        {
            return new man();
        }
    }
    
    /* Class FactoryMan 继承createPeople的工厂类-用于实例化女人类
     */
    class FactoryWomen implements createPeople
    {
        // 创建女人对象(实例化女人类)
        function create()
        {
            return new women();
        }
    }
    
    class  Client
    {
        // 具体生产对象并执行对象方法测试
        public function test() {
            $factory = new FactoryMan();
            $man = $factory->create();
            $man->say();
    
            $factory = new FactoryWomen();
            $man = $factory->create();
            $man->say();
        }
    }
    
    // 执行
    $demo = new Client;
    $demo->test();

    输出结果

    我是男人-hi
    我是女人-hi

    参考PHP设计模式之工厂模式

  2. 单例模式

    用于为一个类生成一个唯一的对象

    示例代码singleton.php

    <?php
    class Db
    {
        protected static $instance;
    
        public static function getInstance($config = [])
        {
            if (is_null(self::$instance)) {
                self::$instance = new self($config);
            }
            return self::$instance;
        }
    
        public function __construct($config = [])
        {
            // TODO:: 数据库连接操作
    
            echo '这里已经完成数据库的连接!' . PHP_EOL;
        }
    
        public function getData($table)
        {
            // TODO:: 数据库读取数据操作
    
            echo "这里获取了表 [ {$table} ] 的数据了!" . PHP_EOL;
        }
    }
    
    Db::getInstance()->getData('klinson0');
    Db::getInstance()->getData('klinson1');
    Db::getInstance()->getData('klinson2');

    输出结果

    已经完成数据库的连接!
    这里获取了表 [ klinson0 ] 的数据了!
    这里获取了表 [ klinson1 ] 的数据了!
    这里获取了表 [ klinson2 ] 的数据了!
  3. 适配器模式

    适配器模式将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作

    示例代码adapter.php

    <?php
    /* 微博提供的登录类
 */
class Weibo {
    public function myLogin($username,$password){
        echo $username.' is logining weibo with password '. $password. PHP_EOL;
    }
}

/* 微信提供的登录类
 */
class Weixin {

    public function login2($config){
        echo $config['username'].' is logining weixin with password '. $config['password']. PHP_EOL;
    }
}

/* 我们设计的适配器接口
 */
interface Adapter {
    public function login($username, $password);
}

/* 微博的适配器
 */
class WeiboAdapter extends Weibo implements Adapter {

    public function login($username, $password) {
        $this->myLogin($username, $password);
    }
}

/* 微信的适配器类
 */
class WeixinAdapter extends WeiXin implements Adapter {

    public function login($username, $password) {
        $this->login2(['username'=>$username, 'password'=> $password]);
    }
}

/* 主业务类
 */

class User {
    private $adapter;

    public function setAdapter(Adapter $adapter) {
        $this->adapter = $adapter;
    }

    public function login($username, $password){
        $this->adapter->login($username, $password);
    }
}

$weiboUser = new User();
// 实例化微博适配器
$adapter = new WeiboAdapter();
// 设置适配器
$weiboUser->setAdapter($adapter);
$weiboUser->login('klinson', '123456');

$Wxuser = new User();
// 实例化微博适配器
$adapter = new WeixinAdapter();
// 设置适配器
$Wxuser->setAdapter($adapter);
$Wxuser->login('klinson', '123456');
```

输出结果
```
klinson is logining weibo with password 123456
klinson is logining weixin with password 123456
```

  1. 观察者模式

    观察者模式有时也被称作发布/订阅模式,该模式用于为对象实现发布/订阅功能:一旦主体对象状态发生改变,与之关联的观察者对象会收到通知,并进行相应操作。

    将一个系统分割成一个一些类相互协作的类有一个不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。观察者就是解决这类的耦合关系的。

    消息队列系统、事件都使用了观察者模式。

    PHP 为观察者模式定义了两个接口:SplSubject 和 SplObserver。SplSubject 可以看做主体对象的抽象,SplObserver 可以看做观察者对象的抽象,要实现观察者模式,只需让主体对象实现 SplSubject ,观察者对象实现 SplObserver,并实现相应方法即可。

    参考PHP 设计模式系列 —— 观察者模式(Observer)

  2. 策略模式

    定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)

    参考PHP设计模式(十二)—策略模式 ( Strategy Pattern ) PHP设计模式之策略模式

  3. 注册树模式
    注册模式(Registry)也叫做注册树模式,注册器模式。注册模式为应用中经常使用的对象创建一个中央存储器来存放这些对象 —— 通常通过一个只包含静态方法的抽象类来实现(或者通过单例模式)。参考laravel框架的app()定义

    示例代码register.php

    <?php
    class Register
    {
        // 树的枝干-用于储存树上的果实(实例)
        public static $objects;
    
        /** 将实例插入注册树中
         *
         ** @param $alias 对象别名-注册树中的名称
         ** @param $object 对象实例
         */
        public static function set($alias, $object)
        {
            self::$objects[$alias] = $object;
        }
    
        /**
         ** 从注册树中读取实例
         *
         ** @param $alias 对象别名-注册树中的名称
         *
         ** @return mixed 返回的对象实例
         */
        public static function get($alias)
        {
            if (isset(self::$objects[$alias])) {
                return self::$objects[$alias];
            } else {
                echo '您要找的对象实例不存在哦' . PHP_EOL;
                return false;
            }
    
        }
    
        /**
         ** 销毁注册树中的实例
         *
         ** @param $alias 对象别名-注册树中的名称
         */
        public static function unset($alias)
        {
            unset(self::$objects[$alias]);
        }
    }
    
    /**
     ** Class demo 演示类
     */
    class demo
    {
        /*
         ** 测试方法
         */
        public function test()
        {
            echo '看这里看这里' . PHP_EOL;
        }
    }
    
    // 实例化测试类,获取对象实例
    $demo = new demo();
    // 注册到树上
    $tree = Register::set('demo', $demo);
    // 取出来
    $de_true = Register::get('demo');
    // 测试
    $de_true->test();
    // 销毁
    Register::unset('demo');
    // 尝试再次取出来
    $de_true_two = Register::get('de');
    // 尝试再次测试
    var_dump($de_true_two);

    输出结果

    看这里看这里
    您要找的对象实例不存在哦
    bool(false)

    参考PHP设计模式之注册树模式

魔术方法

  1. __construct()
  2. __destruct()
  3. __call()
  4. __callStatic()
  5. __get()
  6. __set()
  7. __isset()
  8. __unset()
  9. __sleep()
  10. __wakeup()
  11. __toString()
  12. __close()

加密

对称加密

对称加密又叫做私钥加密,即信息的发送方和接收方使用同一个密钥去加密和解密数据

对称加密的特点是算法公开、加密和解密速度快,适合于对大数据量进行加密

常见的对称加密算法有DES、3DES、TDEA、Blowfish、RC5和IDEA。

其加密过程如下:明文 + 加密算法 + 私钥 => 密文

解密过程如下:密文 + 解密算法 + 私钥 => 明文

对称加密中用到的密钥叫做私钥,私钥表示个人私有的密钥,即该密钥不能被泄露。

其加密过程中的私钥与解密过程中用到的私钥是同一个密钥,这也是称加密之所以称之为“对称”的原因。由于对称加密的算法是公开的,所以一旦私钥被泄露,那么密文就很容易被破解,所以对称加密的缺点是密钥安全管理困难。

非对称加密

非对称加密也叫做公钥加密。

非对称加密与对称加密相比,其安全性更好。对称加密的通信双方使用相同的密钥,如果一方的密钥遭泄露,那么整个通信就会被破解。而非对称加密使用一对密钥,即公钥和私钥,且二者成对出现。私钥被自己保存,不能对外泄露。

公钥指的是公共的密钥,任何人都可以获得该密钥。用公钥或私钥中的任何一个进行加密,用另一个进行解密。

被公钥加密过的密文只能被私钥解密,过程如下:

明文 + 加密算法 + 公钥 => 密文, 密文 + 解密算法 + 私钥 => 明文

被私钥加密过的密文只能被公钥解密,过程如下:(私钥加密,公钥解密一般被用于数字签名。数字签名是用于防篡改和防止假冒的,因为只有一人拥有私钥。甲方通过私钥对数据进行签名,乙方通过甲方的公钥验证签名,如果成功,说明确实是甲方发来的,并且数据没有被修改。一旦相反,公钥是公开的,大家都能做签名,就没意义了)

明文 + 加密算法 + 私钥 => 密文, 密文 + 解密算法 + 公钥 => 明文

由于加密和解密使用了两个不同的密钥,这就是非对称加密“非对称”的原因。

非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据进行加密。

在非对称加密中使用的主要算法有:RSA、Elgamal、Rabin、D-H、ECC(椭圆曲线加密算法)等。

hash算法(非加密算法)

  1. md5
  2. sha1

数据编码方式

  1. base64
  2. 二进制

网络协议

http状态码

  1. 200 请求成功
  2. 204 not content
  3. 206 reset content
  4. 301 永久重定向
  5. 302 临时重定向
  6. 307 临时重定向
  7. 400 错误请求
  8. 401 缺少认证信息
  9. 403 拒绝
  10. 404 不存在
  11. 500 服务器异常
  12. 502 Bad Gateway
  13. 503 服务器超负载或停机维护

OSI七层协议

  1. 物理层 建立、维护、断开物理连接
  2. 数据链路层 建立逻辑连接,进行硬件地址寻址,差错校验等功能
  3. 网络层 进行逻辑地址寻址,实现不同网络之间的路径选择
  4. 传输层 定义传输数据的协议端口号,一级流控和差错校验。协议有TCP/UDP,数据包一旦离开网卡即进入网络传输层
  5. 会话层 建立、管理、终止会话
  6. 表示层 数据的表示、安全、压缩
  7. 应用层 网络服务与最终用户的一个借口,协议有:http(80),ftp(21),tftp,smtp(25),snmp,dns(53),telnet(23),https(443),pop3(110),dhcp

参考HTTP、TCP、UDP、Socket解读

HTTP协议的工作特点和工作原理

工作特点:

  1. 基于B/S模式
  2. 通信开销小,简单快速,传输成本低
  3. 使用灵活,可使用超文本传输协议
  4. 节省传输时间
  5. 无状态

工作原理:

客户端发送请求给服务器,建立一个TCP连接,指定端口号,默认80,连接到服务器,服务器监听到浏览器的请求,一旦监听到客户端的请求,分析请求类型后,服务器会向客户端发送状态信息和数据内容

HTTP协议常见请求头/响应头

  1. Content-Type 指定数据内容类型
  2. Accept 指定客户端能接受数据内容类型
  3. Origin 最初请求来源(POST)
  4. Cookie
  5. Cache-Control 指定请求的缓存机制
  6. User-Agent 用户浏览器信息
  7. Referrer 上级请求路径
  8. X-Forwarded-For 请求端真实ip
  9. Access-Control-Allow-Origin 允许其他请求域名,用于跨域
  10. Last-Modified 最后响应时间

HTTPS的工作原理

HTTPS是一种基于SSL/TLS的HTTP协议,所有的HTTP数据都是SSL/TLS协议封装之上传输的

HTTPS是在HTTP协议的基础上,添加了SSL/TLS握手一级数据加密传输,也属于应用层协议

HTTPS为了兼顾安全与效率,同时使用了对称加密(快)和非对称加密(慢)。数据是被对称加密传输的,对称加密过程需要客户端的一个密钥,为了确保能把该密钥安全传输到服务器端,采用非对称加密对该密钥进行加密传输,总的来说,对数据进行对称加密,对称加密所要使用的密钥通过非对称加密传输。
完整流程
以下图片来源 图解HTTPS

Image text

HTTPS在传输的过程中会涉及到三个密钥:

服务器端的公钥和私钥,用来进行非对称加密

客户端生成的随机密钥,用来进行对称加密

一个HTTPS请求实际上包含了两次HTTP传输,可以细分为8步。

  1. 客户端向服务器发起HTTPS请求,连接到服务器的443端口
  2. 服务器端有一个密钥对,即公钥和私钥,是用来进行非对称加密使用的,服务器端保存着私钥,不能将其泄露,公钥可以发送给任何人。
  3. 服务器将自己的公钥发送给客户端。
  4. 客户端收到服务器端的公钥之后,会对公钥进行检查,验证其合法性,如果发现发现公钥有问题,那么HTTPS传输就无法继续。严格的说,这里应该是验证服务器发送的数字证书的合法性,关于客户端如何验证数字证书的合法性,下文会进行说明。如果公钥合格,那么客户端会生成一个随机值,这个随机值就是用于进行对称加密的密钥,我们将该密钥称之为client key,即客户端密钥,这样在概念上和服务器端的密钥容易进行区分。然后用服务器的公钥对客户端密钥进行非对称加密,这样客户端密钥就变成密文了,至此,HTTPS中的第一次HTTP请求结束。
  5. 客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端密钥发送给服务器。
  6. 服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。
  7. 然后服务器将加密后的密文发送给客户端。

8.客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成。

mysql相关知识

数据表引擎

  1. innodb引擎

默认事务型引擎,最重要最广泛的存储引擎,性能非常优秀

数据存储在共享表空间,可通过配置分开

对主键查询的性能高于其他类型的存储引擎

内部做了很多优化,从磁盘读取数据时自动在内存构建hash索引,插入数据时自动构建插入缓冲区

通过一些机制和工具支持真正的热备份

支持崩溃后的安全恢复

支持行级锁

支持外键

  1. MyISAM引擎

5.1版本前是默认引擎

拥有全文索引、压缩、空间函数

不支持事务和行级锁,不支持奔溃后安全恢复

表存储在两个文件,MYD和MYI

设计简单,某些场景下性能很好

  1. 其他引擎:

Archive、Blackhole、CSV、Memory

MySQL锁机制

当多个查询同一时刻进行数据修改时,会产生并发控制的问题

  1. 共享锁(读锁)
  2. 排他锁(写锁)

锁粒度

  1. 表锁

系统性能开销最小,会锁定整张表,myisam使用表锁

  1. 行锁

最大程度的支持并发处理,但是也带来了最大的锁开销,innodb实现行级锁

char与varchar

  1. char
char是定长的,根据定义的字符串长度分配足量空间
char会根据需要采用空格进行填充以方便比较
char适合存储很短的字符串,或者所有值都接近同一个长度
char长度超过设定的长度,会被截断
  1. varchar
varchar用于存储可变长字符串,比定长类型更加节省空间
varchar使用1或2个额外字节记录字符串的长度,列长度小于255字节用1个字节表示,否则用2个
varchar长度超过设定的长度,会被截断
  1. 比较
对于经常变更的数据,char比varchar更好,char不容易产生碎片
对于非常短的列,char比varchar在存储空间上更有效率
只分配真正需要的空间,更长的列会消耗更多的内存

索引

  1. 大大减少服务器需要扫描的数据量
  2. 帮助服务器避免排序和临时表
  3. 将随机I/O变顺序I/O
  4. 大大提高查询速度,降低写的速度,占用磁盘空间
索引类型
  1. 普通索引
  2. 主键索引
  3. 唯一索引
  4. 组合索引
  5. 外键索引
  6. 全文索引

索引创建原则

  1. 最适合索引的列是出现在where子句的列,或连接子句中的列,而不是出现在select的关键字后的列
  2. 索引列的基数越大,索引效果越好
  3. 对字符串进行索引,应指定一个前缀长度,可以节省大量的索引空间
  4. 根据情况创建复合索引,复合索引可以提高查询效率
  5. 避免创建过多索引,索引会额外占用磁盘空间,减低写操作效率
  6. 主键尽可能选择较短的数据类型,可以有效减少索引的磁盘占用,提高效率

索引的注意事项

  1. 复合索引遵循前缀原则
  2. like查询,%不能在前,可以使用全文索引
  3. column is null 可以使用索引
  4. 如果MySQL估计使用索引比全表扫描更慢,会放弃使用索引

mysql优化

查询速度慢的原因

  1. 打开慢查询日志,通过pt-query-dugest分析
  2. show profile,通过set profiling=1;开启,服务器上执行的所有语句消耗时间都会记录到临时表。show profile for query QUERY_ID查询指定查询
  3. show status,查询一些计数器,猜出哪些代价高或消耗时间多
  4. show processlist,查询线程状态进行分析
  5. explain,分析单个SQL语句查询

优化查询过程中的数据访问

  1. 访问数据太多导致性能下降
  2. 确定应用程序是否检索大量超过需要的数据,可能是太多列或者行
  3. 确定mysql是否分析大量不必要的数据行
  4. 查询不需要的记录,使用limit限制
  5. 夺标关联返回全部列指定A.id,A.name
  6. 总数取出全部列,select * 会让优化器无法完成所有覆盖扫码的优化
  7. 重复查询相同的数据,可以缓存数据
  8. 改变数据库和表的结构,修改数据表范式
  9. 重写SQL语句,让优化器可以更优的执行

优化长难得查询语句

  1. MySQL内部每秒能扫描内存中上百万行数据,相比之下,响应数据给客户端就要慢得多
  2. 使用尽可能少的查询是好的,但是有时将一个大的查询分解为多个小的查询是很有必要的
  3. 分解关联查询,将一个关联查询分解为多个sql来执行,让缓存效率更高,执行单个查询可以减少锁的竞争,在应用层做关联可以更容易对数据库进行拆分,查询效率会有大幅提升,较少冗余记录的查询

优化特定类型的查询语句

  1. 优化count()查询,count(*)会忽略所有列,直接统计所有列数,因此不要用count(列名)
  2. 优化关联查询,确定ON或者USING子句的列上有索引;确保GROUP BYORDER BY中只有一个表的列,这样MySQL才有可能使用索引
  3. 优化子查询 建议使用关联查询替代
  4. 优化GROUP BYDISTINCT,建立索引进行优化
  5. 优化LIMIT分页,可以通过记录上次查询的最大ID,如果根据id排序时,下次查询根据该ID来查询(如:ID > maxID)
  6. 优化UNION查询,UNION ALL性能比UNION

MySQL提升(高可扩展和高可用)

分区表

工作原理

对用户而言,分区表是一个独立的逻辑表,但是底层MySQL将其分成了多个物理子表,对于用户来说是透明的,每一个分区表都会使用一个独立的表文件。

创建表的时候使用partition by子句定义每个分区存放的数据,执行查询时,优化器会根据分区定义过滤那些没有我们需要数据的分区,这样查询只需要查询所需数据在的分区即可

分区的主要目的是将数据按照一个较粗的粒度分在不同的表中,这样可以将相关的数据存放在一起,而且如果想一次性删除整个分区的数据也很方便

适用场景
  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. 水平切分:表很大,分割后可以减低在查询时需要读的数据和索引的页数,同时也减低了索引的层数,提高查询速度
使用场景:
1. 表中数据本身就有独立性,例如表中分别记录各个地区的数据或者不同时期的数据,特别是有些数据常用,有些不常用
2. 需要把数据存放在多个介质

缺点:
1. 给应用增加复杂度,通常查询时需要多个表名,查询所有数据都需要UNION操作
2. 在许多数据库应用中,这种复杂性会超过他带来的优点,查询时会增加读一个索引层的磁盘次数
  1. 垂直分表:把主键和一些列放在一个表,然后把主键和另外的列放在另一张表中
使用场景:
1. 如果一个表中某些列常用,而另外一些列不常用
2. 可以使数据行变小,一个数据页能存储更多数据,查询时减少I/O次数

缺点:
1. 管理冗余列,查询所有数据需要JOIN操作
2. 有些分表的策略基于应用层的逻辑算法,一旦逻辑算法改变,整个分表逻辑都会改变,扩展性较差
3. 对于应用层来说,逻辑算法无疑增加开发成本

主从复制

工作原理
  1. 在主库上把数据更改记录到二进制日志
  2. 从库将主库的日志复制到自己的中继日志
  3. 从库读取中继日志中的事件,将其重放到从库数据中
解决问题
  1. 数据分布:随意停止或开始复制,并在不同地理位置分布数据备份
  2. 负载均衡:减低单个服务器压力
  3. 高可用和故障切换:帮助应用程序避免单点失败
  4. 升级测试:可以使用更高版本的MySQL作为从库

MySQL安全

安全操作

  1. 使用预处理语句防SQL这几日
  2. 写入数据库的数据要进行特殊字符转移
  3. 查询错误信息不要返回给用户,将错误记录到日志

安全设置

  1. 定期做数据备份
  2. 不给查询用户root权限,合理分配权限
  3. 关闭远程访问数据库权限
  4. 修改root口令,不用默认口令,使用较复杂的口令
  5. 删除多余的用户
  6. 改变root用户的名称
  7. 限制一般用户浏览其他库
  8. 限制用户对数据文件的访问权限

MVC

MVC工作原理

  1. model 数据模型操作层,是应用程序中用于处理应用程序数据逻辑的部分
  2. view 视图层,是应用程序中处理数据显示的部分。
  3. controller 业务处理层,是应用程序中处理用户交互的部分。

单一入口

工作原理

用一个处理程序文件处理所有的HTTP请求,根据请求时的参数的不同区分不同的模块和操作请求

优势

  1. 可以进行统一的安全性检查
  2. 集中处理程序

劣势

  1. URL不美观(解决方法:URL重写)
  2. 处理效率会降低(可忽略)

模板引擎

PHP是一种HTML内嵌式在服务端执行的脚本语言,但是PHP又很多可以使PHP代码和HTML代码分开的模板引擎,例如:smarty

工作原理

模板引擎就是庞大的完善的正则表达式替换库

算法

排序算法

示例代码sort.php

冒泡排序

原理:两两相邻的数进行比较,如果反序就交换,否则不交换

时间复杂度:最坏(O(n^2)), 平均(O(n^2))

空间复杂度:O(1)

function bubbling_sort($arr)
{
    $len = count($arr);
    if ($len <= 1) return $arr;

    for ($i = 0; $i < $len - 1; $i++) {
        for ($j = 0; $j < $len - $i -1; $j++) {
            if ($arr[$j] > $arr[$j+1]) {
                $tmp = $arr[$j+1];
                $arr[$j+1] = $arr[$j];
                $arr[$j] = $tmp;
            }
        }
    }

    return $arr;
}

快速排序

原理:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据和另一部分的所有数据都要笑,然后按照此方法对这两部分数据分别进行快速排序,整个排序过程可以递归完成

时间复杂度:最坏(O(n^2)), 平均(O(nlog2n))

空间复杂度:最差(O(n)),平均(O(log2n))

function quick_sort($arr)
{
    $len = count($arr);
    if ($len <= 1) return $arr;

    $base = $arr[0];
    $left = [];
    $right = [];

    for ($i = 1; $i < $len; $i++) {
        if ($arr[$i] > $base) $right[] = $arr[$i];
        else $left[] = $arr[$i];
    }

    $left = quick_sort($left);
    $right = quick_sort($right);

    return array_merge($left, [$base], $right);
}

选择排序

原理:每次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,知道全部待排序的数据元素排外

时间复杂度:最坏(O(n^2)), 平均(O(n^2))

空间复杂度:O(1)

function select_sort($arr)
{
    $len = count($arr);
    if ($len <= 1) return $arr;

    for ($i = 0; $i < $len - 1; $i++) {
        $min = $i;

        for ($j = $i+1; $j < $len; $j ++) {
            if ($arr[$min] > $arr[$j]) {
                $min = $j;
            }
        }

        if ($min !== $i) {
            $tmp = $arr[$i];
            $arr[$i] = $arr[$min];
            $arr[$min] = $tmp;
        }
    }

    return $arr;
}

直接插入排序

原理:每次从无序表中取出第一个元素,把他插入到有序表的合适位置,使有序表仍然有序

时间复杂度:最坏(O(n^2)), 平均(O(n^2))

空间复杂度:O(1)

希尔排序

原理:把待排序的数据根据增量分成几个子序列,对子序列进行插入排序,知道增量为1,直接插入进行插入排序;增量的排序,一般是数组的长度的一半,再变为原来增量的一半,直到增量为1

时间复杂度:最坏(O(n^2)), 平均(O(nlog2n))

空间复杂度:O(1)

堆排序

原理:把待排序的元素按照大小在二叉树位置上排序,排序好的元素要满足:父节点的元素要大于子节点;这个过程叫做堆化过程,如果根节点存放的最大的数,则叫做大根堆,如果是最小,就叫小跟堆,可以把根节点拿出来,然后再堆化,循环到最后一个节点

时间复杂度:最坏(O(nlog2n)), 平均(O(nlog2n))

空间复杂度:O(1)

归并排序

原理:将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个有序的子序列,再把有序的子序列合并为整体有序序列

时间复杂度:最坏(O(nlog2n)), 平均(O(nlog2n))

空间复杂度:O(n)

查找算法

二分查找

原理:从数组的中间元素开始,如果中间元素正好是要查找的元素,搜索结果,如果某一个特定元素大于或者小于中间元素的那一半中查找,而且跟开始一样从中间开始比较,如果某一步骤数组为空,代表找不到

时间复杂度:最坏(O(nlog2n)), 平均(O(nlog2n))

空间复杂度:迭代(O(1)), 递归(O(log2n))

顺序查找

原理:按一定的顺序检查数组中每一个元素,直到要找到锁要寻找的特定指为止

时间复杂度:最坏(O(n)), 平均(O(n))

空间复杂度:O(1)

b-tree

B-Tree是为磁盘等外存储设备设计的一种平衡查找树

一棵m阶的B-Tree有如下特性:

  1. 每个节点最多有m个孩子。
  2. 除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。
  3. 若根节点不是叶子节点,则至少有2个孩子
  4. 所有叶子节点都在同一层,且不包含其它关键字信息
  5. 每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn)
  6. 关键字的个数n满足:ceil(m/2)-1 <= n <= m-1
  7. ki(i=1,…n)为关键字,且关键字升序排序。
  8. Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1)

image

b+tree

B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。

B+Tree相对于B-Tree有几点不同:

  1. 非叶子节点只存储键值信息。
  2. 所有叶子节点之间都有一个链指针。
  3. 数据记录都存放在叶子节点中。
  4. 将B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息

image

优化

高并发和大流量解决方案

高并发的问题,应关注

  1. QPS:每秒钟请求或查询数量,在互联网领域指每秒响应的请求数(指HTTP请求)
  2. 吞吐量:单位时间内处理的请求数量(通常由QPS和并发数决定)
  3. 响应时间:从请求发出到收到响应花费时间
  4. PV:综合浏览量(Page View),即页面浏览量或者点击量,一个访客在24小时内访问的页面数量。同一个人浏览你的网站同一个页面,只记作一次PV
  5. UV:独立访客(UniQue Visitor),即一定时间范围内相同访客多次访问网站,只能计算为1个独立访客
  6. 带宽:计算带宽大小需关注两个指标,峰值流量和页面的平均大小
  7. 日网站带宽=PV/统计时间(秒)平均页面大小(KB)8
  8. 峰值一般是平均值的倍数
  9. QPS不等于并发并发连接数。QPS是每秒HTTP请求数量,并发连接数是系统同时处理的请求数量
  10. 二八定律(80%的访问量集中在20%的时间):(总PV数80%)/(6小时秒速20%)=峰值每秒请求数(QPS)
  11. 压力测试:能承受最大的并发数和最大承受的QPS值

常用性能测试工具

ab,wrk,Apache JMeter, http_load, Web Bench, Siege

ab
使用方法:
# 模拟并发请求100次,总请求5000次
ab -c 100 -n 5000 http://example.com
注意事项:
  1. 测试机器与被测机器分开
  2. 不要对线上服务做压力测试
  3. 观察测试工具所在机器,以及被测试的前端机的CPU、内存、网络等都不超过最高限度的75%

QPS指标

  1. QPS达到50,可以称之为小型网站,一般服务器都可以应付
  2. QPS达到100;瓶颈:MySQL查询达到瓶颈;优化方案:数据库缓存层,数据库负载均衡
  3. QPS达到800;瓶颈:带宽速度达到瓶颈;优化方案:CDN加速,负载均衡
  4. QPS达到1000;瓶颈:缓存服务器的带宽达到瓶颈;优化方案:静态HTML缓存
  5. QPS达到2000;瓶颈:文件系统访问锁成为灾难;优化方案:做业务分离,分布式存储

高并发优化方案

流量优化
  1. 防盗链处理
前端优化
  1. 减少HTTP请求
  2. 添加异步请求
  3. 启用浏览器缓存和文件压缩
  4. CDN加速
  5. 建立独立的图片服务器
服务端优化
  1. 页面静态化
  2. 并发处理
数据库优化
  1. 数据库缓存
  2. 分库分表、分区操作
  3. 读写分离
  4. 负载均衡
web服务器优化
  1. 负载均衡

web资源防盗链

盗链定义

  1. 倒链是指在自己的页面上展示一些并不在服务器上的内容
  2. 获得他人服务器上的资源地址,绕过别人的资源展示页面,直接在自己的页面上向最终用户提供此内容
  3. 常见的是小站盗用大站的图片、音乐、视频、软件等资源
  4. 倒链可以减轻自己的服务器负担

防盗链定义

防止别人通过一些技术手段绕过本站的资源展示页面,盗用本站的资源,让绕开本站资源展示页面的资源链接失效,可以大大减轻服务器及带宽的压力

防盗链的工作原理

  1. 通过Referer或者计算签名,网站可以检测目标网页访问的来源网页,如果是资源文件,则可以跟踪到显示他的网页地址
  2. 一旦检测到来源不是本站即进行阻止或返回指定的页面

防盗链实现方法

Referer
  1. NGINX模块ngx_http_referer_module用来阻挡来源非法的域名请求
  2. NGINX指令valid_referers,全局变量$invalid_referer
配置:
valid_referers none|blocked|server_names|string...;
  1. none: Referer来源头部为空的情况,比如直接打开
  2. blocked: Referer来源头部不为空,但是里面的值被代理或者防火墙删除了,这些值都不以http://或者https://开头
  3. server_names: Referer来源头部包含当前的server_names

配置例子:

location ~.*\.(gif|jpg|png|flv|swf|rar}zip)$
{
    valid_referers none blocked imooc.com *.imooc.com;
    if ($invalid_referer)
    {
        #return 403;
        rewrite ^/ http://www.imooc.com/403.jpg;
    }
}

减少HTTP请求

HTTP连接产生的开销

  1. 域名解析
  2. TCP连接
  3. 发送请求
  4. 等待
  5. 下载资源
  6. 解析

解决方案

  1. 减少组件的数量,并由此减少HTTP请求的数量
  2. 图片地图:图片地图允许你在一个图片上关联多个URL。目标URL的选择取决于用户蛋鸡了图片上的哪个位置
  3. CSS Sprites:css 精灵,通过使用合并图片,通过指定css的background-image和background-position来显示元素
  4. 合并脚本和样式表适
  5. 图片使用base64编码减少页面请求数

浏览器缓存和数据压缩

HTTP缓存机制分类

  1. 200 from cache:直接从本地缓存中获取响应,最快速,最省流量,因为根本没有向服务器发送请求
  2. 304 Not Modified:协商缓存,浏览器在本地没有命中的情况下,请求头中发送一定的校验数据到服务端,如果服务端的数据没有改变,浏览器从本地缓存响应,返回304。特点:快速,发送的数据很少,只返回一些基本的响应头信息,数据量很小,不发送实际响应体
  3. 200 OK:以上两种缓存全部失败,服务器返回完整响应。没有用到缓存,相对最慢
header设置HTTP缓存机制
  1. pragma:HTTP1.0时代的遗留产物,该字段被设置为no-cache时,会告知浏览器禁用本地缓存,即每次都向服务器发送请求
  2. Expires:HTTP1.0时代用来启用本地缓存的字段,设置值如‘Thu, 31 Dec 2037 23:55:55 GMT’的格林威治的时间。但浏览器与服务器的时间无法保持一致,如果差距大就会影响缓存结果
  3. Cache-Control:HTTP1.1针对Expires时间不一致的解决方案,运用Cache-Control告知浏览器缓存过期的时间间隔而不是时刻,即使具体时间不一致,也不影响缓存的管理

优先级:Pragma > Cache-Control > Expires

Cache-Control配置
  1. no-store:禁止浏览器缓存响应
  2. no-cache:不允许直接使用本地缓存,先发起请求和服务器协商
  3. max-age=delta-seconds:告知浏览器该响应本缓存的有效的最长期限,以秒为单位

协商缓存

  1. 当浏览器没有命中本地缓存,如本地缓存过期或者响应中声名不允许直接使用本地缓存,那么浏览器肯定会发起服务端请求
  2. 服务端会验证数据是否修改,如果没有就通知浏览器使用本地缓存
header设置协商缓存
  1. Last-Modified:通知浏览器资源的最后修改时间,设置值如‘Thu, 31 Dec 2037 23:55:55 GMT’的格林威治的时间
  2. If-Modified-Since:得到资源的最后修改时间后,会将这个信息通过If-Modified-Since提交到服务器做检查,如果没有修改,返回304状态码,设置值如‘Thu, 31 Dec 2037 23:55:55 GMT’的格林威治的时间
  3. ETag:HTTP1.1推出,文件的指纹标识符,如果文件内容修改,指纹也会改变,设置值如‘5a643fc7-38a3’
  4. If-None-Match:本地缓存失效,会携带此值去请求服务端,服务端判断该资源是否改变,如果没有改变,直接使用本地缓存,返回304

缓存策略的选择

适合缓存的内容
  1. 不变的图像,如logo,图标等
  2. js、css静态文件
  3. 可下载的内容,媒体文件
适合协商缓存
  1. HTML文件
  2. 经常替换的图片
  3. 经常修改的js、css文件,js、css文件的加载可以加入文件的签名来拒绝缓存,如‘index.css?签名’,‘index.签名.js’
不建议缓存的内容
  1. 用户隐私等敏感数据
  2. 经常改变的API数据接口

NGINX配置缓存策略

本地缓存配置

  1. add_header指令:添加状态码为2XX和3XX的响应头信息,设置代码add_header name value [always];,可以设置Pragma、Expires、Cache-Control,可以继承
  2. expires指令:通知浏览器过期时长,设置代码expires time;
  3. Etag指令:指定签名,设置代码etag on|off,默认on

前端代码和资源压缩

优势

  1. 让资源文件更小,加快文件在网络中的传输,让网页更快的展现,降低带宽和流量的开销

压缩方式

  1. js、css、图片、html代码的压缩
  2. gzip压缩
gzip配置
gzip on|off; #是否开启gzip
gzip_buffers 32 4K|16 8K; #缓冲(在内存中缓存几块?每块多大)
gzip_comp_level [1-9] #推荐6,压缩级别(级别越高,压得越小,越浪费CPU计算资源)
gzip_disable #正则匹配UA,什么样的Uri不进行gzip
gzip_min_length 200 #开始压缩的最小长度
gzip_http_version 1.0|1.1 #开始压缩的http协议版本
gzip_proxied #设置请求者代理服务器,该如何缓存内容
gzip_types text/plain application/xml image/png #对哪些类型的文件压缩,如txt、xml、css
gzip_vary on|off #是否传输gzip压缩标志

CDN加速

定义

  1. CDN的全称content delivery network,内容分发网络
  2. 尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定
  3. 在网络各处放置节点服务器所构成的有的互联网基础之上的一层智能虚拟网络
  4. CDN系统能够实现地根据网络流量和各节点的连接、负载状况以及到用户距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上

优势

  1. 本地cache加速,提高了企业站点(尤其含有大量图片和静态页面站点)的访问速度
  2. 跨运营商的网络加速,保证不同网络的用户都能得到良好的访问质量
  3. 远程访问用户根据DNS负载均衡技术只能选择cache服务器
  4. 自动生成服务器的远程Mirror(镜像)cache服务器,远程用户访问时从cache服务器上读取数据,减少远程访问的带宽、分担网络流量、减轻原站点web服务器负载等功能
  5. 广泛分布的cdn节点加上节点之间的智能冗余机制,可以有效地预防黑客入侵

工作原理

  1. 用户发起请求
  2. 智能DNS的解析(根据IP判断地理位置、接入网类型、选择路由最短和负载最轻的服务器)
  3. 取得缓存服务器ip
  4. 把内容返回给用户(如果缓存中有,没有就执行5、6、7)
  5. 向源站发起请求
  6. 将结果返回给用户
  7. 将结果存入缓存服务器

适用场景

  1. 站点或者应用中大量静态资源的加速分发,例如css、js、图片和HTML
  2. 大文件下载
  3. 直播网站

独立图片服务器

必要性

  1. 分担web服务器的I/O负载,将耗费资源的图片服务器分离出来,提高服务器的性能和稳定性
  2. 能够专门对图片服务器进行优化,为图片服务器设置针对性的缓存方案,减少带宽成本,提高访问速度
  3. 提高网站的可扩展性,通过增加图片服务器,提高图片吞吐能力

采用独立域名

原因:
  1. 同一域名下浏览器的并发连接数有限制,突破浏览器连接数的限制
  2. 由于cookie的原因,对缓存不利,大部分web cache都只缓存不带cookie的请求,导致每次的图片请求都不能命中cache

如何图片上传和同步

  1. NFS共享方式
  2. 利用FTP同步

动态语言静态化

将现有的PHP等动态语言的逻辑代码生成为静态的HTML文件,用户访问动态脚本重定向到静态HTML文件的过程。对实时性要求不高

原因:

  1. 动态脚本通过会做逻辑计算和数据查询,访问量越大,服务器压力越大
  2. 访问量大时可能会造成CPU负载过高,数据库服务器压力过大
  3. 静态化可以减低逻辑处理压力,降低数据库服务器查询压力

实现方法

  1. 使用模板引擎
  2. 利用ob系列函数
ob_start();//打开输出控制缓冲
ob_get_content();//返回输出缓冲区内容
ob_clean();//清空输出缓冲区
ob_end_flush();//冲刷出(送出)输出缓冲区内容并关闭缓冲

并发处理

进程(Process)

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

进程的三态模型:运行、就绪、阻塞

进程的五态模型:新建态、活跃就绪/静止就绪、运行、活跃阻塞/静止阻塞、终止态

  1. 新建态:对应于进程刚刚被创建时没有被提交的状态,并等待系统完成创建进程的所有必要信息
  2. 终止态:进程已结束运行,回收除进程控制块之外的其他资源,并让其他进程从进程控制块中收集有关信息。
  3. 活跃就绪:是指进程在主存并且可被调度的状态。
  4. 静止就绪(挂起就绪):是指进程被对换到辅存时的就绪状态,是不能被直接调度的状态,只有当主存中没有活跃就绪态进程,或者是挂起就绪态进程具有更高的优先级,系统将把挂起就绪态进程调回主存并转换为活跃就绪。
  5. 活跃阻塞:是指进程已在主存,一旦等待的事件产生便进入活跃就绪状态。
  6. 静止阻塞:进程对换到辅存时的阻塞状态,一旦等待的事件产生便进入静止就绪状态。

线程

线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。

在单个程序中同时运行多个线程完成不同的工作,称为多线程。

协程

协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

区别

线程与进程
  1. 线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间
  2. 进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
  3. 线程是处理器调度的基本单位但进程不是
  4. 二者均可并发执行
  5. 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
协程和线程
  1. 一个线程可以多个协程,一个进程也可以单独拥有多个协程
  2. 线程进程都是同步机制,而协程则是异步
  3. 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态

例子

  1. 单进程单线程:一个人在一个桌子上吃菜
  2. 单进程多线程:多个人在同一个桌子上一起吃菜
  3. 多进程单线程:多个人每个人在自己的桌子上吃菜

同步阻塞

多进程模式
  1. 创建一个 socket
  2. 进入 while循环,阻塞在进程accept操作上,等待客户端连接进入主进程在多进程模型下通过fork刨建子进程
  3. 收到数据后服务器程序进行处理然后使用send向客户端发送响应
  4. 当客户端连接关闭时,子进程/线程退出并销毁所有资源。主进程/线程会回收掉此子进程/线程。
多线程模式
  1. 多线程模型下可以创建子线程
  2. 子进程/线程创建成功后进入while循环,阻塞在recv调用上,等待客户端向服务器发送数据
  3. 收到数据后服务器程序进行处理然后使用send向客户端发送响应
  4. 当客户端连接关闭时,子进程/线程退出并销毁所有资源。主进程/线程会回收掉此子进程/线程。
缺点
  1. 这种模型严重依赖进程的数量解决并发问题
  2. 启动大量进程会带来额外的进程调度消耗

异步非阻塞

现在各种高并发异步IO的服务器程序都是基于epoll实现的

IO复用异步非阻塞程序使用经典的Reactor模型,Reactor顾名思义就是反应堆的意思,它本身不处理任何数据收发。只是可以监视一个 socket句柄的事件变化

reactor模型
  • Add:添加一个 SOCKET到 Reactor
  • Set:修改 SOCKET对应的事件,如可读可写
  • Del:从 Reactor中移除
  • Callback:事件发生后回调指定的函数

常见reactor模型

  1. Nginx:多线程 Reactor
  2. Swoole:多线程 Reactor+多进程Worker

数据库缓存

mysql查询缓存

查询缓存可以看做是SQL文本和查询结果的映射,第二次查询的SQL和第一次查询的SQL全相同,则会使用缓存

表的结构或数据发生改变时,查询缓存中的数据不再有效

配置:
query_cache_type

查询缓存类型,有0、1、2三个取值。0则不使用查询缓存。1表示始终使用查询缓存。2表示按需使用查询缓存。

query_cache_size

默认情况下 query_cache_size为0,表示为查询缓存预留的内存为0,则无法使用查询缓存

SET GLOBAL query_cache_size= 134217728
使用

querycache_type为1时,亦可关闭查询缓存

SELECT SQL_NO_CACHE FROM my_table WHERE condition;

query_cache_type为2时,可按需使用查询缓存

SELECT SQL_CACHE FROM my_table WHERE condition;
其他

查看命中次数

SHOW STATUS LIKE'Qcache_hits';

清理缓存

FLUSH QUERY CACHE∥清理查询缓存内存碎片
RESET QUERY CACHE∥从查询缓存中移出所有查询
FLUSH TABLES;//关闭所有打开的表,同时该操作将会清空查询
缓存中的内容

redis/memcache 缓存

  • Redis,依赖客户端来实现分布式读写
  • Memcache本身没有数据冗余机制
  • Redis支持(RDB快照、AOF),依赖快照进行持久化,aof增强了可靠性的同时,对性能有所影响
  • Memcache不支持持久化,通常做缓存,提升性能;
  • Memcache在并发场景下,用cas保证一致性, redis事务支持比较弱,只能保证事务中的每个操作连续执行
  • Redis支持多种类的数据类型
  • Redis用于数据量较小的高性能操作和运算上
  • Memcache用于在动态系统中减少数据库负载,提升性能;适合做缓存,提高性能
区别
  1. 数据结构:Memcache只支持key value存储方式,Redis支持更多的数据类型,比如Key value、hash、list、set、zset;
  2. 多线程:Memcache支持多线程,Redis支持单线程;CPU利用方面Memcache优于Redis;
  3. 持久化:Memcache不支持持久化,Redis支持持久化;
  4. 内存利用率:Memcache高,Redis低(采用压缩的情况下比Memcache高);
  5. 过期策略:Memcache过期后,不删除缓存,会导致下次取数据数据的问题,Redis有专门线程,清除缓存数据;
缓存可能问题
  1. 缓存穿透 : DB 承受了没有必要的查询流量,意思就是查到空值的时候没有做缓存处理,再次查询的时候继续读库了
  2. 缓存击穿:热点 Key,大量并发读请求引起的小雪崩, 就是缓存在某个时间点过期的时候,恰好在这个时间点对这个 Key 有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端 DB 压垮
  3. 缓存雪崩:缓存设置同一过期时间,引发的大量的读取数据库操作
redis的持久化策略
  1. RDB(快照持久化)
  2. AOF(只追加文件持久化)
redis数据结构
  1. string(key=>value)
  2. 哈希(hash)
  3. 列表(list)
  4. 集合(set)
  5. 有序集合(zset)
redis瓶颈

参考资料[关于Redis的一些思考和总结
](https://www.jianshu.com/p/b88...

对cpu会成为Redis的性能瓶颈的担忧是可以理解的,但是在实际使用过程中cpu或许不是制约Redis的关键因素,网络io可能才是最大的瓶颈。

因为CPU并不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那么久顺理成章的采用了单线程的方案。

redis阻塞原因
内因
  1. 不合理使用API和数据结构
  2. CPU饱和的问题
  3. 持久化相关的阻塞
外因
  1. CPU竞争
  2. 内存交换
  3. 网络问题
Redis的数据过期策略
  1. Redis配置项hz定义了serverCron任务的执行周期,默认为10,即cpu空闲时每秒执行10次。
  2. 每次过期key清理的视觉不超过cpu时间的25%,即若hz=1,则一次清理时间最大为250ms,若hz=10,则一次清理时间最大为25ms。
  3. 清理时依次遍历所有的db。
  4. 从db中随机取20个key,判断是否过期,若过期则清理。
  5. 若有5个以上key过期,则重复步骤4,否则遍历下一个db。
  6. 在清理过程中,若达到了25%cpu时间,则退出清理过程。
Redis的数据淘汰策略
  1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用 的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数 据淘汰
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据 淘汰
  4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据

mysql 优化

数据表数据类型优化

  • tinyint、 smallint、 bigint 考虑空间的问题,考虑范围的问题
  • char、varchar
  • enum 特定、固定的分类可以使用enum存储,效率更快
  • ip地址 ip2long() 转成长整型存储

索引优化

索引创建原则
  • 索引不是越多越好,在合适的字段上创建合适的索引
  • 复合索引的前缀原则
索引注意事项
  • 复合索引的前缀原则
  • like查询%的问题
  • 全表扫描优化
  • or条件索引使用情况
  • 字符串类型索引失效的问题

SQL语句的优化

优化查询过程中的数据访问
  • 使用 Limit
  • 返回列不用*
优化长难句的查询语句
  • 变复杂为简单
  • 切分查询
  • 分解关联查询
优化特定类型的查询语句
  • 优化 count()
  • 优化关联查询
  • 优化子查询
  • 优化 Group by和 distinct
  • 优化 limit和 union

存储引擎的优化

尽量使用 Inno DB存储引擎

数据表结构设计的优化

分区操作
  • 通过特定的策略对数据表进行物理拆分
  • 对用户透明
  • partition by
分库分表
  • 水平拆分
  • 垂直拆分

数据库服务器架构的优化

  • 主从复制
  • 读写分离
  • 双主热备
  • 负载均衡
负载均衡
  • 通过LVS的三种基本模式实现负载均衡
  • MyCat数据库中间件实现负载均衡

web服务器的负载均衡、请求分发

七层负载均衡实现

基于URL等应用层信息的负载均衡

Nginx的 proxy是它一个很强大的功能,实现了7层负载均衡

nginx负载均衡
优点

功能强大,性能卓越,运行稳定
配置简单灵活
能够自动剔除工作不正常的后端服务器
上传文件使用异步模式
支持多种分配策略,可以分配权重,分配方式灵活

Nginx负载均衡策略

内置策略: IP Hash、加权轮询
扩展策略:fair策略、通用hash、一致性hash

加权轮询策略

首先将请求都分给高权重的机器,直到该机器的权值降到了比其他机器低,才开始将请求分给下一个高权重的机器

当所有后端机器都down掉时,Ngnx会立即将所有机器的标志位清成初始状态,以避免造成所有的机器都处在 timeout的状态

IP Hash策略

Nginx内置的另一个负载均衡的策略,流程和轮询很类似,只是其中的算法和具体的策略有些变化
IP Hash算法是一种变相的轮询算法

fair策略

根据后端服务器的响应时间判断负载情况,从中选出负载最轻的机器进行分流

通用Hash、一致性Hash策略

通用hash比较简单,可以以 Nginx内置的变量为key进行hash,

一致性hash采用 Nginx了内置的一致性hash环,支持 memcache

配置
http {
    upstream cluster {
        server srvl;
        server srv2;
        server srv3;
    }
    server {
        listen 80;
        location /
        proxy_pass http: //cluster;
    }
}

四层负载均衡实现

通过报文中的目标地址和端口,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器

LvS实现服务器集群负载均衡有三种方式,NAT,DR和TUN

攻击与安全

hash 碰撞

参考资料
原理

经过特殊构造的键值, 使得PHP每一次插入都会造成Hash冲突, 从而使得PHP中array的底层Hash表退化成链表

哈希表是通过特定的哈希算法将索引转换成特定的index然后映射到对应的槽中,然后采用拉链法,在一个槽中使用链表将数据进行存储,链表的时间复杂度为O(n)。

image

这样在每次插入的时候需要遍历一遍这个链表, 大家可以想象, 第一次插入, 需要遍历0个元素, 第二次是1个, 第三次是3个, 第65536个是65535个, 那么总共就需要65534*65535/2=2147385345次遍历….

PHP的Hash算法是开源的, 已知的, 所以有心人也可以做到

示例攻击数据

{
    "format": "json",
    "list": {
          "v1" : 1,
          "攻击数据":{
              "0":0,
              "65536":0,
              "131072":0,
                …………
              "234553344":0
          }
     }
}

php示例代码hash.php

<?php
$size = pow(2, 16);

$startTime = microtime(true);
$array = array();
for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) {
    $array[$key] = 0;
}
$endTime = microtime(true);
echo '插入 ', $size, ' 个恶意的元素需要 ', $endTime - $startTime, ' 秒', "\n";

$startTime = microtime(true);
$array = array();
for ($key = 0, $maxKey = $size - 1; $key <= $maxKey; ++$key) {
    $array[$key] = 0;
}
$endTime = microtime(true);
echo '插入 ', $size, ' 个普通元素需要 ', $endTime - $startTime, ' 秒', "\n";

输出结果

插入 65536 个恶意的元素需要 5.0739450454712 秒
插入 65536 个普通元素需要 0.0010519027709961 秒
解决方法
  1. 打补丁,修改hash算法。
  2. 限制POST的参数个数,限制POST的请求长度。
  3. 使用防火墙检测异常请求
  4. 增加权限验证,最大可能的在jsonDecode()之前把非法用户拒绝
  5. 重写jsonDecode()方法
  6. 在php5.3以上的版本中,post参数的数量存在最大的限制max_input_vars => 1000

klinson
141 声望12 粉丝