吴峰的博客

1.php数组函数有哪些,都是干什么的

array                   -- 新建一个数组

array_diff              -- 计算数组的差集

array_map               -- 将回调函数作用到给定数组的单元上

array_merge             -- 合并一个或多个数组

array_pop               -- 将数组最后一个单元弹出(出栈)

array_push              -- 将一个或多个单元压入数组的末尾(入栈)

array_rand              -- 从数组中随机取出一个或多个单元

array_replace           -- 使用传递的数组替换第一个数组的元素

sort                    -- 对数组排序

rsort                   -- 对数组逆向排序

asort                   -- 对数组进行排序并保持索引关系

next                    -- 将数组中的内部指针向前移动一位

ksort                   -- 对数组按照键名排序

key                     -- 从关联数组中取得键名

in_array                -- 检查数组中是否存在某个值

end                     -- 将数组的内部指针指向最后一个单元

each                    -- 返回数组中当前的键/值对并将数组指针向前移动一步

count                   -- 计算数组中的单元数目或对象中的属性个数


2.将二维数组转换成一维数组用哪个函数

array_column() 返回输入数组中某个单一列的值。


3.字符串转换数组和数组转换字符串用的是什么

implode 将数组变成字符串

explode 分割字符串,返回一个数组

这里如果字符串没有任何符号可以进行分割的话,就需要考虑别的方法了

str_split — 将字符串转换为数组


4.检查字符是否存在一个字符串中,他的返回值是什么?

strpos

返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。注释: 字符串位置从 0 开始,不是从 1 开始。


5.Strstr函数是干什么的

strstr() 函数搜索字符串在另一字符串中是否存在,如果是,返回该字符串及剩余部分,否则返回 FALSE。


6.PHP各版本的区别

PHP5.2 以前:autoload, PDO 和 MySQLi, 类型约束

PHP5.2:JSON 支持

PHP5.3:弃用的功能,匿名函数,新增魔术方法,命名空间,后期静态绑定,Heredoc 和 Nowdoc, const, 三元运算符,Phar

PHP5.4:Short Open Tag, 数组简写形式,Traits, 内置 Web 服务器,细节修改

PHP5.5:yield, list() 用于 foreach, 细节修改

PHP5.6: 常量增强,可变函数参数,命名空间增强

1).php5.2以前

1.1) autoload的使用;当在代码中使用一个未定义的类的时候,该函数就会被调用

现在已经不再使用了,因为一个项目中只能有一个__autoload();现在使用spl_autoload_register();

1.2) PDO和MiSQLi的使用

1.3) 类型约束。

      通过类型约束可以限制参数的类型,不过这个机制不完善。

2). php5.2 

(2006-2011)

2.1) JSON的支持

  增加了json_encode(),json_decode()等函数

3).php5.3

(2009-2012)

PHP5.3 算是一个非常大的更新,新增了大量新特征,同时也做了一些不向下兼容的修改

3.1) 弃用的功能

3.2) 匿名函数

$func=function($arg){

   echo $arg;

}

$func(‘hello‘);

3.2) 新增了魔术方法 __invoke(),__callStatic();

随着匿名函数的加入,PHP 引入了一个新的魔术方法 __invoke().

该魔术方法会在将一个对象作为函数调用时被调用:

class A

{

    public function __invoke($str)

    {

        print "A::__invoke(): {$str}";

    }

}

$a = new A;

$a("Hello World");

输出毫无疑问是:  A::__invoke(): Hello World

__callStatic() 则会在调用一个不存在的静态方法时被调用。

3.3) 命名空间

<?php

// 命名空间的分隔符是反斜杠,该声明语句必须在文件第一行。

// 命名空间中可以包含任意代码,但只有 **类, 函数, 常量** 受命名空间影响。

namespace XXOO\Test;

// 该类的完整限定名是 \XXOO\Test\A , 其中第一个反斜杠表示全局命名空间。

class A{}

// 你还可以在已经文件中定义第二个命名空间,接下来的代码将都位于 \Other\Test2 .

namespace Other\Test2;

// 实例化来自其他命名空间的对象:

$a = new \XXOO\Test\A;

