[日常] 面试知识点总结(持续更新)

2019-09-10 10:56:48 浏览数 (1)

代码语言:javascript复制
数据结构和算法:
    物理结构和逻辑结构
        1.逻辑结构(集合结构,线性结构,树形结构,图形结构)
        2.物理结构一般是讲内存,顺序存储结构,链式存储结构
    浅谈算法中,高斯算法从1加到100,循环的话是100次,高斯的方法只需要一次
        1.推导大O阶:O(1) O(n) O(n^2) O(logn)
            1.常数1取代时间所有加法常数
            2.只保留最高项
            3.去除项相乘的常数,去掉系数
        2.O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^3)<O(2^n)<O(n!)时间复杂度从小到大
        3.一般是指的最坏运行时间
    线性表:
        线性表顺序存储结构的三个属性:
            1.存储空间的起始位置:数组data,他的存储位置就是存储空间的存储位置
            2.最大存储容量:数组长度MAXSIZE
            3.当前长度:length
            获取元素:L.data[i-1] , 时间复杂度是O(1)

            插入元素:  时间复 杂度都是O(n)
            1.如果插入位置不合理,抛出异常
            2.线性表长度大于等于数组长度,抛出异常或动态增加容量
            3.从最后一个元素开始向前遍历到第i位置,分别向后移动一个位置 L->data[k 1]=L->data[k]
            4.将元素填入位置i处 L->data[i-1]=e
            5.表长加1

            删除元素: 时间复杂度都是O(n)
            1.如果删除位置不合理,抛出异常
            2.取出删除元素 L.data[i-1] 
            3.从删除位置开始遍历到最后一个位置,向前移动一个位置 L->data[k-1]=L->data[k]
            4.表长减一
        单链表结构和顺序存储结构的优缺点:
            1.存储分配方式=>顺序存储是用一段连续的存储单元依次存储线性表的数据元素;单链表采用链式存储结构,用一组任意存储单元存放
            2.时间性能=>查找 顺序结构是O(1) 单链表是O(n) ;插入和删除 顺序存储是O(n) 单链表是O(1)
            3.空间性能=>顺序存储是预分配存储空间 ; 单链表不需要预分配,元素个数不受限制

        1.将单链表中终端结点的指针端由空指针改为指向头结点,单循环链表,循环链表和单链表的主要差异就在于循环的判断条件上
        原来是判断p->next是否为空,现在则是p->next不等于头结点,则循环未结束
        2.指向终端结点的尾指针代表该循环链表
        3.两条循环链表合并成一个循环链表
        p=rearA->next;保存A表的头结点
        rearA->next=rearB->next->next;将本是B表的第一个结点赋值给rearA->next
        rearB->next=p; 将原A表的头结点赋值给rearB->next free(p)

        1.双向链表就是在单链表的每个结点中,再设置一个指向其前驱结点的指针域
        2.双向链表的插入操作,将s插入到p和p->next之间 , 先搞定s的前驱和后继 , 再搞定后结点的前驱 ,最后搞定前结点的后继
        s->prior=p ;把p赋值给s的前驱
        s->next=p->next ;把p->next赋值给s的后继
        p->next->prior=s ;把s赋值给p->next的前驱
        p->next =s ;     把s赋值给p的后继
        3.删除结点p
        p->prior->next=p->next ;把p->next赋值给p->prior的后继
        p->next->prior=p->prior ;把p->prior赋值给p->next的前驱

        反转链表:
            1.常见方法分为迭代和递归,迭代是从头到尾,递归是从尾到头
            2.设置两个指针,old和new,每一项添加在new的后面,新链表头指针指向新的链表头
            3.old->next不能直接指向new,而是应该设置一个临时指针tmp,指向old->next指向的地址空间,保存原链表数据,然后old->next指向new,new往前移动到old处new=old,最后old=tmp取回数据
            while(old!=null){
                tmp=old->next
                old->next=new
                new=old
                old=tmp
            }
    二叉链表:
        1.链式存储结构.每个结点有两个孩子,设计一个数据域,两个指针域,叫做二叉链表
        2.遍历二叉树
        二叉树的遍历:从根结点出发,按照某种次序依次访问二叉树中的所有结点,每个结点仅被访问一次[访问和次序],对于计算机来说它只会处理线性序列
        前序遍历:先访问根结点,前序遍历左子树,前序遍历右子树;中左右
        中序遍历:左中右 preOrderFunc(T->lchild);printf("%c",T->data);preOrderFunc(T->rchild)
        后序遍历:左右中  preOrderFunc(T->lchild);preOrderFunc(T->rchild);printf("%c",T->data);
        层序遍历:一层层的遍历
        前序遍历递归代码:printf("%c",T->data);preOrderFunc(T->lchild);preOrderFunc(T->rchild)

        推导遍历结果:
        前序abcdef(中左右) 中序cbaedf(左中右) 后序遍历结果是多少(左右中) cbefda
                a
            b        d
          c           e  f
        中序abcdefg 后序bdcafge   前序  eacbdgf
                  e
            a               g
              c          f    
             b  d
        1.已知前中可以唯一确定一颗二叉树
        2.已知后中可以唯一确定一棵二叉树
        3.已知前后不能确定一棵二叉树

        二叉树创建:
        利用递归的原理,只不过在原来打印结点的地方,改成了生成结点,给结点赋值的操作
        if(ch=='#'){*T=NULL;}else{malloc();(*T)->data=ch;createFunc((*T)->lchild);createFunc((*T)->rchild);}

    二叉树:n个结点的有限集合,根结点的左子树和右子树组成,查找起来效率特别高
    1.每个结点最多有两颗子树
    2.左子树和右子树都是有顺序的,次序不能颠倒
    3.即使只有一棵树,也要区分左子树和右子树
    4.斜树,左斜树,右斜树,每一层只有一个结点
    5.满二叉树,很完美,所有的分支结点都存在左子树,右子树,所有叶子在同一层
    6.完全二叉树,按层序编号,结点位置和满二叉树一致,按层序标号不能有空档

    队列:
        1.队列是先进先出的线性表,FIFO,允许插入的一端叫队尾,允许删除的一端队头
        2.队列的抽象数据类型
        InitQueue(*Q):初始化操作 QueueLength(Q):返回队列的长度
        DestroyQueue(*Q):销毁队列 ClearQueue(*Q):清空队列 QueueEmpty(Q):判断队列是否为空
        GetHead(Q,*e):用e返回队列的队头元素
        EnQueue(*Q,e):插入e到队列的队尾元素
        DeQueue(*Q,*e):删除队列的队头元素
        3.引入两个指针front指向队头元素,rear指向队尾元素的下一个位置,队列顺序存储的不足,出队列时每个元素都移动,如果不移动会有假溢出问题
        4.循环队列,把队列头尾相接的顺序存储结构,解决假溢出问题
    广度优先:
        $oUrls=get(ROOT,$headers);
        $result=array();
        $queue=array();
        foreach($oUrls as $u){
            $result[$u]=true;
            array_push($queue,$u);
            while(!empty($queue)){
                $v=array_pop($queue);
                $temp=get($v,$headers);
                foreach($temp as $j){
                        if(!isset($result[$j])){
                                echo $j."rn";
                                $result[$j]=true;
                                array_push($queue,$j);
                        }   
                }   
            }   
        }
        var_dump($result);

    哈希表查找:
        1.顺序表查找:挨个比较
            for(i=1;i<=n;i  ){a[i]==key return}
            顺序表查找的优化:
                解决每次都要对i是否小于n作比较,设置一个哨兵,如果查找失败,一定会在结尾a[0]处等于key,此时返回0;免去了每次都判断是否越界
                a[0]=key;i=n;while(a[i]!=key){i--}
        2.有序表查找:二分法查找
            1.折半查找:取中间记录的查找方法,又称为二分查找.前提是线性表中的记录必须是有序的,取中间记录作为比较对象,若关键字相等则查找成功,若小于则在左半区查找,若大于则在右半区查找
                left mid right
                while(left<right){
                    mid=(left right)/2
                    if(key<a[mid]){right=mid-1}
                    else if(key>a[mid]){left=mid 1}
                    else{return mid}
                }return 0
        3.散列表查找:存储的时候使用散列函数计算出地址,直接通过存储位置查找
            1.定向查找的存储技术,通过关键字和哈希函数得到一个位置
            2.不适合查找关键字相同,不适合查找范围的,对于一对一的查找最好
    排序算法:
        非线性比较类:
            交换排序
                冒泡:平均O(n^2),最坏O(n^2) 最好O(n) 空间O(1) 稳定
                快速:平均O(nlogn),最坏O(n^2),平均O(nlogn),空间O(nlogn) 不稳定
            插入排序
                插入:平均O(n^2),最坏O(n^2) 最好O(n) 空间O(1) 稳定
                希尔:平均O(n^1.3),最坏O(n^2),最好O(n),空间O(1) 不稳定
            选择排序
                选择:平均O(n^2),最坏O(n^2),最好O(n^2),空间O(1) 不稳定
                堆  :平均O(nlogn),最坏O(nlogn),平均O(nlogn),空间O(1) 不稳定
            归并排序
                二路归并:平均O(nlogn),最坏O(nlogn),平均O(nlogn),空间O(1) 稳定
                多路归并
        线性非比较类:
            计数:平均O(n k),最坏O(n k),最好O(n k),空间O(n k) 稳定
            桶:平均O(n k),最坏O(n^2),最好O(n),空间O(n k) 稳定
            基数:平均O(n*k),最坏O(n*k),最好O(n*k),空间O(n k) 稳定
        稳定:原来a在b前面,a=b,排序后a任然在b前面  冒泡,插入,归并,计数,桶,基数
        不稳定:a=b,排序前a在b前面,排序后可能在后面,快速,希尔,选择,堆
        冒泡排序:
            1.比较相邻元素,从第一个开始较大的逐渐往后移动,后面是所有已经排好序的了
            2.for{for{}},arr[j]>arr[j 1]
            for($x=0;$x<count($arr)-1;$x  ){
                for($y=0;$y<count($arr)-$x-1;$y  ){
                    if($arr[$y]>$arr[$y 1]){
                        $temp=$arr[$y];
                        $arr[$y]=$arr[$y 1];
                        $arr[$y 1]=$temp;
                    }
                }
            }
        选择:
            1.两层循环,假定第一层循环的i元素是最小值,
            2.内层循环找出比i还小的元素,交换下他们
            3.数组分成前后两个部分,前部分是排序的,后部分是无序的
            4.两层循环,先假定当前循环的第一个索引为最小值,内部循环找比该索引还小的值,找到交换
            function selectSort(&$arr){
                    $len=count($arr);
                    for($i=0;$i<$len;$i  ){
                            $minIndex=$i;//假定当前i是最小值
                            for($j=$i 1;$j<$len;$j  ){
                                    if($arr[$j]<$arr[$minIndex]){
                                            $minIndex=$j;
                                            break;
                                    }   
                            }   
                            $t=$arr[$i];
                            $arr[$i]=$arr[$minIndex];
                            $arr[$minIndex]=$t;
                    }   
                    return $arr;
            }
        插入排序:
            定义数组长度变量$len,使用count()函数,参数:数组
            for循环数组,条件:从第二个开始,遍历数组,循环内
             定义临时变量$temp,赋值当前元素
             for循环数组,条件:遍历当前元素前面的所有元素
             判断当前元素与它前面的元素的大小,利用临时变量,转换变量
             function insert_sort($arr){
                $len=count($arr);
                for($i=1;$i<$len;$i  ){
                    $temp=$arr[$i];
                    for($j=$i-1;$j>=0;$j--){
                        if($temp<$arr[$j]){
                            $arr[$j 1]=$arr[$j];
                            $arr[$j]=$temp;
                        }else{
                            break;
                        }
                    }
                }
                return $arr;
            }
        快速排序:
            1.基于二分的思想
            2.第一个作为基准数,左右各一个指针,同时扫描,右边先走,找到比基准数小的停下
            左边再走,找到比基准数大的停下,左右交换
            3.当左右相遇的时候,把当前的和基准数调换,递归调用
            4.快速排序的最差时间复杂度和冒泡排序是一样的都是O(N2),它的平均时间复杂度为O(NlogN)
            quickSort &arr,left,right
                if left>right return
                temp=arr[left]
                i=left
                j=right
                while i<j
                    while arr[j]>=temp && i<j
                        j--
                    while arr[i]<=temp && i<j
                        i  
                    t=arr[i]
                    a[i]=arr[j]
                    a[j]=t;
                arr[left]=arr[i]
                arr[i]=temp

                quickSort(arr,left,i-1)
                quickSort(arr,i 1,right)
        归并排序:
                function merge(&$A,$left,$mid,$right,$temp){
                    //7.左堆起始
                    $i=$left;
                    //8.右堆起始
                    $j=$mid 1;
                    //9.临时数组起始
                    $t=0;
                    //10.左右堆数组都没到末尾
                    while($i<=$mid && $j<=$right){
                            //11.左堆小于等于右堆时
                            if($A[$i]<=$A[$j]){
                                    //12.左堆赋给临时数组,索引加1
                                    $temp[$t  ]=$A[$i  ];
                            }else{
                                    //13.右堆赋给临时数组,索引加1
                                    $temp[$t  ]=$A[$j  ];
                            }  
                    }  
                    //14.左堆剩余的全部加进临时数组
                    while($i<=$mid){
                            $temp[$t  ]=$A[$i  ];
                    }  
                    //15.右堆剩余全部加进临时数组
                    while($j<=$right){
                            $temp[$t  ]=$A[$j  ];
                    }  
                    //16.临时数组的元素重新赋回原数组
                    for($i=0;$i<$t;$i  ){
                            $A[$left $i]=$temp[$i];
                    }  
            }
             
            //1.利用分治法思想,递归的切分排序元素
            function mergeSort(&$A,$left,$right,$temp){
                    //2.最左只能小于最右,等于的时候就一个元素,大于是不可能的
                    if($left<$right){
                            //3.获取中间的元素
                            $mid=intval(($left $right)/2);
                            //4.递归左半区
                            mergeSort($A,$left,$mid,$temp);
                            //5.递归右半区
                            mergeSort($A,$mid 1,$right,$temp);
                            //6.合并两个有序数组为一个有序数组
                            merge($A,$left,$mid,$right,$temp);
                    }   
            }
             
            $A=array(2,4,6,1,5,7,3,8,9);
            $temp=array();
            mergeSort($A,0,count($A)-1,$temp);
            var_dump($A);
        排序内容超过内存使用外部排序
            外部排序总体思路
            1、从整个大文件里面循环读取一定量的数据到内存里面,然后把这部分数据排好序之后,写入一个小文件。达到把整个大文件分割成一定量的小文件的目的。
            2、因为每个小文件里面的数据都是已经排好序的了,所以可以对小文件进行归并的操作,最终得到的大文件就是完全排好序的。

            有时,待排序的文件很大,计算机内存不能容纳整个文件,这时候对文件就不能使用内部排序了(这里说明一下,其实所有的排序都是在内存中做的,这里说的内部排序是指待排序的内容在内存中就可以完成,而外部排序是指待排序的内容不能在内存中一下子完成,它需要做内外存的内容交换),外部排序常采用的排序方法也是归并排序,这种归并方法由两个不同的阶段组成:
            1.采用适当的内部排序方法对输入文件的每个片段进行排序,将排好序的片段(称为归并段)写到外部存储器中(通常由一个可用的磁盘作为临时缓冲区),这样临时缓冲区中的每个归并段的内容是有序的。
            2.利用归并算法,归并第一阶段生成的归并段,直到只剩下一个归并段为止。
            例如要对外存中4500个记录进行归并,而内存大小只能容纳750个记录,在第一阶段,我们可以每次读取750个记录进行排序,这样可以分六次读取,进行排序,可以得到六个有序的归并段

            每个归并段的大小是750个记录,记住,这些归并段已经全部写到临时缓冲区(由一个可用的磁盘充当),这是第一步的排序结果。完成第二步该怎么做呢?这时候归并算法就有用处了,算法描述如下:
            1.将内存空间划分为三份,每份大小250个记录,其中两个用作输入缓冲区,另外一个用作输出缓冲区。首先对Segment_1和Segment_2进行归并,先从每个归并段中读取250个记录到输入缓冲区,对其归并,归并结果放到输出缓冲区,当输出缓冲区满后,将其写到临时缓冲区内,如果某个输入缓冲区空了,则从相应的归并段中再读取250个记录进行继续归并,反复以上步骤,直至Segment_1和Segment_2全都排好序,形成一个大小为1500的记录,然后对Segment_3和Segment_4、Segment_5和Segment_6进行同样的操作。
            2.对归并好的大小为1500的记录进行如同步骤1一样的操作,进行继续排序,直至最后形成大小为4500的归并段,至此,排序结束。

            以上对外部排序如何使用归并算法进行排序进行了简要总结,提高外部排序需要考虑以下问题:
            1、如何减少排序所需的归并趟数。
            2、如果高效利用程序缓冲区,使得输入、输出和CPU运行尽可能地重叠。
            3、如何生成初始归并段(Segment)和如何对归并段进行归并。
    请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径.路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子,如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子
        1.回溯法解决的典型题,根据给定的数组matrix,初始化一个标志位数组,初始为false表示未走过,true表示走过
        2.根据行数和列数遍历数组,先找到一个与目标字符串str的第一个元素相匹配的矩阵元素,进入judge方法
        3.根据i和j先确定一维数组的位置,因为给定的matrix是一个一维数组
        4.确定递归终止条件:matrix数组越界,
    二叉堆
        1.堆(二叉堆):可以视为一棵完全的二叉树,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示,每一个结点对应数组中的一个元素
        2.给出某个结点的下标,可以计算出父结点的和孩子结点的下标; parent(i)=floor(i/2) left(i)=2i right=2i 1
        3.最大堆和最小堆,最大堆:根结点是最大值,最小堆:根结点是最小值
        4.堆排序就是把最大堆堆顶的最大数取出,剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,直到剩余数只有一个结束
        5.最大堆调整(维护最大堆,子节点永远小于父结点) ;创建最大堆(把一个数组调整成最大堆的数组);堆排序(创建最大堆,交换,维护最大堆)

        TOPK问题
            topK算法,简而言之,就是求n个数据里的前m大个数据,一般而言,m<<n,也就是说,n可能有几千万,而m只是10或者20这样的两位数。
            for ($i=$k; $i<$n; $i  ) {
                if ($list[$i] > $heap->getTopNode()) {
                    $heap->setTopNode($list[$i]);
                }
            }
TCP:
    OSI七层/五层协议:物理层,数据链路层,网络层,传输层,应用层(会话层/表示层)
    数据链路层:做到封装成帧,透明传输,差错检验
    数据链路层的差错检测:路由器能做到的是传输接收后如果有错误就丢掉,用的CRC循环冗余检验,不提供可靠传输
    网络层:负责在不同网络之间尽力转发数据包,不负责丢失重传,也不负责顺序
        查看路由表:route -n
        ARP协议可以将网络层地址到任意物理地址转换,从IP地址到MAC地址转换
    TTL是什么?有什么用处,通常那些工具会用到它?(ping? traceroute? ifconfig? netstat?)
        1.Time To Live是生存时间的意思,就是说这个ping的数据包能在网络上存在多少时间。当我们对网络上的主机进行ping操作的时候,我们本地机器会发出一个数据包,数据包经过一定数量的路由器传送到目的主机,但是由于很多的原因,一些数据包不能正常传送到目的主机,那如果不给这些数据包一个生存时间的话,这些数据包会一直在网络上传送,导致网络开销的增大。当数据包传送到一个路由器之后,TTL就自动减1,如果减到0了还是没有传送到目的主机,那么就自动丢失。
        不同操作系统发出的Ping数据包TTL值不同,不过大多为64,125,255这几种,你的ping命令返回TTL结果是64,说明此ping包没有经过路由器,你ping的是内网机器.
        2.每经过一个路由就会被减去一,如果它变成0,包会被丢掉。它的主要目的是防止包在有回路的网络上死转,浪费网络资源。ping和traceroute用到它。
        3.TTL的主要目的是防止包在有回路的网络上死转,因为包的TTL最终后变成0而使得此包从网上消失(此时往往路由器会送一个ICMP包回来,traceroute就是根据这个做的)
    路由表是做什么用的?在linux环境中怎么来配置一条默认路由?
        简: 路由表是用来决定如何将包从一个子网传送到另一个子网的,换局话说就是用来决定从一个网卡接收到的包应该送的哪一张网卡上的。在Linux上可以用“route add default gw <默认路由器IP>”来配置一条默认路由。
        详: 路由表是用来决定如何将包从一个子网传送到另一个子网的,换局话说就是用来决定从一个网卡接收到的包应该送的哪一张网卡上的。路由表的每一行至少有目标网络号、netmask、到这个子网应该使用的网卡。当路由器从一个网卡接收到一个包时,它扫描路由表的每一行,用里面的netmask和包里的目标IP地址做并逻辑运算(&)找出目标网络号,如果此网络号和这一行里的网络号相同就将这条路由保留下来做为备用路由,如果已经有备用路由了就在这两条路由里将网络号最长的留下来,另一条丢掉,如此接着扫描下一行直到结束。如果扫描结束任没有找到任何路由,就用默认路由。确定路由后,直接将包送到对应的网卡上去。在具体的实现中,路由表可能包含更多的信息为选路由算法的细节所用。题外话:路由算法其实效率很差,而且不scalable,解决办法是使用IP交换机,比如MPLS。
        在Linux上可以用“route add default gw <默认路由器IP>”来配置一条默认路由。
    在网络中有两台主机A和B,并通过路由器和其他交换设备连接起来,已经确认物理连接正确无误,怎么来测试这两台机器是否连通?如果不通,怎么来判断故障点?怎么排除故障?
        答:测试这两台机器是否连通:从一台机器ping另一台机器,如果ping不通,用traceroute可以确定是哪个路由器不能连通,然后再找问题是在交换设备/hup/cable等。
    传输层
        TCP报头:
            1.源端口和目的端口:各占2个字节,分别写入源端口和目的端口;
            2.序列号:占4个字节,TCP连接中传送的字节流中的每个字节都按顺序编号。例如,一段报文的序号字段值是 301 ,而携带的数据共有100字段,显然下一个报文段(如果还有的话)的数据序号应该从401开始;
            3.确认号:占4个字节,是期望收到对方下一个报文的第一个数据字节的序号。例如,B收到了A发送过来的报文,其序列号字段是501,而数据长度是200字节,这表明B正确的收到了A发送的到序号700为止的数据。因此,B期望收到A的下一个数据序号是701,于是B在发送给A的确认报文段中把确认号置为701;
            4.数据偏移报头长度:占4位,它指出TCP报文的数据距离TCP报文段的起始处有多远;
            5.保留:占6位,保留今后使用,但目前应都位0;
            6.标志位
            同步SYN,在连接建立时用来同步序号。当SYN=1,ACK=0,表明是连接请求报文,若同意连接,则响应报文中应该使SYN=1,ACK=1;
            确认ACK,仅当ACK=1时,确认号字段才有效。TCP规定,在连接建立后所有报文的传输都必须把ACK置1;
            终止FIN,用来释放连接。当FIN=1,表明此报文的发送方的数据已经发送完毕,并且要求释放;
            紧急URG,当URG=1,表明紧急指针字段有效。告诉系统此报文段中有紧急数据;
            推送PSH,当两个应用进程进行交互式通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应,这时候就将PSH=1;
            复位RST,当RST=1,表明TCP连接中出现严重差错,必须释放连接,然后再重新建立连接;
            7.窗口大小:占2字节,指的是通知接收方,发送本报文你需要有多大的空间来接受;
            8.检验和:占2字节,校验首部和数据这两部分;
            9.紧急指针:占2字节,指出本报文段中的紧急数据的字节数;
            10.选项:长度可变,定义一些其他的可选的参数。
        TCP报文长度是由什么确定的?
            MTU:最大传输单元,以太网的MTU为1500Bytes
            MSS:最大分解大小,为每次TCP数据包每次传输的最大数据的分段大小,由发送端通知接收端,发送大于MTU就会被分片
            TCP最小数据长度为1460Bytes
            这个跟具体传输网络有关,以太网的MTU为1500字节,Internet的MTU为576字节。
            MTU是网络层的传输单元,那么MSS = MTU - 20字节(IP首部) - 20字节(TCP首部)。所以以太网的MSS为1460字节,而Internet的MSS为536字节。
            TCP最大负载65535-40B
            TCP报文段的最大负载为65495字节,因为每个数据段必须适合IP的载荷能力,不能超过65535字节,IP头20B,TCP包头20B,故最大负载为65535- 20-20=65495B
        TCP(传输控制协议) 需要将要传输的文件分段传输,建立会话,可靠传输,流量控制
            1.面向连接,只能有两个端点,点到点;提供全双工(同时收和发)既要发了信息,也得收信息确认对方有没有收到
                客户端 ===> SYN MSS=1460(最大数据包是1460字节) [seq=0  序列号是0]  ===> 服务器
                客户端 <=== SYN,ACK [seq=0,ack=1  序列号是0,确认号是1] MSS=1424(服务器最大数据包是1424字节) WS=7(window) win=3737600(服务器最多缓存3737600字节)<=== 服务器
                客户端 ===> ACK [seq=1,ack=1 序列号是1,确认号是1] win=66816(客户端最多缓存是66816字节) ===> 服务器
            2.面向字节流,比如 发送文件,文件二进制=>TCP发送缓存=>TCP接收缓存=>应用程序,这也是发送和接收窗口技术
            3.TCP协议使用滑动窗口技术实现可靠传输
                1.停止等待协议效率不高,连续发送确认是窗口技术
                2.以字节为单位的滑动窗口技术,连续发送,接收窗口收到后确认,往右滑动发送窗口,接收窗口也要往右滑动
                3.如果中间有顺序的包丢了,接收窗口发送确认号的时候,会发丢之前的ack号,选择重发的包序号,选择确认
                4.超时重传,tcp每发送一个报文段,就设置一次计时器,重传时间到但还没收到确认,就重传这一报文段,这个时间是加权平均的往返时间
            TCP的重发机制是怎么实现的?
                1)滑动窗口机制,确立收发的边界,能让发送方知道已经发送了多少(已确认)、尚未确认的字节数、尚待发送的字节数;让接收方知道(已经确认收到的字节数)
                2) 超时重传,tcp每发送一个报文段,就设置一次计时器,重传时间到但还没收到确认,就重传这一报文段,这个时间是加权平均的往返时间
                3)选择重传,用于对传输出错的序列进行重传,如果中间有顺序的包丢了,接收窗口发送确认号的时候,会发丢之前的ack号,选择重发的包序号,选择确认
            TCP报文长度是在TCP三次握手中那一次确定的?
                这个是关于TCP报文的最大报文段长度mss的相关问题。在TCP连接的前两次握手中(SYN报文中),通信双方都会在选项字段中告知对方自己期待收到最大报文长度(mss值),以双方两个SYN报文中最小的mss最为本次数据传输的mss值。通信双方以“协商”的方式来确定报文长度的,前两次握手是告诉对方自己的mss值,在第三次握手确定mss值

            TCP丢包现象:
                TCP Out_of_Order的原因分析: 
                一般来说是网络拥塞,导致顺序包抵达时间不同,延时太长,或者包丢失,需要重新组合数据单元,因为他们可能是由不同的路径到达你的电脑上面。 
                TCP Retransmission原因分析: 
                很明显是上面的超时引发的数据重传。 
                TCP dup ack XXX#X原因分析: 
                就是重复应答#前的表示报文到哪个序号丢失,#后面的是表示第几次丢失。 
                tcp previous segment not captured原因分析 
                意思就是报文没有捕捉到,出现报文的丢失。 
            TCP的传输连接管理:
                1.连接建立=>数据传输=>连接释放
                2.主动发起连接的是客户端,被动接受连接的是服务器
                3.  客户端 ==> SYN是1同步 ,ACK确认标志是0,seq序号是x  ==> 服务器
                    客户端  <==  SYN是1同步 ,ACK确认标志是1,seq序号是y,ack确认号是x 1 <==服务器
                    客户端  ==>  ACK确认标志是1,seq序号是x 1,ack确认号是y 1 ==>服务器
                    1.最开始的时候客户端和服务器都是处于CLOSED状态。主动打开连接的为客户端,被动打开连接的是服务器。
                    2.TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;
                    3.TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
                    4.TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x 1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。
                    5.TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y 1,自己的序列号seq=x 1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
                当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。 
                4.为什么需要第三次握手再次确认,因为服务器需要确认客户端收到我的回复
                5.状态转移
                    1.客户端发送完变成 SYN-SENT , 服务端接收到后变成SYN-RECEIVED,客户端接收到确认变成 ESTABLISHED,服务端收到确认变成 ESTABLISHED   
                    2.当客户端访问不存在的IP时,可以看到客户端变成SYN-SENT状态,接收不到服务端的确认回复
                    3.SYN攻击,可以伪造来源ip,因此可以看到服务端变成SYN-RECEIVED状态,接收不到客户端的确认回复
                6.四次挥手
                客户端(主动关闭)  ==> FIN标志是1,seq序号是u ==>服务器
                客户端  <== ACK确认标志是1,seq序号是v,ack确认号是u 1 <== 服务器
                客户端  <== FIN标志是1,ACK确认标志是1,seq序号是w,ack确认号是u 1 <== 服务器
                客户端  ==> ACK确认标志是1,seq序号是u 1,ack确认号是w 1 ==>服务器
                7.状态转移
                主动关闭的一方是time_wait的状态
                被动关闭的一方是close_wait的状态
        TCP三次握手中,accept函数是发生在TCP三次握手的那个阶段?
            TCP服务端accept发生在三次握手之后
            客户端
            socket()==>connect()==>write()==>read()
            服务端
            socket()==>bind()==>listen()==>accept()==>read()==>write()
            1.accept过程发生在三次握手之后
            2.在调用listen函数之后,一个socket会从主动连接的套接字变为listen 套接字,accept后listen套接字变成连接套接字,listen继续接收更多连接
            3.客户端调用connect后,会阻塞,此时是第一次握手
            4.服务端调用accept后,会阻塞,等待客户端返回ACK确认,然后会返回,因此是在第三次握手后返回
            5.调用accept函数返回是一个连接套接字,它代表着一个网络已经存在的点对点连接
        如何保证TCP连接的可靠性
            1.数据包校验,发送方计算校验和,接收方结算校验和,进行对比
            2.应答机制,seq序列号与ack确认号
            3.超时重传机制,发送后启动定时器,进行重传
            4.连接管理,三次和四次
            5.对失序数据包重排序
            6.流量控制和拥塞控制,使用滑动窗口协商大小
        TCP在listen时的参数backlog的意义
            linux内核中会维护两个队列:
              1)未完成队列:接收到一个SYN建立连接请求,处于SYN_RCVD状态
              2)已完成队列:已完成TCP三次握手过程,处于ESTABLISHED状态
              3)当有一个SYN到来请求建立连接时,就在未完成队列中新建一项。当三次握手过程完成后,就将套接口从未完成队列移动到已完成队列。
              4)backlog曾被定义为两个队列的总和的最大值,Berkely实现中的backlog值为上面两队列之和再乘以1.5。
             5)如果当客户端SYN到达的时候队列已满,TCP将会忽略后续到达的SYN,但是不会给客户端发送RST信息,因为此时允许客户端重传SYN分节。如果启用syncookies (net.ipv4.tcp_syncookies = 1),新的连接不进入未完成队列,不受影响
              6)backlog 即上述已完成队列的大小, 这个设置是个参考值,不是精确值. 内核会做些调整
              SYN 洪水攻击(syn flood attack)
                通过伪造IP向服务器发送SYN包,塞满服务器的未完成队列,服务器发送SYN ACK包 没回复,反复SYN ACK包,使服务器不可用.
                启用syncookies 是简单有效的抵御措施.
                启用syncookies,仅未完成队列满后才生效.
        如果TCP连接过程中,第三次握手失败怎么办?
            server端发送了SYN ACK报文后就会启动一个定时器,等待client返回的ACK报文。如果第三次握手失败的话client给server返回了ACK报文,server并不能收到这个ACK报文。那么server端就会启动超时重传机制,超过规定时间后重新发送SYN ACK,重传次数根据/proc/sys/net/ipv4/tcp_synack_retries来指定,默认是5次。如果重传指定次数到了后,仍然未收到ACK应答,那么一段时间后,server自动关闭这个连接。但是client认为这个连接已经建立,如果client端向server写数据,server端将以RST包响应
        流量控制和拥塞控制
            流量控制:数据的传送与接收过程当中很可能出现收方来不及接收的情况,这时就需要对发方进行控制,以免数据丢失。流量控制用于防止在端口阻塞的情况下丢帧,这种方法是当发送或接收缓冲区开始溢出时通过将阻塞信号发送回源地址实现的。流量控制可以有效的防止由于网络中瞬间的大量数据对网络带来的冲击,保证用户网络高效而稳定的运行。
            1.通信双方主机上都分别有一个“发送窗口”和一个“接受窗口”
            2.TCP连接阶段,双方协商窗口尺寸
            3.发送方根据协商的结果,发送符合窗口尺寸的数据字节流,并等待对方的确认,等待确认机制
            4.发送方根据确认信息,改变窗口的尺寸
            拥塞控制:网络拥塞现象是指到达通信子网中某一部分的分组数量过多,使得该部分网络来不及处理,以致引起这部分乃至整个网络性能下降的现象,严重时甚至会导致网络通信业务陷入停顿,即出现死锁现象。拥塞控制是处理网络拥塞现象的一种机制。
            1.TCP发送方首先发送一个数据报,然后等待对方的回应
            2.得到回应后就把这个窗口的大小加倍,然后连续发送两个数据报
            3.直到出现超时错误,这样,发送端就了解到了通信双方的线路承载能力,也就确定了拥塞窗口的大小
        关闭连接后为什么客户端最后还要等待2MSL?
            MSL(Maximum Segment Lifetime)报文最大生存时间,2MSL即两倍的MSL,TCP允许不同的实现可以设置不同的MSL值。
            第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
            第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。
        为什么建立连接是三次握手,关闭连接确是四次挥手呢?
            1.建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。 
            2.而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。
        如果已经建立了连接,但是客户端突然出现故障了怎么办?
            TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。 
        SYN flood攻击:
            伪造不存在的地址和目标服务器建立会话
            比如102机器向101机器发起建立会话的SYN请求,但是102机器把源IP地址随意编写了一个,101机器会回复确认连接的ACK请求,此时找不到源地址
        land攻击:
            目标地址和源地址都是目标本身,自己联系自己
        三次握手过程中有哪些不安全性

          1)伪装的IP向服务器发送一个SYN请求建立连接,然后服务器向该IP回复SYN和ACK,但是找不到该IP对应的主机,当超时时服务器收不到ACK会重复发送。当大量的攻击者请求建立连接时,服务器就会存在大量未完成三次握手的连接,服务器主机backlog被耗尽而不能响应其它连接。即SYN泛洪攻击 
          防范措施: 
              1、降低SYN timeout时间,使得主机尽快释放半连接的占用 
              2、采用SYN cookie设置,如果短时间内连续收到某个IP的重复SYN请求,则认为受到了该IP的攻击,丢弃来自该IP的后续请求报文 
              3、在网关处设置过滤,拒绝将一个源IP地址不属于其来源子网的包进行更远的路由 
          2)当一个主机向服务器发送SYN请求连接,服务器回复ACK和SYN后,攻击者截获ACK和SYN。然后伪装成原始主机继续与服务器进行通信

         UDP(用户报文协议) 一个数据包就能完成数据通信,不需要建立会话,不分段,不用流量控制,不可靠传输
        用UDP协议通讯时怎样得知目标机是否获得了数据包?
            1.可以在每个数据包中插入一个唯一的ID,比如timestamp或者递增的int。
          2.发送方在发送数据时将此ID和发送时间记录在本地。
          3.接收方在收到数据后将ID再发给发送方作为回应.
            4.发送方如果收到回应,则知道接收方已经收到相应的数据包;如果在指定时间内没有收到回应,则数据包可能丢失,需要重复上面的过程重新发送一次,直到确定对方收到。
         Tcp流, udp的数据报,之间有什么区别,为什么TCP要叫做数据流?
         TCP流和UDP数据报之间的区别
            1.TCP本身是面向连接的协议,S和C之间要使用TCP,必须先建立连接,数据就在该连接上流动,可以是双向的,没有边界。所以叫数据流 ,占系统资源多
            2.UDP不是面向连接的,不存在建立连接,释放连接,每个数据包都是独立的包,有边界,一般不会合并。
            3.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证
        UDP使用场景
            1.需要资源少,在网络情况比较好的内网,或者对于丢包不敏感的应用。如DHCP协议就是基于UDP的。一般的获取IP地址都是内网请求,而且一次获取不到IP又没事。
            2.又比如基于UDP的RTP,TFTP,丢一帧数据问题也不大。再比如一些设备发现协议等等。
            3.不需要一对一沟通,建立连接,而是可以广播的应用。DHCP就是一种广播的形式。VXLAN也是需要用到组播,也是基于UDP协议的。
            4.需要处理速度快,时延低,可以容忍少数丢包,但是要求即便网络拥塞,也毫不退缩,一往无前的时候。QUIC是Google提出的一种基于UDP改进的通信协议,其目的是降低网络通信的延迟,提供更好的用户互动体验。
         SSL:位于传输层和应用层之间,专门实现在传输之前加密,在接收端给应用层之前解密;使用非对称加密技术
         SSL原理
            1.客户端与服务端建立连接
            2.互相Hello(包含支持的版本、算法;加上随机数)
            3.服务端发送公钥
            客户端发送公钥(双向验证才需要,单向跳过)
            服务端验证客户端公钥(双向验证才需要,单向跳过)
            4.客户端验证服务端公钥
            5.交换DH参数(如果用DH密钥交换算法)
            6.客户端使用公钥生成PreMaster Secret,并发送给服务端(DH根据随机数和参数直接算)
            7.服务端使用私钥解密PreMaster Secret,得到对称密钥(DH根据随机数和参数直接算)
            8.使用对称密钥通讯
            SSL客户端(也是TCP的客户端)在TCP链接建立之后,发出一个ClientHello来发起握手,这个消息里面包含了自己可实现的算法列表和其它一些需要的消息,SSL的服务器端会回应一个ServerHello,这里面确定了这次通信所需要的算法,然后发过去自己的证书(里面包含了身份和自己的公钥)。Client在收到这个消息后会生成一个秘密消息,用SSL服务器的公钥加密后传过去,SSL服务器端用自己的私钥解密后,会话密钥协商成功,双方可以用同一份会话密钥来通信了。
        HTTPS工作原理
            一.首先HTTP请求服务端生成证书,客户端对证书的有效期、合法性、域名是否与请求的域名一致、证书的公钥(RSA加密)等进行校验;
            二.客户端如果校验通过后,就根据证书的公钥的有效, 生成随机数,随机数使用公钥进行加密(RSA加密);
            三.消息体产生的后,对它的摘要进行MD5(或者SHA1)算法加密,此时就得到了RSA签名;
            四.发送给服务端,此时只有服务端(RSA私钥)能解密。
            五.解密得到的随机数,再用AES加密,作为密钥(此时的密钥只有客户端和服务端知道)。
            六.秘钥的协商过程是非对称加密,之后的通讯过程是使用此秘钥的对称加密, 非对称加密算法的性能是非常低的,一般的HTTPS连接只在第一次握手时使用非对称加密,通过握手交换对称加密密钥,在之后的通信走对称加密

        TLS(传输层安全)是更为安全的升级版 SSL
            TLS是升级版的SSL,但是还有按照传统称为了SSL
        长连接、短连接的区别和使用
            长连接:client 方与 server 方先建立连接,连接建立后不断开,然后再进行报文发送和接收。这种方式下由于通讯连接一直存在。此种方式常用于 P2P 通信。
            短连接:Client 方与 server 每进行一次报文收发交易时才进行通讯连接,交易完毕后立即断开连接。此方式常用于一点对多点通讯。C/S 通信。
            长连接与短连接的使用时机:
            长连接:短连接多用于操作频繁,点对点的通讯,而且连接数不能太多的情况。每个 TCP 连 接的建立都需要三次握手,每个 TCP 连接的断开要四次握手。如果每次操作都要建立连接然后再操作的话处理速度会降低,所以每次操作下次操作时直接发送数据 就可以了,不用再建立 TCP 连接。例如:数据库的连接用长连接,如果用短连接频繁的通信会造成 socket 错误,频繁的 socket 创建也是对资源的浪 费。
            短连接:web 网站的 http 服务一般都用短连接。因为长连接对于服务器来说要耗费一定 的资源。像 web 网站这么频繁的成千上万甚至上亿客户端的连接用短连接更省一些资源。试想如果都用长连接,而且同时用成千上万的用户,每个用户都占有一个 连接的话,可想而知服务器的压力有多大。所以并发量大,但是每个用户又不需频繁操作的情况下需要短连接。
            参考 http://www.cnblogs.com/Roberts/archive/2010/12/05/1986550.html
        socket 连接步骤
            Socket(套接字)概念:套接字(socket)是通信的基石,是支持 TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的 IP 地址,本地进程的协议端口,远地主机的 IP 地址,远地进程的协议端口。
            Socket 连接过程
            建立 Socket 连接至少需要一对套接字,其中一个运行于客户端,称为 ClientSocket ,另一个运行于服务器端,称为 ServerSocket
            套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
            服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
            客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
            连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端
            套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