class B{}

// 你还可以用花括号定义第三个命名空间

namespace Other {

    // 实例化来自子命名空间的对象:

    $b = new Test2\B;

    // 导入来自其他命名空间的名称,并重命名,

    // 注意只能导入类,不能用于函数和常量。

    use \XXOO\Test\A as ClassA

}

3.4) 后期静态绑定

#self 的语义本来就是“当前类”

class A

{

    static public function callFuncXXOO()

    {

        print static::funcXXOO();

    }

    // ...

}

二.php7的新特性

./bin/php -v  #查看PHP版本

./bin/php -m  #查看安装的模块

1).变量类型

function test(int $a,string $b,array $c):int{

}

2).错误异常

try/catch

3).zval使用栈内存

节约了内存分配

php5

zval*val;make_std_zval(val);

php7 zval val;


7.浏览器输入一个curl 从开始到结束经历了什么

整体上来说有三个重要步骤:

一.域名解析

-当我们在浏览器输入地址或客户端发送请求之后,首先根据地址判断是否是本机地址,

-然后本机设置的host当中寻找

-前面都没找到则去DNS服务器查询域名对应的ip地址


二.建立TCP连接

-根据ip地址寻址,然后访问指定得端口(如果没有指定默认使用80端口)

-由客户端向服务器发送tcp连接请求(这里分为套接字、数据报方式不细说了),经过交换机-->路由器-->防火墙,建立连接

-然后进行三次握手四次挥手保证连接可靠性


三.发送请求

-客户端发送http请求

-服务器端接受请求并处理

-客户端根据服务器端的response渲染界面或进行逻辑处理


一个网页从请求到最终显示的完整过程一般可分为如下7个步骤: 

一. 在浏览器中输入网址; 

二. 发送至DNS服务器并获得域名对应的WEB服务器的IP地址;  --(DNS(domain name system)是一种可以将域名和IP地址相互映射的层次结构的分布式数据库系统 --然后访问指定的端口(默认80))

三. 与WEB服务器建立TCP连接; --传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议 然后进行三次握手四次挥手保证连接可靠性

四. 浏览器向WEB服务器的IP地址发送相应的HTTP请求;  

五. WEB服务器响应请求并返回指定URL的数据,或错误信息,如果设定重定向,则重定向到新的URL地址。 

六. 浏览器下载数据后解析HTML源文件,解析的过程中实现对页面的排版,解析完成后在浏览器中显示基础页面。 

七. 分析页面中的超链接并显示在当前页面,重复以上过程直至无超链接需要发送,完成全部显示。


8.tcp和http的都区别

一、TCP连接

手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接。TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在“无差别”的网络之上。

建立起一个TCP连接需要经过“三次握手”:

第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

握 手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连 接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写 了,就是服务器和客户端交互,最终确定断开)


二、HTTP连接

HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。

HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

1)在HTTP 1.0中,客户端的每次请求都要求建立一次单独的连接,在处理完本次请求后,就自动释放连接。

2)在HTTP 1.1中则可以在一次连接中处理多个请求,并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求。

由 于HTTP在每次请求结束后都会主动释放连接,因此HTTP连接是一种“短连接”,要保持客户端程序的在线状态,需要不断地向服务器发起连接请求。通常的 做法是即时不需要获得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求,服务器在收到该请求后对客户端进行回复,表明知道客 户端“在线”。若服务器长时间无法收到客户端的请求,则认为客户端“下线”,若客户端长时间无法收到服务器的回复,则认为网络已经断开。



9.tcp和udp的区别

TCP与UDP区别总结:

一、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接

二、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付

三、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的

UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)

四、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

五、TCP首部开销20字节;UDP的首部开销小,只有8个字节

六、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

TCP与UDP基本区别

  1.基于连接与无连接

  2.TCP要求系统资源较多,UDP较少; 

  3.UDP程序结构较简单 

  4.流模式(TCP)与数据报模式(UDP); 

  5.TCP保证数据正确性,UDP可能丢包 

  6.TCP保证数据顺序,UDP不保证 


10.nginx和php的联系?(cgi、fastcgi、fpm)

CGI,通用网关接口,用于WEB服务器和应用程序间的交互,定义输入输出规范。

用户的请求通过WEB服务器(如Nginx)转发给FastCGI进程,FastCGI进程再调用应用程序进行处理(如php解析器),应用程序的处理结果如html返回给FastCGI,FastCGI返回给Nginx 进行输出。

假设这里WEB服务器是Nginx,应用程序是 PHP,而 php-fpm 是管理 FastCGI 的,这也就是 php-fpm,FastCGI,和 Nginx 之间的关系。

FastCGI 用来提高 cgi 程序性能,启动一个master,再启动多个 worker,不需要每次解析 php.ini. 而 php-fpm 实现了 FastCGI 协议,是 FastCGI 的进程管理器,支持平滑重启,可以启动的时候预先生成多个进程。

nginx->FastCGI->应用程序(php解析器)->(返回)FastCGI->(返回)nginx

php-fpm 实现了 FastCGI 协议,是 FastCGI 的进程管理器


11.nginx转发

请求转发:通过路径匹配把用户的请求转发到不同的服务器

location  /proxy/ {

proxy_pass http://127.0.0.1:81/;

}

在Nginx中配置对应的微服务服务器地址即可,打开nginx的conf目录下的nginx.conf

 server {

        listen       9001;#开放给用户的访问端口,即nginx的监听端口

        server_name  localhost;#主机名

# ~表示正则匹配   匹配路径

        location ~ /eduservice/{ 

            proxy_pass http://127.0.0.1:8001;  #转发服务器地址

        }

        location ~ /eduoss/{

            proxy_pass http://127.0.0.1:8002;

        }location ~ /eduvod/{

            proxy_pass http://127.0.0.1:8003;

        }

        location ~ /cmsservice/{

            proxy_pass http://127.0.0.1:8003;

        }

        location ~ /ucenterservice/{

            proxy_pass http://127.0.0.1:8004;

        }

        location ~ /edusms/{

            proxy_pass http://127.0.0.1:8005;

        }

        location ~ /orderservice/{

            proxy_pass http://127.0.0.1:8006;

        }

        location ~ /staservice/{

            proxy_pass http://127.0.0.1:8007;

        }


12.nginx负载均衡

一个服务使用集群部署,用户请求时通过负载均衡算法把用户的请求分摊到不同的服务器上。常见的负载均衡算法有轮询、随机、加权轮询、ip哈希、最小请求时间等。


13.select和epoll区别

(1)select==>时间复杂度O(n)

它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。

(2)poll==>时间复杂度O(n)

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.

(3)epoll==>时间复杂度O(1)

epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))

select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。  

epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POSIX所规定,一般操作系统均有实现


14.HTTP2.0做了哪些改变

1). HTTP 2.0中的二进制分帧层突破了限制:客户端和服务器可以把HTTP消息分解为互不依赖的帧,然后乱序发送,最后再在另一端把它们重新组合起来。

2). 把HTTP消息分解为很多独立的帧之后,就可以通过优化这些帧的交错和传输顺序,进一步提升性能。

3). HTTP 2.0通过让所有数据流共用同一个连接,可以更有效地使用TCP连接。

4). 服务器除了对最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确地请求。

5). HTTP 2.0会压缩首部元数据,针对之前的数据只编码发送差异数据。


15.阻塞和非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。


16.redis常用的类型及类型的用处

string

此类型和memcache相似,作为常规的key-value缓存应用。

例如微博数、粉丝数等

注:一个键最大能存储512MB

hash

redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象(应为对象可能会包含很多属性)

常用命令:hget hset hgetall

主要用来存储对象信息

list

list列表是简单的字符串列表,按照插入顺序排序(内部实现为LinkedList),可以选择将一个链表插入到头部或尾部

常用命令 :lpush(添加左边元素),rpush,lpop(移除左边第一个元素),rpop,lrange(获取列表片段,LRANGE key start stop)等。

应用场景:Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现。

set

案例:在微博中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。

zset

常用命令:zadd,zrange

实现方式:Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,跳跃表按score从小到大保存所有集合元素。使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。时间复杂度与红黑树相同,增加、删除的操作较为简单。