HTTP:
    说一下什么是Http协议?
        浏览器客户端和服务器端之间数据传输的格式规范,格式简称为“超文本传输协议”。
    客户端浏览器解析HTML内容
        客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。
        例如:在浏览器地址栏键入URL,按下回车之后会经历以下流程:
        1、浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
        2、解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接;
        3、浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;
        4、服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;
        5、释放 TCP连接;
        6、浏览器将该 html 文本并显示内容; 
    一次完整的HTTP请求所经历的7个步骤
        HTTP通信机制是在一次完整的HTTP通信过程中,Web浏览器与Web服务器之间将完成下列7个步骤:
        1.建立TCP连接
        在HTTP工作开始之前,Web浏览器首先要通过网络与Web服务器建立连接,该连接是通过TCP来完成的,该协议与IP协议共同构建 Internet,即著名的TCP/IP协议族,因此Internet又被称作是TCP/IP网络。HTTP是比TCP更高层次的应用层协议,根据规则, 只有低层协议建立之后才能,才能进行更高层协议的连接,因此,首先要建立TCP连接,一般TCP连接的端口号是80。
        2.Web浏览器向Web服务器发送请求行
        一旦建立了TCP连接,Web浏览器就会向Web服务器发送请求命令。例如:GET /sample/hello.jsp HTTP/1.1。
        3.Web浏览器发送请求头
        浏览器发送其请求命令之后,还要以头信息的形式向Web服务器发送一些别的信息,之后浏览器发送了一空白行来通知服务器,它已经结束了该头信息的发送。
        4.Web服务器应答
        客户机向服务器发出请求后,服务器会客户机回送应答, HTTP/1.1 200 OK ,应答的第一部分是协议的版本号和应答状态码。
        5.Web服务器发送应答头
        6.Web服务器向浏览器发送数据
        Web服务器向浏览器发送头信息后,它会发送一个空白行来表示头信息的发送到此为结束,接着,它就以Content-Type应答头信息所描述的格式发送用户所请求的实际数据。
        7.Web服务器关闭TCP连接
        一般情况下,一旦Web服务器向浏览器发送了响应数据,它就要关闭TCP连接,然后如果浏览器或者服务器在其头信息加入了这行代码:
        Connection:keep-alive
        TCP连接在发送后将仍然保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保持连接节省了为每个请求建立新连接所需的时间,还节约了网络带宽。

        建立TCP连接->发送请求行->发送请求头->(到达服务器)发送状态行->发送响应头->发送响应数据->断TCP连接

    16.什么是代理?
        代理是位于客户端和服务器之间的HTTP中间实体。接收所有客户端的HTTP请求,并将这些请求转发给服务器(可能会对请求进行修改之后转发)。
        17.什么是缓存?
        缓存HTTP的仓库,使常用页面的副本可以保存在离客户端更近的地方。
        18.什么是网关?
        网关是一种特殊的服务器,作为其他服务器的中间实体使用。通常用于将HTTP流量转换成其他的协议,例如fastcgi。
        19.什么是隧道?
        隧道是建立起来之后,就会在两条连接之间对原始数据进行盲转发的HTTP应用程序。常见用途是通过HTTP连接承载加密的安全套接字层(SSL)流量,这样SSL流量就可以穿过只允许Web流量通过的防火墙了。
        20.什么是Agent代理?
        用户Agent代理是代表用户发起HTTP的客户端程序。比如Web浏览器。另外有些自动发送HTTP请求并获取内容的代理,比如“网络蜘蛛”或者“Web机器人”。
    HTTP连接管理:
        1.HTTP如何使用TCP连接的
        2.TCP连接的时延,瓶颈,存在的障碍
        3.HTTP的优化,并行连接,keep-alive,管道连接
        4.HTTP就是HTTP over TCP over IP,HTTPS是HTTP和TCP之间插入放入TLS或者SSL
        5.保持TCP连接的正确运行,四个值<源ip地址,源端口,目的ip,目的端口
        6.HTTP时间线,请求=>DNS查询=>响应=>请求=>服务器处理=>响应=>关闭
        7.TCP性能点:TCP连接建立握手(花费50%时间);
            TCP延迟确认算法(占第二);
            TIME_WAIT时延和端口耗尽(记录最近所关闭连接的IP地址和端口号,2MSL通常2分钟)
            TCP慢启动拥塞控制;数据聚集的Nagle算法;
    HTTP1.1版本新特性

        a、默认持久连接节省通信量,只要客户端服务端任意一端没有明确提出断开TCP连接,就一直保持连接,可以发送多次HTTP请求
        b、管线化,客户端可以同时发出多个HTTP请求,而不用一个个等待响应
        c、断点续传
        实际上就是利用HTTP消息头使用分块传输编码,将实体主体分块传输。

    HTTP优化方案
        TCP复用:TCP连接复用是将多个客户端的HTTP请求复用到一个服务器端TCP连接上,而HTTP复用则是一个客户端的多个HTTP请求通过一个TCP连接进行处理。前者是负载均衡设备的独特功能;而后者是HTTP 1.1协议所支持的新功能,目前被大多数浏览器所支持。
        内容缓存:将经常用到的内容进行缓存起来,那么客户端就可以直接在内存中获取相应的数据了。
        压缩:将文本数据进行压缩,减少带宽
        SSL加速(SSL Acceleration):使用SSL协议对HTTP协议进行加密,在通道内加密并加速
        TCP缓冲:通过采用TCP缓冲技术,可以提高服务器端响应时间和处理效率,减少由于通信链路问题给服务器造成的连接负担。
    HTTP/2新特性
        二进制分帧、多路复用、流优先级、服务器推送、头部压缩、应用层协商协议
        *强制SSL,虽然规范没有强制,但是所有浏览器均只支持SSL下的HTTP/2
        HTTP/2 Frequently Asked Questions
    HTTP首部(head头信息)
        1.通用首部:Date
            通用缓存首部(Cache-Control)
        2.请求首部:Accept
            条件请求首部(if-),安全请求首部(Authorization)
        3.响应首部:Server
        4.实体首部(用于主体部分的首部):content-type:
        5.扩展首部:非标准的,自己定义的
    HTTP请求报文与响应报文格式
        1.请求报文包含四部分:
        a、请求行:包含请求方法、URI、HTTP版本信息
        b、请求首部字段
        c、请求内容实体
        d、空行
        2.响应报文包含四部分:
        a、状态行:包含HTTP版本、状态码、状态码的原因短语
        b、响应首部字段
        c、响应内容实体
        d、空行
        3.常见的首部:
        通用首部字段(请求报文与响应报文都会使用的首部字段)
        Date:创建报文时间
        Connection:连接的管理
        Cache-Control:缓存的控制
        Transfer-Encoding:报文主体的传输编码方式
        4.请求首部字段(请求报文会使用的首部字段)
        Host:请求资源所在服务器
        Accept:可处理的媒体类型
        Accept-Charset:可接收的字符集
        Accept-Encoding:可接受的内容编码
        Accept-Language:可接受的自然语言
        Content-Length:表示请求消息正文的长度
        Authorization:授权信息,通常出现在对服务器发送的WWW-Authenticate头的应答中
        Cookie:这是最重要的请求头信息之一
        From:请求发送者的email地址,由一些特殊的Web客户程序使用,浏览器不会用到它
        Pragma:指定“no-cache”值表示服务器必须返回一个刷新后的文档,即使它是代理服务器而且已经有了页面的本地拷贝
        Referer:包含一个URL,用户从该URL代表的页面出发访问当前请求的页面
        User-Agent:浏览器类型,如果Servlet返回的内容与浏览器类型有关则该值非常有用
        UA-Pixels,UA-Color,UA-OS,UA-CPU:由某些版本的IE浏览器所发送的非标准的请求头,表示屏幕大小、颜色深度、操作系统和CPU类型
        5.响应首部字段(响应报文会使用的首部字段)
        Accept-Ranges:可接受的字节范围
        Location:令客户端重新定向到的URI
        Server:HTTP服务器的安装信息
        6.实体首部字段(请求报文与响应报文的的实体部分使用的首部字段)
        Allow:资源可支持的HTTP方法
        Content-Type:实体主类的类型
        Content-Encoding:实体主体适用的编码方式
        Content-Language:实体主体的自然语言
        Content-Length:实体主体的的字节数
        Content-Range:实体主体的位置范围,一般用于发出部分请求时使用
    常见的MIME类型如下:
        MIME的编码:
        MIME提供了一种可以在邮件中附加多种不同编码文件的方法,弥补了原来的信息格式的不足。实际上不仅仅是邮件编码,现在MIME经成为HTTP协议标准的一个部分。
        MIME定义了两种编码方法Base64与QP(Quote-Printable)
        text/html : HTML格式
        text/plain :纯文本格式      
        text/xml :  XML格式
        image/gif :gif图片格式    
        image/jpeg :jpg图片格式 
        image/png:png图片格式
        以application开头的媒体格式类型:
        application/xhtml xml :XHTML格式
        application/xml     : XML数据格式
        application/atom xml  :Atom XML聚合格式    
        application/json    : JSON数据格式
        application/pdf       :pdf格式  
        application/msword  : Word文档格式
        application/octet-stream : 二进制流数据(如常见的文件下载)
        application/x-www-form-urlencoded : <form encType=””>中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)
        另外一种常见的媒体格式是上传文件之时使用的:
        multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式
    HTTP状态码:
        100-199 信息性状态码
            100 continue 请继续
            101 switching protocols 切换协议,返回upgraded头
        200-299 成功状态码
            200 ok
            201 created  创建资源
            202 accepted 请求已经接收到,不保证完成
            203 non-authoritative information 非权威信息,不是来自于源端服务器
            204 no content 没有内容
            205 reset content 重置内容,主要是对浏览器html元素
            206 partial content 执行了部分内容,断点续传必考
        300-399 重定向状态码
            300 multiple choices 多项选择,会返回一个选项列表
            301 moved permanently 资源被移除,location中包含url
            302 Found 与301类似,客户端应该使用location中的url临时定位
            303 see other 允许post请求的响应重定向
            304 not modified 资源没有修改,返回的时候不能有主体内容,还是本地的内容,缓存必考
            305 use proxy  使用代理来请求资源
            307 temporary redirect 临时重定向,与301类似
            因为http1.0和http1.1的差别因此有交叉
        4xx系列和客户端有关:
            400 bad request 错误请求
            401 unauthorized 没权限
            402 payment required 未使用
            403 forbidden 禁止
            404 not found
            405 methord not allowed 请求url不支持的方法,应该返回allow首部告诉允许啥
            406 not acceptable 客户端指定参数说明可以接受什么类型的文本
            407 proxy authentication required 要求代理服务器认证权限
            408 request timeout 请求超时
            409 conflict 请求冲突
            410 gone 类似404
            411 length required 需要请求中包含content-length
            412 precondition failed  先决条件失败
            413 request entity too large 客户端发的内容太大
            414 request uri too long 请求的url太长
            415 unsuport media type 不支持的媒体类型
            416 requested range not satisfiable 请求的范围不满足,无效
            417 expectation failed 服务器无法满足请求
            nginx自定义的状态码:
            495, https certificate error
            496, https no certificate
            497, http to https
            498, canceled
            499, client has closed connection是客户端等到超时主动关掉的
        500-599 服务器错误状态码
            500 internal server error 服务器程序内部错误
            501 not implemented 没有实现,超出了服务器的范围
            502 bad gateway 代理或者网关下一链路收到未响应
            503 service unavailable 服务不可用
            504 gateway timeout 类似408,超时来自代理
            505 http version not supported http协议版本不支持
    主流的WEB服务:REST,SOAP,RPC
        1.REST是基于HTTP协议的一个补充,他的每一次请求都是一个HTTP请求,然后根据不同的method来处理不同的逻辑
        2.SOAP是W3C在跨网络信息传递和远程计算机函数调用方面的一个标准。但是SOAP非常复杂
        3.GO天生提供方便的RPC机制
        4.有连接的流式Socket(SOCK_STREAM)TCP 和无连接数据报式Socket(SOCK_DGRAM)UDP
        5.IPv4的地址位数为32位,也就是最多有2的32次方的网络设备可以联到Internet上,IPv6采用128位地址长度,几乎可以不受限制地提供地址
    什么是Http协议无状态协议?怎么解决Http协议无状态协议?
        无状态协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息
        也就是说,当客户端一次HTTP请求完成以后,客户端再发送一次HTTP请求,HTTP并不知道当前客户端是一个”老用户“。
        可以使用Cookie来解决无状态的问题,Cookie就相当于一个通行证,第一次访问的时候给客户端发送一个Cookie,当客户端再次来的时候,拿着Cookie(通行证),那么服务器就知道这个是”老用户“。

    URI和URL的区别
        1.URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。
        Web上可用的每种资源如HTML文档、图像、视频片段、程序等都是一个来URI来定位的
        URI一般由三部组成:
        ①访问资源的命名机制
        ②存放资源的主机名
        ③资源自身的名称,由路径表示,着重强调于资源。

        2.URL是uniform resource locator,统一资源定位器,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
        URL是Internet上用来描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上,特别是著名的Mosaic。
        采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。URL一般由三部组成:
        ①协议(或称为服务方式)
        ②存有该资源的主机IP地址(有时也包括端口号)
        ③主机资源的具体地址。如目录和文件名等

        3.URN,uniform resource name,统一资源命名,是通过名字来标识资源,比如mailto:java-net@java.sun.com。

        4.URI包含URL和URN,URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL和URN都是一种URI。笼统地说,每个 URL 都是 URI,但不一定每个 URI 都是 URL。这是因为 URI 还包括一个子类,即统一资源名称 (URN),它命名资源但不指定如何定位资源。上面的 mailto、news 和 isbn URI 都是 URN 的示例。
        在Java的URI中,一个URI实例可以代表绝对的,也可以是相对的,只要它符合URI的语法规则。而URL类则不仅符合语义,还包含了定位该资源的信息,因此它不能是相对的。
        在Java类库中,URI类不包含任何访问资源的方法,它唯一的作用就是解析。相反的是,URL类可以打开一个到达资源的流。
    get与post请求区别?
        区别一:
        get重点在从服务器上获取资源。
        post重点在向服务器发送数据。
        区别二:
        get传输数据是通过URL请求,以field(字段)= value的形式,置于URL后,并用"?"连接,多个请求数据间用"&"连接,如http://127.0.0.1/Test/login.action?name=admin&password=admin,这个过程用户是可见的。
        post传输数据通过Http的post机制,将字段与对应值封存在请求实体中发送给服务器,这个过程对用户是不可见的。
        GET参数通过URL传递,POST放在Request body中。
        区别三:
        Get传输的数据量小,因为受URL长度限制,但效率较高。
        Post可以传输大量数据,所以上传文件时只能用Post方式。
        区别四:
        get是不安全的,因为URL是可见的,可能会泄露私密信息,如密码等。
        post较get安全性较高。
        区别五:
        get方式只能支持ASCII字符,向服务器传的中文字符可能会乱码。
        post支持标准字符集,可以正确传递中文字符。
        有说post发了两个TCP包,存疑
        GET在浏览器回退时是无害的,而POST会再次提交请求。
        GET产生的URL地址可以被Bookmark,而POST不可以。
        GET请求会被浏览器主动cache,而POST不会,除非手动设置。
        GET请求只能进行url编码,而POST支持多种编码方式。
        GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
        GET请求在URL中传送的参数是有长度限制的,而POST没有。
        简述POST 和GET传输的最大容量分别是多少?2MB(可在php.ini中更改),1024B
        在ajax: post不被缓存,get被缓存所以一般在请求结尾加Math.random();
        SERVER端接受:因为在submit提交的时候是按不同方式进行编码的,所以服务端在接受的时候会按照不同的方式进行接受!
    Http协议中有那些请求方式?
        GET: 用于请求访问已经被URI(统一资源标识符)识别的资源,可以通过URL传参给服务器
        POST:用于传输信息给服务器,主要功能与GET方法类似,但一般推荐使用POST方式。
        PUT: 传输文件,报文主体中包含文件内容,保存到对应URI位置。
        HEAD: 获得报文首部,与GET方法类似,只是不返回报文主体,一般用于验证URI是否有效。
        DELETE:删除文件,与PUT方法相反,删除对应URI位置的文件。
        OPTIONS:查询相应URI支持的HTTP方法。
        TRACE:请求服务器回送收到的请求信息,主要用于测试或诊断