应用场景:排行榜


17.redis zset和set的区别

set和zset都是集合

set是对string类型的无序集合,zset是有序的集合

set中的值是不能重复的;


18.redis setnx可不可以设置过期时间(不可以使用set设置)


19.redis延迟队列

继之前用rabbitMQ实现延时队列,Redis由于其自身的Zset数据结构,也同样可以实现延时的操作

Zset本质就是Set结构上加了个排序的功能,除了添加数据value之外,还提供另一属性score,这一属性在添加修改元素时候可以指定,每次指定后,Zset会自动重新按新的值调整顺序。可以理解为有两列字段的数据表,一列存value,一列存顺序编号。操作中key理解为zset的名字,那么对延时队列又有何用呢?试想如果score代表的是想要执行时间的时间戳,在某个时间将它插入Zset集合中,它变会按照时间戳大小进行排序,也就是对执行时间前后进行排序,这样的话,起一个死循环线程不断地进行取第一个key值,如果当前时间戳大于等于该key值的socre就将它取出来进行消费删除,就可以达到延时执行的目的, 注意不需要遍历整个Zset集合,以免造成性能浪费。

生产环境使用注意:

由于这种实现方式简单,但在生产环境下大多是多实例部署,所以存在并发问题,即缓存的查找和删除不具有原子性(zrangeWithScores和zrem操作不是一个命令,不具有原子性),会导致消息的多次发送问题,这个问题的避免方法如下:

1).可以采用单独一个实例部署解决(不具备高可用特性,容易单机出现故障后消息不能及时发送)

2).采用redis的lua脚本进行原子操作,即原子操作查找和删除(实现难度大)


20.如果使用redis常用的几个类型如何实现延迟队列(zset list ?)

 延迟队列的有序集合ZSET,存放K=ID和需要的执行时间戳,根据时间戳排序

 LIST结构,每个Topic一个LIST,list存放的都是当前需要被消费的JOB


21.Mysql explain 关注哪些参数

type 链接类型

最为常见的扫描方式有:

system:系统表,少量数据,往往不需要进行磁盘IO;

const:常量连接;

eq_ref:主键索引(primary key)或者非空唯一索引(unique not null)等值扫描;

ref:非主键非唯一索引等值扫描;

range:范围扫描;

index:索引树扫描;

ALL:全表扫描(full table scan);

上面各类扫描方式由快到慢:

system > const > eq_ref > ref > range > index > ALL

Extra

如果extra中出现以下几个字段很有可能是因为没有走 索引的排序。可能是由于一部分字段没有走索引。

Using temporary

Using filesort

Using join buffer

排序 order by ,group by ,distinct,排序条件上没有索引

explain select * from city where countrycode='CHN' order by population;

# 如果population没有建立索引就会出现上述情况

在join 的条件列上没有建立索引

key_len: 越短越好,如果太长的话,建议替换为前缀索引。

rows(取到的结果所需要扫描的行数):越短越好,如果太长建议要对语句进行优化。


22.use flie代表什么

use filesort会在内存中排序,但是如果结果集过大不能使用内存就会使用外部排序。(无论filesort底层用的哪种排序都是额外的排序,只不过内存排序比外部排序快一点,都需要优化sql啦~,这里只探究filesort原理)


23.Mysql不走索引的条件

1.使用了计算,则不会使用该索引(如果需要计算,千万不要计算到索引列,想方设法让其计算到表达式的另一边去。)

2.索引列使用了函数

3.索引列使用了Like %XXX

4.字符串列与数字直接比较

5.尽量避免 OR 操作(所以除非每个列都建立了索引,否则不建议使用OR,在多列OR中,可以考虑用UNION 替换)


24.mysql回表

所谓的回表查询,先定位主键值,再定位行记录,它的性能较扫一遍索引树更低。

回表就是先通过数据库索引扫描出数据所在的行,再通过行主键id取出索引中未提供的数据,即基于非主键索引的查询需要多扫描一棵索引树。

因此,可以通过索引先查询出id字段,再通过主键id字段,查询行中的字段数据,即通过再次查询提供MySQL查询速度。