MySQL:
    mysql分层:
    1.client  ==>连接层 ==>服务层==>引擎层==>存储层 server
    3.连接层:
        提供与客户端连接的服务
    4.服务层:
        1.提供各种用户使用的接口(增删改查),sql解析
        2.提供SQL优化器(MySQL Query Optimizer),重写查询,决定表的读取顺序,选择合适的索引
            mysql的hint关键字有很多比如:SQL_NO_CACHE FORCE_INDEX SQL_BUFFER_RESULT
    5.引擎层:innoDB和MyISAM
        1.innoDB:事务优先(适合高并发修改操作;行锁)
        2.MyISAM:读性能优先
        3.show engines;查询支持哪些引擎
        4.查看当前默认的引擎 show variables like '%storage_engine%';default_storage_engine
    存储引擎:
        MyISAM存储引擎:数据库文件类型就包括.frm、.MYD、.MYI
        innoDB存储引擎:数据库文件类型就包括.frm、ibdata1、.ibd
        MEMORY存储引擎:使用存储在内存中的数据来创建表
        ARCHIVE存储引擎:该存储引擎非常适合存储大量独立的、作为历史记录的数据
    sql的解析过程比如:
    from ... on ... where ... group by  ... having ... select ... order by ... limit
    mysql join:笛卡尔积形式
        左连接:left join,左表全部,右表没有用null
        右连接:right join,右表全部,左表没有用null
        内连接:inner join,左右表中都有的 from 两张表 where的也是内联;SELECT 列名1,列名2... FROM 表1 INNER JOIN 表2 ON 表1.外键=表2.主键
        左表独有:left join where b is null
        右表独有:right join where a is null
        全连接:union
    join原理:
        1.MySQL内部采用了一种叫做 nested loop join的算法。Nested Loop Join 实际上就是通过驱动表的结果集作为循环基础数据,然后一条一条的通过该结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果。
        2.如果还有第三个参与 Join,则再通过前两个表的 Join 结果集作为循环基础数据,再一次通过循环查询条件到第三个表中查询数据,如此往复,基本上MySQL采用的是最容易理解的算法来实现join。所以驱动表的选择非常重要,驱动表的数据小可以显著降低扫描的行数
        3.条件中尽量能够过滤一些行将驱动表变得小一点,用小表去驱动大表 
        4.explain中可以看到谁是驱动表
        5.有三种nested loop join方法,循环嵌套Simple Nested-Loop Join,索引嵌套Index Nested-Loop Join,Block Nested-Loop Join(多了个join buffer)
        6.索引嵌套,不再需要一条条记录进行比较,而可以通过索引来减少比较,从而加速查询。这也就是平时我们在做关联查询的时候必须要求关联字段有索引的一个主要原因

    innoDB引擎的四大特性:插入缓冲,二次写,自适应哈希索引,预读
    事务:本地事务和分布式事务
        事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元,组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交,只要其中任一操作执行失败,都将导致整个事务的回滚。
        简单地说,事务提供一种“要么什么都不做,要么做全套(All or Nothing)”机制

    事务的四种隔离级别:读未提交(read uncommitted),读已提交(read committed),可重复读(repeatable read),串行(serializable)
        读未提交:脏读 , 不可重复读 , 幻读
        读已提交:不可重复读 , 幻读
        可重复读:幻读
        脏读:一个事务读取到了另一个事务未提交的数据结果。
        大多数数据库的默认级别就是读已提交,比如Sql Server , Oracle。Mysql的默认隔离级别是可重复读
    不可重复读
        一个事务对同一行数据重复读取两次,但是却得到了不同的结果。
        (1) 虚读:事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值。
        (2) 幻读(Phantom Reads):事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N M条数据了,就产生了幻读。

    事务的ACID特性:
        原子性(atomicity):一个事务是一个不可分割的最小工作单位,事务中的所有操作要么都做,要么都不做。
        一致性(consistency):事务前后数据的完整性必须保持一致.事务必须是使数据库从一个一致性状态变到另一个一致性状态,一致性与原子性是密切相关的。
        隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。有四种隔离级别
        持久性(durability):指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
    事务特性的实现:
        1.事务的 ACID 是通过 InnoDB 日志和锁来保证。
        2.原子性和一致性通过 Undo Log 来实现。Undo Log 的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为 Undo Log)。然后进行数据的修改。如果出现了错误或者用户执行了 Rollback 语句,系统可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态。和 Undo Log 相反,Redo Log 记录的是新数据的备份。在事务提交前,只要将 Redo Log 持久化即可,不需要将数据持久化。
        3.事务的隔离性是通过数据库锁的机制实现的,持久性通过 Redo Log(重做日志)来实现,

    分布式事务
        1.本质上来说,分布式事务就是为了保证不同数据库的数据一致性

        分布式事务XA以及Mysql XA
        上面聊了那么多ACID,相信大家对事务已经有了很多的了解,下面我们来聊聊分布式的事务。
        首先我们来看看分布式事务与传统事务有什么不同的地方:
        1.通信异常:我们的系统架构从集中式走向分布式,必然要引入网络这个概念。那么由于网络本身的不可靠性因此也引入了额外的问题。这里不管是路由器,DNS或者还是网络故障等等,都有可能导致分布式节点之间的网络通信出现问题,进而导致系统状态的异常。
        2.网络分区:由于网络发生异常,导致分布式各节点之间的延迟不断增大,最终导致组成分布式的节点中只有部分节点能够正常通讯,而另外一些节点则不能,我们称这个现象为网络分区。当出现网络分区时,分布式系统中就会出现局部的小集群,在极端情况下,这些局部小集群会独立完成原本需要整个分布式系统才能完成的功能,这对分布式系统的一致性提出了很大的挑战。
        3.三态:在分布式系统的每一次请求中,都可能存在三态的概念:成功,失败和超时。这里可能是因为网络不不可靠因素或者是其他不可预知的因素,最终的结果都是调用者不能够清晰的知道这次调用的结果,也就无法对接下来的操作作出准确的判断。
        所以基于在分布式系统环境下会出现的上述三个问题,同样有别与集中式系统的ACID特性,在分布式系统中有接下来的两个理论用于支持分布式事务:
        
    分布式事务的基础理论特性
        1.CAP
        2.BASE
        1.CAP:首先说说CAP,在一个分布式系统不可能同时满足一致性(Consistency),可用性(Avaliablity)和分区容错性(Partition tolerance)这三个基本需求,最多只能满足其中的两个。

        一致性:这里要特殊强调一下,这里的一致性和ACID中的一致性是有所不同的,这里的一致性指的在分布式系统中多个副本之间能够保持的一致性,即使数据变更,也能够保证不同数据副本的一致性。
        可用性:可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一次的请求总是能够在有限的时间内给出相应结果。这里的有限时间指的是必须在系统执行的相应时间之内给出相应的结果。不应该出现系统执行成功,但是返回结果超时的情况。
        分区容错性:分布式系统在遇到任何网络分区故障的时候,仍然能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
        以上就是对CAP定理的一些解释,因此我们对分布式系统进行构建的时候,需要根据实际的业务场景进行取舍。但是对于一个分布式系统而言,分区容错性应该可以说是一个基本的要求,为什么这么说 ,非常简单因为一个分布式系统,那么各个组件必然是分到网络中的不同节点上了,因此也就一定会出现网络的问题。所以解决分区容错性也就是一个基本的需求,所以我们需要将经历花费在A和C的上面进行权衡。

        2.BASE:
        BASE其实是对CAP中一致性和可用性权衡的结果,其来源是大规模互联网分布式实践的总结,是由CAP定理逐渐演化而来,其核心思想是即使无法做到强一致性,但是每个应用都可以根据自身的业务特点,采用适当的方式使系统能够达到最终的一致性。
        BA(Basically Avaliblity)基本可用
        S (Soft state)软状态
        E (Eventually consistent)最终一致性
        1.基本可用:这里怎么理解基本可用呢,其实就是在分布式系统出现不可预知的故障的时候,允许损失部分可用性,但请注意这并不等同于系统不可用。
        这里可以体现在相应时间上的损失和功能上的损失。相应时间上的损失也就是超过正常系统的相应时间,可以适当的增加一些。然后功能上的损失实现的方式有很多,比如在系统压力过大的时候可以进行页面的整体或者部分降级,或者通过消息Mq的方式进行延迟处理等等。
        2.软状态:指系统可以存在数据的中间状态,并认为该状态并不会影响系统的整体可用性,只是存在一些系统延迟而已。
        3.最终一致性:最终一致性是强调系统中所有数据的副本,在经过一段时间的同步之后,最终都能够达到一个一致性的状态,这也算是在分布式环境下的一种妥协
    分布式事务的解决:
        1.TCC Try 阶段 Confirm 阶段 Cancel 阶段 强隔离性,严格一致性要求的活动业务。执行时间较短的业务。
        2.本地消息表  将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理
        3.MQ 事务在 RocketMQ 中实现了分布式事务,实际上是对本地消息表的一个封装,将本地消息表移动到了 MQ 内部
        4.Saga 事务 Saga 是 30 年前一篇数据库伦理提到的一个概念。其核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作
    微服务的坏处:
        上面说过出现分布式事务的两个原因,其中有个原因是因为微服务过多。我见过太多团队一个人维护几个微服务,太多团队过度设计,搞得所有人疲劳不堪。
        而微服务过多就会引出分布式事务,这个时候我不会建议你去采用下面任何一种方案,而是请把需要事务的微服务聚合成一个单机服务,使用数据库的本地事务。
        因为不论任何一种方案都会增加你系统的复杂度,这样的成本实在是太高了,千万不要因为追求某些设计,而引入不必要的成本和复杂度。

    乐观锁和悲观锁
        悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
        乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
        两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适

    MYSQL的锁机制:
        1.无论何时只要有多个查询在同一时刻修改数据,都会产生并发控制的问题
        2.讨论mysql在两个层面,服务器层和存储引擎层,如何并发控制读写
        3.举了个mbox邮箱文件的例子,说如果有多个进程同时对mbox文件写东西,那么在文件的末尾会,交叉混乱的添加,比如进程1写了几行,进程2也写了几行,互相交叉,数据就是错误的了.设计良好的mbox需要加锁,比如进程1锁住了文件,进程2必须等待进程1结束,锁释放才能去写.但是这样的话就不支持并发了,同一时刻只有一个进程可以写数据
        4.读取时可能也会有问题,比如一个进程正在读数据,另一个进程同时想去删数据,此时就是不安全的;共享锁叫读锁,排他锁叫写锁
        5.读锁是共享的,它不会阻塞其他读锁;写锁是排他的,它会阻塞其他读锁和写锁;读读不互斥,读写互斥,写写互斥
        6.mysql每时每刻都在发生锁定,当某用户在修改数据时,会阻塞其他用户读取该数据
        7.mysql中有两种锁粒度,锁住整张表和锁住表中一行
        表锁:当某用户修改数据时,会获取写锁,此时会锁住整张表,其他用户都不能读和写,myisam
        行锁:当某用户修改某几行数据,会获取写锁,此时只是锁住那几行,那几行其他用户不能读和写;其他行没有影响,但是管理锁会消耗资源,innodb
        8.使用命令来锁表
            unlock tables 解锁所有行
            lock tables 表名 read或者write
    FTWRL 和 lock tables … read/write 的相同点是什么?
        lock tables … read/write 与 FTWRL 类似,可以用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。需要注意,lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。
    MySQL 中全局锁 FTWRL 的作用和副作用是什么?
        MySQL 的全局锁加锁命令是 Flush tables with read lock (FTWRL)。当你需要让整个库处于只读状态的时候,可以使用这个命令。但是这个命令会造成以下语句会被阻塞(副作用):数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。
    在开始之前,我先来解释两个概念。快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。OK,下面来看几个问题!(以下问题都是基于 innoDB 存储引擎)
        1、简单的 select 查询,如:select * from xttblog where id = 1; 简单的 select 操作,属于快照读,不加锁。
        2、属于当前读的常见 SQL 语句有哪些?
            select * from xttblog where ? lock in share mode;
            select * from xttblog where ? for update;
            insert into xttblog values (…);
            update xttblog set ? where ?;
            delete from xttblog where ?;
        常见的上面 5 种 SQL 都是属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加 S 锁 (共享锁)外,其他的操作,都加的是 X 锁 (排它锁)。
        3、如果 id 是主键,且是 RC 隔离级别,SQL:delete from xttblog where id = 10 会加锁吗?
        会加 X 锁,排它锁。只需要将主键上,id = 10 的记录加上 X 锁即可。
        4、问题 3 种的 id 主键改为唯一索引后,SQL:delete from xttblog where id = 10 会加锁吗?
        若 id 列是 unique 列,其上有 unique 索引。那么 SQL 需要加两个 X 锁,一个对应于 id unique 索引上的 id = 10 的记录,另一把锁对应于聚簇索引上的相对于 id unique 对应的记录。
    MySQL 的 InnoDB 中获得一个一致性视图的方法有多少种?分别是什么?
        有多种方法。第一种是加全局锁;Flush tables with read lock (FTWRL)。第二种是在可重复读隔离级别下开启一个事务。第三种就是官方自带的逻辑备份工具是 mysqldump;当 mysqldump 使用参数 –single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。第四种是使用 set global readonly=true 的方式。
    MVCC 多版本并发控制实现的事务:
        1.没有一个统一的实现标准,实现了非阻塞的读操作,写操作也只锁定必要的行
        2.通过保存数据在某个时间点的快照实现的
        3.典型的有乐观并发控制和悲观并发控制
        4.innodb的mvcc是每次事务都有递增的版本号,通过在每行记录的后面添加两列隐藏字段,两列分别是是创建版本号和删除版本号,存储操作它事务的版本号
        5.在事务中增删改查就是对两列版本号字段进行操作

        insert 为新插入的每一行保存当前事务版本号到 行创建版本号字段
        update 插入一行新的保存当前事务创建版本号,修改原行数据的删除版本号为本次事务的版本号
        delete 修改行的删除版本号字段为本次事务的版本号
        select 查询 创建版本号字段 小于等于当前事务版本的数据    确保该记录是本次之前就存在的或本次事务新插的
               查询 删除版本号字段 不存在或者大于当前版本的数据 确保该记录在本次事务之前没删除

        6.这样的设计就不需要加锁了,读和操作性能好,但是需要额外的存储空间
        7.mvcc只在REPEATABLE READ和READ COMMITED两个隔离下工作;READ UNCOMMITED总是读取最新数据;SERIALIZABLE对读取的行都加锁
    mysql 死锁:
        1.两个或多个事务在同一个资源上相互占用,并请求锁定对方占用的资源,导致恶性循环
        2.解决这种问题,检测到死锁的循环依赖,立即返回一个错误
        3.时间达到了锁等待超时限定,放弃锁请求
        4.将持有最少行级写锁的事务回滚
        5.如果是真正的数据冲突,这种是很难避免的,必须要提交或回滚其中一个事务
        ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
    Schema与数据类型优化
        A.选择优化的数据类型 1.数据类型的选择原则:* 更小的通常更好* 简单就好* 尽量避免NULL 2.应该尽量只在对小数进行精确计算时才使用DECIMAL,使用int类型通过程序控制单位效果更好
        3.使用VARCHAR合适的情况:字符串列的最大长度比平均长度大很多;列的更新很少,所以碎片不是问题;使用了像UTF-8这样复杂的字符集,每个字符都使用不同的字节数进行存储
        4.CHAR适合存储很短的字符串,或者所有值都接近同一个长度;不容易产生碎片,在存储空间上更有效率
        5.通常应该尽量使用TIMESTAMP,它比DATETIME空间效率更高
        6.MySQL schema设计中的陷阱 1.不好的设计:* 太多的列 * 太多的关联 * 全能的枚举 * 变相的枚举
    范式和反范式
        1.范式的优点:* 范式化的更新操作通常比反范式化要快* 当数据较好地范式化时,就只有很少或者没有重复数据,所以只需要修改更少的数据
        * 范式化的表通常更小,可以更好地放在内存里,所以执行操作会更快
        * 很少有多余的数据意味着检索列表数据时更少需要DISTINCT或者GROUP BY语句
        2.范式化设计的缺点是通常需要关联
        3.反范式的优点:避免关联,避免了随机I/O,能使用更有效的索引策略
    通俗地理解三个范式?  
        答:第一范式:1NF是对属性的原子性约束,要求属性具有原子性,不可再分解;
        第二范式:2NF是对记录的惟一性约束,要求记录有惟一标识,即实体的惟一性;  
        第三范式:3NF是对字段冗余性的约束,即任何字段不能由其他字段派生出来,它要求字段没有冗余
    缓存表和汇总表
        1.有时提升性能最好的方法是同一张表中保存衍生的冗余数据,有时也需要创建一张完全独立的汇总表或缓存表
        2.物化视图,MySQL并不原生支持,Flexviews
        3.如果应用在表中保存计数器,则在更新计数器时可能踫到并发问题,创建一张独立的表存储计数器,可以帮助避免缓存失效
        4.解决独立表并发问题可以建多行,根据id随机更新,然后统计时sum()* 按天或小时可以单独建行,旧时间可定时任务合并到统一的一行
    加快ALTER TABLE操作的速度
        1.两种方式:* 一是在一台不提供服务的机器上执行ALTER TABLE操作,然后和提供服务的主库进行切换* 2.是通过“影子拷贝”,创建一张新表,然后通过重命名和删表操作交换两张表及里面的数据
        3..快速创建MyISAM索引,先禁用索引,导入数据,然后重新启用索引
    A.索引基础
        1.索引可以包含一个或多个列的值,如果索引包含多个列,那么列的顺序也十分重要,因为MySQL只能高效地使用索引的最左前缀列
        2.ORM工具能够产生符合逻辑的、合法的查询,除非只是生成非常基本的查询,否则它很难生成适合索引的查询
        3.在MySQL中,索引是在存储引擎层而不是服务器层实现的,所以,并没有统一的索引标准:不同存储引擎的索引的工作方式并不一样,也不是所有的存储引擎都支持所有类型的索引
        4.B-Tree意味着所有的值都是按顺序存储的,并且每一个叶子页到根的距离相同,能够加快访问数据的速度,从索引的根节点开始进行搜索,适用于全键值、键值范围或键前缀查找
        5.B-Tree索引的限制:
            * 如果不是按照索引的最左列开始查找,则无法使用索引
            * 不能跳过索引中的列
            * 如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查找
        6.哈希索引(hash index)基于哈希表实现,只有精确匹配索引所有列的查询才有效,只有Memory引擎显式支持哈希索引
        7.哈希索引的限制:
            * 哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行
            * 哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序
            * 哈希索引也不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容来计算哈希值的
            * 只支持等值比较查询,不支持任何范围查询
            * 访问哈希索引的数据非常快,除非有很多哈希冲突
            * 如果哈希冲突很多的话,一些索引维护操作的代价也会很高
        8.空间数据索引(R-Tree),MyISAM表支持空间索引,可以用作地理数据存储,开源数据库系统中对GIS的解决方案做得比较好的是PostgreSQL的PostGIS
        9.全文索引,适用于MATCH AGAINST操作,而不是普通的WHERE条件操作
    B.索引的优点
        1.三个优点:* 索引大大减少了服务器需要扫描的数据量* 索引可以帮助服务器避免排序和临时表* 索引可以将随机I/O变为顺序I/O
        2.索引三星系统:* 索引将相关的记录放到一起则获得一星* 如果索引中的数据顺序和查找中的排序一致则获得二星* 如果索引中的列包含了查询中需要的全部列则获得三星
    C.高性能的索引策略
        1.独立的列:如果查询中的列不是独立的,则MySQL不会使用索引。“独立的列”是指索引列不能是表达式的一部分,也不能是函数的参数
        2.前缀索引和索引选择性
            * 通常可以索引开始的部分字符,可以大大节约索引空间,但也会降低索引的选择性
            * 索引的选择性是指,不重复的索引值(也称为基数,cardinality)和数据表的记录总数(#T)的比值,范围从1/#T到1之间,选择性越高则查询效率越高,因为选择性高的索引可以让MySQL在查找时过滤掉更多的行
            * MySQL无法使用前缀索引做ORDERY BY和GROUP BY,也无法做覆盖扫描
        3.选择合适的索引列顺序
            * 正确的索引列顺序依赖于使用该索引的查询,并且同时需要考虑如何更好地满足排序和分组的需要
            * 在一个多列B-Tree索引中,索引列的顺序意味着索引首先按照最左列进行排序,其次是第二列
            * 将选择性最高的列放到索引最前列
        4.聚簇索引:并不是一种单独的索引类型,而是一种数据存储方式
            * 最好避免随机的(不连续且值的分布范围非常大)聚簇索引,特别是对于I/O密集型的应用
        5.覆盖索引:如果一个索引包含(或者说覆盖)所有需要查询的字段的值,就称为覆盖索引
            * 覆盖索引必须要存储索引列的值,
        6.如果EXPLAIN出来的type列的值为“index”,则说明MySQL使用了索引扫描来做排序
        7.压缩(前缀)索引,默认只压缩字符串,减少索引大小,对于CPU密集型应用,因为扫描需要随机查找,压缩索引在MyISAM上要慢好几倍
        8.重复索引是指在相同的列上按照相同的顺序创建的相同类型的索引,应该避免这样创建重复索引
        9.索引可以让查询锁定更少的行
    D.维护索引和表
        1.CHECK TABLE检查表是否损坏,ALTER TABLE innodb_tb1 ENGINE=INNODB;修复表
        2.records_in_range()通过向存储引擎传入两个边界值获取在这个范围大概有多少条记录,对于innodb不精确
        3.info()返回各种类型的数据,包括索引的基数
        4.可以使用SHOW INDEX FROM命令来查看索引的基数
        5.B-Tree索引可能会碎片化,这会降低查询的效率

    mysql中的索引类型(index/key):
        普通索引:默认的
        主键索引:自增字段一定是,唯一性约束;主键不一定自增
        唯一索引:提供唯一性约束,可以有多个唯一索引
        全文索引:不支持中文全文检索,一般用第三方,coreseek/xunsearch
        外键索引:只有InnoDB支持,效率不高不推荐,只使用外键思想保证数据一致性和完整性
        联合索引:一般可以用于覆盖索引,不用回表查数据
    mysql索引:
        1.索引如果没有特别指明类型,一般是说b树索引,b树索引使用b树数据结构存储数据,实际上很多存储引擎使用的是b 树,每一个叶子节点都包含指向下一个叶子节点的指针,从而方便叶子节点的范围遍历
        2.底层的存储引擎也可能使用不同的存储结构,NDB集群存储引擎使用了T树,InnoDB使用的是B 树
        3.MyISAM使用前缀压缩技术使得索引更小,InnoDB按照原数据格式进行存储,MyISAM通过数据的物理位置引用被索引的行,InnoDB根据主键引用被索引的行
        4.b树意味着所有的值是按照顺序存储的,并且每一个叶子页到根的距离相同
        5.b树索引能够加快访问数据的速度,存储引擎不需要再进行全表扫描来获取需要的数据,取而代之的是从索引的根节点开始进行搜索,根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针向下层查找.通过比较节点页的值和要查找的值可以找到合适的指针进入下层子节点.树的深度和表的大小直接相关
        6.叶子节点比较特别,他们的指针指向的是被索引的数据,而不是其他的节点页
        7.b树对索引列是顺序存储的,所以很适合查找范围数据.
        8.索引对多个值进行排序的依据是,定义索引时列的顺序,比如联合索引key(a,b,c),这三个列的顺序
        9.上面的联合索引对以下查询语句有效
            全值匹配 where a=x and b=x and c=x
            最左前缀 where a=x
            匹配列前缀 where a like x%
            匹配范围值 where a>x and a<x
            精确匹配某一列范围匹配另一列 where a=x and b like x%
        10.因为索引树的节点是有序的,可以用于查询中的order by操作,如果可以按照某种方式查到值,那么也可以按这种方式排序
        11.聚簇索引:不是一种单独的索引类型,是一种数据存储方式,在同一个结构中保存b树索引和数据行,一个表只能有一个聚簇索引,innodb通过主键聚集数据,二级索引(非聚簇)需要两次索引查找,如果主键是一个随机的例如uuid,性能就会很糟糕,主键尽量使用自增id.
        12.覆盖索引:一个索引覆盖所有需要查询的字段的值,称为覆盖索引,对innodb表特别有用,查询时在explain的extra列可以看到using index,使用时也要符合最左前缀原则
        聚簇索引和非聚簇索引的区别
            聚簇索引的叶节点就是数据节点,而非聚簇索引的页节点仍然是索引检点,并保留一个链接指向对应数据块,这里可以参考为什么表需要建立主键那篇文章
        在mysql中用a,b,c三个字段建立一个复合索引a_b_c,请问以下哪个查询效率最差?
            A、select * from test where a=10 and b>50
            B、select * from test where a=10 and b>10 order by c
            C、select * from test where a=10 and b=10 order by a
            D、select * from test where a=10 and b = 10 order by c
            参考答案:B
            答案解析:
            最左匹配原则:该复合索引包含a,a_b,a_b_c。 复合索引只有在前面的字段为精确查询时,才会用上后面的复合索引,一旦出现不精确查询,则不会使用复合索引。A选项中,使用a_b索引。B选项中,使用了a_b索引,且有order by c故效率最差。C选项中,使用a_b索引。D选项中,使用a_b_c索引
        何时需要建立索引
            1.主键自动建立唯一索引。
            2.频繁作为查询条件的字段应该创建索引。
            3.查询中与其他表关联的字段,
            4.外键关系建立索引。
            5.频繁更新的字段不适合建立索引,因为更新不单单要跟新数据还要跟新索引,加重了IO的负担。
            6.where条件里用不到的字段不创建索引。
            7.在高并发下推荐创建复合索引。
            8.查询中排序的字段,排序字段如果通过索引去访问将大大提高排序速度。
            9.查询中统计或分组字段。
        主键索引和唯一索引的区别:
            1.主键创建后一定包含一个唯一性索引,唯一性索引并不一定就是主键
            2.唯一索引允许空值,而主键列不允许为空值
            3.一个表最多只能创建一个主键,但可以创建多个唯一索引
            1) 如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页
            2) 如果使用非自增主键(如身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置,此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。
            4.主要区别总结如下:主键是一种约束,唯一索引是一种索引,两者在本质上是不同的。主键创建后一定包含一个唯一性索引,主键列在创建时,已经默认为空值   唯一索引了.唯一性索引并不一定就是主键。唯一性索引列允许空值,而主键列不允许为空值。主键可以被其他表引用为外键,而唯一索引不能。一个表最多只能创建一个主键,但可以创建多个唯一索引。主键更适合那些不容易更改的唯一标识,如自动递增列、身份证号等。在 RBO 模式下,主键的执行计划优先级要高于唯一索引。 两者可以提高查询的速度。
            5.所谓的一张表多个主键,我们称之为联合主键。可以由多个列形成联合主键,但是主键只能有一个
            6.唯一索引和普通索引区别就是不能重复,唯一索引找到目标后就不再继续寻找了,普通索引还会继续去寻找下一个
        为什么要给表加上主键?
            1.一个没加主键的表,它的数据无序的放置在磁盘存储器上,一行一行的排列的很整齐.
            2.一个加了主键的表,并不能被称之为「表」。如果给表上了主键,那么表在磁盘上的存储结构就由整齐排列的结构转变成了树状结构,并且是「平衡树」结构,换句话说,就是整个表就变成了一个索引。没错,再说一遍,整个表变成了一个索引,也就是所谓的「聚集索引」。 这就是为什么一个表只能有一个主键,一个表只能有一个「聚集索引」,因为主键的作用就是把「表」的数据格式转换成「索引(平衡树)」的格式放置。
            3.通过主键去查,叶子节点就是数据行
            4.给表中多个字段加上常规的索引,那么就会出现多个独立的索引结构.字段中的数据就会被复制一份出来,用于生成索引,叶子节点是主键ID,这也就是非聚集索引.
            5.通过其他索引字段去查,那么叶子节点是主键ID,然后再去根据主键查,聚集索引(主键)是通往真实数据所在的唯一路径
            7.有一种例外可以不使用聚集索引就能查询出所需要的数据,这种非主流的方法称之为「覆盖索引」查询,也就是平时所说的复合索引或者多字段索引查询
        唯一性太差的字段不宜建立索引的原因是什么?
            因为mysql首先会将索引中的键值取出来与内存中存储表数据的页中的数据相比较,但是数据页中的数据的顺序和索引队列中键值的顺序并不是一致的。假如索引中的键值 a 先在数据页x中找到了符合的数据,然后又在数据页 y 中找到了符合条件的数据,这时 mysql 便会把数据页 x 销毁掉,把数据页 Y 读到内存中。如果这时候还有键值 b,然后键值 b 找的数据又在数据页 x 上,则 mysql 又要把数据页 x 读到内存中。也就是说从索引去寻找对应的表数据的时候是随机访问的。(实际情况应该是内存中缓存了好几页的数据,应该不只一页,但是这里假定线程内存中只存在一张页表)。这样的随机访问所造成的 io 消耗是比全表扫描的 io 消耗来得大的,还不如遍历整张表。
        全文索引:
            InnoDB引擎对FULLTEXT索引的支持是MySQL5.6新引入的特性,之前只有MyISAM引擎支持FULLTEXT索引。对于FULLTEXT索引的内容可以使用MATCH()…AGAINST语法进行查询
            MySql自带的全文索引只能对英文进行全文检索,目前无法对中文进行全文检索。如果需要对包含中文在内的文本数据进行全文检索,我们需要采用Sphinx(斯芬克斯)/Coreseek技术来处理中文
            如果查询字符串的长度过短将无法得到期望的搜索结果。MySql全文索引所能找到的词默认最小长度为4个字符。另外,如果查询的字符串包含停止词,那么该停止词将会被忽略
    explain的解释:
        select_type:SIMPLE(普通的select),PRIMARY(有子查询),UNION(有联合查询)
        table:输出行所用的表
        type:连接类型 从最好到最差的连接类型为const、eq_reg、ref、range、index和all
            (1)system:表示只有一行记录,一般不会出现,没有意义。      
            (2)const:表示通过索引一次就可以找到。      
            (3)eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条数据与之对应。      
            (4)ref:非唯一性索引扫描,返回匹配的所有行。      
            (5)range:值检索给定范围的行,between、<>、in等。eq_ref或者ref_or_null。且查询需要访问表的整行数据,即不能直接通过二级索引的元组数据获得查询结果(索引覆盖)。      
            (6)index:遍历所有索引。type列是index,如果没有覆盖全部查询列,速度慢,只有当索引的列顺序和order by子句的顺序完全一致并且所有列的排序顺序都一样,才能使用索引来做排序    
            (7)all:遍历全表
        possible_keys:显示可能应用在这张表中的索引
        key: 实际使用的索引
        key_len:使用的索引的长度。在不损失精确性的情况下,长度越短越好
            mysql索引的长度计算:
            1.所有的索引字段,如果没有设置not null,则需要加一个字节。
            2.定长字段,int占4个字节、date占3个字节、char(n)占n个字符。
            3.变长字段,varchar(n),则有n个字符 两个字节。
            4.不同的字符集,一个字符占用的字节数不同。latin1编码的,一个字符占用1个字节,gbk编码的,一个字符占用2个字节,utf8编码的,一个字符占用3个字节。utf8mb4是一个字符占4个字节
            5.使用explain语句查询到的key_len字段,可以适用于上面的计算规则,可以看到查询是否使用到了联合索引
            6.mysql优化器会对条件中的 and的前后顺序根据多列索引顺序自动纠正过来
        rows:通过索引查询到的数据量,需要扫描的行数
        extra:
            (1) Using filesort:文件内排序,说明mysql使用了外部排序。      
            (2) Using temporary:使用了保存中间结构。(order by 、group by),使用了隐式临时表   
            (3) Using index:效率不错,表示使用了索引,使用到了覆盖索引。  
    mysqldump客户端逻辑备份程序,可以生成一组sql或csv,文本,xml
        1.如果不使用--single-transaction选项,mysqldump至少需要SELECT权限,SHOW VIEW,TRIGGER和LOCK TABLES权限
        2.对于大规模备份和还原,物理备份更合适,以原始格式复制数据文件,可以快速恢复
        3.表主要是InnoDB表考虑使用MySQL Enterprise Backup产品的mysqlbackup命令;主要MyISAM表,考虑使用mysqlhotcopy
        4.mysqldump默认是逐行检索,要启用内存缓冲,使用--skip-quick,此时如果是大表内存会可能有问题
    对于 InnoDB 引擎的数据库,逻辑备份一般采用哪种方式?为什么?

    逻辑备份一般会采用全局锁,限制整库为只读。对于全部是 InnoDB 引擎的库,建议你选择使用 –single-transaction 参数,对应用会更友好。
        因为 set global readonly=true 的方式有时候会被用作其他逻辑,比如用来判断一个库是主库还是备库。另外整个库设置为 readonly 之后,如果客户端发生异常,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高。
        而 FTWRL 在执行前有读写的话,FTWRL 都会等待,直到读写执行完毕后才会执行。FTWRL 执行的时候要刷脏页的数据到磁盘,因为要保持数据的一致性。FTWRL 更适合不支持事务的存储引擎。
        而 InnoDB 是支持事务的。当使用 mysqldump 并且带上参数 –single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于 MVCC 的支持,这个过程中数据是可以正常更新的。
    全库只读,为什么不使用 set global readonly=true 的方式?
        set global readonly=true 确实可以让全库进入只读状态,但还是不建议你使用,主要有两个原因。
        一是,在有些系统中,readonly 的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。因此,修改 global 变量的方式影响面更大,因此不建议你使用。
        二是,在异常处理机制上有差异。将整个库设置为 readonly 之后,如果客户端发生异常,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高。
    utf8mb4:
        1.可以获取更好的兼容性,可以存储emoji表情符,建议使用 utf8mb4 而非 utf8,事实上,最新版的phpmyadmin默认字符集就是utf8mb4。
        2.数据库表采用utf8mb4编码,其中varchar(255)的column进行了唯一键索引,而mysql默认情况下单个列的索引不能超过767位(不同版本可能存在差异) 
        3.utf8mb4编码,一个字最多可以占用4个字节,那255*4就会超出767的字符限制了
        4.解决索引长度不够问题[Err] 1071 - Specified key was too long; max key length is 767 bytes
        设置: innodb_large_prefix=1 改为on就是限制3072 innodb_file_format=Antelope 
    mysql分区:
        1.只有InnoDB和NDB存储引擎才能这样做
        2.通过检查SHOW PLUGINS语句的输出来确定您的MySQL服务器是否支持分区,| partition | ACTIVE | STORAGE ENGINE | NULL | GPL |
    mysql分库分表:
        1.如果只是为了分页,可以考虑这种分表,就是表的id是范围性的,且id是连续的,比如第一张表id是1到10万,第二张是10万到20万,这样分页应该没什么问题。
        2.其他的分表方式,建议用sphinx先建索引,然后查询分页
        3.减小数据库的负担,缩短查询时间。mysql中有一种机制是表锁定和行锁定,是为了保证数据的完整性。表锁定表示你们都不能对这张表进行操作,必须等我对表操作完才行。行锁定也一样,别的sql必须等我对这条数据操作完了,才能对这条数据进行操作
        4.不管是分表还是垂直分库都是为了解决 写负载的 而不是读负载。 分表以后只有一种查询方式会效率高,那就是根据分表键查。其他查询条件相反会慢于没分表前的正表查询。 所以 能不分尽量不要分表。大数据量的查询可以使用读写分离,旧数据归档
        5.SELECT * FROM table WHERE id >= (SELECT id FROM table LIMIT 1000000, 1) LIMIT 10;分页
        6.当只需要一条数据时,使用limit 1 
    分表哈希:
        目的是扩展表的时候,数据不需要迁移,如果使用取模算法,扩展表以后数据需要迁移

        初始化表的配置是:
        'tableIdFormat'     => '%s_%d',
        'maxNodeNumber'     => 1024,
        'numberOfNodes'     => 16,
        表名例如 table_0,table_64,...,table_960 以64为进制进行建表

        扩充表之后的配置为:
        'tableIdFormat'     => '%s_%d',
        'maxNodeNumber'     => 1088,
        'numberOfNodes'     => 17,
        表名例如 table_0,table_64,...,table_1024 以64为进制进行建表

        哈希函数为:
        getEmailHash($email, $mod = 1000) {
            $m = md5($email, true);
            $f = 0;
            $wm = 65536 % $mod;
            for ($i = 0; $i < 4;   $i) {
                $n = ord($m[$i * 2   1]) * 256   ord($m[$i * 2]);
                $cm = $n % $mod;
                for ($j = 0; $j < $i;   $j) {
                    $cm = $cm * $wm;
                    if ($cm > $mod) {
                        $cm = $cm % $mod;
                    }
                }
                $f  = $cm;
            }
            return $f % $mod;    
        }
        function getNodeId($hash) {
            $unit = floor($maxNodeNumber / $numberOfNodes);        
            $id = floor($hash / $unit);
            return $id * $unit;
        }
    1.mysql的批量更新用法 case when
    UPDATE `post` SET 
    `parent_id` = CASE `id` 
    WHEN '1' THEN '100' 
    WHEN '2' THEN '100'
    END,`title` = CASE `id` 
    WHEN '1' THEN 'A' 
    WHEN '2' THEN 'A'
    END WHERE `id` IN ('1','2','3','4','5')
    2.MySQL提示“too many connections” ,sleep的连接太多了
    MySQL配置文件/etc/my.cnf,设置成max_connections=1000,wait_timeout=5
    3.MySQL server has gone away和wait_timeout有关系.两次sql执行间隔时间太长
    所以在new PDO的时候,要根据wait_timeout的值判断,大于的就重新new,不大于的就单例返回

    Float、Decimal 存储金额的区别:
        float用于表示浮点数值数据的大致数值数据类型。浮点数据为近似值;因此,并非数据类型范围内的所有值都能精确地表示。
        decimal带固定精度和小数位数的数值数据类型。
        使用int数据库存储的是金额的分值,显示的时候在转化为元
        使用decimalmysql中decimal存储类型的使用
        column_name  decimal(P,D);
        D:代表小数点后的位数
        P:有效数字数的精度,小数点也算一位
    mysql的text字段:
        分为TINYTEXT, TEXT, MEDIUMTEXT,LONGTEXT, 都是表示数据长度类型的一种。
        TINYTEXT: 256 bytes
        TEXT: 65,535 bytes => ~64kb
        MEDIUMTEXT: 16,777,215 bytes => ~16MB
        LONGTEXT: 4,294,967,295 bytes => ~4GB
    timestamp和datetime的区别和大坑
        1.timestamp占用4个字节;datetime占用8个字节
        2.timestamp范围1970-01-01 00:00:01.000000 到 2038-01-19 03:14:07.999999;datetime是1000-01-01 00:00:00.000000 到 9999-12-31 23:59:59.999999
        3.timestamp默认支持not null default CURRENT_TIMESTAMP自动更新当前时间;datetime 在5.6版本后才支持,需要手动指定not null default CURRENT_TIMESTAMP
        4.timestamp转成utc存储,查询再自动转回来;datetime原样存储
        存储时间精确到秒建议TIMESTAMP类型,使用4个字节;datetime类型使用8个字节,时间戳类型数据最高精确微秒(百万分之一秒),数据类型定义为timestamp(N),N取值范围为0-6,默认为0,如需要精确到毫秒则设置为Timestamp(3),如需要精确到微秒则设置为timestamp(6)
    char和varchar的区别?
        答:是一种固定长度的类型,varchar则是一种可变长度的类型,它们的区别是:
        char(M)类型的数据列里,每个值都占用M个字节,如果某个长度小于M,MySQL就会在它的右边用空格字符补足.(在检索操作中那些填补出来的空格字符将被去掉)在varchar(M)类型的数据列里,每个值只占用刚好够用的字节再加上一个用来记录其长度的字节(即总长度为L 1字节).
    exists和in的使用方式:  
        #对B查询涉及id,使用索引,故B表效率高,可用大表 -->外小内大
        select * from A where exists (select * from B where A.id=B.id);
        #对A查询涉及id,使用索引,故A表效率高,可用大表 -->外大内小
        select * from A where A.id in (select id from B);

    优化实战:
        在进行查询导出登陆日志的项目中,根据联合索引的最左前缀原则,把中间空缺的字段type加上,type in (1,2,3,4),后面的time字段的范围查询也可以用到索引了
    分布式事务的核心:
        A银行通过本地事务保证日志记录 后台线程轮询保证消息不丢失。B银行通过本地事务保证日志记录从而保证消息不重复消费!B银行在回调A银行的接口时会通知处理结果,如果转账失败,A银行会根据处理结果进行回滚
        基于MQ的方案,只达到了最终一致性。思考一下,分布式环境,能否实现强一致性呢?有没有什么方案,毕竟我们使用传统数据库本地事务习惯了,所以一看到事务二字,都会联想到ACID强一致性了
        https://www.cnblogs.com/sujing/p/11006424.html

        最终一致性
        对于那些性能要求很高,但对一致性要求不高的系统,往往不苛求系统的实时一致性,只要在允许的时间段内达到最终一致性即可,可采用事务补偿的方式。与事务在执行中发生错误后立即回滚的方式不同,事务补偿是一种事后检查补救的措施,一些常见的实现方法有:对数据进行对账检查,基于日志进行对比,定期同标准数据来源进行同步等等。事务补偿还要结合业务系统来考虑

    数据库分库分表思路:
     垂直分表是按字段分出来
     水平分表是按数据分
     1、根据数值范围
            按照时间区间或ID区间来切分。例如:按日期将不同月甚至是日的数据分散到不同的库中;将userId为1~9999的记录分到第一个库,10000~20000的分到第二个库,以此类推。某种意义上,某些系统中使用的"冷热数据分离",将一些使用较少的历史数据迁移到其他库中,业务功能上只提供热点数据的查询,也是类似的实践。
        优点:
            单表大小可控
            天然便于水平扩展,后期如果想对整个分片集群扩容时,只需要添加节点即可,无需对其他分片的数据进行迁移
            使用分片字段进行范围查找时,连续分片可快速定位分片进行快速查询,有效避免跨分片查询的问题。
        缺点:
            热点数据成为性能瓶颈。连续分片可能存在数据热点,例如按时间字段分片,有些分片存储最近时间段内的数据,可能会被频繁的读写,而有些分片存储的历史数据,则很少被查询
     2、根据数值取模
            一般采用hash取模mod的切分方式,例如:将 Customer 表根据 cusno 字段切分到4个库中,余数为0的放到第一个库,余数为1的放到第二个库,以此类推。这样同一个用户的数据会分散到同一个库中,如果查询条件带有cusno字段,则可明确定位到相应库去查询。
        优点:
            数据分片相对比较均匀,不容易出现热点和并发访问的瓶颈
        缺点:
            后期分片集群扩容时,需要迁移旧的数据(使用一致性hash算法能较好的避免这个问题)
            容易面临跨分片查询的复杂问题。比如上例中,如果频繁用到的查询条件中不带cusno时,将会导致无法定位数据库,从而需要同时向4个库发起查询,再在内存中合并数据,取最小集返回给应用,分库反而成为拖累。

    分库分表后面临的问题
        1)事务支持
            分库分表后,就成了分布式事务了。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价; 如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。
            当更新内容同时分布在不同库中,不可避免会带来跨库事务问题。跨分片事务也是分布式事务,没有简单的方案,一般可使用"XA协议"和"两阶段提交"处理。
            分布式事务能最大限度保证了数据库操作的原子性。但在提交事务时需要协调多个节点,推后了提交事务的时间点,延长了事务的执行时间。导致事务在访问共享资源时发生冲突或死锁的概率增高。随着数据库节点的增多,这种趋势会越来越严重,从而成为系统在数据库层面上水平扩展的枷锁。
        2)跨库join
        只要是进行切分,跨节点Join的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。
            1)全局表
            全局表,也可看做是"数据字典表",就是系统中所有模块都可能依赖的一些表,为了避免跨库join查询,可以将这类表在每个数据库中都保存一份。这些数据通常很少会进行修改,所以也不担心一致性的问题。
            2)字段冗余
            一种典型的反范式设计,利用空间换时间,为了性能而避免join查询。例如:订单表保存userId时候,也将userName冗余保存一份,这样查询订单详情时就不需要再去查询"买家user表"了。
            但这种方法适用场景也有限,比较适用于依赖字段比较少的情况。而冗余字段的数据一致性也较难保证,就像上面订单表的例子,买家修改了userName后,是否需要在历史订单中同步更新呢?这也要结合实际业务场景进行考虑。
            3)数据组装
            在系统层面,分两次查询,第一次查询的结果集中找出关联数据id,然后根据id发起第二次请求得到关联数据。最后将获得到的数据进行字段拼装。
            4)ER分片
            关系型数据库中,如果可以先确定表之间的关联关系,并将那些存在关联关系的表记录存放在同一个分片上,那么就能较好的避免跨分片join问题。在1:1或1:n的情况下,通常按照主表的ID主键切分
        分库分表方案产品
        3)跨节点的分页limit,count,order by,group by以及聚合函数问题
        这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。解决方案:与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。
        4)数据迁移,容量规划,扩容等问题
        来自淘宝综合业务平台团队,它利用对2的倍数取余具有向前兼容的特性(如对4取余得1的数对2取余也是1)来分配数据,避免了行级别的数据迁移,但是依然需要进行表级别的迁移,同时对扩容规模和分表数量都有限制。总得来说,这些方案都不是十分的理想,多多少少都存在一些缺点,这也从一个侧面反映出了Sharding扩容的难度。
        5)ID问题
        一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的ID无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得ID,以便进行SQL路由.
        一些常见的主键生成策略
            UUID
            使用UUID作主键是最简单的方案,但是缺点也是非常明显的。由于UUID非常的长,除占用大量存储空间外,最主要的问题是在索引上,在建立索引和基于索引进行查询时都存在性能问题。
            Twitter的分布式自增ID算法Snowflake
            在分布式系统中,需要生成全局UID的场合还是比较多的,twitter的snowflake解决了这种需求,实现也还是很简单的,除去配置信息,核心代码就是毫秒级时间41位 机器ID 10位 毫秒内序列12位。
            每个表配置不同的起始值。比如,u1 配置 auto_increment = 1,然后步长为 3。u2 配置起始值为 2,步长也为 3,u3 配置起始值为 3,步长也为 3。这样做了之后,主键 Id 都能够自动增长,且能够做到不重复。
        6)跨分片的排序分页
        般来讲,分页时需要按照指定字段进行排序。当排序字段就是分片字段的时候,我们通过分片规则可以比较容易定位到指定的分片,而当排序字段非分片字段的时候,情况就会变得比较复杂了。为了最终结果的准确性,我们需要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序,最后再返回给用户
        中间件推荐
    数据表类型有哪些
        答:MyISAM、InnoDB、HEAP、BOB,ARCHIVE,CSV等。
        MyISAM:成熟、稳定、易于管理,快速读取。一些功能不支持(事务等),表级锁。
        InnoDB:支持事务、外键等特性、数据行锁定。空间占用大,不支持全文索引等。
    如何进行SQL优化?(关于后边的解释同学们可以进行理解,到时根据自己的理解把大体意思说出来即可)
            答:(1)选择正确的存储引擎
            以 MySQL为例,包括有两个存储引擎 MyISAM 和 InnoDB,每个引擎都有利有弊。
            MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。
            InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。但是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。
            (2)优化字段的数据类型
            记住一个原则,越小的列会越快。如果一个表只会有几列罢了(比如说字典表,配置表),那么,我们就没有理由使用 INT 来做主键,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT 会更经济一些。如果你不需要记录时间,使用 DATE 要比 DATETIME 好得多。当然,你也需要留够足够的扩展空间。
            (3)为搜索字段添加索引
            索引并不一定就是给主键或是唯一的字段。如果在你的表中,有某个字段你总要会经常用来做搜索,那么最好是为其建立索引,除非你要搜索的字段是大的文本字段,那应该建立全文索引。
            (4)避免使用Select 从数据库里读出越多的数据,那么查询就会变得越慢。并且,如果你的数据库服务器和WEB服务器是两台独立的服务器的话,这还会增加网络传输的负载。即使你要查询数据表的所有字段,也尽量不要用通配符,善用内置提供的字段排除定义也许能给带来更多的便利。
            (5)使用 ENUM 而不是 VARCHAR
            ENUM 类型是非常快和紧凑的。在实际上,其保存的是 TINYINT,但其外表上显示为字符串。这样一来,用这个字段来做一些选项列表变得相当的完美。例如,性别、民族、部门和状态之类的这些字段的取值是有限而且固定的,那么,你应该使用 ENUM 而不是 VARCHAR。
            (6)尽可能的使用 NOT NULL
            除非你有一个很特别的原因去使用 NULL 值,你应该总是让你的字段保持 NOT NULL。 NULL其实需要额外的空间,并且,在你进行比较的时候,你的程序会更复杂。 当然,这里并不是说你就不能使用NULL了,现实情况是很复杂的,依然会有些情况下,你需要使用NULL值。
            (7)固定长度的表会更快
            如果表中的所有字段都是“固定长度”的,整个表会被认为是 “static” 或 “fixed-length”。 例如,表中没有如下类型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一个这些字段,那么这个表就不是“固定长度静态表”了,这样,MySQL 引擎会用另一种方法来处理。
            固定长度的表会提高性能,因为MySQL搜寻得会更快一些,因为这些固定的长度是很容易计算下一个数据的偏移量的,所以读取的自然也会很快。而如果字段不是定长的,那么,每一次要找下一条的话,需要程序找到主键。
            并且,固定长度的表也更容易被缓存和重建。不过,唯一的副作用是,固定长度的字段会浪费一些空间,因为定长的字段无论你用不用,他都是要分配那么多的空间。
            当只需要一行数据时候使用limit1
            1.从结构层: web服务器采用负载均衡服务器,mysql服务器采用主从复制,读写分离            
            2.从储存层: 采用合适的存储引擎,采用三范式            
            3.从设计层: 采用分区分表,索引,表的字段采用合适的字段属性,适当的采用逆范式,开启mysql缓存            
            4.sql语句层:结果一样的情况下,采用效率高,速度快节省资源的sql语句执行
            实时性低、消耗大的查询加缓存
            大量数据写入可以攒一些批量写入
            选用SSD作为存储介质
            使用MySQL自带分区功能,对外透明(不常用)
            分库分表降低单实例、单表的负担
            写高效的SQL语句,看看有没有写低效的SQL语句,比如生成笛卡尔积的全连接啊,大量的Group By和order by,没有limit等等.必要的时候,把数据库逻辑封装到DBMS端的存储过程里面.缓存查询结果,explain每一个sql语句。
            系统架构设计方面,表散列,把海量数据散列到几个不同的表里面.快慢表,快表只留最新数据,慢表是历史存档.集群,主服务器Read & write,从服务器read only,或者N台服务器,各机器互为Master。
    简述 MySQL 主从复制原理及配置主从的完整步骤。
        MySQL 主从是一个异步过程(网络条件上佳的话,同步效果几乎是实时),原理就是从库得到主库的 BinLog,然后执行这个 BinLog 的内容,达到两边数据库数据一致的目的。具体工作步骤如下:
        主mysql服务器将数据库更新记录到binlog中,使用自己的log dump线程将binlog先读取然后加锁,再发送到从库,在从库当读取完成,甚至在发动给从节点之前,锁会被释放;
        当从库上执行start slave命令之后,从节点会创建一个I/O线程用来连接主节点,请求主库中更新的binlog。I/O线程接收到主节点binlog dump进程发来的更新之后,保存在本地relay log中。
        从库此时还有一个SQL线程,它负责读取relay log中的内容,解析成具体的操作并执行,最终保证主从数据的一致性。
        切记!在从库上使用 show slave statusG;看到结果里的 Slave_IO_Running:Yes 和 Slave_SQL_Running:Yes,才算是同步成功,两个 YES 缺一不可。
        注意: MySQL 只读实例的 Binlog 日志是没有记录更新信息的,所以它的 Binlog 无法使用。