25.mysql innodb的数据结构(b 和 b+ 区别)

B-树,这里的 B 表示 balance( 平衡的意思),B-树是一种多路自平衡的搜索树(B树是一颗多路平衡查找树)

它类似普通的平衡二叉树,不同的一点是B-树允许每个节点有更多的子节点。

B-树有如下特点:

所有键值分布在整颗树中(索引值和具体data都在每个节点里);

任何一个关键字出现且只出现在一个结点中;

搜索有可能在非叶子结点结束(最好情况O(1)就能找到数据);

在关键字全集内做一次查找,性能逼近二分查找;


B+树是B-树的变体,也是一种多路搜索树, 它与 B- 树的不同之处在于:

所有关键字存储在叶子节点出现,内部节点(非叶子节点并不存储真正的 data)

为所有叶子结点增加了一个链指针

因为内节点并不存储 data,所以一般B+树的叶节点和内节点大小不同,而B-树的每个节点大小一般是相同的,为一页。

为了增加 区间访问性,一般会对B+树做一些优化。


B-树和B+树的区别

1.B+树内节点不存储数据,所有 data 存储在叶节点导致查询时间复杂度固定为 log n。而B-树查询时间复杂度不固定,与 key 在树中的位置有关,最好为O(1)。

2. B+树叶节点两两相连可大大增加区间访问性,可使用在范围查询等,而B-树每个节点 key 和 data 在一起,则无法区间查找。

3.B+树更适合外部存储。由于内节点无 data 域,每个节点能索引的范围更大更精确


26.mysql mvcc

一般我们认为MVCC有下面几个特点:

每行数据都存在一个版本,每次数据更新时都更新该版本

修改时Copy出当前版本随意修改,个事务之间无干扰

保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)

也不是说MVCC就无处可用,对一些一致性要求不高的场景和对单一数据的操作的场景还是可以发挥作用的,比如多个事务同时更改用户在线数,如果某个事务更新失败则重新计算后重试,直至成功。这样使用MVCC会极大地提高并发数,并消除线程锁。

MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低,虽然实现机制有所不同,但大都实现了非阻塞的读操作,写操作也只锁定必要的行。

MVCC的实现是通过保存数据在某个时间点的快照来实现的,也就是说,不管需要执行多长时间,只要事务开始时间相同,每个事务看到的数据都是一致的,事务开始的时间不同时,每个事务对同一张表,同一时刻看到的数据可能是不一样的(因为不同的时间点可能数据就已经产生了不同的快照版本,而每个事务在默认的RR隔离级别下只能看到事务开始时的数据快照)。说道不同的存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制,下面简单说明MVCC是如何工作的:

innodb的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列,一个保存了行的创建时间,一个保存了行的过期时间(或删除时间),当然,存储的并不是实际的时间值,而是系统版本号(system version number),每开始一个新的事务,系统版本号都会自动递增,事务开始时刻的系统版本号作为事务的版本号,用来和查询到的每行记录的版本号进行比较,下面看repeatable read隔离级别下,MVCC具体是如何操作的:


27.mysql事务隔离级别

mysql默认的事务隔离级别为repeatable-read

事务隔离级别     脏读 不可重复读 幻读

读未提交(read-uncommitted) 是 是 是

不可重复读(read-committed) 否 是 是

可重复读(repeatable-read) 否 否 是

串行化(serializable)  否 否 否


28.脏读和幻读的区别

  1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

  2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。

  3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。


29.设计模式(单例、工厂、策略)都用到了哪些地方解决了什么问题。

单例模式

单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式是一种常见的设计模式,在计算机系统中,线程池、缓存、日志对象、对话框、打印机、数据库操作、显卡的驱动程序常被设计成单例。

单例模式分3种:懒汉式单例、饿汉式单例、登记式单例。

单例模式有以下3个特点:

1).只能有一个实例。

2).必须自行创建这个实例。

3).必须给其他对象提供这一实例。

工厂模式

工厂模式是我们最常用的实例化对象模式,是用工厂方法代替new操作的一种模式。

使用工厂模式的好处是,如果你想要更改所实例化的类名等,则只需更改该工厂方法内容即可,不需逐一寻找代例化的地方(new处)修改了。为系统结构提供灵活的动态扩展机制,减少了耦合。

策略模式

策略模式是对象的行为模式,用意是对一组算法的封装。动态的选择需要的算法并使用。

策略模式指的是程序中涉及决策控制的一种模式。策略模式功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性思想。

策略模式的三个角色:

1).抽象策略角色

2).具体策略角色

3).环境角色(对抽象策略角色的引用)

实现步骤:

1).定义抽象角色类(定义好各个实现的共同抽象方法)

2).定义具体策略类(具体实现父类的共同方法)

3).定义环境角色类(私有化申明抽象角色变量,重载构造方法,执行抽象方法)


30.秒杀场景,如何保证不会出现问题

秒杀设计思路:

前端:页面静态化,禁止重复提交。

(1).页面静态化:将前端可以静态的资源静态化。

(2).禁止重复提交:秒杀开始之后,可以对用户点击后响应前按钮置灰。

后端:可拓展,缓存,限流,削峰,异步处理

(1).可拓展:服务的可扩展,可以水平添加机器将用户请求分担到不同的机器上去。数据库可扩展,支持分库分表,对于用户的请求,映射到不同的数据库,减少单台数据库的压力。

(2).内存缓存:参加秒杀系统的商品是事先可知的,可以将参加秒杀的商品信息事先缓存到redis等缓存系统中,这样可以大大的提高系统的吞吐量,减少关系型数据库的读写压力。

(3).限流: 一单秒杀开始,实际秒杀成功的用户只是库存的数量,在库存没有之后,将前端的秒杀入口关闭。

(4).削峰:数据库削峰。对于秒杀系统瞬时会有大量用户涌入,所以在抢购一开始会有很高的瞬间峰值。对于关系型数据库而言,这个是致命的,是压垮系统很重要的原因,所以如何把瞬间的高流量变成一段时间平稳的流量也是设计秒杀系统很重要的思路。实现削峰的常用的方法有利用缓存和消息中间件等技术。

(5).异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。

数据库层特点

数据库层是最脆弱的一层,一般在应用设计时在上游就需要把请求拦截掉,数据库层只承担“能力范围内”的访问请求。所以,上面通过在服务层引入队列和缓存,让最底层的数据库高枕无忧。

案例:利用redis+mysql实现简单的秒杀系统

Redis是一个分布式key-value缓存系统,value支持多种数据结构,这里value可以选择两种类型,String(或者hash):主要用于记录商品的库存,对商品减库存。Set集合(这里不要用list集合,list集合是可重复的,set是不可重复的,可以保证一个用户只卖一次,如果一个用户可以买多次那么可以使用list集合):用于存储用户的id获取其他唯一确定一个用户的值。

在秒杀开始的前:可以使用批处理,将参加秒杀的产品信息缓存到redis中。这里将产品的业务唯一字段作为key,库存作为value。这里的key要和前端缓存的key一致。

在秒杀开始时:用户大量提交。根据用户提交的产品信息,获取到redis中需要的key值,查询缓存(为了保证缓存有效,如果第一次没有查询到,可以到数据库查询,然后在缓存一下,不过一般不会出现),得到库存量,判断当前库存是否大于零,如果大于零,判断当前的set集合中是否用该用户ID,如果没有,减库存并且将用户的ID放入集合中,并对库存减一,如果库存为0,提示用户,商品已售完等文案信息,如果集合中已经存在该用户id,则不做任何处理,直接处理下一个请求。直到最后库存售完,上面的过程可以利用redis事务和watch功能完成对数据一致性的控制即超卖问题。

库存售完后:程序开始启动一个有个后台线程,可以阻塞等待商品库存售完的通知,在上面一步,库存一旦售完,后台进程获取set集合中的用户信息,异步处理需要操作的购买等后续操作。

Tags:
PHP
评论 (0)
    说点什么吧... (取消回复)

    正在加载验证码......

    请先拖动验证码到相应位置

Copyright 吴峰的博客 © 2014-2016 管理员邮箱:phpwufeng@163.com   统计:   ICP备案:鲁ICP备16004939号-1