Redis:
    RESP 是redis客户端和服务端之前使用的一种通讯协议;
        RESP 的特点:实现简单、快速解析、可读性好
        1. 简单字符串 Simple Strings, 以 " "加号 开头
        2. 错误 Errors, 以"-"减号 开头
        3. 整数型 Integer, 以 ":" 冒号开头
        4. 大字符串类型 Bulk Strings, 以 "$"美元符号开头,长度限制512M
        5. 数组类型 Arrays,以 "*"星号开头
    五种数据类型必记:
        字符串(string), 散列(hash), 列表(list), 集合(set), 有序集合(sorted set) 
    Redis支持的数据类型?
        String字符串:
            格式: set key value
            string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
            string类型是Redis最基本的数据类型,一个键最大能存储512MB。
            Hash(哈希)
            格式: hmset name  key1 value1 key2 value2
            Redis hash 是一个键值(key=>value)对集合。
            Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
        Hash(哈希)适合存储详情类数据
            格式: hmset name  key1 value1 key2 value2
            Redis hash 是一个键值(key=>value)对集合。
            Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
        List(列表)
            Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
            可以基于lrange做分页效果,如果要取出某一条肯定是要遍历的,因为链表啊

            格式: lpush  name  value
            在 key 对应 list 的头部添加字符串元素
            格式: rpush  name  value
            在 key 对应 list 的尾部添加字符串元素
            格式: lrem name  index
            key 对应 list 中删除 count 个和 value 相同的元素
            格式: llen name 
            返回 key 对应 list 的长度
        Set(集合)
            格式: sadd  name  value
            Redis的Set是string类型的无序集合。
            集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
            可以做全局去重的功能,也可以做并交差的操作

        zset(sorted set:有序集合),适合存储列表数据,可以做top N操作,可以做延时任务
            格式: zadd  name score value
            Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
            不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
            zset的成员是唯一的,但分数(score)却可以重复。
            zadd:向Sorted Set中添加元素
            zrem:删除Sorted Set中的指定元素
            zrange:按照从小到大的顺序返回指定区间内的元素
    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 操作。
        如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。
        (4)排行榜/计数器
        Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。
        所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:
        当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:
        ZRANGE user_scores 0 10 WITHSCORES
        Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。
        (5)发布/订阅
        最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!
        位操作(大量状态数据)
        分布式锁与单线程机制(Redis单线程)
    redis的设计与实现:
        1.假如有一个用户关系模块,要实现一个共同关注功能,计算出两个用户关注了哪些相同的用户,本质上是计算两个用户关注集合的交集,如果使用关系数据库,需要
        对两个数据表执行join操作,对合并的结果执行去重distinct操作,非常复杂
        2.Redis直接内置了集合数据类型,支持对集合执行交集/并集/差集等集合计算操作,交集操作可以直接用于共同关注功能,使用之后速度更快代码量更少,可读性大大提高
        3.越来越多的疑问:五种数据类型是由什么数据结构实现的?字符串数据类型既可以存储字符串,又可以存储整数浮点数,二进制位,在内部是怎么存储这些值的?
        有些命令只能对特定数据类型执行,是如何进行类型检查的?怎样存储各种不同类型的键值对?过期键是怎样实现自动删除的?发布与订阅/脚本/事务等特性是如何实现的?使用什么模型处理客户端的命令请求?一条命令从发送到返回需要经历的步骤?
        4.第一版发布的时候还不是很完善,作者一边注释源码一边写,只介绍了内部机制和单机特性,新版添加了关于二进制位操作/排序/复制/Sentinel和集群等主题的新章节
        5.数据结构与对象,单机数据库的实现,多机数据库的实现,独立功能的实现
        6.数据库里面的每个键值对都是由对象组成的:数据库键总是字符串对象;键的值可以是字符串对象/列表对象(list object)/哈希对象(hash object)/集合对象(set object)/有序集合对象(sorted set object),这五种中的其中一种
        7.第一部分和第二部分单机功能比较重要:第一部分,简单动态字符串,链表,字典,跳跃表,整数集合,压缩列表,对象
        8.Redis自己构建了一个SDS的类型用来保存所有的字符串对象,包括键值对的键,值中存储字符串对象的底层也是SDS
    Redis有哪几种数据淘汰策略?
        noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
        allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
        volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
        allkeys-random: 回收随机的键使得新添加的数据有空间存放。
        volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
        volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
    缓存预热解决方案:
        直接写个缓存刷新页面,上线时手工操作下;
        数据量不大,可以在项目启动的时候自动进行加载;
        定时刷新缓存;

    缓存更新方案
        redis自带的过期方案
        定时去清理过期的缓存;
        当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
    redis的设计与实现-list的底层实现 链表
        1.链表提供了高效的节点重排能力,顺序性的节点访问方式,通过增删节点调整链表的长度,C语言不内置,Redis构建了自己的链表实现
        2.列表键的底层实现之一就是链表,当元素比较多,元素都是比较长的字符串,就会使用链表作为底层实现
        3.发布与订阅,慢查询,监视器等功能也用到了链表,redis本身使用链表保存多个客户端的状态信息
        4.每个链表节点使用adlist.h/listNode结构表示,通过prev和next指针组成双端链表;使用adlist.h/list结构操作更方便,提供了表头指针head,表尾指针tail,长度计数len,特定类型的函数等
        5.链表表头前置和表尾后置都是指向null,所以是无环链表,设置不同类型特定函数,可以用于保存不同类型的值

    Redis 哈希(Hash)
        Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
        Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。
        hget hset
            1.hmset user:1000 username taoshihan birthyear 1991 verified 1  //设置hash中的多个域
            2.hget user:1000 username //hget是取回单个域
            3.hgetall user:1000  //hgetall取回所有域
            4.hmget user:1000 username birthyear //hmget取回多个域
    redis是单线程的为什么那么快:
        1.数据存于内存
        2.用了多路复用I/O
    内部数据结构:
        简单动态字符串SDS
        双端链表
            双端链表及其节点的性能特征如下:
            节点带有前驱和后继指针
            链表是双向的,所以对表头和表尾操作都是O(1)
            链表节点可以会被维护,LLEN复杂度为O(1)
        哈希表
            字典的底层实现为哈希表,每个字典含有2个哈希表,一般只是用0号哈希表,1号哈希表是在rehash过程中才使用的
            哈希表使用链地址法来解决碰撞问题
            rehash可以用于扩展或者收缩哈希表
            对哈希表的rehash是分多次、渐进式进行的
        跳跃表
            由很多层结构组成,level是通过一定的概率随机产生的
            每一层都是一个有序的链表,默认是升序
            最底层的链表包含所有元素
            如果一个元素出现在Level i的链表中,则它在Level i之下的链表也都会出现
            每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素
            插入和删除的时间复杂度是O(logn),当达到了一定的数据规模之后,它的效率与红黑树差不多
    Redis 底层数据结构有一下数据类型:
        a.简单动态字符串(simple dynamic string)SDS:SDS 与 C 字符串的区别:传统的C 字符串 使用长度为N 1 的字符串数组来表示长度为N 的字符串,这样做在获取字符串长度,字符串扩展等操作的时候效率低下,杜绝缓冲区溢出
        b.链表:链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度。
        链表在Redis 中的应用非常广泛,比如列表键的底层实现之一就是链表。当一个列表键包含了数量较多的元素,又或者列表中包含的元素都是比较长的字符串时,Redis 就会使用链表作为列表键的底层实现。
        c.字典:字典,又称为符号表,关联数组,或者映射,是一种用于保存键值对的抽象数据结构。可以说Redis里所有的结构都是用字典来存储的。那么字典是如何来使先的呢?字典的结构从高层到底层实现分别是:字典(dict),字典哈希表(dictht),哈希表节点(dictEntry)
        d.跳跃表:跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。Redis 只在两个地方用到了跳跃表,一个是实现有序集合键,另外一个是在集群节点中用作内部数据结构
        e.整数集合:整数集合是集合建的底层实现之一,当一个集合中只包含整数,且这个集合中的元素数量不多时,redis就会使用整数集合intset作为集合的底层实现
        f.压缩列表:压缩列表是列表键和哈希键的底层实现之一。当一个列表键只有少量列表项,并且每个列表项要么就是小整数,要么就是长度比较短的字符串,那么Redis 就会使用压缩列表来做列表键的底层实现。redis 3.2以后,quicklist作为列表键的实现底层实现之一,代替了压缩列表

    主从复制模式下,主挂了怎么办?redis提供了哨兵模式(高可用):
        1.通过哨兵节点进行自主监控主从节点以及其他哨兵节点,发现主节点故障时自主进行故障转移
        2.三个定时监控任务
            1 每隔10s,每个S节点(哨兵节点)会向主节点和从节点发送info命令获取最新的拓扑结构
            2 每隔2s,每个S节点会向某频道上发送该S节点对于主节点的判断以及当前Sl节点的信息,同时每个Sentinel节点也会订阅该频道,来了解其他S节点以及它们对主节点的判断(做客观下线依据)
            3 每隔1s,每个S节点会向主节点、从节点、其余S节点发送一条ping命令做一次心跳检测(心跳检测机制),来确认这些节点当前是否可达
        3.选举出某一哨兵节点作为领导者
        4.故障转移(选举新主节点流程)

    Redis 有哪些架构模式?讲讲各自的特点
        单机版
        特点:简单
        问题:
        1、内存容量有限 2、处理能力有限 3、无法高可用。

        主从复制
        Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。 只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。

        特点:
        1、master/slave 角色
        2、master/slave 数据相同
        3、降低 master 读压力在转交从库
        问题:
        无法保证高可用
        没有解决 master 写的压力

        哨兵
        Redis sentinel 是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移。其中三个特性:
        监控(Monitoring):    Sentinel  会不断地检查你的主服务器和从服务器是否运作正常。
        提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
        自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。

        特点:
        1、保证高可用
        2、监控各个节点
        3、自动故障迁移
        缺点:主从模式,切换需要时间丢数据
        没有解决 master 写的压力

        集群(proxy 型):
        Twemproxy 是一个 Twitter 开源的一个 redis 和 memcache 快速/轻量级代理服务器; Twemproxy 是一个快速的单线程代理程序,支持 Memcached ASCII 协议和 redis 协议。
        特点:1、多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins 
        2、支持失败节点自动删除
        3、后端 Sharding 分片逻辑对业务透明,业务方的读写方式和操作单个 Redis 一致
        缺点:增加了新的 proxy,需要维护其高可用。
        failover 逻辑需要自己实现,其本身不能支持故障的自动转移可扩展性差,进行扩缩容都需要手动干预

        集群(直连型):
        从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。

        特点:
        1、无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。
        2、数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。
        3、可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。
        4、高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本
        5、实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色提升。

        缺点:
        1、资源隔离性较差,容易出现相互影响的情况。
        2、数据通过异步复制,不保证数据的强一致性
    是否使用过Redis集群,集群的原理是什么?
        Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
        Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。
    redis包含三种集群策略
        主从复制
          在主从复制中,数据库分为俩类,主数据库(master)和从数据库(slave)。其中主从复制有如下特点:主数据库可以进行读写操作,当读写操作导致数据变化时会自动将数据同步给从数据库。从数据库一般都是只读的,并且接收主数据库同步过来的数据,一个master可以拥有多个slave,但是一个slave只能对应一个master
        哨兵
          哨兵的作用是监控 redis系统的运行状况,他的功能如下:监控主从数据库是否正常运行,master出现故障时,自动将slave转化为master,多哨兵配置的时候,哨兵之间也会自动监控,多个哨兵可以监控同一个redis。集群Master失效,哨兵模式下的哨兵leader会根据slave-priority等级、复制偏移量最大(即复制越完整)、id等选出需要继任的slave。
        集群
          即使使用哨兵,redis每个实例也是全量存储,每个redis存储的内容都是完整的数据,浪费内存且有木桶效应。为了最大化利用内存,可以采用集群,就是分布式存储。即每台redis存储不同的内容,使用集群,只需要将每个数据库节点的cluster-enable配置打开即可。每个集群中至少需要三个主数据库才能正常运行。Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽。
    Redis的同步机制了解么?
        主从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。
        无论是初次连接还是重新连接,当建立一个从服务器时,从服务器都将从主服务器发送一个SYNC命令。接到SYNC命令的主服务器将开始执行BGSAVE,并在保存操作执行期间,将所有新执行的命令都保存到一个缓冲区里面,当BGSAVE执行完毕后,主服务器将执行保存操作所得到的.rdb文件发送给从服务器,从服务器接收这个.rdb文件,并将文件中的数据载入到内存中。之后主服务器会以Redis命令协议的格式,将写命令缓冲区中积累的所有内容都发送给从服务器。
    bgsave的原理是什么?
        你给出两个词汇就可以了,fork和cow。fork是指redis通过创建子进程来进行bgsave操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
    主从模式下宕机怎么办
        1.slave宕机相对简单,slave启动后会自动同步数据,增量同步。
        2.master宕机:手动恢复
        在从数据库中执行SLAVEOFNO ONE命令,断开主从关系并且将从库提升为主库继续服务;
        将主库重新启动后,执行SLAVEOF命令,将其设置为其他库的从库,这时数据就能更新回来
        3.哨兵功能自动恢复
        通过sentinel模式启动redis后,自动监控master/slave的运行状态, 已经被集成在redis2.4 的版本中如果Master异常,则会进行Master-Slave切换,将其中一个Slave作为Master,将之前的Master作为Slave
        基本原理是:心跳机制 投票裁决

     说说Redis哈希槽的概念?
        Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
    Redis中的管道有什么用?
        一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。
        可以解决大量数据的插入工作
        这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多POP3协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程
    redis如何防止高并发?        
        其实redis是不会存在并发问题的,因为他是单进程的,再多的命令都是一个接一个地执行的。我们使用的时候,可能会出现并发问题,比如获得和设定这一对。
    使用过Redis分布式锁么,它是怎么实现的?
        先拿setnx「set if not exists」的缩写)来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?
        set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!
    假如Redis里面有1亿个key,其中有10万个key是以固定的前缀开头的,如何将这些全部找出来?
        使用keys命令可以扫出指定模式的key列表。
        但是,如果这个redis正在给线上的业务提供服务,因为redis的单线程的,所以keys命令会导致线程阻塞一段时间,线上服务会停顿,直到命令执行完毕,服务才能恢复。
        为了解决这个问题,可以使用scan命令,scan命令可以无阻塞的提取出指定模式的key列表,会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys命令长。
    使用过Redis做异步队列么,你是怎么用的?有什么缺点?
        一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
        缺点:在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。
        能不能生产一次消费多次呢?
        使用pub/sub主题订阅者模式,可以实现1:N的消息队列。
    Redis实现消息队列
        普通队列:一般使用list结构作为队列,rpush生产消息,lpop消费消息,blpop阻塞消费。
        消费多次:生产一次消费多次的情况使用发布/订阅模式
        延时队列:使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理
    延迟任务的场景是:
        1.现有的解决方案是?
            通过linux的crontab触发定时任务
            扫描业务表,筛选出符合条件的数据对其进行操作
        2.存在的问题是什么?
        由于每种类型的任务都设有扫描间隔,任务不能精确处理
        扫描业务库,影响业务正常操作
        任务的执行过于密集,容易导致服务器间隔性压力
        存在系统单点,触发定时调度的服务挂了,所有任务都不会执行
        系统不具容错能力,一旦错过了,任务就不会再被执行
        没有统一的视图来查看任务的执行情况
        没有告警来提示失败的任务

        有序集合(Sorted Set)是Redis提供的一种数据结构,具有set和hash的特点。其中每个元素都关联一个score,并以这个score来排序。其内部实现用到了两个数据结构:hash table和 skip list(跳跃表)
        将延迟任务加到Sorted Set,将延迟时间设为score
        启动一个线程不断判断Sorted Set中第一个元素的score是否大于当前时间
        如果大于,从Sorted Set中移除任务并添加到执行队列中
        如果小于,进行短暂休眠后重试
        ZRANGE salary 0 -1 WITHSCORES             # 显示整个有序集成员
        1) "jack"
        2) "3500"
        3) "tom"
        4) "5000"
        5) "boss"
        6) "10086"
        redis 127.0.0.1:6379> ZRANGE salary 1 2 WITHSCORES              # 显示有序集下标区间 1 至 2 的成员
        1) "tom"
        2) "5000"
        3) "boss"
        4) "10086"
        redis 127.0.0.1:6379> ZRANGE salary 0 200000 WITHSCORES         # 测试 end 下标超出最大下标时的情况
        1) "jack"
        2) "3500"
        3) "tom"
        4) "5000"
        5) "boss"
        6) "10086"
        redis > ZRANGE salary 200000 3000000 WITHSCORES                  # 测试当给定区间不存在于有序集时的情况

    消息队列适用于异步/串行化处理任务。架构上可用于解耦消息通讯。
        按角色分为生产者和消费者:
        生产者,产生消息的应用
        消费者,处理消息的应用
        实现:Redis、Beanstalkd、RabbitMQ、RocketMQ、Kafka等
    什么是缓存穿透?
    一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。

    如何避免缓存穿透?
    1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
    2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。

    缓存雪崩问题?
    当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
    如何避免缓存雪崩?
        1.在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
        2.做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
        3.不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
        4.如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。
    解决大量重建缓存问题
        1.互斥锁:此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可,
        2.永不过期:
        从缓存层面来看,确实没有设置过期时间,所以不会出现热点 key 过期后产生的问题,也就是“物理”不过期。
        从功能层面来看,为每个 value 设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。
        3.缺点
        互斥锁:这种方案思路比较简单,但是存在一定的隐患,如果构建缓存过程出现问题或者时间较长,可能会存在死锁和线程池阻塞的风险,但是这种方法能够较好的降低后端存储负载并在一致性上做的比较好。
        永不过期:这种方案由于没有设置真正的过期时间,实际上已经不存在热点 key 产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。
    什么是Redis持久化?Redis有哪几种持久化方式?优缺点是什么?
        持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
        Redis 提供了两种持久化方式:RDB(默认) 和AOF ,RDB内存快照和AOF日志文件
        1.RDB:
        rdb是Redis DataBase缩写功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数
        2.AOF:
        Aof是Append-only file缩写每当执行服务器(定时)任务或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作
            WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件
            SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
        存储结构:
        内容是redis通讯协议(RESP )格式的命令文本存储。
        比较:
        1、aof文件比rdb更新频率高,优先使用aof还原数据。
        2、aof比rdb更安全也更大
        3、rdb性能比aof好
        4、如果两个都配了,会优先使用AOF模式
    redis中的持久化方案
        RDB:快照形式,定期把内存中当前时刻的数据保存到磁盘。Redis默认支持的持久化方案。速度快但是服务器断电的时候会丢失部分数据
        AOF:append only file。把所有对redis数据库操作的命令,增删改操作的命令。保存到文件中。数据库恢复时把所有的命令执行一遍即可。两种持久化方案同时开启使用AOF文件来恢复数据库.能保证数据的完整性,但是速度慢。
    Redis相比memcached有哪些优势?
        (1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
        (2) redis的速度比memcached快很多
        (3) redis可以持久化其数据
    Memcache与Redis的区别都有哪些?
        1)存储方式
        Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。
        Redis可以把数据保存在硬盘上,用来保证数据的持久性。
        2)数据类型
        Memcache的数据类型比较简单。
        Redis有复杂的数据类型。
        3)使用底层模型不同
        它们之间底层的实现方式 以及与客户端之间通信的应用协议不一样。
        4)value大小
        redis最大可以达到1GB,而memcache只有1MB
    Redis常见性能问题和解决方案?
        (1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
        (2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
        (3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
        (4) 尽量避免在压力很大的主库上增加从库
        (5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
        这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。
    redis缓存一个列表
        * 1、实现关于新闻列表缓存 、收藏 、点赞 、视频点击等功能使用redis缓存技术
        * 2、列表新闻数据缓存模式 , 采用sorted set   hash 使用zadd 缓存文章id , hset 缓存文章标题等详情信息
        $redis->zadd($videoIdtablePrefix , time() $key  ,   $value['id']);
        $redis->hset($hashKey , $articleId , serialize($value)); 

        $redis->ZREVRANGE($videoIdtablePrefix ,$start ,$end); // 获取最新的分页集合 
        * 3、视屏点赞存储在hash 中
        * 4、用户对单个视频的收藏和点赞使用hash 
        * 5、视频点击统计使用hash ,一小时同步数据
        * 6、后台抓取回来的数据缓存问题

Memcached
    memcached是如何分配内存的?为什么不用malloc/free!?究竟为什么使用slab呢?
        实际上,这是一个编译时选项。默认会使用内部的slab分配器。您确实确实应该使用内建的slab分配器。最早的时候,memcached只使用 malloc/free来管理内存。然而,这种方式不能与OS的内存管理以前很好地工作。反复地malloc/free造成了内存碎片,OS最终花费大量的时间去查找连续的内存块来满足malloc的请求,而不是运行memcached进程。如果您不同意,当然可以使用malloc!只是不要在邮件列表中抱怨啊:)

        slab分配器就是为了解决这个问题而生的。内存被分配并划分成chunks,一直被重复使用。因为内存被划分成大小不等的slabs,如果item 的大小与被选择存放它的slab不是很合适的话,就会浪费一些内存。Steven Grimm正在这方面已经做出了有效的改进。
    memcached能保证数据存储的原子性吗?
        1.所有的被发送到memcached的单个命令是完全原子的。如果您针对同一份数据同时发送了一个set命令和一个get命令,它们不会影响对方。它们将被串行化、先后执行。即使在多线程模式,所有的命令都是原子的,除非程序有bug:) 
        2.命令序列不是原子的。如果您通过get命令获取了一个item,修改了它,然后想把它set回memcached,我们不保证这个item没有被其他进程(process,未必是操作系统中的进程)操作过。在并发的情况下,您也可能覆写了一个被其他进程set的item。
        memcached 1.2.5以及更高版本,提供了gets和cas命令,它们可以解决上面的问题。如果您使用gets命令查询某个key的item,memcached会给您返回该item当前值的唯一标识。3.如果您覆写了这个item并想把它写回到memcached中,您可以通过cas命令把那个唯一标识一起发送给 memcached。如果该item存放在memcached中的唯一标识与您提供的一致,您的写操作将会成功。如果另一个进程在这期间也修改了这个 item,那么该item存放在memcached中的唯一标识将会改变,您的写操作就会失败。
    memcached最大能存储多大的单个item?(1Mbyte)
        Memcached的内存存储引擎(引擎将来可插拔…),使用slabs来管理内存。内存被分成大小不等的slabs chunks(先分成大小相等的slabs,然后每个slab被分成大小相等chunks,不同slab的chunk大小是不相等的)。chunk的大小依次从一个最小数开始,按某个因子增长,直到达到最大的可能值。
        如果最小值为400B,最大值是1MB,因子是1.20,各个slab的chunk的大小依次是:slab1 – 400B slab2 – 480B slab3 – 576B …
        slab中chunk越大,它和前面的slab之间的间隙就越大。因此,最大值越大,内存利用率越低。Memcached必须为每个slab预先分配内存,因此如果设置了较小的因子和较大的最大值,会需要更多的内存。
        还有其他原因使得您不要这样向memcached中存取很大的数据…不要尝试把巨大的网页放到mencached中。把这样大的数据结构load和unpack到内存中需要花费很长的时间,从而导致您的网站性能反而不好。
    memcached的cache机制是怎样的?
        Memcached主要的cache机制是LRU(最近最少用)算法 超时失效。当您存数据到memcached中,可以指定该数据在缓存中可以呆多久。如果memcached的内存不够用了,过期的slabs会优先被替换,接着就轮到最老的未被使用的slabs。
    memcached如何处理容错的?
        不处理!:) 在memcached节点失效的情况下,集群没有必要做任何容错处理。如果发生了节点失效,应对的措施完全取决于用户。节点失效时,下面列出几种方案供您选择:
        忽略它! 在失效节点被恢复或替换之前,还有很多其他节点可以应对节点失效带来的影响。
        把失效的节点从节点列表中移除。做这个操作千万要小心!在默认情况下(余数式哈希算法),客户端添加或移除节点,会导致所有的缓存数据不可用!因为哈希参照的节点列表变化了,大部分key会因为哈希值的改变而被映射到(与原来)不同的节点上。
        启动热备节点,接管失效节点所占用的IP。这样可以防止哈希紊乱(hashing chaos)。
        如果希望添加和移除节点,而不影响原先的哈希结果,可以使用一致性哈希算法(consistent hashing)。您可以百度一下一致性哈希算法。支持一致性哈希的客户端已经很成熟,而且被广泛使用。去尝试一下吧!
        两次哈希(reshing)。当客户端存取数据时,如果发现一个节点down了,就再做一次哈希(哈希算法与前一次不同),重新选择另一个节点(需要注意的时,客户端并没有把down的节点从节点列表中移除,下次还是有可能先哈希到它)。如果某个节点时好时坏,两次哈希的方法就有风险了,好的节点和坏的节点上都可能存在脏数据(stale data)。
    Memcached服务分布式集群如何实现?
        特殊说明:Memcached集群和web服务集群是不一样的,所有Memcached的数据总和才是数据库的数据。每台Memcached都是部分数据。
        (一台memcached的数据,就是一部分mysql数据库的数据)
        a、程序端实现
        程序加载所有mc的ip列表,通过对key做hash (一致性哈希算法)
        例如:web1 (key)===>对应A,B,C,D,E,F,G.....若干台服务器。(通过哈希算法实现)
        b、负载均衡器
        通过对key做hash (一致性哈希算法)
        一致哈希算法的目的是不但保证每个对象只请求一个对应的服务器,而且当节点宕机,缓存服务器的更新重新分配比例降到最低。
    一致性Hash算法解释
        1.在解决分布式系统中负载均衡的问题时候可以使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡的作用。
        2.普通的余数hash(hash(比如用户id)%服务器机器数)算法伸缩性很差,当新增或者下线服务器机器时候,用户id与服务器的映射关系会大量失效。一致性hash则利用hash环对其进行了改进。

        先准备0~2^32的圆盘
        将存储数据key的hash值映射到圆盘上
        将服务器标识的hash值也映射到圆盘上
        顺时针查找,将数据存储到第一个找到的服务器上

    简述Memcached内存管理机制原理?
        a.malloc的全称是memory allocation ,中文叫动态内存分配,当无法知道内存具体位置的时候,想要绑定真正的内存空间,就需要用到动态的分配内存。
        早期的Memcached内存管理方式是通过malloc的分配的内存,使用完后通过free来回收内存,这种方式容易产生内存碎片,并降低操作系统对内存的管理效率。加重操作系统内存管理器的负担,最坏的情况下,会导致操作系统比memcached进程本身还慢,为了解决这个问题,Slab Allocation内存分配机制就延生了。
        b.现在Memcached利用Slab Allocation机制来分配和管理内存。Slab Allocation机制原理是按照预先规定的大小,将分配给memcached的内存分割成特定长度的内存块(chunk),再把尺寸相同的内存块,分成组(chunks slab class),这些内存块不会释放,可以重复利用。
        c.slab allocator还有重复使用已分配的内存的目的。 也就是说,分配到的内存不会释放,而是重复利用。
        Slab Allocation的主要术语
        Page:分配给Slab的内存空间,默认是1MB。分配给Slab之后根据slab的大小切分成chunk。
        Chunk:用于缓存记录的内存空间。
        Slab Class:特定大小的chunk的组。
        d.memcached根据收到的数据的大小,选择最适合数据大小的slab(图2)。 memcached中保存着slab内空闲chunk的列表,根据该列表选择chunk, 然后将数据缓存于其中。
        e.
        Slab Allocator解决了当初的内存碎片问题,但新的机制也给memcached带来了新的问题。这个问题就是,由于分配的是特定长度的内存,因此无法有效利用分配的内存。 例如,将100字节的数据缓存到128字节的chunk中,剩余的28字节就浪费了
        f.使用Growth Factor对Slab Allocator内存管理机制进行调优
    memcache 的数据删除机制:
        当数据过期时,memcache 并不会直接从内存中删除数据,因为 memcache 并不会回收已经分配的内存,而只是在 get 数据时检查时间戳是否过期,如果过期那么客户端就不可见,但是原来分配的内存可以重复使用,这叫做惰性失效。所以 memcache 不会对过期的数据进行监视,因此也就节省了 cpu 的资源。
        但是在 memcache 中使用 LRU 机制进行删除数据,即最近最少使用。通过计数器来记录哪些数据最少被使用来删除它,所以也有可能删除一些尚未过期或者永久有效的数据。
        在每个 slab 中数据都是存放在链表上的,链表有 head 和 tail 指针,分别指向最老和最新的数据。当 LRU 机制启动时,两个指针同时发挥作用查询失效数据,如果没有失效数据那么就会删除最近的最少被使用的数据。
        LRU 只针对每个 slab,并不针对整体。只有在 slab 不能分配新的 page 内存时才会调用 LRU。
        一种有效缓解使用 LRU 的方法是
        1. 避免大对象
        如果系统上只有及个别几个大对象的话,会浪费内存空间,因为 Slab 申请了 Page 是不能释放内存的,及个别大对象会导致 Slab 申请了内存资源而得不到充分的利用。
        2. 调整增长因子
        根据项目的需求调整增长因子,使内存充分利用。

    Memache 和 memcached 的区别?
        memcached 支持 Binary Protocol,而 memcache 不支持,意味着 memcached 会有更高的性能。
        不过,还需要注意的是,memcached 目前还不支持长连接。
        memcached 比 memcache 支持更多的 memcache 协议,大概也就是说 memcached 有更多的方法,比如 getMulti () 和 setMulti () 函数非常有用,但是 memcache 并不支持。
        Memcache 内存满了怎么办?
        存到硬盘,操作系统都有虚拟内存,当内存满了,都会存到虚拟内存里而虚拟内存是存放在硬盘上,所以 MEMCACHE 满了会存到硬盘要么就增加内存空间,要么将一些临时用的数据操作完之后,立即销毁,避免占内存。
    redis相比memcached有哪些劣势
        由于Redis只使用单核,而Memcached可以使用多核;在100k以上的数据中,Memcached性能要高于Redis;对于key-value这样简单的数据储存,memcache的内存使用率更高。如果采用hash结构,redis的内存使用率会更高。
PHP-FPM:
    cgi:一种协议,CGI/1.1 标准
    fastcgi:一种升级版协议,常驻型
    php-cgi:解释PHP脚本的程序,实现了fastcgi协议,进程管理较差
    php-fpm:是fastcgi进程的管理器,升级版php-cgi,升级了进程调度
    php-fpm控制子进程的个数:
        pm = dynamic #对于专用服务器,pm可以设置为static。
        #如何控制子进程,选项有static和dynamic。如果选择static,则由pm.max_children指定固定的子进程数。如果选择dynamic,则由下开参数决定:
        pm.max_children #,子进程最大数
        pm.start_servers #,启动时的进程
        pm.min_spare_servers #,保证空闲进程数最小值,如果空闲进程小于此值,则创建新的子进程
        pm.max_spare_servers #,保证空闲进程数最大值,如果空闲进程大于此值,此进行清理
    fastcgi通过端口监听和通过文件监听的区别
        监听方式    形式    nginx链接fastcgi方式
        端口监听    fastcgi_pass 127.0.0.1:9000    TCP链接
        文件监听    fastcgi_pass /tmp/php_cgi.sock    Unix domain Socket
    php-fpm开启慢查询日志:
        /etc/php/7.0/fpm/pool.d/www.conf 
        slowlog = /var/log/php-fpm-$pool.log.slow
        request_slowlog_timeout = 5
    配置php-fpm的php的错误日志:
        /usr/local/php7.1.10/etc/php-fpm.d/www.conf
        php_admin_value[error_log] = /var/log/php-fpm/fpm-php.www.log
        php_admin_flag[log_errors] = on
        php-fpm的默认错误日志,在这里配置/etc/php/7.0/fpm/php-fpm.conf error_log,只是fpm启动信息的日志,没有php的错误日志
    配置临时文件目录:
        env[TMP] = /data1/phptmp
        env[TMPDIR] = /data1/phptmp
        env[TEMP] = /data1/phptmp
    PHP-FPM的优点:
        1.先进的进程控制,优雅的停止启动
        2.能够使用不同的uid/gid/chroot/environment启动worker,使用不同的php.ini,监听不同的端口
        3.stdout stderr日志记录
        4.opcode cache破坏的情况下紧急重启
        5.加速上传支持
        6.slowlog慢日志记录脚本,可以记录PHP跟踪和远程进程的execute_data, ptrace或者类似工具读取和分析
        7.fastcgi_finish_request()刷新所有数据,当在做耗时操作的时候,比如视频转换和统计处理,在fastcgi_finish_request()之后,该脚本仍将占用FPM进程。因此,对于长时间运行的任务过度使用它可能会占用所有FPM线程,直到pm.max_children
        8.动态静态子进程产生
        9.基础的SPAI状态,基于php.ini的配置文件
    php-fpm优化方法
        php-fpm存在两种方式,一种是直接开启指定数量的php-fpm进程,不再增加或者减少;
        另一种则是开始时开启一定数量的php-fpm进程,当请求量变大时,动态的增加php-fpm进程数到上限,当空闲时自动释放空闲的进程数到一个下限。
        这两种不同的执行方式,可以根据服务器的实际需求来进行调整。

        要用到的一些参数,分别是pm、pm.max_children、pm.start_servers、pm.min_spare_servers和pm.max_spare_servers。
        pm表示使用那种方式,有两个值可以选择,就是static(静态)或者dynamic(动态)。
        pm.max_children:静态方式下开启的php-fpm进程数量,在动态方式下他限定php-fpm的最大进程数(这里要注意pm.max_spare_servers的值只能小于等于pm.max_children)
        pm.start_servers:动态方式下的起始php-fpm进程数量。
        pm.min_spare_servers:动态方式空闲状态下的最小php-fpm进程数量。
        pm.max_spare_servers:动态方式空闲状态下的最大php-fpm进程数量。
        如果dm设置为static,那么其实只有pm.max_children这个参数生效。系统会开启设置的数量个php-fpm进程。
        如果dm设置为dynamic,4个参数都生效。系统会在php-fpm运行开始时启动pm.start_servers个php-fpm进程,然后根据系统的需求动态在pm.min_spare_servers和pm.max_spare_servers之间调整php-fpm进程数。

        那么,对于服务器,选择哪种执行方式比较好呢?事实上,跟Apache一样,运行的PHP程序在执行完成后,或多或少会有内存泄露的问题。这也是为什么开始时一个php-fpm进程只占用3M左右内存,运行一段时间后就会上升到20-30M的原因了。
        所以,动态方式因为会结束掉多余的进程,可以回收释放一些内存,所以推荐在内存较少的服务器或者VPS上使用。具体最大数量根据 内存/20M 得到。
        比如说512M的VPS,建议pm.max_spare_servers设置为20(512*0.8/20)。至于pm.min_spare_servers,则建议根据服务器的负载情况来设置,比较合适的值在5~10之间。

        然后对于比较大内存的服务器来说,设置为静态的话会提高效率。
        因为频繁开关php-fpm进程也会有时滞,所以内存够大的情况下开静态效果会更好。数量也可以根据 内存/30M 得到。
        比如说2GB内存的服务器,可以设置为50;4GB内存可以设置为100等。
        比如,如果是512M的vps,设置的参数如下:
        pm=dynamic
        pm.max_children=20
        pm.start_servers=5
        pm.min_spare_servers=5
        pm.max_spare_servers=20
        可以最大的节省内存并提高执行效率
    fastcgi 与 cgi 的区别?
            1)cgi,web 服务器会根据请求的内容,然后会 fork 一个新进程来运行外部 c 程序(或 perl 脚本…), 这个进程会把处理完的数据返回给 web 服务器,最后 web 服务器把内容发送给用户,刚才 fork 的进程也随之退出。
            如果下次用户还请求该动态脚本,那么 web 服务器又再次 fork 一个新进程,周而复始的进行。
            2)fastcgi,web 服务器收到一个请求时,他不会重新 fork 一个进程(因为这个进程在 web 服务器启动时就开启了,而且不会退出),web 服务器直接把内容传递给这个进程(进程间通信,但 fastcgi 使用了别的方式,tcp 方式通信),这个进程收到请求后进行处理,把结果返回给 web 服务器,最后自己接着等待下一个请求的到来,而不是退出。
            3)综上,差别在于是否重复 fork 进程,处理请求。

    CGI模式:CGI一般是可执行程序,例如exe文件,每次都fork一个进程来运行外部的exe文件,并且只能处理一个用户请求,处理完成就会退出.当用户请求数量非常多时,会频繁的fork进程和退出进程,占用大量系统的资源效能低下.
    ISAPI模式:ISAPI是微软提供的一套标准,PHP的ISAPI模式意思是PHP在windows系统上的IIS进行配合的运行模式,在PHP5.3之后不再支持,php5isapi.dll文件,PHP进程和IIS进程合一块
    APACHE2HANDLER模式:PHP作为Apache的模块,PHP进程和Apache进程合一块
    FastCGI模式:在web服务器启动时候,FastCGI处理进程就开启而且不会退出.接收到请求后,服务器通过TCP或者本地socket直接把内容传递给FastCGI进程,不需要每次都fork进程

    PHP运行原理
        FastCGI与cgi区别:cgi是请求过来再初始化进程并执行代码,FastCGI预先初始化一些进程,请求过来可以直接分发处理,并且进程重复使用,实现了一个进程池。
        与php-fpm关系:php-fpm是一个被纳入php核心的FastCGI进程管理程序(FastCGI Process Manager),FastCGI模式的关键部分是对fastcgi进程的有效管理。
        与nginx通讯:nginx通过tcp socket或者unix socket与fastcgi进程通讯。
    ISAPI 模式运行 PHP
        ISAPI模式是微软提供的一套标准,PHP的ISAPI模式意思是PHP在windows系统上的IIS进行配合的运行模式
    下列哪种PHP运行模式在PHP5.3之后不再支持?
        A、CGI
        B、FASTCIG
        C、ISAPI
        D、CLI
        参考答案:C
        答案解析:
        在PHP5.3以后,PHP不再有ISAPI模式,安装后也不再有php5isapi.dll这个文件。要在IIS6上使用高版本PHP,必须安装FastCGI扩展,然后使IIS6支持FastCGI。
    php-fpm的配置:
            [global]
            pid = run/php-fpm.pid
            error_log = log/php-fpm.log
            log_level = notice
            #错误级别. 可用级别为: alert(必须立即处理), error(错误情况), warning(警告情况), notice(一般重要信息), debug(调试信息). 默认: notice.
            rlimit_files = 65535
            #设置核心rlimit最大限制值.
            [www]
            user = joy
            group = joy
            listen = 127.0.0.1:9000
            #fpm监听端口,即nginx中php处理的地址
            listen.backlog = 2048
            #backlog数,-1表示无限制,由操作系统决定,此行注释掉就行。
            pm = dynamic
            pm.max_children = 1024
            #,子进程最大数
            pm.start_servers = 10
            #控制服务启动时创建的进程数
            pm.min_spare_servers = 10
            #,保证空闲进程数最小值,如果空闲进程小于此值,则创建新的子进程
            pm.max_spare_servers = 60
            #保证空闲进程数最大值,如果空闲进程大于此值,此进行清理
            pm.max_requests = 102400
            #设置每个子进程重生之前服务的请求数. 对于可能存在内存泄漏的第三方模块来说是非常有用的. 如果设置为 ‘0’ 则一直接受请求. 等同于 PHP_FCGI_MAX_REQUESTS 环境变量. 默认值: 0.
            request_terminate_timeout = 10s
            #设置单个请求的超时中止时间. 该选项可能会对php.ini设置中的’max_execution_time’因为某些特殊原因没有中止运行的脚本有用. 设置为 ‘0’ 表示 ‘Off’.当经常出现502错误时可以尝试更改此选项。
            request_slowlog_timeout = 10s
            #当一个请求该设置的超时时间后,就会将对应的PHP调用堆栈信息完整写入到慢日志中. 设置为 ‘0’ 表示 ‘Off’
            slowlog = var/log/$pool.log.slow
            #慢请求的记录日志,配合request_slowlog_timeout使用
            设置”max_children”也需要根据服务器的性能进行设定,一台服务器正常情况下每一个php-cgi所耗费的内存在20M左右,因 此我的”max_children”我设置成40个,20M*40=800M也就是说在峰值的时候所有PHP-CGI所耗内存在800M以内,低于我的有 效内存1Gb。而如果我的”max_children”设置的较小,比如5-10个,那么php-cgi就会“很累”,处理速度也很慢,等待的时间也较 长。如果长时间没有得到处理的请求就会出现504 Gateway Time-out这个错误,而正在处理的很累的那几个php-cgi如果遇到了问题就会出现502 Bad gateway这个错误。
            nginx会直接把 请求转交给php-fpm,而php-fpm再分配php-cgi进程来处理相关的请求,之后再依次返回,最后由nginx把结果反馈给客户端浏览器
            chroot =
            #启动时的Chroot目录. 所定义的目录需要是绝对路径. 如果没有设置, 则chroot不被使用.
            chdir =
            #设置启动目录,启动时会自动Chdir到该目录. 所定义的目录需要是绝对路径. 默认值: 当前目录,或者/目录(chroot时)
            catch_workers_output = yes
            #重定向运行过程中的stdout和stderr到主要的错误日志文件中. 如果没有设置, stdout 和 stderr 将会根据FastCGI的规则被重定向到 /dev/null . 默认值: 空.
    FPM初始化失败:
        前面那行就是错误原因
        lsof看看哪个进程在占用文件
    swoole与fastcgi区别:
        swoole接管了nginx和php-fpm这部分的功能,由类似的进程结构实现。避免了fastcgi每次请求初始化后所有文件都需要重新加载。
        进程结构:一个master(相当于nginx角色),一个manager(相当于fpm角色),若干worker(相当于fastcgi进程),若干taskWorker(用于异步处理长时间的任务)。异步事件模型,并实现了一些异步IO库。2.0开始内置协程(Coroutine)
    学习swoole需要了解的东西
        了解Linux操作系统进程和线程的概念
        了解Linux进程/线程切换调度的基本知识
        了解进程间通信的基本知识,如管道、UnixSocket、消息队列、共享内存
        SOCKET
        了解SOCKET的基本操作如accept/connect、send/recv、close、listen、bind
        了解SOCKET的接收缓存区、发送缓存区、阻塞/非阻塞、超时等概念
        IO复用
        了解select/poll/epoll
        了解基于select/epoll实现的事件循环,Reactor模型
        了解可读事件、可写事件
        TCP/IP网络协议
        了解TCP/IP协议
        了解TCP、UDP传输协议
        调试工具
        使用 gdb 调试Linux程序
        使用 strace 跟踪进程的系统调用
        使用 tcpdump 跟踪网络通信过程
        其他Linux系统工具,如ps、lsof、top、vmstat、netstat、sar、ss等
    rpc框架:基于tcp传输,使用socket来,制定一个协议
    四种IO模型
        1.同步阻塞IO(Blocking IO):即传统的IO模型。
        2.同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。
        3.IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。
        4.异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。
        同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。
        阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。

        同步阻塞的伪代码:
            read(socket, buffer);
            process(buffer);
        同步非阻塞的伪代码,要求socket被设置为NONBLOCK:
            while(read(socket, buffer) != SUCCESS)
            process(buffer);
        异步阻塞(IO多路复用,select,poll,epoll都是IO多路复用的机制)
            select(socket);
            while(1) 
                sockets = select();
                for(socket in sockets) {
                    if(can_read(socket)) 
                        read(socket, buffer);
                        process(buffer);
 

    1.vmstat指令查看操作系统每秒进程切换的次数。system-- -----in 每秒中断;cs 每秒上下文切换 数量
    vmstat 报告进程,内存,内存分页,IO,traps,CPU活动
        procs -----------r列 等待cpu进程数;b列 不可中断休眠进程数
        memory---------- swpd列 换出到磁盘的块数;free 空闲块数;buff 缓冲区块数;cache 缓存块数
        swap-- -----si 换入;so 换出 磁盘
        io---- --bi 读取;bo 写出 块数
        system-- -----in 每秒中断;cs 每秒上下文切换 数量
        cpu----- 花费的百分比;st 是虚拟机偷走的;
        内存不足的表现:
        free memory 急剧减少,回收buffer和cache也无济于事,大量使用交换分区(swpd),页面交换(swap)频繁,读写磁盘数量(io)增多,缺页中断(in)增多,上下文切换(cs)次数增多,等待IO的进程数(b)增多,大量CPU时间用于等待IO(wa)。

        CPU密集型:cpu的us列值高,sy列cpu系统利用率
        IO密集型:procs的值高,cpu的wa值高
        发生内存交换:swap的si so有很高的值
        空闲的机器:cpu的idle列是100%

    2.pcntl没有提供进程间通信的功能
    pcntl不支持重定向标准输入和输出
    pcntl只提供了fork这样原始的接口,容易使用错误

    以Apache模块的方式安装PHP,在文件http.conf中首先要怎样动态装载PHP模块,然后再用什么语句使得Apache把所有扩展名为php的文件都作为PHP脚本处理。
        答案:动态装载PHP模块:LoadModule php5_module "c:/php/php5apache2.dll"
        把所有扩展名为php的文件都作为ppp脚本处理: 
        AddType application/x-httpd-php-source .phps 
        AddType application/x-httpd-php .php .php5 .php4 .php3 .phtml
        NGINX:
    PATH_INFO的配置:
        1.PATH_INFO是一个CGI 1.1的标准,不要误解为nginx的功能
        2.PHP对该标准进行了支持,PHP中有两个pathinfo,环境变量$_SERVER['PATH_INFO'];pathinfo() 函数
        3.pathinfo() 函数以数组的形式返回文件路径的信息,先不管它
        4.nginx的配置项是对$_SERVER['PATH_INFO]值进行设置,如果不配置默认是没有的
        5.因为路径部分是这样的index.php/111,所以location ~ .php {} 把php后面的$必须以php结尾去掉
        6.传递PATH_INFO参数:
        fastcgi_param  PATH_INFO $fastcgi_path_info; 此时
         ["PHP_SELF"]=>string(3) "NFO"
         ["PATH_INFO"]=>string(3) "NFO"
        7.
        fastcgi_split_path_info ^((?U). .php)(/?. )$;此时
        ["PATH_INFO"]=>"/111"
        ["PHP_SELF"]=>"/index.php/111"
        8.重写隐藏index.php,如果请求的是文件名才重写
        if (!-e $request_filename) {
            rewrite ^/(.*)$ /index.php/$1 last;
            break;
        }
        此时:
         ["PATH_INFO"]=>"/111"
         ["PHP_SELF"]=>"/index.php/111"
        REQUEST_URI是域名后面的url所有的部分,  ["REQUEST_URI"]=>string(24) "/test/server.php/ss?a=ss"

    nginx可以用信号控制:
        kill -s HUP 8587 将HUP信号发送到主进程,使用新配置启动新的工作进程,正常关闭旧工作进程,即打开日志文件和新的侦听套接字。
        kill -s USR2 8587 即时升级可执行文件
    location 
        location = /uri    =开头表示精确匹配,只有完全匹配上才能生效。
        location ^~ /uri   ^~ 开头对URL路径进行前缀匹配,并且在正则之前。
        location ~ pattern  ~开头表示区分大小写的正则匹配。
        location ~* pattern  ~*开头表示不区分大小写的正则匹配。
        location /uri     不带任何修饰符,也表示前缀匹配,但是在正则匹配之后。
        location /      通用匹配,任何未匹配到其它location的请求都会匹配到,相当于switch中的default。  

    Nginx 的特点:
        1.处理静态文件
        2.反向代理加速
        3.fastCGI,简单的负载均衡和容错
        4.模块化的结构
        5.分阶段资源分配技术,使得它的 CPU 与内存占用率非常低,保持 10,000 个没有活动的连接,它只占 2.5M 内存
        6.支持内核 Poll 模型,能经受高负载的考验,有报告表明能支持高达 50,000 个并发连接数
        7.采用 master-slave 模型,能够充分利用 SMP 的优势,且能够减少工作进程在磁盘 I/O 的阻塞延迟。当采用 select()/poll() 调用时,还可以限制每个进程的连接数
        8.强大的 Upstream 与 Filter 链,有点像 Unix 的管道
        9.采用了一些 os 提供的最新特性如对 sendfile (Linux2.2 ),accept-filter (FreeBSD4.1 ),TCP_DEFER_ACCEPT (Linux 2.4 )的支持
        10.正向代理代理客户端,反向代理代理服务端

        Nginx 架构:
        1.默认采用多进程后台模式启动,可以手动配置成单进程前台模式用于调试,进程数一般和cpu内核数相同,太多进程会导致竞争cpu资源,带来不必要的上下文切换
        2.发送kill -HUP pid的信号给master进程,master进程会从新加载配置文件,启动新的worker进程,退出老的worker进程,也是-s reload所做的
        3.在master进程建立好需要listen的 socket,然后fork出子进程,子进程抢accept_mutex的互斥锁,抢到的子进程进行 accept处理
        4.每个子进程采用异步非阻塞事件处理, select/poll/epoll/kqueue的系统调用,设置超时时间,当事件没准备好时,放到 epoll 里面,事件准备好了,我们就去读写,当读写返回 EAGAIN(再试一次)时,我们将它再次加入到 epoll 里面,线程还是只有一个,在请求间进行不断的循环切换,这里的切换没有代价,只要内存够大就行
        5.apache那种简单的多线程,每个请求会独占一个工作线程,当并发数上到几千时,就同时有几千的线程在处理请求占用内存大,线程间上下文切换占用的cpu开销大
        6.事件处理通常包含,网络信号(异步非阻塞),信号,定时器(放在一颗维护定时器的红黑树里面)

        nginx的connection
        1.主进程监听端口创建socket,fork出子进程,子进程互斥锁竞争accept新的连接,三次握手建立连接以后,异步非阻塞读写事件处理,nginx或客户端主动关掉连接
        2.每个进程都有连接数的限制,ulimit -n,超过时创建socket会失败
        3.nginx能建立的最大连接数 worker_连接数*worker_进程数;作为反向代理时则为worker_连接数*worker_进程数 /2 ,nginx也要请求另外的处理服务占用一个连接
        4.利用accept_mutex锁来平衡每个worker进程的连接数

        nginx与keepalive
        1.http1.0和http1.1都支持长连接,默认1.0是关闭的,1.1是打开的
        2.http1.0需要指定Connection:keep-alive表示使用长连接,响应头中会包含content-length,客户端依据这个长度表示接收完成,否则会一直接收数据
        3.http1.1不需要指定connection,响应头中Transfer-encoding 为 chunked则会是流式传输,每块会包含当前块的长度;如果非chunked则要有content-length,否则会一直接收直到服务端主动断开
        4.keepalive_timeout 来配置超时时间,如果为0则会直接关闭,默认65秒
        单点故障解决方案:
            Keepalived   nginx 实现nginx的高可用
            通过keepalived来实现同一个虚拟IP映射到两台Nginx代理服务器,如果主服务器挂掉或者主服务器的keepalived挂掉又或者主服务器的Nginx挂掉(Nginx挂掉后会杀死keepalived的进程,在脚本中有控制)那从服务器的keepalived会检测到并会接管原先MASTER的网络功能,这种方式来实现Nginx的高可用性(如上文中的keepalived简要介绍)
        nginx等高可用实现
            使用双机通过 Keepalived 工具来实现 Nginx 的高可用(High Avaiability),达到一台Nginx入口服务器宕机,另一台备机自动接管服务的效果。
            参考链接:https://segmentfault.com/a/1190000002881132
        nginx的请求处理:
            1.worker进程中有个函数,无限循环,不断处理收到的客户端请求,并进行处理
            2.系统提供的事件处理机制(select/epoll/kqueue)
            3.接收数据,初始化HTTP Request ,处理请求头请求体
            4.读取配置文件进行处理阶段,location rewrite filter等
            5.产生响应发送给客户端

            1.Epoll是poll的改进版,在高并发下能同时处理大量文件描述符,nginx使用了
            2.Poll是监控资源是否可用,轮询整个文件描述符集合,例如在多个socket连接建立后,可以知道哪些连接发了请求,与select比不会清空文件描述符
            3.Epoll只会查询被内核IO事件唤醒的集合,只有发生IO的socket会调用callback函数
            4.文件描述符,一切皆文件网络是文件键盘是文件,像写文件那样传输网络数据,通过/proc/的文件看到进程的资源使用情况
        负载均衡策略:
            轮询:按照时间顺序,逐一分配到不同的后端服务器
            加权轮询:weight值越大,分配到的访问几率越高
            ip_hash:每个请求按访问IP的hash结果分配,这样来自同一个IP的请求固定访问一个后端服务器,可以解决分布式session问题,但不是最优的解决办法,另一个即集中式session存储校验,将session放到redis集群当中。
            url_hash:按照访问的URL的hash结果来分配请求,使一个URL始终定向到同一个后端服务器
            less_conn:最少连接数,哪个机器连接数少,就分发
            hash关键数值:hash自定义的key
        请根据你的理解,简述负载均衡的实现方式

            负载均衡主要分为两种:硬件(F5)和软件(NGINX、Haproxy、LVS),硬件效果比较牛逼,它是把 4-7 层的负载均衡功能做到一个硬件里面,但是价格昂贵最近用的越来越少了。
            软件的负载均衡又分两种:四层和七层:四层是在 IP/TCP 协议栈上把网络包的 IP 地址和端口进行修改,达到转发的目的;七层就是在应用层里把 HTTP 请求、URL 等具体的应用数据发送到具体的服务器上。四层的效率比七层的高,四层一般安排在架构的前端,七层一般就是在具体服务器的前端。软件负载均衡比较常见的几个调度分配方式如下:
            轮询:访问请求依序分发给后端服务器;
            加权轮询:访问请求依序分发后端服务器,服务器权重越高被分发的几率也越大;
            最小连接数:将访问请求分发给当前连接数最小的一台后端服务器,服务器权重越高被分发的几率也越大。

    Nginx负载均衡的优点是:    
        1.工作在网络的7层之上,可以针对http应用做一些分流的策略,比如针对域名、目录结构,它的正则规则比HAProxy更为强大和灵活,这也是它目前广泛流行的主要原因之一,Nginx单凭这点可利用的场合就远多于LVS了。    
        2.Nginx对网络稳定性的依赖非常小,理论上能ping通就就能进行负载功能,这个也是它的优势之一;相反LVS对网络稳定性依赖比较大,这点本人深有体会;    
        3.Nginx安装和配置比较简单,测试起来比较方便,它基本能把错误用日志打印出来。LVS的配置、测试就要花比较长的时间了,LVS对网络依赖比较大。    
        4.可以承担高负载压力且稳定,在硬件不差的情况下一般能支撑几万次的并发量,负载度比LVS相对小些。    
        5.Nginx可以通过端口检测到服务器内部的故障,比如根据服务器处理网页返回的状态码、超时等等,并且会把返回错误的请求重新提交到另一个节点,不过其中缺点就是不支持url来检测。比如用户正在上传一个文件,而处理该上传的节点刚好在上传过程中出现故障,Nginx会把上传切到另一台服务器重新处理,而LVS就直接断掉了,如果是上传一个很大的文件或者很重要的文件的话,用户可能会因此而不满。    
        6.Nginx不仅仅是一款优秀的负载均衡器/反向代理软件,它同时也是功能强大的Web应用服务器。LNMP也是近几年非常流行的web架构,在高流量的环境中稳定性也很好。    
        7.Nginx现在作为Web反向加速缓存越来越成熟了,速度比传统的Squid服务器更快,可以考虑用其作为反向代理加速器。    
        8.Nginx可作为中层反向代理使用,这一层面Nginx基本上无对手,唯一可以对比Nginx的就只有 lighttpd了,不过 lighttpd目前还没有做到Nginx完全的功能,配置也不那么清晰易读,社区资料也远远没Nginx活跃。    
        9.Nginx也可作为静态网页和图片服务器,这方面的性能也无对手。还有Nginx社区非常活跃,第三方模块也很多。
    Nginx负载均衡的缺点是:    
        1.Nginx仅能支持http、https和Email协议,这样就在适用范围上面小些,这个是它的缺点。    
        2.对后端服务器的健康检查,只支持通过端口来检测,不支持通过url来检测。不支持Session的直接保持,但能通过ip_hash来解决。
    LVS的负载均衡优点是:    
        LVS:使用Linux内核集群实现一个高性能、高可用的负载均衡服务器,它具有很好的可伸缩性(Scalability)、可靠性(Reliability)和可管理性(Manageability)。
        1.抗负载能力强、是工作在网络4层之上仅作分发之用,没有流量的产生,这个特点也决定了它在负载均衡软件里的性能最强的,对内存和cpu资源消耗比较低。    
        2.配置性比较低,这是一个缺点也是一个优点,因为没有可太多配置的东西,所以并不需要太多接触,大大减少了人为出错的几率。    
        3.工作稳定,因为其本身抗负载能力很强,自身有完整的双机热备方案,如LVS Keepalived,不过我们在项目实施中用得最多的还是LVS/DR Keepalived。    
        4.无流量,LVS只分发请求,而流量并不从它本身出去,这点保证了均衡器IO的性能不会受到大流量的影响。    
        5.应用范围比较广,因为LVS工作在4层,所以它几乎可以对所有应用做负载均衡,包括http、数据库、在线聊天室等等。
    LVS的缺点是:    
        1.软件本身不支持正则表达式处理,不能做动静分离;而现在许多网站在这方面都有较强的需求,这个是Nginx/HAProxy Keepalived的优势所在。    
        2.如果是网站应用比较庞大的话,LVS/DR Keepalived实施起来就比较复杂了,特别后面有 Windows Server的机器的话,如果实施及配置还有维护过程就比较复杂了,相对而言,Nginx/HAProxy Keepalived就简单多了。
    HAProxy的特点是:    
        1.HAProxy也是支持虚拟主机的。    
        2.HAProxy的优点能够补充Nginx的一些缺点,比如支持Session的保持,Cookie的引导;同时支持通过获取指定的url来检测后端服务器的状态。    
        3.HAProxy跟LVS类似,本身就只是一款负载均衡软件;单纯从效率上来讲HAProxy会比Nginx有更出色的负载均衡速度,在并发处理上也是优于Nginx的。    
        4.HAProxy支持TCP协议的负载均衡转发,可以对MySQL读进行负载均衡,对后端的MySQL节点进行检测和负载均衡,大家可以用LVS Keepalived对MySQL主从做负载均衡。    
        5.HAProxy负载均衡策略非常多,HAProxy的负载均衡算法现在具体有如下8种:        
        ① roundrobin,表示简单的轮询,这个不多说,这个是负载均衡基本都具备的;         
        ② static-rr,表示根据权重,建议关注;         
        ③ leastconn,表示最少连接者先处理,建议关注;         ④ source,表示根据请求源IP,这个跟Nginx的IP_hash机制类似,我们用其作为解决session问题的一种方法,建议关注;         
        ⑤ ri,表示根据请求的URI;         
        ⑥ rl_param,表示根据请求的URl参数’balance url_param’ requires an URL parameter name;         
        ⑦ hdr(name),表示根据HTTP请求头来锁定每一次HTTP请求;         
        ⑧ rdp-cookie(name),表示根据据cookie(name)来锁定并哈希每一次TCP请求。
    Nginx和LVS对比的总结:    
        1.Nginx工作在网络的7层,所以它可以针对http应用本身来做分流策略,比如针对域名、目录结构等,相比之下LVS并不具备这样的功能,所以Nginx单凭这点可利用的场合就远多于LVS了;但Nginx有用的这些功能使其可调整度要高于LVS,所以经常要去触碰触碰,触碰多了,人为出问题的几率也就会大。    
        2.Nginx对网络稳定性的依赖较小,理论上只要ping得通,网页访问正常,Nginx就能连得通,这是Nginx的一大优势!Nginx同时还能区分内外网,如果是同时拥有内外网的节点,就相当于单机拥有了备份线路;LVS就比较依赖于网络环境,目前来看服务器在同一网段内并且LVS使用direct方式分流,效果较能得到保证。另外注意,LVS需要向托管商至少申请多一个ip来做Visual IP,貌似是不能用本身的IP来做VIP的。要做好LVS管理员,确实得跟进学习很多有关网络通信方面的知识,就不再是一个HTTP那么简单了。    
        3.Nginx安装和配置比较简单,测试起来也很方便,因为它基本能把错误用日志打印出来。LVS的安装和配置、测试就要花比较长的时间了;LVS对网络依赖比较大,很多时候不能配置成功都是因为网络问题而不是配置问题,出了问题要解决也相应的会麻烦得多。    
        4.Nginx也同样能承受很高负载且稳定,但负载度和稳定度差LVS还有几个等级:Nginx处理所有流量所以受限于机器IO和配置;本身的bug也还是难以避免的。    
        5.Nginx可以检测到服务器内部的故障,比如根据服务器处理网页返回的状态码、超时等等,并且会把返回错误的请求重新提交到另一个节点。目前LVS中 ldirectd也能支持针对服务器内部的情况来监控,但LVS的原理使其不能重发请求。比如用户正在上传一个文件,而处理该上传的节点刚好在上传过程中出现故障,Nginx会把上传切到另一台服务器重新处理,而LVS就直接断掉了,如果是上传一个很大的文件或者很重要的文件的话,用户可能会因此而恼火。    
        6.Nginx对请求的异步处理可以帮助节点服务器减轻负载,假如使用 apache直接对外服务,那么出现很多的窄带链接时apache服务器将会占用大量内存而不能释放,使用多一个Nginx做apache代理的话,这些窄带链接会被Nginx挡住,apache上就不会堆积过多的请求,这样就减少了相当多的资源占用。这点使用squid也有相同的作用,即使squid本身配置为不缓存,对apache还是有很大帮助的。    
        7.Nginx能支持http、https和email(email的功能比较少用),LVS所支持的应用在这点上会比Nginx更多。在使用上,一般最前端所采取的策略应是LVS,也就是DNS的指向应为LVS均衡器,LVS的优点令它非常适合做这个任务。重要的ip地址,最好交由LVS托管,比如数据库的ip、webservice服务器的ip等等,这些ip地址随着时间推移,使用面会越来越大,如果更换ip则故障会接踵而至。所以将这些重要ip交给 LVS托管是最为稳妥的,这样做的唯一缺点是需要的VIP数量会比较多。Nginx可作为LVS节点机器使用,一是可以利用Nginx的功能,二是可以利用Nginx的性能。当然这一层面也可以直接使用squid,squid的功能方面就比Nginx弱不少了,性能上也有所逊色于Nginx。Nginx也可作为中层代理使用,这一层面Nginx基本上无对手,唯一可以撼动Nginx的就只有lighttpd了,不过lighttpd目前还没有能做到 Nginx完全的功能,配置也不那么清晰易读。另外,中层代理的IP也是重要的,所以中层代理也拥有一个VIP和LVS是最完美的方案了。具体的应用还得具体分析,如果是比较小的网站(日PV小于1000万),用Nginx就完全可以了,如果机器也不少,可以用DNS轮询,LVS所耗费的机器还是比较多的;大型网站或者重要的服务,机器不发愁的时候,要多多考虑利用LVS。
    请写出 Web 服务器的调优要点,以 Nginx 为例。
        尽可能的少用 HTTP,因为 HTTP 是有开销的;
        尽可能的使用 CDN;
        添加 Expire/Cache-Control 头,这个头是缓存用的,可以缓存图片和 Flash 那样不轻易更改的文件,减少访问时间;
        启动 Gzip 压缩,这个没啥好说的了;
        尽可能少的重定向,重定向是需要时间的,增加一次重定向就会多一次 Web需求;
        如果可以,把 Ajax也做缓存;
        减少 DNS 查询,很多网页会有外站的广告,这些广告也是会启动 DNS 查询的,所以如果不缺钱,减少这种广告;
        调好服务器里的 TCP 协议栈,这个无论是 Web 服务器还是应用服务器都是必须的;
    编写一个 Nginx 的访问控制规则,要求只准许 192.168.3.29/24 、10.1.20.6/16 、34.26.157.0/24 这些机器访问,除此之外的机器不准许访问。
        location/{
        access 192.168.3.29/24;
        access 10.1.20.6/16;
        access 34.26.157.0/24;
        deny all;
        }
    如何在 Nginx 中给 favicon.ico 和 robots.txt 设置过期时间。
        这里为 favicon.ico 为 99 天,robots.txt 为 7 天并不记录 404 错误日志。
        location ~(favicon.ico) {
            log_not_found off;
            expires 99d;
            break;
        }
        location ~(robots.txt) {
            log_not_found off;
            expires 7d;
            break;
        }
    如何在 Nginx 中设定某个文件的浏览器缓存过期时间为 600 秒,并不记录访问日志。
        location ^~ /html/scripts/loadhead_1.js {
            access_log off;
            expires 600;
            break;
        }
    如何在 Nginx 中只允许固定 IP 访问网站,并加上密码。
        设定账号是 james,密码是 123456。
        printf "james:$(openssl passwd -crypt 123456)n" >>/usr/local/nginx/conf/passwd
        location  {
            allow 22.27.164.25; #允许的ipd
            deny all;
            auth_basic “KEY”; #登陆该网页的时候,会有这个“KEY”的提示,提示只能是英文,中文不识别。
            auth_basic_user_file /conf/htpasswd;
        }
    Web 服务器为 Nginx,如果访问服务器的 IP 地址是 203.46.97.124 的话,给他展现的主页是 /123.html,其他人就展现 index.html。
        location / {
                if ($remote_addr = 203.46.97.124 ) {
                    rewrite ^.*$ /123.html;
                        }
                root /usr/local/nginx/html;
                index index.html;
                }
PHP:
    位运算
        与(&)运算 0&0=0,0&1=0,1&0=0,1&1=1
        或(|)运算 0|0=0,0|1=1,1|0=1,1|1=1
        异或(^)运算 0^0=0,0^1=1,1^0=1,1^1=0
        非(~)运算 非运算即取反运算,在二进制中1变0,0变1 110101进行非运算后为 001010即1010

    几个取整函数:
        ceil() 函数:进一法取整,即取得比当前数大的下一位整数
        floor() 函数:舍去法取整,即取得比当前数小的最大整数
        
    字符串”r”,”n”,”t”,”x20”分别代表什么?
        “r”代表的含义是:在Linux、unix 中表示返回到当行的最开始位置,在Mac OS 中表示换行且返回到下一行的最开始位置,相当于Windows 里的 n 的效果。
        “n”代表的含义是:在Windows 中表示换行且回到下一行的最开始位置。相当于Mac OS 里的 r 的效果,在Linux、unix 中只表示换行,但不会回到下一行的开始位置。
        “t”所代表的含义是:键盘上的“TAB”键,跳格(移至下一列)。
        “x20”所代表的含义是:是32在ASCII表中16进制的表示。
    include 和 require 的区别
     (1)require 相对 include 的效率会更高
     (2)引用的文件不存在时,require会报致命错误,结束程序运行,而include则只会报warning
     (3)在同一php文件中解释过一次后,不会再解释第二次。而include却会重复的解释包含的文件。所以当php网页中使用循环或条件语句引入文件时,"require"则不会做任何的改变,当出现这种情况,必须使用"include"命令来引入文件。
    session id的生成原理:
        hash_func = md5 / sha1 #可由php.ini配置
        PHPSESSIONID = hash_func(客户端IP   当前时间(秒)  当前时间(微妙)  PHP自带的随机数生产器)
    获取UUID:
        apt-get install uuid uuid-dev
        pecl install uuid配置cli和fpm的ini文件,引入扩展
        php有这个扩展uuid:
        uuid_create()生成唯一id:时间戳、毫秒数、机器码、自增计数
    接口安全性:
        接口的安全性主要围绕Token、Timestamp和Sign三个机制展开设计,保证接口的数据不会被篡改和重复调用,下面具体来看:
        (1)Token授权机制:(Token是客户端访问服务端的凭证)--用户使用用户名密码登录后服务器给客户端返回一个Token(通常是UUID),并将Token-UserId以键值对的形式存放在缓存服务器中。服务端接收到请求后进行Token验证,如果Token不存在,说明请求无效。
        (2)时间戳超时机制:(签名机制保证了数据不会被篡改)用户每次请求都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,如果时间差大于一定时间(比如5分钟),则认为该请求失效。时间戳超时机制是防御DOS攻击的有效手段。
        (3)签名机制:将 Token 和 时间戳 加上其他请求参数再用MD5或SHA-1算法(可根据情况加点盐)加密,加密后的数据就是本次请求的签名sign,服务端接收到请求后以同样的算法得到签名,并跟当前的签名进行比对,如果不一样,说明参数被更改过,直接返回错误标识。
    array系列函数:
        1.in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
        第三个参数是true,就检测类型,否则不检测

        2.array_flip — 交换数组中的键和值,返回一个反转后的 array,例如 array 中的键名变成了值,而 array 中的值成了键名。
        $arr=array_flip($haystack); 
        if ( isset($arr[$needle]) ) 判断是否存在在数组中 

        3.array_map — 为数组的每个元素应用回调函数
        因为in_array不区分大小写,所以使用这个对数组的所有元素转成小写
        return in_array(strtolower($needle), array_map('strtolower', $haystack));
        
        4.分割成数组,使用逗号或空格(包含" ", r, t, n, f)分隔短语,因为在分割数组的时候,空格的数量不一定是几个,explode不能用
            $keywords = preg_split("/[s,] /", "hypertext language, programming");
        print_r($keywords);

        5.array_slice — 从数组中取出一段
            $input = array("a", "b", "c", "d", "e");
            $output = array_slice($input, 2);      // returns "c", "d", and "e"
            $output = array_slice($input, -2, 1);  // returns "d"
            $output = array_slice($input, 0, 3);   // returns "a", "b", and "c"
        6.array_merge 合并两个数组
            $logList=array_merge($logList, $lastMonthLogList);
        7.array_push — 将一个或多个单元压入数组的末尾(入栈)
            array_push($stack, "apple", "raspberry");
            $array[] = $var;相当于这样
            如果是数组,那就相当于二维数组了
        array()创建数组;
        count()返回数组中元素的数目;
        array_column()返回输入数组中某个单一列的值;
        array_combine()通过合并两个数组来创建一个新数组;
        array_reverse()以相反的顺序返回数组;
        array_unique()删除数组中的重复值;
        array_multisort() 对二维数组进行排序
        sort():对数组按照值进行一个排序(按照英文字母的顺序进行排序)
        asort():键值对关联数组进行升序排序
        arsort():对数组进行逆向排序并保持索引关系
        ksort():对数组按照键值进行排序(按照的英文字母的顺序进行排序)
        krsort():对数组按照键值进行逆向排序
        usort() 使用用户自定义的比较函数对数组进行排序。//经常用
        sizeof() 函数计算数组中的单元数目或对象中的属性个数。sizeof() 函数是 count() 函数的别名。
        array_keys():取出所有key
        array_values():取出所有的value
    usort对二维数组排序
        usort($bill,'sortBills');
        function sortBills($a, $b){
            if ($a['create_time'] == $b['create_time']) {
                return 0;
            } else {
                return ($a['create_time'] > $b['create_time']) ? -1 : 1;
            }    
        }
    array array与array_merge()的区别
        二者之间的区别是:
        1 键名为数字时,array_merge()不会覆盖掉原来的值,但+合并数组则会把最先出现的值作为最终结果返回,而把后面的数组拥有相同键名的那些值“抛弃”掉(不是覆盖)
        2 键名为字符时,+仍然把最先出现的值作为最终结果返回,而把后面的数组拥有相同键名的那些值“抛弃”掉,但array_merge()此时会覆盖掉前面相同键名的值
    print、echo、print_r有什么区别?
        echo和print都可以做输出,不同的是,echo不是函数,没有返回值,而print是一个函数有返回值,所以相对而言如果只是输出echo会更快,而print_r通常用于打印变量的相关信息,通常在调试中使用。
        print 是打印字符串
        print_r 则是打印复合类型 如数组 对象
        echo可以一次输出多个值,多个值之间用逗号分隔。
        echo是语言结构(language construct),而并不是真正的函数,因此不能作为表达式的一部分使用。
        echo是php的内部指令,不是函数,无返回值。        
        print():函数print()打印一个值(它的参数),如果字符串成功显示则返回true,否则返回false。只能打印出简单类型变量的值(如int,string),有返回值        
        printf():源于C语言中的printf()。该函数输出格式化的字符串。        
        print_r()和var_dump() print_r()可以把字符串和数字简单地打印出来,而数组则以括起来的键和值得列表形式显示,并以Array开头。但print_r()输出布尔值和NULL的结果没有意义,因为都是打印"n"。因此用var_dump()函数更适合调试。print_r是函数,可以打印出比较复杂的变量(如数组,对象),有返回值        
        var_dump()判断一个变量的类型与长度,并输出变量的数值,如果变量有值输的是变量的值并回返数据类型。此函数显示关于一个或多个表达式的结构信息,包括表达式的类型与值。数组将递归展开值,通过缩进显示其结构。
    PHP处理字符串的常用函数?
        trim()移除字符串两侧的空白字符和其他字符;
        substr_replace()把字符串的一部分替换为另一个字符串;
        substr_count()计算子串在字符串中出现的次数;
        substr()返回字符串的一部分;
        strtolower()把字符串转换为小写字母;
        strtoupper()把字符串转换为大写字母;
        strtr()转换字符串中特定的字符;
        strrchr()查找字符串在另一个字符串中最后一次出现;
        strstr()查找字符串在另一字符串中的第一次出现(对大小写敏感);strrev()反转字符串;strlen()返回字符串的长度;str_replace()替换字符串中的一些字符(对大小写敏感);print()输出一个或多个字符串;explode()把字符串打散为数组;is_string()检测变量是否是字符串;strip_tags()从一个字符串中去除HTML标签;mb_substr()用来截中文与英文的函数
        在php中,字符串的查找有三个系列。返回位置的、返回字符串的、掩码个数匹配。其中,返回位置的的函数一共有两个,strpos()和strrpos();返回字符串的也有两个strstr()和strchr();返回掩码匹配数的函数有strspn()和strcspn()。
        strpos表示从左边开始计数,返回要查找的字符串第一次出现的位置;strrpos表示从右边计数,返回要查找的字符串第一次出现的位置。
        strstr表示从左边计数,返回要查找字符串第一次到结尾的子串(包括查找字符串),当查找的是字符时,可以用ascii码数字来表示字符;stristr表示不区分大小查找;strchr是strstr的别名;strrchr返回字符最后出现到结尾的子串。
        strspn表示从左边计数,第一次出现非掩码之前的子串的字符数;strcspn表示从左边计数,第一次出现掩码之前的子串的字符数。
        str_replace()替换字符串中的一些字符(对大小写敏感)。
        str_split()把字符串分割到数组中。
        str_word_count()计算字符串中的单词数。
    PHP处理时间的常用函数?
        date_default_timezone_get()返回默认时区。
        date_default_timezone_set()设置默认时区。
        date()格式化本地时间/日期。
        getdate()返回日期/时间信息。
        gettimeofday()返回当前时间信息。
        microtime()返回当前时间的微秒数。
        mktime()返回一个日期的 Unix时间戳。
        strtotime()将任何英文文本的日期或时间描述解析为 Unix时间戳。
        time()返回当前时间的 Unix时间戳。
    php魔术方法:
        __construct()[构造], __destruct()[析构], __call()[调用不存在的方法], __callStatic()[调用不存在的静态方法], 
        __get()[获取不存在属性的值], __set()[为不存在属性赋值调用],
        __isset()[isset不存在的属性], __unset()[unset不存在的属性], __sleep()[序列化对象的时候], __wakeup()[反序列化对象的时候], __toString()[把对象转换成字符串的时候调用,echo], __invoke()[把对象当方法调用时], __set_state()[当使用var_export()函数时候调用。接受一个数组参数], __clone()[当使用clone复制一个对象时候调用] 和 __debugInfo() 
    SESSION与COOKIE的区别?
        COOKIE保存在客户端,而SESSION则保存在服务器端
        从安全性来讲,SESSION的安全性更高
        从保存内容的类型的角度来讲,COOKIE只保存字符串(及能够自动转换成字符串)
        从保存内容的大小来看,COOKIE保存的内容是有限的,比较小,而SESSION基本上没有这个限制
        从性能的角度来讲,用SESSION的话,对服务器的压力会更大一些
        SEEION依赖于COOKIE,但如果禁用COOKIE,也可以通过url传递
        2、cookie不是很安全,别人可以分析存放在本地的COOKIE,进行COOKIE欺骗,考虑到安全应当使用session。        
        3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用COOKIE。        
        4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。   
        cookie在客户端保存状态,session在服务器端保存状态。但是由于在服务器端保存状态的时候,在客户端也需要一个标识,所以session也可能要借助cookie来实现保存标识位的作用。
        cookie包括名字,值,域,路径,过期时间。路径和域构成cookie的作用范围。cookie如果不设置过期时间,则这个cookie在浏览器进程 存在时有效,关闭时销毁。如果设置了过期时间,则cookie存储在本地硬盘上,在各浏览器进程间可以共享。
        session存储在服务器端,服务器用一种散列表类型的结构存储信息。当一个连接建立的时候,服务器首先搜索有没有存储的session id,如果没有,则建立一个新的session,将session id返回给客户端,客户端可以选择使用cookie来存储session id。也可以用其他的方法,比如服务器端将session id附在URL上。
        4. 路径:Session 不能区分路径,同一个用户在访问一个网站期间,所有的 Session 在任何一个地方都可以访问到。而 Cookie 中如果设置了路径参数,那么同一个网站中不同路径下的 Cookie 互相是访问不到的。
        6. 大小以及数量限制:每个域名所包含的 cookie 数:IE7/8,FireFox:50 个,Opera30 个; Cookie 总大小:Firefox 和 Safari 允许 cookie 多达 4097 个字节,Opera 允许 cookie 多达 4096 个字 节,InternetExplorer 允许 cookie 多达 4095 个字节;一般认为 Session 没有大小和数量限制。
    cookie禁用下php的使用session
        把php.ini中的session.use_trans_sid设置为1,当客户端的Cookie被禁用或出现问题时,PHP会自动把Session ID附着在URL中
        还有一种方式,就是会自动在表单里生成一个隐藏项,传输session id
    Session可不可以设置失效时间,比如30分钟过期
        设置seesion.cookie_lifetime有30分钟,并设置session.gc_maxlifetime为30分钟
        自己为每一个Session值增加timestamp
        每次访问之前, 判断时间戳
    Cookie存在哪
        如果设置了过期时间,Cookie存在硬盘里
        没有设置过期时间,Cookie存在内存里
    cookie会话攻击防护?
        什么样的Cookie信息可以被攻击者利用
        1. Cookie中包含了不应该让除开发者之外的其他人看到的其他信息,如USERID=1000,USERSTATUS=ONLINE,ACCOUNT_ID=xxx等等这些信息。
        2. Cookie信息进行了加密,但是很容易被攻击者进行解密
        3. 在对Cookie信息的时候没有进行输入验证
        如何防范利用Cookie进行的攻击
        1. 不要在Cookie中保存敏感信息
        2. 不要在Cookie中保存没有经过加密的或者容易被解密的敏感信息
        3. 对从客户端取得的Cookie信息进行严格校验
        4. 记录非法的Cookie信息进行分析,并根据这些信息对系统进行改进。
        5. 使用SSL/TLS来传递Cookie信息
    类的自动加载:
        1. spl_autoload_register() 函数可以注册任意数量的自动加载器,当使用尚未被定义的类(class)和接口(interface)时自动去加载。
        2.__autoload() 函数也能自动加载类和接口,在以后的版本中它可能被弃用
    动态设置php.ini中的include_path 配置选项:
        两种方式
        set_include_path($new_include_path)
        ini_set('include_path',$new_include_path);
        利用常量 PATH_SEPARATOR 可跨平台扩展 include path,可以把自己设置的path加在现有include_path的尾部
    OO设计的基本原则
        单一原则:一个类只做一件事
        开闭原则:对扩展开放,对修改关闭
        里式替换:子类必须能够替换所有父类的使用
        依赖倒置:设计依赖于抽象而不是实现
        最少知识:对象应当尽可能少的去了解其他对象
        接口隔离:接口倾向于小而多
        组合优先:优先使用类的组合而不是继承
        OOA面向对象分析 OOD面向对象设计 OOP面向对象编程
        遵循的基本原则,组合优于继承 B:针对接口编程 C:尽可能降低耦合
    PHP里有哪些设计模式
        单例模式
        工厂模式
        脸面模式(facade)
        注册器模式
        策略模式
        原型模式
        装饰器模式
        平时使用框架和思考过程中就一直在使用设计模式,不建议过分强调设计模式,它是自然而然的产物。为了设计模式而设计模式就是本末倒置了!熟悉设计模式可作为参考,设计模式都是比较成熟的处理特定问题的方案,不需要在这些常见问题上绞尽脑汁。
        设计模式的关注点在于代码的可维护性和可复用性,不是关注性能问题


    单例:
        私有化构造方法
        通过静态方法创建并保持对象
        注意实例化方法需要线程安全
        private static $instance;private function __construct(){}
        public static function getInstance(){
            if(self::instance==null) self::instance=new self();
            return self::instance;
        }
        适用场景:类的对象全局唯一,节约频繁创建对象的资源开销,单例类必须是无状态的

    工厂模式:
        负责生成其他对象的类或方法,这就是工厂模式,下面是一个经常见到的用法
        public function getSetting(){
            if(!$this->setting){
                $this->setting=new Setting();
            }
            return $this->setting;
        }
    注册树模式:
        解决对象全局共享问题,将对象注册到全局的树上
        protected static  $objects;
       function set($alias,$object)//将对象注册到全局的树上
        {
          self::$objects[$alias]=$object;//将对象放到树上
       }
    适配器模式:
        就是将一个类的接口方法转换成我希望用的另一个方法 ,使用统一的API去屏蔽底层的API, 下面是个常见的用处
        public function set($key,$value){
            return $this->mc->set($key,$value,MEMCACHE_COMPRESSED,3600);
        }
    策略模式:
        然后在入口文件中执行判断,解决了在显示时的硬编码的问题
        class MaleUser implements UserStrategy{
            function showAd(){
            }
            function showCategory(){
            }
        }
        $page = new Page();
        if(isset($_GET['male'])){
            $strategy = new MaleUser();
        }else {
            $strategy = new FemaleUser();
        }
        $page->setStrategy($strategy);
        $page->index();
    观察者模式:
        当某个事件发生后,需要执行的逻辑增多时,可以以松耦合的方式去增删逻辑
        abstract class EventGenerator{
            private $observers = array();
            function addObserver(Observer $observer){
                $this->observers[]=$observer;
            }
            function notify(){
                foreach ($this->observers as $observer){
                    $observer->update();
                }
            }
        }
        class Observer2 implements Observer{
            function update(){
                echo "逻辑2<br>";
            }
        }
        $event = new Event();
        $event->addObserver(new Observer1());
        $event->addObserver(new Observer2());
        $event->notify();
    原型模式
        对象克隆以避免创建对象时的消耗
        $c = new Canvas();
        $c->init();
        $canvas1 = clone $c;
    装饰器模式:
        不改变其结构的情况下,向一个现有的对象添加新的功能,构造器传进去,包装一下他的方法
        class RedShapeDecorator extends ShapeDecorator{
        public function __construct(Shape $shape)
        {
            parent::__construct($shape);
        }
        public function draw()
        {
            $this->decoratorShape->draw();
            $this->setRedColor($this->decoratorShape);
        }
        private function setRedColor(Shape $shape)
        {
            print_r("red");
        }
        $circle = new Circle();
        $redCircle = new RedShapeDecorator(new Circle());
        $redCircle->draw();
    依赖注入与控制反转与call_user_func_array
        $conf=array(
            'name'=>'taoshihan',
            'age'=>10
            );
        $user=call_user_func_array(array('User', "createResource"), array($conf));
        依赖注入(DI)/控制反转(Ioc):将依赖关系从程序中提取到外部,并在运行时注入回去。
        好处:可以解耦调用与实现,方便的替换实现。
        注入方法:通过读取配置或者传参。
        实现方式:通常不同实现会实现同一套接口,调用处只调用接口。
    列出一些防范SQL注入、XSS攻击、CSRF攻击的方法
        SQL注入:
        addslashes函数
        mysql_real_escape_string/mysqli_real_escape_string/PDO::quote()
        PDO预处理
        XSS:htmlspecial函数
        CSRF:
        验证HTTP REFER
        使用toke进行验证
        SQL注入:
        解决这个问题的办法是,将 PHP 的内置 mysql_real_escape_string() 函数用作任何用户输入的包装器。这个函数对字符串中的字符进行转义,使字符串不可能传递撇号等特殊字符并让MySQL根据特殊字符进行操作。
        在PHP配置文件中Register_globals=off;设置为关闭状态 //作用将注册全局变量关闭。比如:接收POST表单的值使用$_POST['user'],如果将register_globals=on;直接使用$user可以接收表单的值。
        3 SQL语句书写的时候尽量不要省略小引号(tab键上面那个)和单引号
        4、提高数据库命名技巧,对于一些重要的字段根据程序的特点命名,取不易被猜到的
        5、对于常用的方法加以封装,避免直接暴漏SQL语句
        6、开启PHP安全模式Safe_mode=on;
        7、打开magic_quotes_gpc来防止SQL注入 Magic_quotes_gpc=off;默认是关闭的,它打开后将自动把用户提交的sql语句的查询进行转换,把'转为',这对防止sql注入有重 大作用。因此开启:magic_quotes_gpc=on;
        8、控制错误信息关闭错误提示信息,将错误信息写到系统日志。
        9、使用mysqli或pdo预处理。

    防范跨站点脚本攻击(XSS):
        strip_tags() 函数,这个函数可以清除任何包围在 HTML 标记中的内容或者使用htmlspecialchars() 函数。
        htmlentities — 将字符转换为 HTML 转义字符,对所有html实体转义
        htmlspecialchars只是对 < > " ' &进行转义
        对参数使用白名单过滤
        不允许输入的内容显示到浏览器
        禁止在js标签内输出用户输入的内容
    PHP过滤XSS
        $ra=Array('/([x00-x08,x0b-x0c,x0e-x19])/','/script/','/javascript/','/vbscript/','/expression/','/applet/','/meta/','/xml/','/blink/','/link/','/style/','/embed/','/object/','/frame/','/layer/','/title/','/bgsound/','/base/','/onload/','/onunload/','/onchange/','/onsubmit/','/onreset/','/onselect/','/onblur/','/onfocus/','/onabort/','/onkeydown/','/onkeypress/','/onkeyup/','/onclick/','/ondblclick/','/onmousedown/','/onmousemove/','/onmouseout/','/onmouseover/','/onmouseup/','/onunload/');
        $value  = addslashes($str); //给单引号(')、双引号(")、反斜线()与NUL(NULL字符)加上反斜线转义
        $value  = preg_replace($ra,'',$value);     //删除非打印字符,粗暴式过滤xss可疑字符串
        $str = htmlentities(strip_tags($value)); //去除 HTML 和 PHP 标记并转换为HTML实体
    php异步执行一个脚本:
        echo exec("php /var/www/html/test/2.php >/dev/null 2>&1 &");
        >/dev/null 输出到空设备中  等同于 1>/dev/null
        1代表stdout标准输出
        2代表stderr标准错误 & 表示等同于的意思
        2>&1 2的输出重定向等同于1,都是到空设备中
        system() 输出并返回最后一行shell结果。
        exec() 不输出结果,返回最后一行shell结果,所有结果可以保存到一个返回的数组里面。
        passthru() 只调用命令,把命令的运行结果原样地直接输出到标准输出设备上
    下载安装xunsearch,中文分词全文搜索
        wget http://www.xunsearch.com/download/xunsearch-full-latest.tar.bz2
        tar -xjf xunsearch-full-latest.tar.bz2
        cd xunsearch-full-1.4.13
        sh setup.sh
        /usr/bin/xunsearch
        /usr/bin/xunsearch/bin/xs-ctl.sh restart
        配置文件
        /usr/bin/xunsearch/sdk/php/app/sopans.ini 
        project.name = sopans
        project.default_charset = utf-8
        server.index = 8383
        server.search = 8384
        [id]
        type = id
        [title]
        type = title
        /usr/bin/xunsearch/sdk/php/util/Indexer.php --project=sopans --source=mysql://root:密码@localhost/pan --sql="select * from texts"
    stttotime处理时间:
        date("Y-m-d",strtotime('Monday'));
        date("Y-m-d",strtotime('Next Monday'));下周一
        date("Y-m-d",strtotime('Last Monday'));上周一
        $starttime=date('Y-m-d H:i:s',strtotime("-7 day")); 七天前
        $lastMonth=date("m",  strtotime("-1 month"));一月前
    正则表达式模式修正符:
        i 忽略大小写
        m 多行视作一行
        g 全局匹配
        s .圆点匹配换行符,默认不包括换行   这个相当有用
        x 空白字符除了被转义的或在字符类中的以外完全被忽略,在未转义的字符类之外的 # 以及下一个换行符之间的所有字符,包括两头,也都被忽略。
        e preg_replace() 在替换字符串中对逆向引用作正常的替换
        u 此修正符启用了一个 PCRE 中与 Perl 不兼容的额外功能。模式字符串被当成 UTF-8。
        U : 正则表达式的特点:就是比较”贪婪“  .* .  所有字符都符合这个条件
    贪婪、懒惰与独占
        我们再来看一下究竟什么是贪婪模式。
        下面的几个特殊字符相信大家都知道它们的用法:
        ?: 告诉引擎匹配前导字符0次或一次。事实上是表示前导字符是可选的。
         : 告诉引擎匹配前导字符1次或多次。
        *: 告诉引擎匹配前导字符0次或多次。
        {min, max}: 告诉引擎匹配前导字符min次到max次。min和max都是非负整数。如果有逗号而max被省略了,则表示max没有限制;如果逗号和max都被省略了,则表示重复min次。
        默认情况下,这个几个特殊字符都是贪婪的,也就是说,它会根据前导字符去匹配尽可能多的内容。这也就解释了为什么在第3部分的例子中,第3步以后的事情会发生了。
        在以上字符后加上一个问号(?)则可以开启懒惰模式,在该模式下,正则引擎尽可能少的重复匹配字符,匹配成功之后它会继续匹配剩余的字符串。在上例中,如果将正则换为

        贪婪:会根据前导字符去匹配尽可能多的内容
        X?  X*  X   X{n}  X{n,}  X{n,m}
        懒惰:加了? 正则引擎尽可能少的重复匹配字符,匹配成功之后它会继续匹配剩余的字符串
        X??  X*?  X ?  X{n}? X{n,}? X{n,m}?
        独占:同贪婪模式一样,独占模式一样会匹配最长。不过在独占模式下,正则表达式尽可能长地去匹配字符串,一旦匹配不成功就会结束匹配而不会回溯
        X?   X*   X     X{n}  X{n,}  X{n,m} 

    垃圾回收
        PHP使用基于引用计数的垃圾回收机制,垃圾回收周期为引用数减到非零值时。
    1.性状可以把模块化的实现方式注入多个无关的类中,介于类和接口之间的
    trait MyTrait{},建议一个文件只定义一个性状,导入是在类定义体内use MyTait
    2.DRY原则Dont Repeat Yourself
    3.spl_autoload_register()实现了各自特有的自动加载器
    PSR-1:基本的代码风格,PSR-2严格的代码风格,PSR-3日志记录器接口,PSR-4自动加载(命名空间前缀与文件目录对应起来)
    4.zend opcache php内置的字节码缓存,php解释器不需要每次都读取 解析和编译php代码,会从内存中读取预先编译好的字节码,需要配置扩展和php.ini

    1.PHP引擎,Zend Engine HipHopVM(Facebook开发的,使用即时编译器JIT提升性能) Hack语言(静态类型,新的数据结构,额外的接口)
    2.命名空间:按照一种虚拟的层次结构组织代码,类似 操作系统的文件目录结构,子命名空间用分割,PHP解释器会将其作为前缀加到类等前面,现代PHP组件系统的基础,namespace T;
    3.使用use关键字导入时无需在头加开,PHP假定导入的是完全限定的命名空间
    4.use func导入函数,use constant导入常量
    5.命名空间为PHP Framework Interop Group(PHP-FIG)制定的PSR-4自动加载器标准奠定基础
    6.PHP接口是两个对象之间的契约,都实现了同样的接口
    addDocument(Documentable $document),Documentable是个接口定义了两个公开方法

    PHP的垃圾收集机制是怎样的?
    说明: 
    1)如果,你熟悉PHP源码,那么请从源码入手,回答些问题,会获得额外加分 
    2)如果,你不熟悉PHP源码,那么尽你所能,多写点东西,包括利用自己的编程直觉得到的信息,都可以。 
    3)对,则有分,错误不扣,不写无分。
    PHP的垃圾回收机制:
        1. PHP可以自动进行内存管理,清除不需要的对象,主要使用了引用计数
        2. 在zval结构体中定义了ref_count和is_ref , ref_count是引用计数 ,标识此zval被多少个变量引用 , 为0时会被销毁
        is_ref标识是否使用的 &取地址符强制引用
        3. 为了解决循环引用内存泄露问题 , 使用同步周期回收算法
        比如当数组或对象循环的引用自身 , unset掉数组的时候 , 当refcount-1后还大于0的 , 就会被当成疑似垃圾 , 会进行遍历 ,并且模拟的删除一次refcount-1如果是0就删除 ,如果不是0就恢复

    PHP的写时复制机制(Copy-On-Write)
        例如这种形式
        $a = 1;
        $b = $a; //当把a赋值给b时,在内存中a,b其实是指向同一块内存
        $b = 2; //只有当b值发生变化,才会内存复制赋新值
        写时复制优点:是通过赋值的方式赋值给变量时不会申请新内存来存放新变量所保存的值,而是简单的通过一个计数器来共用内存,只有在其中的一个引用指向变量的值发生变化时才申请新空间来保存值内容以减少对内存的占用。

        从PHP底层基础数据结构来看
        ref_count和is_ref是定义于zval结构体中
        is_ref标识是不是用户使用 & 的强制引用;
        ref_count是引用计数,用于标识此zval被多少个变量引用,即写时复制的自动引用,为0时会被销毁
    1.PHP命名空间与操作系统的物理文件系统不同,这是一个虚拟的概念,没必要和目录结构完全对应
    2.为了兼容PSR-4自动加载器标准,会对应目录结构
    3.可以导入函数和常量,use func xxxxxx; use constant xxxxxx
    4.PHP-FIG制定了PSR-4的标准
    5.使用接口编程可以极大的提高代码扩展能力
    6.trait(性状):表名类可以做什么像是接口;提供模块化实现像是类
    7.定义trait MyTrait{},在其他类的定义体内中使用use MyTrait,php解释器会把性状内容复制过来
    8.这个时候在使用该类的对象的时候,可以直接使用trait中的方法
    9.php生成器yeild,可以极大的节省内存,例如读取大文件的时候,只会为1行分配内存
    10.使用use关键字附加变量到闭包上

    php7新特性
        ?? 运算符(NULL 合并运算符)
        函数返回值类型声明
        标量类型声明
        use 批量声明
        define 可以定义常量数组
        闭包( Closure)增加了一个 call 方法
        详细的可以见官网:php7-new-features
    php7卓越性能背后的优化
        减少内存分配次数
        多使用栈内存
        缓存数组的hash值
        字符串解析成桉树改为宏展开
        使用大块连续内存代替小块破碎内存
        详细的可以参考鸟哥的PPT:PHP7性能之源
    什么是面向对象?
        答:面向对象OO = 面向对象的分析OOA   面向对象的设计OOD   面向对象的编程OOP;通俗的解释就是“万物皆对象”,把所有的事物都看作一个个可以独立的对象(单元),它们可以自己完成自己的功能,而不是像C那样分成一个个函数。
        现在纯正的OO语言主要是Java和C#,PHP、C  也支持OO,C是面向过程的。
        简述 private、 protected、 public修饰符的访问权限。
        private : 私有成员, 在类的内部才可以访问。
        protected : 保护成员,该类内部和继承类中可以访问。
        public : 公共成员,完全公开,没有访问限制
    PHP的匿名类
        (new class{})php
        $conf=new stdClass();
        $conf->tableIdFormat='%s_%d';
        $conf->maxNodeNumber=1024;
        $conf->numberOfNodes=16;
    说一下单引号双引号?        
        ①单引号内部的变量不会执行, 双引号会执行        
        ②单引号解析速度比双引号快。        
        ③单引号只能解析部分特殊字符,双引号可以解析所有特殊字符。
    堆和栈的区别?
        答:栈是编译期间就分配好的内存空间,因此你的代码中必须就栈的大小有明确的定义;
        堆是程序运行期间动态分配的内存空间,你可以根据程序的运行情况确定要分配的堆内存的大小。

    面向对象的特征有哪些方面?
        答:主要有封装,继承,多态。如果是4个方面则加上:抽象。
        下面的解释为理解:
        封装:
        封装是保证软件部件具有优良的模块性的基础,封装的目标就是要实现软件部件的高内聚,低耦合,防止程序相互依赖性而带来的变动影响.
        继承:
        在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并可以加入若干新的内容,或修改原来的方法使之更适合特殊的需要,这就是继承。继承是子类自动共享父类数据和方法的机制,这是类之间的一种关系,提高了软件的可重用性和可扩展性。
        多态:
        多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
        抽象:
        抽象就是找出一些事物的相似和共性之处,然后将这些事物归为一个类,这个类只考虑这些事物的相似和共性之处,并且会忽略与当前主题和目标无关的那些方面,将注意力集中在与当前目标有关的方面。例如,看到一只蚂蚁和大象,你能够想象出它们的相同之处,那就是抽象。

    抽象类和接口的概念以及区别?
        答:抽象类:它是一种特殊的,不能被实例化的类,只能作为其他类的父类使用。使用abstract关键字声明。
        接口是一种特殊的抽象类,也是一个特殊的类,使用interface声明。
        (1)抽象类的操作通过继承关键字extends实现,而接口的使用是通过implements关键字来实现。
        (2)抽象类中有数据成员,可以实现数据的封装,但是接口没有数据成员。
        (3)抽象类中可以有构造方法,但是接口没有构造方法。
        (4)抽象类的方法可以通过private、protected、public关键字修饰(抽象方法不能是private),而接口中的方法只能使用public关键字修饰。
        (5)一个类只能继承于一个抽象类,而一个类可以同时实现多个接口。
        (6)抽象类中可以有成员方法的实现代码,而接口中不可以有成员方法的实现代码。
    什么是构造函数,什么是析构函数,作用是什么?
        答:构造函数(方法)是对象创建完成后第一个被对象自动调用的方法。它存在于每个声明的类中,是一个特殊的成员方法。作用是执行一些初始化的任务。Php中使用__construct()声明构造方法,并且只能声明一个。
        析构函数(方法)作用和构造方法正好相反,是对象被销毁之前最后一个被对象自动调用的方法。是PHP5中新添加的内容作用是用于实现在销毁一个对象之前执行一些特定的操作,诸如关闭文件和释放内存等。

    如何重载父类的方法,举例说明
        答:重载,即覆盖父类的方法,也就是使用子类中的方法替换从父类中继承的方法,也叫方法的重写。
        覆盖父类方法的关键是在子类中创建于父类中相同的方法包括方法的名称、参数和返回值类型。PHP中只要求方法的名称相同即可。
    __autoload()方法的工作原理是什么?
        答:使用这个魔术函数的基本条件是类文件的文件名要和类的名字保持一致。
        当程序执行到实例化某个类的时候,如果在实例化前没有引入这个类文件,那么就自动执行__autoload()函数。
        这个函数会根据实例化的类的名称来查找这个类文件的路径,当判断这个类文件路径下确实存在这个类文件后
        就执行include或者require来载入该类,然后程序继续执行,如果这个路径下不存在该文件时就提示错误。
        使用自动载入的魔术函数可以不必要写很多个include或者require函数

    网页/应用访问慢突然变慢,如何定位问题
        1.top、iostat查看cpu、内存及io占用情况
        内核、程序参数设置不合理
        查看有没有报内核错误,连接数用户打开文件数这些有没有达到上限等等
        2.链路本身慢
        是否跨运营商、用户上下行带宽不够、dns解析慢、服务器内网广播风暴什么的
        3.程序设计不合理
        是否程序本身算法设计太差,数据库语句太过复杂或者刚上线了什么功能引起的
        如果要访问数据库,检查一下是否数据库访问慢
        4.是否被攻击了
        查看服务器是否被DDos了等等
        5.硬件故障
        这个一般直接服务器就挂了,而不是访问慢
    如何解决异常处理?        
        答: 抛出异常:使用try...catch,异常的代码放在try代码块内,如果没有触发异常,则代码继续执行,如果异常被触发,就会抛出一个异常。Catch代码块捕获异常,并创建一个包含异常信息的对象。$e->getMessage(),输出异常的错误信息。            
        解决异常:使用set_error_handler函数获取异常(也可以使用try()和catch()函数),然后使用set_exception_handler()函数设置默认的异常处理程序,register_shutdown_function()函数来执行,执行机制是,php要把调入的函数调入到内存,当页面所有的php语句都执行完成时,再调用此函数
    try catch的使用
        1.try catch可以捕获上一层throw的异常
        2.finally是不管try或者catch任何一块有return, 最终都会执行的块
        3.try也是可以捕获到call_user_func_array回调函数类内部的throw的异常
        4.call_user_func_array只能回调类的静态方法,可以在这个静态方法中进行new对象
        5.在不自定义任何错误处理函数的情况下,try是不能捕获php本身的错误的,包括notice warning error等级别
    如何设计/优化一个访问量比较大的博客/论坛
        1.减少http请求(比如使用雪碧图)
        2.优化数据库(范式、SQL语句、索引、配置、读写分离)
        3.缓存使用(Memcache、Redis)
        4.负载均衡
        5.动态内容静态化 CDN
        6.禁止外部盗链(refer、图片添加水印)
        7.控制大文件
    PHP 做好防盗链的基本思想 防盗链
        什么是盗链?
        盗链是指服务提供商自己不提供服务的内容,通过技术手段绕过其它有利益的最终用户界面 (如广告),直接在自己的网站上向最终用户提供其它服务提供商的服务内容,骗取最终用户的浏览和点击率。受益者不提供资源或提供很少的资源,而真正的服务提供商却得不到任何的收益。
        网站盗链会大量消耗被盗链网站的带宽,而真正的点击率也许会很小,严重损害了被盗链网站的利益。 如何做防盗链?
        不定期更名文件或者目录
        限制引用页
        原理是,服务器获取用户提交信息的网站地址,然后和真正的服务端的地址相比较, 如果一致则表明是站内提交,或者为自己信任的站点提交,否则视为盗链。实现时可以使用 HTTP_REFERER1 和 htaccess 文件 (需要启用 mod_Rewrite),结合正则表达式去匹配用户的每一个访问请求。
        文件伪装
        文件伪装是目前用得最多的一种反盗链技术,一般会结合服务器端动态脚本 (PHP/JSP/ASP)。实际上用户请求的文件地址,只是一个经过伪装的脚本文件,这个脚本文件会对用户的请求作认证,一般会检查 Session,Cookie 或 HTTP_REFERER 作为判断是否为盗链的依据。而真实的文件实际隐藏在用户不能够访问的地方,只有用户通过验证以后才会返回给用户
        加密认证
        这种反盗链方式,先从客户端获取用户信息,然后根据这个信息和用户请求的文件名 字一起加密成字符串 (Session ID) 作为身份验证。只有当认证成功以后,服务端才会把用户需要的文件传送给客户。一般我们会把加密的 Session ID 作为 URL 参数的一部分传递给服务器,由于这个 Session ID 和用户的信息挂钩,所以别人就算是盗取了链接,该 Session ID 也无法通过身份认证,从而达到反盗链的目的。这种方式对于分布式盗链非常有效。
        随机附加码
        每次,在页面里生成一个附加码,并存在数据库里,和对应的图片相关,访问图片时和此附加码对比,相同则输出图片,否则输出 404 图片
        加入水印
    smarty是什么,有什么作用?       
        回答一:smarty是用php写出来的模板引擎,也是目前业界最著名的php模板引擎之一,它分离了逻辑代码和外在的显示,提供了一种易于管理和使用的方法,用来将混杂的php逻辑代码与html代码进行分离        
        回答二:smarty是php中最著名的引擎框架之一,我们公司使用的是TP框架,已经封装好了smarty模板,所以没有单独使用过         
        回答三:smarty是个模板引擎,最显著的地方就是有可以把模板缓存起来。一般模板来说,都是做一个静态页面,然后在里面把一些动态的部分用一切分隔符切开,然后在PHP里打开这个模板文件,把分隔符里面的值替换掉,然后输出来,你可以看下PHPLib里面的template部分。    
        而smarty设定了缓存参数以后,第一次运行时候会把模板打开,在php替换里面值的时候把读取的html和php部分重新生成一个临时的php文件,这样就省去了每次打开都重新读取html了。如果修改了模板,只要重新刷下就行了。

    TP框架有哪些优点?        
        答:ThinkPHP 框架是 PHP 最常见的框架之一,也是目前市面上的主流框架。ThinkPHP 是一个免费开源的,快速、简单的面向对象的轻量级 PHP 开发框架 ,是为了敏捷 WEB 应用开发和简化企业应用开发而诞生的。ThinkPHP 从诞生以来一直秉承简洁 实用的设计原则,在保持出色的性能和至简的代码的同时,也注重易用性。并且拥有众多的原创功能和特性,在易用性、扩展性和性能方面不断优化和改进,已经成长为国内最领先和最具影响力的 WEB 应用开发框架,众多的典型案例确保可以稳定用于商业以及门户级的开发。

    TP的特性有哪些?        
        答: 1.多表查询非常方便,在model中几句代码就可以完成对多表的关联操作               
        2.融合了smarty模板,使前后台分离               
        3.支持多种缓存技术,尤其对memcache技术支持非常好               
        4.命名规范,模型,视图,控制器严格遵循命名规则,通过命名一一对应               
        5.支持多种url模式               
        6.内置ajax返回方法,包括xml,json,html等               
        7.支持应用扩展,类库扩展,驱动扩展等
    TP框架中的大写字母函数?
        U:对url的组装
        A:内部实例化控制器
        S:缓存处理
        R:调用某个控制器的操作方法
        D:实例化自定义模型类
        M:实例化基础模型类
        I:获取参数
        L:设置或者获取当前语言
        C:设置或获取,保存配置
    请介绍一下laravel框架?        
        答: laravel框架的设计思想比较先进,非常适合应用各种开发模式,作为一个框架,它为你准备好了一切,composer是php的未来,没有composer,php肯定要走向没落    laravel框架最大的特点和优秀之处就是集合了php比较新的特点,以及各种各样的设计模式,Ioc模式,依赖注入等
    laravel有那些特点?        
        回答一:           
        1.强大的rest router:用简单的回调函数就可以调用,快速绑定controller和router           
        2.artisan:命令行工具,很多手动的工作都自动化           
        3.可继承的模板,简化view的开发和管理           
        4.blade模板:渲染速度更快           
        5.ORM操作数据库           
        6.migration:管理数据库和版本控制           
        7.测试功能也很强大           
        8.composer也是亮点       
        回答二: laravel框架引入了门面,依赖注入,Ioc模式,以及各种各样的设计模式等
    商城秒杀的实现?        
        答:抢购、秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:            
        1.高并发对数据库产生的压力            
        2 竞争状态下如何解决库存的正确减少(”超卖”问题)        
        对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis。
        第二个问题,我们可以使用redis队列来完成,把要秒杀的商品放入到队列中,因为pop操作是原子的,即使有很多用户同时到达,也是依次执行,文件锁和事务在高并发下性能下降很快,当然还要考虑其他方面的东西,比如抢购页面做成静态的,通过ajax调用接口,其中也可能会出现一个用户抢多次的情况,这时候需要再加上一个排队队列和抢购结果队列及库存队列。高并发情况下,将用户进入排队队列,用一个线程循环处理从排队队列取出一个用户,判断用户是否已在抢购结果队列,如果在,则已抢购,否则未抢购,库存减1,写数据库,将用户入结果队列。
    Redis秒杀实现?
        redis队列解决抢购高并发的原理:
        在程序跟数据库之前呢我们可以利用redis队列做一个缓冲机制,让所有用户的请求进行排队,禀行先进先出的原则(redis中的lpush和rpop),
        lpush程序是把用户的请求压入redis队列,然后用rpop做一个守护进程来取队列中的数据,按规定的抢购名额写好,
        把所有抢购成功的用户写入redis并且生成订单,在lpush程序中查看中奖的用户并且给用户及时提醒抢购结果!
    购物车的实现原理?        
        答:购物车相当于现实中超市的购物车,不同的是一个是实体车,一个是虚拟车而已。用户可以在购物网站的不同页面之间跳转,以选购自己喜爱的商品,点击购买时,该商品就自动保存到你的购物车中,重复选购后,最后将选中的所有商品放在购物车中统一到付款台结账,这也是尽量让客户体验到现实生活中购物的感觉。服务器通过追踪每个用户的行动,以保证在结账时每件商品都物有其主。主要涉及以下几点:    
        1、把商品添加到购物车,即订购    
        2、删除购物车中已定购的商品    
        3、修改购物车中某一本图书的订购数量    
        4、清空购物车    
        5、显示购物车中商品清单及数量、价格
        实现购物车的关键在于服务器识别每一个用户并维持与他们的联系。但是HTTP协议是一种“无状态(Stateless)”的协议,因而服务器不能记住是谁在购买商品,当把商品加入购物车时,服务器也不知道购物车里原先有些什么,使得用户在不同页面间跳转时购物车无法“随身携带”,这都给购物车的实现造成了一定的困难。目前购物车的实现主要是通过cookie、session或结合数据库的方式。下面分析一下它们的机制及作用。
    redis队列消息先进先出需要注意什么?        
        答:通常使用一个list来实现队列操作,这样有一个小限制,所以的任务统一都是先进先出,如果想优先处理某个任务就不太好处理了,这就需要让队列有优先级的概念,我们就可以优先处理高级别的任务,实现方式有以下几种方式:
        1)单一列表实现:队列正常的操作是 左进右出(lpush,rpop)为了先处理高优先级任务,在遇到高级别任务时,可以直接插队,直接放入队列头部(rpush),这样,从队列头部(右侧)获取任务时,取到的就是高优先级的任务(rpop)
        2)使用两个队列,一个普通队列,一个高级队列,针对任务的级别放入不同的队列,获取任务时也很简单,redis的BRPOP命令可以按顺序从多个队列中取值,BRPOP会按照给出的 key 顺序查看,并在找到的第一个非空 list 的尾部弹出一个元素,redis> BRPOP list1 list2 0 list1 做为高优先级任务队列list2 做为普通任务队列这样就实现了先处理高优先级任务,当没有高优先级任务时,就去获取普通任务
    mq中如何避免重复消费
        问题1:消息重复消费 
        描述:用户在页面停止查询时,会导致消费者进程被杀死,因此ACK状态码未反馈至MQ,从而消息一直存留在MQ中,当新的消费者启动时会重新消费; 
        解决方案:消费者每次执行查询前,首先在DB上查询任务的执行状态,若处于「取消/失败/成功」则表示已经由其它消费者消费过,那么直接返回ACK状态码给MQ,将消息从MQ中移除;
        https://blog.csdn.net/yeweiouyang/article/details/74943278

    订单,库存两个表如何保证数据的一致性?          
        答:在一个电子商务系统中,正常的应该是订单生成成功后,相应的库存进行减少必须要保证两者的一致性,但有时候因为某些原因,比如程序逻辑问题,并发等问题,导致下单成功而库存没有减少的情况。这种情况我们是不允许发生的,MySQL的中的事务刚好可以解决这一问题,首先得选择数据库的存储引擎为InnoDB的,事务规定了只有下订单完成了,并且相应的库存减少了才允许提交事务,否则就事务回滚,确保数据一致性。
    O2O用户下单,c端下单,如何保证ba端数据一致?       
        答:O2O为线上和线下模式,O2O模式奉行的是“线上支付 实体店消费”的消费模式,即消费者在网上下单完成支付后,凭消费凭证到实体店消费。 O2O模式是把商家信息和支付程序放在线上进行,而把商品和服务兑现放在线下,也就是说O2O模式适用于快递无法送达的有形产品。数据一致性的问题是O2O行业中最常见的问题,我们可以类似于数据库的主从复制的思路来解决这个问题.O2O有个供应商系统,类似于主服务器,在ç端(从服务器)下单时,数据同步更新到供应商系统端,b,a实时从供应商系统中拉取数据进行同步,比如利用定时任务,定时拉取数据进行同步。
    支付宝流程怎么实现的?        
        答:首先要有一个支付宝账号,接下来向支付宝申请在线支付业务,签署协议。协议生效后有支付宝一方会给网站方一个合作伙伴ID,和安全校验码,有了这两样东西就可以按照支付宝接口文档开发支付宝接口了,中间主要涉及到一个安全问题。整个流程是这样的:我们的网站通过post传递相应的参数(如订单总金额,订单号)到支付页面,支付页面把一系列的参数经过处理,以post的方式提交给支付宝服务器,支付宝服务器进行验证,并对接收的数据进行处理,把处理后的结果返回给我们网站设置的异步和同步回调地址,通过相应的返回参数,来处理相应的业务逻辑,比如返回的参数代表支付成功,更改订单状态。
    什么是单点登录?       
        答:单点登录SSO(Single Sign On)说得简单点就是在一个多系统共存的环境下,用户在一处登录后,就不用在其他系统中登录,也就是用户的一次登录能得到其他所有系统的信任。
        当用户第一次访问应用系统的时候,因为还没有登录,会被引导到认证系统中进行登录;根据用户提供的登录信息,认证系统进行身份校验,如果通过校验,应该返回给用户一个认证的凭据--ticket;
        用户再访问别的应用的时候,就会将这个ticket带上,作为自己认证的凭据,应用系统接受到请求之后会把 ticket送到认证系统进行校验,检查ticket的合法性。如果通过校验,用户就可以在不用再次登录的情况下访问应用系统2和应用系统3了。
        实现主要技术点:    
        1、两个站点共用一个数据验证系统    
        2、主要通过跨域请求的方式来实现验证及session处理。
    如何实现单点登录
        利用 jwt 实现 session 共享,具体使用 jwt 参考 
        http://blog.leapoahead.com/2015/09/07/user-authentication-with-jwt/
    怎么实现第三方登陆?        
        答:第三方登陆主要是基于oauth协议来实现,下面简单说下实现流程:
        1、首先我们需要以开发者的身份向第三方登陆平台申请接入应用,申请成功后,我们会获得一个appID和一个secrectID.
        2、当我们的网站需接入第三方登陆时,会引导用户跳转到第三方的登陆授权页面,此时把之前申请的appID和secrectID带给登陆授权页面。
        3、用户登陆成功后即得到授权,第三方会返回一个临时的code给我们的网站。
        4、我们的网站接受到code后,再次向我们的第三方发起请求,并携带接收的code,从第三方获取access_token.
        5、第三方处理请求后,会返回一个access_token给我们的网站,我们的网站获取到access_token后就可以调用第三方提供的接口了,比如获取用户信息等。最后把该用户信息存入到我们站点的数据库,并把信息保存到session中,实现用户的第三方登陆。
    如何处理负载,高并发(好好看看,经常问到,能回答到主要的东西即可)?        
        答:从低成本、高性能和高扩张性的角度来说有如下处理方案:
        1、HTML静态化其实大家都知道,效率最高、消耗最小的就是纯静态化的html页面,所以我们尽可能使我们的 网站上的页面采用静态页面来实现,这个最简单的方法其实也是最有效的方法。
        2、图片服务器分离把图片单独存储,尽量减少图片等大流量的开销,可以放在一些相关的平台上,如七牛等
        3、数据库集群和库表散列及缓存数据库的并发连接为100,一台数据库远远不够,可以从读写分离、主从复制,数据库集群方面来着手。另外尽量减少数据库的访问,可以使用缓存数据库如memcache、redis。
        4、镜像:尽量减少下载,可以把不同的请求分发到多个镜像端。
        5、负载均衡:Apache的最大并发连接为1500,只能增加服务器,可以从硬件上着手,如F5服务器。当然硬件的成本比较高,我们往往从软件方面着手。负载均衡 (Load Balancing) 建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力,同时能够提高网络的灵活性和可用性。目前使用最为广泛的负载均衡软件是Nginx、LVS、HAProxy。
        3、禁止外部的盗链。
        4、外部网站的图片或者文件盗链往往会带来大量的负载压力,因此应该严格限制外部对自身图片或者文件盗链,目前可以简单的通过refer来控制盗 链,apache自己就可以通过配置来禁止盗链。
        7、使用流量统计软件。 在网站上安装一个流量统计软件,可以即时知道哪些地方耗费了大量流量,哪些页面需要再进行优化。
        8、分库分表。
        9、Sphinx全文索引引擎。

        取模算法的坏处:如果机器数量变化,则会造成缓存key的重新落点到新的机器,这样会造成很多缓存失效,从而对服务器造成压力,高并发的情况下,还可能造成雪崩。

        一致性hash
        简介:http://blog.csdn.net/cywosp/article/details/23397179
        代码实现:http://my.oschina.net/wangdk/blog/133040
        这里我想说的在cache场景时使用一致性hash最大的好处在于当某机宕机或者增加机器时候key的迁移成本是最低,涉及的数据量是最少。大家可以通过普通hash和一致性hash下同等变动机器量来对比下原key命中率的方式来判断迁移成本。

        key1、key2、key3和server1、server2通过hash都能在这个圆环上找到自己的位置,并且通过顺时针的方式来将key定位到server上,server1和server2就是一个排好序的数组,都是数字的,然后判断key1 ,key2,key3位于哪个区间中,那就把那个的下一个server返回来
        //计算哈希值
        function myHash($str) {
            $hash = 0;
            $s    = md5($str);
            $seed = 5;
            $len  = 32;
            for ($i = 0; $i < $len; $i  ) {
                $hash = ($hash << $seed)   $hash   ord($s{$i});
            }
            return $hash & 0x7FFFFFFF;
        }
        class ConsistentHash {
            // server列表
            private $_server_list = array();
            // 延迟排序,因为可能会执行多次addServer
            private $_layze_sorted = FALSE;
             //1.把server的ip计算哈希值,放到一个数组里
            public function addServer($server) {
                $hash = myHash($server);
                $this->_layze_sorted = FALSE;
                if (!isset($this->_server_list[$hash])) {
                    $this->_server_list[$hash] = $server;
                }
                return $this;
            }
            //2.在排好序的数组里找对应的key位于哪个区间         
            public function find($key) {
                // 排序
                if (!$this->_layze_sorted) {
                    krsort($this->_server_list);
                    $this->_layze_sorted = TRUE;
                }
                 //计算key的哈希值
                $hash = myHash($key);
                $len  = sizeof($this->_server_list);
                if ($len == 0) {
                    return FALSE;
                }
                $keys   = array_keys($this->_server_list);
                $values = array_values($this->_server_list);
         
                // 如果不在区间内,则返回最后一个server
                if ($hash <= $keys[0] || $hash >= $keys[$len - 1]) {
                    return $values[$len - 1];
                }
                //判断key位于哪个区间,当前服务器元素和当前的下一个元素进行判断 
                foreach ($keys as $key=>$pos) {
                    $next_pos = NULL;
                    if (isset($keys[$key   1])){
                        $next_pos = $keys[$key   1];
                    }
                    if (is_null($next_pos)) {
                        return $values[$key];
                    }
                    // 区间判断
                    if ($hash <= $pos && $hash >= $next_pos) {
                        return $values[$key];
                    }
                }
            }
        }
            $consisHash = new ConsistentHash();
            $consisHash->addServer("serv1")->addServer("serv2")->addServer("server3");
            echo "key1 at " . $consisHash->find("key1") . ".n";
            echo "key2 at " . $consisHash->find("key2") . ".n";
            echo "key3 at " . $consisHash->find("key3") . ".n";


        系统容错能力
        主要集中在重试机制,高可用,超时配置,服务降级,动态健康检查剔除机制等,
        具体可参考:http://www.infoq.com/cn/articles/qq-web-system-practise

        限流防刷
        这里只想说下比较前置的也就是在Nginx层来做这些策略,通常我们可以通过一些第三方模块来实现,这里只想说下 OpenResty,OpenResty 是一个基于 Nginx与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
        官网:http://openresty.org/cn/
        一些场景使用:http://jinnianshilongnian.iteye.com/blog/2280928
        更多玩法:http://drops.wooyun.org/tips/6403
        最佳实践:https://www.gitbook.com/book/moonbingbing/openresty-best-practices/details

        SSO(Cookie,Session的区别)
        SSO就是我们说的单点登录,说得简单点就是在一个多系统共存的环境下,用户在一处登录后,就不用在其他系统中登录,也就是用户的一次登录能得到其 他所有系统的信任。然后这边早期我们是将信任关系会完全放在客户端,会通过对种下Cookie的解密获取登录信息,然后相对安全的方式是将信任关系置于服 务端,客户端通过携带的token,sessionId等方式向服务端请求验证合法性。
        具体可参考:http://blog.jobbole.com/92339/

        秒杀抢购等
        对于秒杀类主要还是一个流量的限制,是一个漏斗模型,从Nginx开始的结合日志分析的动态限流防刷策略到真正秒杀时的占位排队,到虚库存耗尽时后 将所有超出流量直接前置给纯静态页面的策略,其实都是层层控流的过程,实际到mysql或者redis等持久化库存数据时候的量已经非常可控。
        更多可以参考:http://www.csdn.net/article/2014-11-28/2822858
        一些优化策略:http://chuansong.me/n/1334017
    echo intval(0.58*100) 输出的结果是57,试分析这是为什么?
        原因就是浮点数精度的问题。
        简单的十进制分数如同 0.1 或 0.7 不能在不丢失一点点精度的情况下转换为内部二进制的格式。这就会造成混乱的结果:例如,floor((0.1 0.7)*10) 通常会返回 7 而不是预期中的 8,因为该结果内部的表示其实是类似 7.9999999999…。
        这和一个事实有关,那就是不可能精确的用有限位数表达某些十进制分数。例如,十进制的 1/3 变成了 0.3333333…。所以永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。
        如果确实需要更高的精度,应该使用任意精度数学函数或者 gmp 函数
        你看似有穷的小数, 在计算机的二进制表示里却是无穷的
        浮点数计算,丢失精度问题:php和js都有这个问题,0.58*100=57.99999999999999,是因为浮点数的表示(IEEE 754).
        http://www.laruence.com/2013/03/26/2884.html
        BC系列函数解决http://php.net/manual/zh/ref.bc.php
    用最少的代码写一个求3值最大值的函数.
        function who($a,$b,$c) {
        return $a > $b ? ($a > $c ? $a : $c) : ($b > $c ? $b : $c);
        }
        echo who(33,53,12);
    求两个日期的差数,例如2007-2-5 ~ 2007-3-6 的日期差数
        $date1 = strtotime('2013-09-09');
        $date2 = strtotime('2011-12-11');
        echo ($date1-$date2)/(24*3600);
    echo count('abc'); 输出的结果是什么
        结果为1
    <?php echo count(strlen(“http://php.net”)); ?>的执行结果是?
        答案:1
        讲解:count(var)是用来统计数组或对象的元素个数的。当var是null或者空数组时,结果为0。如果var是普通变量,则返回1。正常情况下返回var中的元素或属性个数。
    用php写出显示客户端Ip与服务器Ip的代码
        echo getenv('REMOTE_ADDR');
        echo getenv('SERVER_ADDR');
        echo gethostbyname('www.baidu.com');
    error_reporting(2047)什么作用?
        相当于 error_reporting('E_ALL'); 输出所有的错误
    打开php.ini中的Safe_mode,会影响哪些参数?至少说出6个。
        此模块打开时,php将检查当前脚本的拥有者是否和被操作文件的拥有者相同,因此,将影响文件操作类函数,程序执行函数 (program Execution Functions)。这些函数有.pathinfo,basename,fopen,system,exec,proc_open 等函数;
    请写一个函数验证电子邮件的格式是否正确(要求使用正则)
        function checkEmail($mail){
        $reg= '/^\w ([- .]\w )*@\w ([-.]\w )*\.\w ([-.]\w )*$/';
        $rst = preg_match($reg,$mail);
        if($rst){
        return TRUE;
        } else {
        return FALSE;
        }
        }
    写一个函数,能够遍历一个文件夹下的所有文件和子文件夹。(目录操作)
    <?php
    $d = dir(dirname(__file__));
    //echo “Handle: ” . $d->handle . “n”;
    //echo “path: ” . $d->path . “n”;
    while ( false !== ($entry = $d->read ()) ) {
    echo $entry . “<br />”;
    }
    $d->close ();
    ?>
        请写一个函数来检查用户提交的数据是否为整数(不区分数据类型,可以为二进制、八进制、十进制、十六进制数字)
        if(!is_numeric($jp_total)||strpos($jp_total,".")!==false){  
            echo "不是整数";  
        }else{  
            echo "是整数";  
        }  
        is_int();

        PHP的strtolower()和strtoupper()函数在安装非中文系统的服务器下可能会导致将汉字转换为乱码,请写两个替代的函数实现兼容Unicode文字的字符串大小写转换
        <?php  
        function mystrtoupper($a){  
            $b = str_split($a, 1);  
            $r = '';  
            foreach($b as $v){  
                $v = ord($v);  
                if($v >= 97 && $v<= 122){  
                    $v -= 32;  
                }  
                $r .= chr($v);  
            }  
            return $r;  
        } 
    PHP的is_writeable()函数存在Bug,无法准确判断一个目录/文件是否可写,请写一个函数来判断目录/文件是否绝对可写
    其中bug存在两个方面,
        1、在windowns中,当文件只有只读属性时,is_writeable()函数才返回false,当返回true时,该文件不一定是可写的。
        如果是目录,在目录中新建文件并通过打开文件来判断;
        如果是文件,可以通过打开文件(fopen),来测试文件是否可写。
        2、在Unix中,当php配置文件中开启safe_mode时(safe_mode=on),is_writeable()同样不可用。
        读取配置文件是否safe_mode是否开启。
    写 5 个不同的自己的函数,来获取一个全路径的文件的扩展名,允许封装 php 库中已有的函数。(新浪)
            */
            // 方法一
            function ext_name1($path){
            $path_info = strrchr($path, '.');//.php
            return ltrim($path_info,'.');
            }
            // 方法二
            function ext_name2($path){
            $path_info = substr($path,strrpos($path, '.'));
            return ltrim($path_info,'.');
            }
            // 方法三
            function ext_name3($path){
            $path_info = pathinfo($path);
            return $path_info['extension'];
            }
            // 方法四
            function ext_name4($path){
            $arr = explode('.', $path);
            return $arr[count($arr)-1];
            }
            // 方法五
            function ext_name5($path){
            $pattern = '/^[^.] .([w] )$/';
            return preg_replace($pattern, '${1}', basename($path));
            }
    * [猴子选大王]
    * @param [type] $m [猴子数]
    * @param [type] $n [出局次数]
    * @return [type] [description]
    */
    //echo "1";
        function monkey($m,$n){
        //定义一个数组
        for($i=1;$i<$m 1;$i  ){
        $arr[]=$i;
        }
        //数组里的任意一个数
        // $arr=rand(1,10);
        //$arr=array(1,2,3,4,5,6,7,8,9);
        //设置数组指针
        $i=0;
        //循环数组,判断猴子次数
        while(count($arr)>1){
        if(($i 1)%$n==0){
        unset($arr[$i]);//把第m只猴子踢出去
        }else{
        array_push($arr,$arr[$i]);//把第m只猴子放在最后面
        unset($arr[$i]);//删除
        }
        $i  ;
        }
        return $arr[$i];//返回结果
        }
    php中WEB上传文件的原理是什么,如何限制上传文件的大小?
        is_uploaded_file() 和 move_uploaded_file()
        利用php的文件函数来实现上传 这段代码分为两个文件,一个为upload.html,一个是upload.php upload.html
        <form enctype=”multipart/form-data” action=”upload.php” method=”post”>
        <input type="hidden" name="max_file_size" value="100000">
        <input name="userfile" type="file">
        <input type="submit" value="上传文件">
        </form>
        请注意我们要实现文件的上传,必须指定为multipart/form-data,否则服务器将不知道要干什么。
        值得注意的是文件upload.html中表单选项 MAX_FILE_SIZE 的隐藏值域,通过设置其Value(值)可以限制上载文件的大小。
        MAX_FILE_SIZE 的值只是对浏览器的一个建议,实际上它可以被简单的绕过。因此不要把对浏览器的限制寄希望于该值。实际上,PHP设置中的上传文件最大值,是不会失效的。但是最好还是在表单中加上MAX_FILE_SIZE,因为它可以避免用户在花时间等待上传大文件之后才发现该文件太大了的麻烦。
        PHP默认的上传限定是最大2M,想上传超过此设定的文件,需要调整PHP、apache等的一些参数. 下面,我们简要介绍一下PHP文件上传涉及到的一些参数:打开php.ini,
        参数 设置 说明
        file_uploads on 是否允许通过HTTP上传文件的开关。默认为ON即是开
        upload_tmp_dir — 文件上传至服务器上存储临时文件的地方,如果没指定就会用系统默认的临时文件夹
        upload_max_filesize 8m 望文生意,即允许上传文件大小的最大值。默认为2M
        post_max_size 8m 指通过表单POST给PHP的所能接收的最大值,包括表单里的所有值。默认为8M
        说明
        一般地,设置好上述四个参数后,在网络正常的情况下,上传<=8M的文件是不成问题 但如果要上传>8M的大体积文件,只设置上述四项还一定能行的通。除非你的网络真有100M/S的上传高速,否则你还得继续设置下面的参数。
        max_execution_time 600 每个PHP页面运行的最大时间值(秒),默认30秒
        max_input_time 600 每个PHP页面接收数据所需的最大时间,默认60秒
        memory_limit 8m 每个PHP页面所吃掉的最大内存,默认8M
        把上述参数修改后,在网络所允许的正常情况下,就可以上传大体积文件了

        window(A)中用window.open打开了window(B),如何从窗口B调用窗口A中的内容?A、B仅仅是窗口的代号,不是窗口名字
        window.opener.document.getElementById()

        用PHP写出一个安全的用户登录系统需要注意哪些方面。
            答:1、密码要使用MD5(密码 字符串)进行加密。
            2、登录表单的名称不要跟数据库字段一样,以免暴漏表字段。
            3、用户表的表名、字段名、密码尽量用不容易被猜到的。
            4、要使用验证码验证登陆,以防止暴力破解。
            5、验证提交的数据是不是来自本网站。
            6、登录后台处理代码数据库部分使用预处理,做好过滤,防止sql注入。
            或者回答:
            1·验证码
            2·U盾
            3·动态口令卡
            4·限制登录次数
            5·使用数字键盘
            6·密码不能粘贴
            7·网址使用ssl,服务器证书https
            8·注册验证必须要使用php验证
            9·以post提交给后端php程序
    PHP安全应从以下几个方面下手
            1、命令注入(Command Injection)
            2、eval注入(Eval Injection)
            3、客户端脚本攻击(Script Insertion)
            4、跨网站脚本攻击(Cross Site Scripting, XSS)
            5、SQL注入攻击(SQL injection)
            6、跨网站请求伪造攻击(Cross Site Request Forgeries, CSRF)
            7、Session 会话劫持(Session Hijacking)
            8、Session 固定攻击(Session Fixation)
            9、HTTP响应拆分攻击(HTTP Response Splitting)
            10、文件上传漏洞(File Upload Attack)
            11、目录穿越漏洞(Directory Traversal)
            12、远程文件包含攻击(Remote Inclusion)
            13、动态函数注入攻击(Dynamic Variable Evaluation)
            14、URL攻击(URL attack)
            15、表单提交欺骗攻击(Spoofed Form Submissions)
            16、HTTP请求欺骗攻击(Spoofed HTTP Requests)
    爬虫模拟登陆,如何跳过验证码?
        1、爬取网站时经常会遇到需要登录的问题,这是就需要用到模拟登录的相关方法。python提供了强大的url库,想做到这个并不难。
        2、首先得明白cookie的作用,cookie是某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据。因此我们需要用Cookielib模块来保持网站的cookie。
        3、这个是要登陆的地址 1 和验证码地址 2
        4、可以发现这个验证码是动态更新的每次打开都不一样,一般这种验证码和cookie是同步的。其次想识别验证码肯定是吃力不讨好的事,因此我们的思路是首先访问验证码页面,保存验证码、获取cookie用于登录,然后再直接向登录地址post数据。
        5、首先通过抓包工具或者火狐或者谷歌浏览器分析登录页面需要post的request和header信息。模拟登录
        验证码地址和post地址
        将cookies绑定自动管理
        使用用户名和密码
        用代码访问验证码地址,获取cookie
        保存验证码到本地
        打开保存的验证码图片输入
        根据抓包信息 构造表单
        根据抓包信息 构造headers
        生成post数据 ?key1=value1&key2=value2的形式
        构造request请求
        打印登录后的页面
        登录成功后便可以利用该cookie访问其他需要登录才能访问的页面。

    非对称加密算法:
        公钥和私钥,加密和解密使用的是两个不同的密钥,所以是非对称
        我的两个秘钥,公钥:123 , 私钥:456
        使用openssl实现非对称加密
        openssl_pkey_get_private() 生成私钥
        openssl_pkey_get_public()  生成公钥
        openssl_private_encrypt() 使用私钥加密数据
        openssl_private_decrypt() 使用私钥解密
        openssl_public_encrypt()  使用公钥加密
        openssl_public_decrypt()  使用公钥解密
        openssl是一个功能强大的工具包,它集成了众多密码算法及实用工具
        openssl genrsa -out pri.key 1024 [生成密钥,包含公钥和私钥,-out输出到文件,1024是密钥的长度]
        openssl rsa -in pri.key -pubout -out pub.key [提取出公钥,-in指定文件,-pubout -out提取输出公钥到文件]
        openssl rsautl -encrypt -in hello -inkey pub.key -pubin -out hello.en 使用公钥加密文件,-in指定要加密的文件,-inkey指定密钥,-pubin表明是用纯公钥文件加密,-out为加密后的文件
        openssl rsautl -decrypt -in hello.en -inkey pri.key -out hello.de
        解密文件,-in指定被加密的文件,-inkey指定私钥文件,-out为解密后的文件。
        Cipher commands:编解码方案
    请简述PHP 5.2的内存池及其内存管理机制、垃圾回收机制
        https://www.jianshu.com/p/63a381a7f70c
    posix 及 perl 兼容正则比较,及函数性能分析
        POSIX 正则和 PCRE 正则最显著的需要知道的不同点:
        PCRE 函数需要模式以分隔符闭合.
        POSIX 兼容正则没有修正符。不像 POSIX, PCRE 扩展没有专门用于大小写不敏感匹配的函数。取而
        代之的是,支持使用 /i 模式修饰符完成同样的工作。其他模式修饰符同样可用于改变匹配策略.
        POSIX 函数从最左面开始寻找最长的匹配,但是 PCRE 在第一个合法匹配后停止。如果字符串 不匹
        配这没有什么区别,但是如果匹配,两者在结果和速度上都会有差别。为了说明这个不同,考虑下面的例子 (来自 Jeffrey Friedl 的《精通正则表达式》一书). 使用模式 one (self)?(selfsufficient)? 在字符串 oneselfsufficient 上匹配,PCRE 会匹配到 oneself, 但是使用 POSIX, 结果将是整个字符串 oneselfsufficient. 两个子串都匹配原始字符串,但是 POSIX 将 最长的最为结果.
        PCRE 可用的修饰符: (i,s,m)
    正则表达式引擎
        DFA,确定性有穷自动机。NFA,非确定性有穷自动机。
        DFA,文本主导引擎,NFA,表达式主导引擎
        DFA引擎则搜索更快一些。但是NFA以表达式为主导,更容易操纵,因此一般程序员更偏爱NFA引擎,NFA的功能更多一些!
     PDO、adoDB、PHPLib 数据库抽象层比较
        PHP 数据库抽象层就是指,封装了数据库底层操作的介于 PHP 逻辑程序代码和数据库之间的中间件。
        PDO 以 PHP 5.1 为基础进行设计,它使用 C 语言做底层开发,设计沿承 PHP 的特点,以简洁易用为准,从严格意义上讲,PDO 应该归为 PHP 5 的 SPL 库之一,而不应该归于数据抽象层,因为其本身和 MySQL 和 MySQLi 扩展库的功能类似。PDO 是不适合用在打算或者有可能会变更数据库的系 统中的。
        ADODB 不管后端数据库如何,存取数据库的方式都是一致的;
        转移数据库平台时,程序代码也不必做太大的更动,事实上只需要改动数据库配置文 件。提供了大量的拼装方法,目的就是针对不同的数据库在抽象层的底层对这些语句进行针对性的翻译,以适应不同的数据库方言!但是这个抽象层似乎体积过于庞 大了,全部文件大概有 500K 左右,如果你做一个很小的网站的话,用这个似乎大材小用了
        PHPLib 可能是伴随 PHP 一同成长最老的数据库抽象层(但和 ADODB 相比,它只算是一个 MySQL 抽象类库),这个抽象类使用方法相当简单,体积小,是小型网站开发不错的选择。
        PDO 提供预处理语句查询、错误异常处理、灵活取得查询结果(返回数组、字符串、对象、回调函数)、字符过滤防止 SQL 攻击、事务处理、存储过程。
        ADODB 支持 缓存查询、移动记录集、(HTML、分页、选择菜单生成)、事务处理、输出到文件。 参考 http://apps.hi.baidu.com/share/detail/4636...
    到商店里买200的商品返还100优惠券(可以在本商店代替现金)。请问实际上折扣是多少?
        如果使用优惠券买东西不能获得新的优惠券,那么
      总过花去了200元,可以买到200 100元的商品,所以实际折扣为 200/300 = 67%.
  腾讯笔试题:统计论坛在线人数分布
  求一个论坛的在线人数,假设有一个论坛,其注册ID有两亿个,每个ID从登陆到退出会向一个日志文件中记下登陆时间和退出时间,要求写一个算法统计一天中论坛的用户在线分布,取样粒度为秒。
  分析:
        一天总共有 3600*24 = 86400秒。定义一个长度为86400的整数数组int delta[86400],每个整数对应这一秒的人数变化值,可能为正也可能为负。开始时将数组元素都初始化为0。然后依次读入每个用户的登录时间和退出时间,将与登录时间对应的整数值加1,将与退出时间对应的整数值减1。这样处理一遍后数组中存储了每秒中的人数变化情况。
  定义另外一个长度为86400的整数数组int online_num[86400],每个整数对应这一秒的论坛在线人数,假设一天开始时论坛在线人数为0,则第1秒的人数online_num[0] = delta[0]。第n 1秒的人数online_num[n] = online_num[n-1]   delta[n]。这样我们就获得了一天中任意时间的在线人数。
    ORM
        数据库的表(table) --> 类(class)
        记录(record,行数据)--> 对象(object)
        字段(field)--> 对象的属性(attribute)

    IaaS, PaaS和SaaS是云计算的三种服务模式
    SaaS:Software-as-a-Service(软件即服务)提供给客户的服务是运营商运行在云计算基础设施上的应用程序,用户可以在各种设备上通过客户端界面访问,如浏览器
    PaaS:Platform-as-a-Service(平台即服务)提供给消费者的服务是把客户采用提供的开发语言和工具(例如Java,python, .Net等)开发的或收购的应用程序部署到供应商的云计算基础设施上去
     IaaS: Infrastructure-as-a-Service(基础设施即服务)提供给消费者的服务是对所有计算基础设施的利用,包括处理CPU、内存、存储、网络和其它基本的计算资源,用户能够部署和运行任意软件,包括操作系统和应用程序
Linux
    说说 Linux 启动大致过程?
        加载 BIOS–>读取 MBR–>Boot Loader–>加载内核–>用户层 Init 依据 Inittab 文件来设定系统运行的等级(一般 3 或者 5,3 是多用户命令行,5 是图形界面)–>Init 进程执行 rc.syninit–>启动内核模块–>执行不同级别运行的脚本程序–>执行 /etc/rc.d/rc.local (本地运行服务)–>执行 /bin/login,就可以登录了。
        这道题可以扩展一下:Init 系统运行等级一共有几种,每一种都是什么?
        0:关机,只要是0就不能开机
        1:单用户模式,不能被远程登陆
        2:多用户不能上网模式
        3:多用户可以上网模式
        4:未使用
        5:有图形的 Linux
        6:重启,只要是 6 就会不断的重启,子子孙孙无穷匮焉的重启
     Linux 系统是由那些部分组成?
        Linux 由系统内核,Shell,文件系统和应用程序四部分组成。
    文件系统 ext2、ext3、ext4 的区别是啥?
        ext3 和 ext2 的主要区别在于 ext3 引入Journal。
        ext2 和 ext3 的格式完全相同,只是在 ext 3 硬盘最后面有一部分空间用来存放 Journal(日志)的记录;
        在 ext2  中,写资料到硬盘中时,先将资料写入缓存中,当缓存写满时才会写入硬盘中;
        在 ext3 中,写资料到硬盘中时,先将资料写入缓存中,待缓存写满时系统先通知 Journal,再将资料写入硬盘,完成后再通知 Journal,资料已完成写入工作;
        在 ext3 中,也就是有 Journal 机制里,系统开机时检查 Journal 的资料,来查看是否有错误产生,这样就快了很多;
        ext4 和 ext3 的主要区别在于:首先 ext4 与 ext3 兼容,ext3 只支持 32000 个子目录,而 ext4 支持无限数量的子目录;ext3 所支持的 16TB 文件系统和最大的 2TB 的文件,而 ext4 分别支持 1EB(1,048,576TB,1EB=1024PB,1PB=1024TB)的文件系统,以及 16TB 的文件;ext3 的数据块分配策略是尽快分配,而 ext4 是尽可能地延迟分配,直到文件在 Cache 中写完才开始分配数据块并写入磁盘;ext4 允许关闭日志,以便某些有特殊需求的用户可以借此进一步提升性能等等等等。
    Linux进程属性
        进程:是用pid表示,它的数值是唯一的
        父进程:用ppid表示
        启动进程的用户:用UID表示
        启动进程的用户所属的组:用GID表示
        进程的状态:运行R,就绪W,休眠S,僵尸Z
    如何杀死指定的进程?
        $ ps -ef |grep 进程名 |grep -v grep|awk  '{print $2}' |xargs kill -9
        注意:这里 awk 后面是单引号不是双引号。
    linux空间未释放问题:
        1.已删除的文件还在被某进程占用,lsof | grep aaa,找到该进程就kill掉
    top 和 ps 命令在进程占有资源率的统计方式有什么不同?
        ps 命令是显示在执行 ps 这个命令时刻所有进程的情况,而 top 是动态的监控进程的情况。
        top 命令显示系统总的统计信息,比如时间、CPU 情况、 内存状态和分区信息等等。
        ps -ef 这个是一个比较常见的搭配方式,-e 是所有进程,-f 是文件之间的关系。
        ps -aux 也是很常用的,意思是显示包含其他使用者的进程。ps 命令也可以搭配 -more 和管道符使用,也可以搭配输出重定向。
        top -n 2 指的是更新两次之后就停;top -d 3 指的是更新周期是三秒;top -p 574 指的是显示 pid 为 574 的进程。top 状态下按 b 是显示高亮。
    说说符号链接与硬链接的区别?
        硬链接是复制,享用同一个 inode,不能跨分区,不能连目录,a 变 b 也变,但是 a 删 b 不删。
        符号链接就是 -s,不享用同一个 inode,可以跨分区可以连目录,等于快捷方式。
        硬链接和软链接的区别:硬链接:一个 inode 号对应多个文件名,软链接:就是一个普通文件,数据块内容是另一文件指向
    进程五状态模型
        运行态:该进程正在执行;
        就绪态:进程做好了准备,只要有机会就开始执行;
        阻塞态:进程在某些事件发生前不能执行,如I/O 操作完成;
        新建态:刚刚创建的进程,操作系统还没有把它加入到可执行进程组中。通常是进程控制块已经创建但还没有加载到内存中的新进程;
        退出态:操作系统从可执行进程组中释放出的进程,或者是因为它自身停止了,或者是因为某种原因被取消。
    多线程比多进程的优势:
        在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种”昂贵”的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间
        答案二:
        根本区别就一点:用多进程每个进程有自己的地址空间(address space),线程则共享地址空间。所有其它区别都是由此而来的:
        1)速度:线程产生的速度快,线程间的通讯快、切换快等,因为他们在同一个地址空间内。
        2)资源利用率:线程的资源利用率比较好也是因为他们在同一个地址空间内。
        3)同步问题:线程使用公共变量/内存时需要使用同步机制还是因为他们在同一个地址空间内。
    多线程如何同步
        windows --- 线程同步有四种方式:临界区、内核对象、互斥量、信号量。
        Linux --- 线程同步有最常用的是:互斥锁、条件变量和信号量。
    epoll与select的区别
        1)select在一个进程中打开的最大fd是有限制的,由FD_SETSIZE设置,默认值是2048。不过 epoll则没有这个限制,它所支持的fd上限是最大可以打开文件的数目,这个数字一般远大于2048,一般来说内存越大,fd上限越大,1G内存都能达到大约10w左右。
        2)select的轮询机制是系统会去查找每个fd是否数据已准备好,当fd很多的时候,效率当然就直线下降了,epoll采用基于事件的通知方式,一旦某个fd数据就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,而不需要不断的去轮询查找就绪的描述符,这就是epoll高效最本质的原因。
        3)无论是select还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的,而select则做了不必要的拷贝
    linux按照时间顺序排序文件列表
        ll -t   -r是反转倒序,按时间顺序反转倒序
    请简述Linux/BSD系统下进程间通讯的方式有哪些,并具体说明在PHP下如何实现
        答: 
        1.管道(Pipe)及有名管道(namedpipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信 
        2.信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数) 
        3.消息队列:消息队列是消息的链接表,包括Posix消息队列systemV消息队列.有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息.消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点. 
        共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式.是针对其他通信机制运行效率较低而设计的.往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥. 
        4.信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。 
        5.套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信.起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和SystemV的变种都支持套接字.
        PHP版本实现:https://www.jianshu.com/p/08bcf724196b
    
    请描述Apache 2.x版本的MPM(Multi-Processing Module)机制,并具体说明在不同的MPM机制下如何支持PHP?
        答:常用的应该就只有3个:worker|prefork|perchild 
        prefork:在功能上就是使用Apache的运行方式,一个父进程,然后根据设置以及连接情况生成相应的子进程数。这种模式可靠性和健壮性都是最好的。但是在性能上,开销过大。达不到我们这些“吸血鬼”的要求了^_^。如果连接数过多的话,会导致我们无法远程登陆,一定要等到连接数下降后才能连接,这也是最让我头痛的事情。 
        worker:混合线程/进程的MPM。一个父进程,后面是带有线程的子进程。每个子进程的线程数是固定且相同的。这是最“平庸”的一个模式,但也是使用人最多的一种模式。因为它性能等各方面比较均衡。性能上要比prefork好一些,只是牺牲了一点点的健壮性和可靠性而已。一般推荐使用这个选项。 
        perchild:也是混合线程/进程的MPM。当启动perchild MPM时,它会建立指定数量的子进程,且每个子进程都具有指定数量的线程,如负载增加了,那它不会建立新的进程(子进程是固定的),只是在子进程下建立新的线程。它还有一个特点就是可以为每一个子进程配置不同的用户和组。也可以为每个虚拟主机指定一个子进程。这种模式性能是最佳的,但是可靠性和健壮性就相对是最差的。各取所需,我个人觉得这种模式也不错,如果你不用第三方的模块的话
    统计目录大小:
        du -sh
    指定nologin用户执行命令
        su -s /bin/bash -c "ls" www
    diff命令的含义:有三种模式,默认normal,-c context ;-u unified模式 
        diff /usr/local/etc/sinamail/EntAdmin.conf /usr/local/entplatform-entadminnew/conf/EntAdmin.conf 
        4c4  第四行,a=add,c=change,d=delete,改变
        31a32,33 第31行 增加 第32到第33行
    Linux diff比较两个目录的不同:
        diff dir1 dir2  -urNaq
        -a  --text  Treat all files as text.
        -u  -U NUM  --unified[=NUM]  Output NUM (default 3) lines of unified[统一] context.
        -u,-U<列数>或--unified=<列数>:以合并的方式来显示文件内容的不同;
        -N  --new-file  Treat absent[缺少] files as empty.
        -r  --recursive  Recursively compare any subdirectories found.
        -q  --brief  Output only whether files differ.[不显示内容]
    uniq -d是只打印重复行 -u是只打印独一无二的行
        文件A : abcd
        文件B: cdef
        取并集:A   B sort A B|uniq
        取交集: sort A B|uniq -d
        取差集:A - B sort A B B|uniq -u
        取差集:B - A sort A B A|uniq -u
    sed替换
        sed 's/@/@/g'
        1.sed替换/提取命令的使用,s是替换取代
        2.里面的#是分割符号作用,一般用/就可以,sed 's/^.*scws-(.*).tar.bz2/1/'
        3.sed的正则表达式提取子字符串,sed 's/正则/1/' 正则里面使用括号代表子表达式,1代表取第一个
        sed 's/./-/g' sed 's/目标/替换成/g'  s替换 g全部 
        sed '/^$/d'
    curl -s --slient 静默模式,不显示错误和进度
        iconv -f gbk -t utf-8 iconv命令 from gbk  to utf-8
        grep -oP  -o only只显示匹配到的那行的那一部分信息 -P perl正则表达式
        正则的[]代表匹配的范围,注意是单个字母,例如:[d.]  匹配的范围数字和点
        零宽断言:即使它匹配到,又要去掉它本身,匹配前一个(?<=xxx),匹配后一个(?=xxx)
        grep -A 2 匹配行和下面的几行
        ?是懒惰模式,只匹配一次就停止
        .表示除n之外的任意字符
        *表示匹配0-无穷
        (ifconfig|grep eth0 -A 1|grep -oP '(?<=addr:)[d.] ')
        tac 文本行倒叙
        curl模拟header:
        curl -H "Host: www.baidu.com" -H "User-Agent: PostmanRuntime/6.4.1" "https://www.baidu.com" -v
        curl  -H "Host:i1.mail.sina.com.cn" "10.41.14.100:8181/api.php?a=UpdateSetting&e=chenlei1697@sina.com&mobileClientNotice=1"
    while 循环执行:
        i=0;while [ $i -lt 100 ];do i=`expr $i   1 `;echo `expr 630892806   $i`"@qq.com";done
    运行级别原理:
        目录下/etc/rc.d/init.d 有许多服务器脚本文件,称为service,命名规则为 S(两位数)服务名 K(两位数)服务名
        查看运行级别:runlevel 
        chkconfig nginx on,这个命令的原理是在/etc/rc.d/rc级别.d/下面增加了一个软连接
    centos查看当前系统的启动级别
        vim /etc/inittab  
            id:3:initdefault:
        /etc/rc.d/init.d

        service 系统服务脚本:
        目录/etc/init.d
        1.$0    当前脚本的文件名
        2.shell特殊变量$n    传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2
        3.case的分支判断
        case $1 in
        "xxx")
                echo "xxx";
                ;;
        esac
    Linux shell函数的定义与调用:
        shell函数必须先定义在调用;
        声明时,无需使用关键字,可以不加function;
        通过local可以定义函数内的局部变量;
        shell函数返回值,0是成功,非0是错误,只能返回0-255之内的整数值

    全文检索技术
        Solr是新一代的全文检索组件,基于Lucene,还能支持HTTP的访问方式,PHP调用Solr也很方便。 MySQL中把一个字段建立FULLTEXT索引,就可以实现全文检索,目前MyISAM和InnoDB的table都支持FULLTEXT索引,InnoDB引擎对FULLTEXT索引的支持是MySQL5.6新引入的特性,之前只有MyISAM引擎支持FULLTEXT索引。对于FULLTEXT索引的内容可以使用MATCH()…AGAINST语法进行查询
        Sphinx是一个基于SQL的全文检索引擎,可以结合MySQL做全文搜索,它可以提供比数据库本身更专业的搜索功能。 Lucene附带的二元分词分析器CJKAnalyzer切词速度很快,能满足一般的全文检索

    Linux的调度策略:
        1.进程是操作系统虚拟出的概念,组织计算机中的任务,安排进程执行的模块称为调度器
        2.进程状态, 就绪,执行,阻塞状态,调度器是CPU时间管理员,负责两件事:就绪进程来执行,执行中的进程打断成就绪状态.
        3.上下文切换就是指进程在CPU中切换执行的过程,内核承担了上下文切换的任务
        4.进程优先级是调度器分配CPU时间的依据,根据优先级分为实时进程和普通进程.0到99留给系统创建的实时进程,100到139是用户创建的普通进程,普通进程默认优先级120,使用nice命令修改,nice -n -20 ./app
        5.Linux2.4是O(n)调度器,Linux2.6开始是O(1)调度器,O(n)调度器每个时间片开始时检查所有就绪进程,按优先级顺序执行.O(1)调度器使用两个队列来执行
        6.完全公平调度器使用了红黑树的数据结构取代了O(1)调度器
    进程间通讯的方式有哪些,各有什么优缺点
        Linux 进程间通信(IPC)以下以几部分发展而来:UNIX进程间通信、基于System V进程间通信、基于Socket进程间通信和POSIX进程间通信。
        UNIX进程间通信方式包括:管道、FIFO、信号。
        System V进程间通信方式包括:System V消息队列、System V信号灯、System V共享内存
        POSIX进程间通信包括:posix消息队列、posix信号灯、posix共享内存。
        现在linux使用的进程间通信方式:
        (1)管道(pipe)和有名管道(FIFO)
        (2)信号(signal)
        (3)消息队列
        (4)共享内存
        (5)信号量
        (6)套接字(socket)
    PHP进程间通信的几种方式
    消息队列,信号量 共享内存,信号,管道,socket
        管道:
        优点是所有的UNIX实现都支持, 并且在最后一个访问管道的进程终止后,管道就被完全删除;
        缺陷是管道只允许单向传输或者用于父子进程之间
        系统IPC:
        优点是功能强大,能在毫不相关进程之间进行通讯;
        缺陷是关键字KEY_T使用了内核标识,占用了内核资源,而且只能被显式删除,而且不能使用SOCKET的一些机制,例如select,epoll等.
        socket可以跨网络通讯,其他进程间通讯的方式都不可以,只能是本机进程通讯。
    容器化
        1.Linux 发展出了另一种虚拟化技术:Linux 容器(Linux Containers,缩写为 LXC)
        2.Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离
        3.Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口
        4.Docker 是服务器----客户端架构。命令行运行docker命令的时候,需要本机有 Docker 服务
        curl -sSL https://get.docker.com/ | sh
        service docker start
        5.Docker 把应用程序及其依赖,打包在 image 文件里面
        6.容器实例,本身也是一个文件,称为容器文件
        docker image pull 名称 //拉取image文件
        docker image ls//列出所有image
        docker image rm [imageName] //删除 image 文件
        docker container run hello-world //运行image文件
        docker container start [containID]//运行已经存在的容器
        docker container kill [containID] //终止容器
        docker container ls --all  //列出所有容器,包括终止的
        docker container rm [containID]//删除容器

        hello-world:
        1.docker run hello-world
        2.没有这个image会自动拉取镜像,然后运行起来

        在ubuntu的docker中运行ubuntu
        1.docker container run -p 6666:80  -it dc86b7b90238  bash //端口映射外面的6666到内部的80
        2.docker container exec  -it 3ce8952ce68d  bash  //在运行的容器中执行命令,-i

        在ubuntu的docker中运行centos
        1.docker pull centos:6
        2.给运行的容器映射本地端口
            1)docker commit  6e54eac36507  centos_image1//提交运行中的容器为一个镜像
            2)docker run -d -it -p 6667:80 centos_image1 /bin/bash  //从新run新的镜像

        制作自己的 Docker 容器
        1. .dockerignore 忽略打包的文件
        2. Dockerfile 文件
        3. docker image build -t koa-demo .  //创建image文件
        4. docker container run -p 8000:3000 -it koa-demo /bin/bash //从image文件生成容器运行
        容器的 3000 端口映射到本机的 8000 端口,-it参数 容器的 Shell 映射到当前的 Shell
    指针和引用的区别?
      (1)指针有空间,存放的是变量的地址,引用只是变量的别名
       (2)指针可以为NULL,引用不可以为空
      (3)指针可以在初始化以后改变指向,引用则一旦初始化就不能改变
      (4)有const指针,无const引用
      (5)指针可以有二级操作(**p),引用无
      (6)操作指针指向的变量需要解引用(*p),而操作引用即可达到操作变量的目的
      (7)指针和引用自增操作的含义不同
    tcpdump
        tcpdump -i eth0 -s 0 -n -l port 53 
        tcpdump查看端口514的数据
        tcpdump 'port 514' -i eth0
        tcpdump -XvvennSs 0 -i eth0 tcp[20:2]=0x4745 or tcp[20:2]=0x4854 看http协议
        tcpdump 'port 80' -i eth0 -w tcp.pcap 保存成文件用 wireshark打开
        1.使用telnet与tcpdump互相配合进行测试
        115.159.28.111.51142 > 10.141.14.117.http: Flags [S], seq 1784777886, win 29200,
        10.141.14.117.http > 115.159.28.111.51142: Flags [S.], seq 1181145550, ack 1784777887, win 28960,
        115.159.28.111.51142 > 10.141.14.117.http: Flags [.], ack 1, win 229,
        1.常见参数
            tcpdump -i eth0 -nn -s0 -v port 80
            -i  选择监控的网卡
            -nn 不解析主机名和端口号,捕获大量数据,名称解析会降低解析速度
            -s0  捕获长度无限制
            -v  增加输出中显示的详细信息量
            port 80 端口过滤器,只捕获80端口的流量,通常是HTTP
        2.
        tcpdump -A -s0 port 80
        -A 输出ASCII数据
        -X 输出十六进制数据和ASCII数据
        3.
        tcpdump -i eth0 udp
        udp 过滤器,只捕获udp数据
        proto 17 协议17等效于udp
        proto 6  等效于tcp
        4.
        tcpdump -i eth0 host 10.10.1.1
        host 过滤器,基于IP地址过滤
        5.
        tcpdump -i eth0 dst 10.105.38.204
        dst 过滤器,根据目的IP过滤
        src 过滤器,根据来源IP过滤
        6.
        tcpdump -i eth0 -s0 -w test.pcap
        -w 写入一个文件,可以在Wireshark中分析
        7.
        tcpdump -i eth0 -s0 -l port 80 | grep 'Server:'
        -l 配合一些管道命令的时候例如grep
        8.
        组合过滤
        and or &&
        or or ||
        not or !
        9.
        快速提取HTTP UA
        tcpdump -nn -A -s1500 -l | grep "User-Agent:"
        使用egrep 匹配 UA和Host
        tcpdump -nn -A -s1500 -l | egrep -i 'User-Agent:|Host:'
        10.
        匹配GET的数据包
        tcpdump -s 0 -A -vv 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'
        匹配POST包,POST的数据可能不在包里
        tcpdump -s 0 -A -vv 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504f5354'
        11.
        匹配HTTP请求头
        tcpdump -s 0 -v -n -l | egrep -i "POST /|GET /|Host:"
        匹配一些POST的数据
        tcpdump -s 0 -A -n -l | egrep -i "POST /|pwd=|passwd=|password=|Host:"
        匹配一些cookie信息
        tcpdump -nn -A -s0 -l | egrep -i 'Set-Cookie|Host:|Cookie:'
        12.
        捕获DNS请求和响应
        tcpdump -i eth0 -s0 port 53
        13.
        使用tcpdump捕获并在Wireshark中查看
        使用ssh远程连接服务器执行tcpdump命令,并在本地的wireshark分析
        ssh root@remotesystem 'tcpdump -s0 -c 1000 -nn -w - not port 22' | wireshark -k -i -
        ssh ubuntu@115.159.28.111 'sudo tcpdump -s0 -c 1000 -nn -w - not port 22' | wireshark -k -i -
        14.
        配合shell获取最高的IP数
        tcpdump -nnn -t -c 200 | cut -f 1,2,3,4 -d '.' | sort | uniq -c | sort -nr | head -n 20
        15.捕获DHCP的请求和响应
        tcpdump -v -n port 67 or 68
    上传下载本地文件到服务器
        scp local_file_path username@servername:reFilemote_file_path
        scp username@servername:remote_file_path local_file_path 
        scp ubuntu@123.206.7.231:/var/www/html/video/apis/douban.sh douban.sh
        -r 是上传本地目录到远程

    SSH是一种网络协议,用于计算机之间的加密登录
    OpenSSH,它是自由软件,基于ssh协议
    远程主机有两个钥匙:公钥 私钥
    1.远程主机收到用户的登录请求,把自己的公钥发给用户
    2.用户使用这个公钥,将登录密码加密后,发送给远程主机
    3.远程主机用自己的私钥,解密登录密码,如果密码正确,就同意用户登录

    登陆的第二种方式:公钥登录,用户有两个钥匙:公钥 id_rsa.pub,私钥id_rsa
    生成两个钥匙 ssh-keygen
    将公钥发给远程 
    ssh ubuntu@123.206.7.231 'mkdir -p .ssh && cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub
    注意远程主机 当前用户主目录 .ssh目录的权限

    DNS的通信过程:
        tcpdump -i eth0 -s 0 -n -l port 53 

        10:44:10.203166 IP 10.105.38.204.47751 > 10.236.158.114.53: 44627  A? www.huiful.com. (32)
        我的IP地址10.105.38.204端口47751向DNS服务器的10.236.158.114端口53发送请求
        44627是我这个请求的标识, 代表启用递归查询,A?表示A类型的查询,32代表DNS查询报文长度

        3.DNS的响应
        10:44:10.203634 IP 10.236.158.114.53 > 10.105.38.204.47751: 44627 1/0/0 A 123.207.167.115 (48)
        44627和请求对应,1/0/0  1个应答资源 0个授权记录 0个额外信息记录 A记录IP地址 48是报文长度

        host -t A www.baidu.com的DNS查询过程:
        IP 10.105.38.204.39732 > 10.236.158.106.53: 11431  A? www.baidu.com. (31)
        IP 10.236.158.106.53 > 10.105.38.204.39732: 11431 3/0/0 CNAME www.a.shifen.com., A 115.239.210.27, A 115.239.211.112 (90)
    dig -t ns sopans.com 查询ns服务器
        dig -t mx sopans.com 查看mx记录
        dig  trace www.sopans.com DNS 迭代查询流程
        DNS会逐级缓存,如果要查询最新的信息,可以这样
        dig @dns1.hichina.com -t a www.sopans.com
        dig @8.8.8.8 -t a www.sopans.com
    date 打印和设置系统时间和tar命令
        date  %F  展示当前时间2017-03-08
        monthYear=`date   "%Y%m"`;
        day=`date  "%d"`;

        `命令替换`
        tar -czf 包名称 *, c是create创建, z是gzip压缩,f是file使用文件形式 --exclude 排除目录
        tar -xf 包名称 , x是extract提取
        查找命令find / -mtime  10 -exec rm {} ; mtime是 modified time修改时间,单位是天,  10是10天以前;-exec是执行命令, -exec 命令 {} ;大括号会被当前查找的结果替换,是固定的转义用的.例如: find /root -mtime  10 -exec wc -l {} ; 统计10天前文件的行数
    查找 /usr/local/apache/logs 目录最后修改时间大于 30 天的文件并删除
        find 命令以及相关搭配命令是笔试中的重点,因为在现实中运用的情况最多,所以必考必考必考!!!
        $ find /usr/local/apache/logs -type f -mtime  30 -ok rm {} ;
        使用 mtime  30 来描述修改时间大于 30 天,使用 -type -f 来描述文件,然后使用 -ok 命令将所有满足的文件都执行下一步操作。
        这里是删除文件,所以比较人性化的用 ok,删之前询问一下,如果简单暴力就可以直接 -exec,直接枪毙掉。用了 -exec 的话是不用 -f 的,多此一举。
    httpd -S 查看apache的配置文件的地址
    httpd -V 查看配置文件
    apache禁止目录浏览
        Options -Indexes  FollowSymLinks  ExecCGI中的 Indexes改为-Indexes
        在Indexes前加 代表允许目录浏览,加-代表禁止目录浏览。之后访问不存在默认首页的目录将显示HTTP 403错误页面。
    统计在一台前端机上高峰时间TCP连接的情况,统计命令:
        netstat -n | awk '/^tcp/ {  S[$NF]} END {for(a in S) print a, S[a]}'
        netstat -n可以查看网络连接情况
        netstat -tan | grep "ESTABLISHED" | grep ":80" | wc -l
    查找端口
    netstat -ltunp -a所有 -n禁止显示别名 -t tcp的 -u是udp的 -p显示进程 -l 监听中
        netstat -nb查看的都是tcp,因为都是会话,udp不建立会话
        查看端口的链接数:
        netstat -npt | grep 5050 |wc -l
    如何统计出一台 Web Server 上的各个状态(ESTABLISHED / SYN_SENT / SYN_RECV 等)的个数?
        $ netstat -antl|grep ESTABLISTHED|wc -l
        $ netstat -antl|grep SYN_SENT|wc -l
        $ netstat -antl|grep SYN_RECV|wc -l
        netstat 命令的 -t 参数是查询 TCP 协议的链接,-l 参数是查询 Listen 状态下的链接。
        netstat -an 的话会出现大概三个部分的内容,一部分是 TCP 协议内容,一部分是 UDP 协议的内容,还有一部分是 Unix Socket 方面的链接,Active UNIX domain sockets (servers and established)。
    awk基础编程:
        awk每次读一行数据,如果设置了-F选项,是在这一行使用分隔符分,$0是全部
        awk由模式和动作组成,模式,/正则模式/动作 条件判断模式{动作} 可以有多个;     特殊的模式(BEGIN,END)cat 1.txt | awk '$1=="aaa"{print 2}/aaa/{print 2}{print $1}' 
        条件操作符,~匹配正则 !~不匹配  cat 1.txt |awk 'BEGIN{}{if($1~/END/) print $1}END{}' 
        保存结果,使用>重定向符号
        cat 1.txt | awk 'split_after==1{n  ;split_after=0} /--END--/ {split_after=1}{print > "acert" n ".pem"}'

        awk配合二维数组:
        cat 1.txt | awk 'BEGIN{i=0;j=0} {a[i][j]=$1;j  ;} /END/{j=0;i  } END{h=length(a);for(n=0;n<h;n  ){ p=length(a[n]);for(o=0;o<p;o  ){r=2;if(n==0) r=1;if(n==(h-1)) r=3;print a[n][o] >"test"r".txt"} }}' 


        查询访问最多的ip:
        cat /var/log/apache/webface-access.log|awk -F " " '{a[$1] =1}END{for(i in a){print a[i]"次数==="  i}}'|sort -g|tail
        awk的BEGIN和END表达式:
        BEGIN{}里面的操作,在awk扫描输入之前执行
        {}中间的将在扫描每行的时候执行
        END{}里面的操作,在awk扫描输入之后执行
        查询占内存最高的10个进程 ps aux | awk '{print $2,$3,$4,$11}' | sort -nk3|tail
        总共占用的cpu
        ps aux | awk '{print $3,$4,$11}' | sort -nk1|awk 'BEGIN{cpu=0}{cpu =$2}END{print cpu}'
        awk '{}':读入有'n'换行符分割的一条记录,按分隔符是"空白键"分割这一行,
        例如:last -n 5|awk '{print $1"  "$3}'
             ll |awk 'BEGIN{size=0;}{size =$5;}END{print size;}'使用begin end统计文件占用字节数
            查看系统的信息:
        # uname -a # 查看内核/操作系统/CPU信息
        # head -n 1 /etc/issue # 查看操作系统版本
        # cat /proc/cpuinfo # 查看CPU信息
        # hostname # 查看计算机名
        # lspci -tv # 列出所有PCI设备
        # lsusb -tv # 列出所有USB设备
        # lsmod # 列出加载的内核模块
        # env # 查看环境变量
    统计某一天网站的访问量
        推荐篇文章,讲awk实际使用的shell在手分析服务器日志不愁
    按照以下要求配置一个防火墙规则
        a. 对所有地址开放本服务器的80端口、22端口、10~21端口。
        b. 其他机器可以用ping命令来探测本服务器的链接情况
        c. 其他没有被准许的端口将禁止访问
        $ iptables -I INPUT -p tcp -dport 80 -j ACCEPT
        $ iptables -I INPUT -p tcp -dport 22 -j ACCEPT
        $ iptables -I INPUT -P tcp -dport 10:21 -i ACCEPT
        $ iptables -I INPUT -p icmp -j ACCEPT
        $ iptables -I INPUT -j REJECT
        iptables 的内容主要包括 “四表 五链”,不过具体问道哪四表哪五链的可能性很小,倒是这种结合实际情况直接让写一连串的规则考题蛮常见的。这道题很基础,写 iptables 有点在 CCNP 里写 ACL 控制访问列表的意思。
    如何修改文件为当前用户只读
        chmod u=r 文件名
        chmod 的mode字符串:
        mode:权限设定字符串,格式:[ugoa][ -=][rwxX]
        u 用户 g 群组 o 其他 a所有
          添加 - 取消 = 唯一设定
        例如:chmod a rw,o-w 1.txt
    守护进程
        1.调用fork()创建出来一个新的进程,这个新进程会是将来的守护进程
        2.在新守护进程的父进程中,调用exit(),为了守护进程的爷爷进程确认父进程结束
        3.在新守护进程中,调用setsid(),使得该进程有一个新的进程组和新的会话,保证了该进程不与控制终端相关联
        4.用chdir()将当前工作目录改为根目录,因为前面fork出来的新进程,当前工作目录可能在文件系统的任何地方
        5.关闭所有文件描述符
        6.打开 0 1 2号文件描述符(标准输入,标准输出,标准错误),把它们重定向到/dev/null
    cron
        cron&crontab
           cron是一个在后台运行的守护进程,而crontab是一个设置cron的工具。cron调度的是/etc/crontab文件。
       6. cron.allow&cron.deny
           crontab使用的两个文件,cron不会用到它们。
           7. cron.daily&cron.hourly&cron.weekly&cron.monthly
           cron.daily、cron.hourly、cron.weekly和cron.monthly这四个目录均位于/etc下,但cron和 crontab两个并不处理。它们是由配置在/etc/crontab中的run-crons处理,run-crons是位于目录/usr/lib /cron下的一个Shell脚本文件:
           每五分钟执行  */5 * * * *
        每小时执行     0 * * * *
        每天执行        0 0 * * *
        每周执行       0 0 * * 0
        每月执行        0 0 1 * *
        每年执行       0 0 1 1 *
    统计日志并发邮件的脚本
        #!/bin/bash
        logBasePath="/data1/mailLog/app/api-app/";
        monthYear=`date   "%Y%m"`;
        day=`date  "%d"`;

        logPath="${logBasePath}${monthYear}/${day}/*.gz";
        tmpFile="/tmp/${monthYear}${day}-devices.log";
        echo "start...";
        zcat $logPath|grep 11232|grep -oP "device_id=.*"|uniq|sort -u > $tmpFile;
        echo $tmpFile;
        num=`wc -l ${tmpFile}`;
        php /usr/home/shihan1/shells/mobileDevices/sumMobileDevice.php  "${num}"

        require_once 'class.phpmailer.php';
        date_default_timezone_set("Asia/Shanghai");
        $users=array(
        'shihan1@staff.sina.com.cn',
        'wangjing1@staff.sina.com.cn',
        'liwei23@staff.sina.com.cn'
        );

        $emailBody="设备ID列表和个数: {$argv[1]}";
        $date=date("Y-m-d");
        sendmail($users,"{$date}移动端设备统计",$emailBody);

        function sendmail($emails,$subject,$eml,$author='日志统计'){
            $phpMailer            = new PHPMailer();
            $phpMailer->Host      = 'smtp.sina.com';
            $phpMailer->SMTPDebug = false;
            $phpMailer->SMTPAuth  = true;
            $phpMailer->Port      = 25;
            $phpMailer->Username  = 'applytotry@sina.com';
            $phpMailer->Password  = 'sinanet';
            if(!is_array($emails)){
                $emails=array($emails);
            }
            foreach($emails as $address){
                $phpMailer->AddAddress($address);
            }
            $phpMailer->CharSet = 'UTF-8';
            $phpMailer->SetFrom("applytotry@sina.com",$author);
            $phpMailer->Subject = $subject;
            $phpMailer->AltBody = '您的客户端不支持HTML,请更新其它工具浏览';
            $phpMailer->MsgHTML($eml);
            $phpMailer->Mailer  = 'smtp';
            $ret                = $phpMailer->Send();
            return $ret;
        }
    gcc的常用参数:
        gcc GNU编译器套件 yum -y install gcc* gcc  gcc-c  
        -I 编译的时候指定头文件的路径
        -c 生成二进制文件.o
        -o 指定输出名字 

        库:将源代码->二进制格式的源代码,加密的作用
        使用:1.头文件,2.制作出来的库

        静态库和动态库:win(静).a,win(动).dll;linux(静).o,linux(动).so

        动态库的制作和使用,后缀是.so
        命名规则:libxxx.so
        制作步骤:将源文件生成.o文件  gcc a.c b.c -c -fpic
        打包 gcc -shared -O a.o b.o libxxx.so 

        如何防止DDOS 攻击?如提供足够资源给你,要保证用户访问不影响。
        首先确定攻击源范围,如果是处于公司内部,那么暂时性的将这一区域的内部网络封掉,如果是外部IP 那么通过防火墙或者软件进行IP过滤,这样能够一定程度上减缓承受的攻击压力。其次,开启备用服务器,如果攻击流量过大,限制大流量访问,保证大多数用户的正常使用。​

        简述CC攻击原理?如何防止CC 攻击?受到CC攻击如何处理? 
        CC攻击的原理就是攻击者控制某些主机不停地发大量数据包
        给对方服务器造成服务器资源耗尽,一直到宕机崩溃。CC主要是用来攻击页面的,每个人都有这样的体验:当一个网页访问的人数特别多的时候,打开网页就慢了,CC就是模拟多个用户(多少线程就是多少用户)不停地进行访问那些需要大量数据操作(就是需要大量CPU时间)的页面,造成服务器资源的浪费,CPU长时间处于100%,永远都有处理不完的连接直至就网络拥塞,正常的访问被中止。
        防御CC攻击可以通过多种方法,禁止网站代理访问,尽量将网站做成静态页面,限制连接数量,修改最大超时时间等。
        写一个脚本,抓取日志文件中非正常访问的IP,因为这类的攻击不会如同正常用户一样读取网站的所以信息,一般是比较有针对性的访问,将这类IP屏蔽掉。为了防止可能的误伤,可以在对这个屏蔽做一个有效期。​

        你用过那些LVS ,并讲述LVS各个模式的特点和区别?  

        LVS/DR模式用得比较多吧,这个模式效率最好。因为他的原理是把收到的包改了下MAC地址就丢给交换机了这样就造成两个大缺点1是所有负载机器都要在同一个ip段才能响应,所以在idc上架的时候,为了方便扩容一开始就要预留好同ip段的ip,留ip是要钱的2所有用户发过来的包交换机都要处理2次,加大了前端交换机的压力3这种模式对收包少,回包多的应用(http常规应用)非常有效,但是如果是注册、登录之类(post大数据)的应用服务器,由于收包较多,lvs的服务器压力还是很大的(所以大量注册登录数据l的vs服务器的前面还是需要dns轮询来分流lvs服务器的),但是好歹把应用的计算压力给分担掉了nat模式效率惨,原理和iptable做网关上网差不多,lvs服务器压力最大最大的好处是只需要一个对外ip就可以了,内网ip随便配置,但是压力摆在那里,能配多少ip都么用另外一个模式用的少也不怎么有兴趣了解

        一千万 并发,你有那些方案?【提示这些单用LVS 承受不起的,】
        先用智能dns负载到不同的lvs上如果有钱可以在lvs前端上f5智能dns——F5——LVS三层负载分流,最后最大的压力其实还是在数据库上​
    F5负载均衡:
        F5配置最简单负载均衡,需要配置的参数有Node(节点)、Pool(资源池)、和Virtual Server(虚拟服务器),它们的关系是,先配置Node,然后配置VS。Node是最基本的定义,如每个服务器就是一个Node,负载均衡Pool是一组Node接收和处理流量的一组设备,如web服务器集群。BIGIP系统将客户机流量请求发送到Pool成员中的任一服务器上(Node),然后将Pool与BIGIP系统中的Virtual server相关联,最后,BIGIP系统将进入Virtual Server中流量传输到Pool成员,Pool再传达给Node。
    负载均衡算法:
        负载均衡设备本身都是以负载均衡算法为基础的,负载均衡算法分为两种:静态负载均衡算法和动态负载均衡算法.  
        轮询(RoundRobin):顺序循环将请求一次顺序循环地连接每个服务器。 当其中某个服务器发生第二到第7层的故障,BIGIP就把其从顺序循环队列中拿出,不参加下一次的轮询,直到其恢复正常。   ·比率(Ratio):给每个服务器分配一个加权值为比例,根椐这个比例,把用户的请求分配到每个服务器。当其中某个服务器发生第二到第7层的故障,BIGIP就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。  
        优先权(Priority):给所有服务器分组,给每个组定义优先权,BIGIP用户的请求,分配给优先级最高的服务器组(在同一组内,采用轮询或比率算法,分配用户的请求);当最高优先级中所有服务器出现故障,BIGIP才将请求送给次优先级的服务器组。这种方式,实际为用户提供一种热备份的方式。   
        最少的连接方式(Least Connection):传递新的连接给那些进行最少连接处理的服务器。当其中某个服务器发生第二到第7层的故障,BIGIP就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。  
        最快模式(Fastest):传递连接给那些响应最快的服务器。当其中某个服务器发生第二到第7层的故障,BIGIP就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。
        观察模式(Observed):连接数目和响应时间以这两项的最佳平衡为依据为新的请求选择服务器。当其中某个服务器发生第二到第7层的故障,BIGIP就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。
        预测模式(Predictive):BIGIP利用收集到的服务器当前的性能指标,进行预测分析,选择一台服务器在下一个时间片内,其性能将达到最佳的服务器相应用户的请求。(被BIGIP进行检测) 
        动态性能分配(DynamicRatio-APM):BIGIP收集到的应用程序和应用服务器的各项性能参数,动态调整流量分配。
        动态服务器补充(DynamicServer Act.):当主服务器群中因故障导致数量减少时,动态地将备份服务器补充至主服务器群。
        服务质量(QoS):按不同的优先级对数据流进行分配。
        服务类型(ToS):按不同的服务类型(在Type of Field中标识)对数据流进行分配。
        规则模式:针对不同的数据流设置导向规则,用户可自行编辑流量分配规则,BIGIP利用这些规则对通过的数据流实施导向控制

        ​IO 性能不足,你如何优调?
        简单的就是加硬件罗raid卡加大缓存,用更快的硬盘软件的话磁盘写入用deadline,禁止文件系统的日志优化性能oracle的话还尽量用裸磁盘做数据盘,不同业务还可以分开写到不同硬盘​


        ​面对大并发,如何 MySQL 优化?
        数据库方面双主带多从,读写分离罗改应用代码,把不常修改的数据全部读入memcache中(比如用户登录用的帐号数据),这样基本把mysql的读压力分担走优化mysql语句,该用myiasm表的用myiasm表(比如不太太重要的用户帐号数据表),数据库设置concurrent_insert直接从表尾并发插入,这样可以有效降低大量注册与登录的锁竞争​

        讲术 Memecahe 工作原理和优缺?
        memcache就是一个key-value的~~怎么说呢,nosql内存数据库?反正就是key-value形式把数据存放在内存的一个程序优点速度快,部署方便缺点吃内存,掉电就没数据了。基本还是做mysql前端缓存或者存放诸如session之类丢掉后可以随时取回来的数据大部分应用实现都需要修改代码,新项目随便弄,一开始设计好就行,旧项目得改代码,大型点的项目以前没上,现在我估计没人愿意去改代码上​

        讲术CDN工作原理和优缺? 
        优点就是分流罗,可以有效分担静态资源的压力最大缺点是各地数据同步需要一段时间,更新一个重要静态文件的话,生效时间急死人,而且价格也不便宜​

        你如何监视服务器质量和网络质量?用个哪些工具及优缺点?

        cacti绘图漂亮,查看以往数据非常方便,但是报警功能弱我觉得最大的缺点还是rrdtool上,cacti   rrdtool暂时没有把旧数据写到数据库的插件,导致做数据处理会很麻烦取数据方便有snmp,能写各种脚本想要什么数据就有什么数据.​
        nagios报警强大,定义好报警脚本想短信猫就短信猫,想post短信平台就post短信平台但是不适合查询以往数据,最大缺点不能像cacti那样一次行取多个数据进行记录、绘图,只能通过返回值确认是否故障。一般都和cacti同时使用​



    Golang

    Golang核心编程:

    区块链研发工程师(分布式账本技术,互联网数据库技术,特点是去中心化)
    Go服务器端/游戏软件工程师(现在主流是C C  ,处理日志,数据打包,文件处理,美团后台流量支撑,处理大并发;游戏后台数据通道)
    Golang分布式/云计算软件工程师(盛大云,cdn,京东消息推送系统,分布式文件处理)

    Golang的应用领域:
    区块链应用
    后端服务器应用:主站后台流量(排序,推荐,搜索等),提供负载均衡,cache,容器,按条件分流;游戏服务器(通讯,逻辑,数据存储)
    云计算/云服务后台应用:cdn内容分发网络,cdn的调度系统,分发系统,监控系统,短域名服务;分布式文件系统;说明golang的计算能力很强

    高效而愉快的学习
    先整体框架,再细节
    工作中用到什么,快速学习的能力
    学习软件编程是在琢磨别人是怎么做,而不是我认为应该怎么做的过程
    把重点放在逻辑处理和编程思想上,而不是语法本身

    如何深入的学习一个新技术或者知识点
    1.项目开发过程中,需要解决某个问题
    2.先看看是否能用传统的技术解决,使用新技术
    3.研究原理和基本语法
    4.快速入门案例,简单,了解新技术的基本使用
    5.研究技术的细节,这个地方是最能体现程序员的能力,也是最废时间的

    Go语言的特点:
    1.从c语言继承了许多特性,数据类型,参数,指针等
    2.引入了包的概念,每一个文件归属一个包,不能单独存在
    3.引入了垃圾回收机制
    4.天然并发,goroutine,基于CPS并发模型
    5.支持管道通信机制
    6.支持函数返回多个值
    7.新的创新,比如切片slice,延时执行defer等

    开发工具:VSCode Sublime Text  Vim  Emacs

    windows搭建开发环境:
    1.下载安装就可以,
    https://studygolang.com/dl
    比如我的目录 D:golang    code目录(存放第三方类库) go目录(golang安装目录)  workspace目录(我自己的代码目录)
    2.三个环境变量
    PATH变量,执行go安装的目录D:golanggobin;
    GOPATH变量,D:golanggocode
    GOROOT变量,D:golanggoworkspace

    Golang执行流程分析
    1.编译后的文件会把资源打包进去,因此会变得很大,可以拷贝到没有Go运行环境的地方执行
    2.程序如果有错误,编译时,会在错误的那行进行提示
    3.Golang默认会给每行后面加分号
    4.同一个main包下只能有一个main函数,同一个目录下只能是同一个包

    Golang标准库
    1.Go标准库下的包和源文件 D:golanggosrc
    2.文档http://docscn.studygolang.com/pkg/
    3.调用包的方式 import 包名   包名.函数名

    Goroutines和线程:
    1.动态栈:
        1)线程都有一个固定大小的内存块(一般会是2MB)来做栈
        2)一个goroutine会以一个很小的栈开始其生命周期,一般只需要2KB,不是固定的;栈的大小会根据需要动态地伸缩
    2.Goroutine调度:
        1)线程是使用硬件定时器进行的调度,速度慢
        2)Go是使用的自己的调度器,在线程的基础上调度,不需要进入内核的上下文
    3.GOMAXPROCS环境变量可以确定启动多少线程同时执行go代码
    4.goroutine没有可以被程序员获取到的身份(id)的概念

项目中遇到的问题和怎么解决的,你觉得做得最好的项目

1.登陆日志统计展示问题  分表 索引优化
2.免费邮箱接口调试问题
3.客户端APP接口问题

0 人点赞