Opcache 开启前后性能对比

测试环境介绍

机器是一台基于 vmware 的虚拟机

程序是 yii2-app-basic , HelloController.php 文件内容如下:

测试命令: ab -n10000 -c100 ‘http://yii2.app.com:8090/index.php?r=/hello/world’

测试结果:

 Opcache CPU占用 Requests per second(吞吐率)
未开启 99% 520
开启 99% 5480

用 strace 追踪 php-fpm 的系统调用(strace 会严重影响性能,追踪系统调用时用的测试命令是:ab -n1000 -c100 ‘http://yii2.app.com:8090/index.php?r=/hello/world’,总请求减少为之前的10%),结果如下:

测试结果分析:

Opcache 开启前后 cpu 使用率都达到了 100% 说明系统瓶颈在 cpu。开启 Opcache 后系统调用少了很多,特别是 fstat,mumap,open,mmap,开启后,这几个系统调用可以忽略不计。Opcache 省去了每次加载和解析 PHP 脚本的开销,一次加载解析后后续请求不用去读源码,因此少了这么多系统调用。

结论:提高PHP程序的性能,最重要也最有效的方法就是开启 Opcache。

其它:  最初 fpm 配置是监听端口,吞吐率在开启Opcache前只有340左右;开启Opcache后在900左右,cpu占有80%,无法达到100%;如果请求过多,则会出现超时错误。后来改 fpm 监听 unix sock,性能一下子上来了。Yii2 开启  debug 模式后,吞吐率为 1200  左右。

Is this php’s bug?

I am reading yii2’s source code right now. I found that this method yii\di\Instance::ensure something werid。

I changed the code as following:

I got output as :

string(15) “yii\di\Instance”

Can anyone explain?

#Answer

Just now(2016-04-12 15:20:58),I asked Laruence about this questing in qq group.  He answered me that get_calss(null) will return current scope. Then I read the php documetation , It says:

If object is omitted when inside a class, the name of that class is returned.

So, RTFM si right.

compile php with openssl on mac osx error

从源码手动编译 PHP 时出现如下错误:

解决办法

MakeFile 里面找到类似下面这一行:

删除所有的 -lssl 和 -lcrypto 然后添加 libssl.dylib 和 libcrypto.dylib 的路径(如果你安装了 brew,那么则是 /usr/local/opt/openssl/lib/),重新运行 make 命令,done。

附上我修改后的 MakeFile EXTRA_LIBS 那一行:

 

PHP 数值型字符串比较的坑

上面代码在 PHP5.5 返回 false,在 PHP5.3 返回 true。

分享一个 MySQL 分库分表类

当一个表数据记录过大时就会出现性能瓶颈,而一般对应的解决办法是要么做分区表,要么分表,分区表就不说了,分表又分为垂直分割和水平分割,具体区别请自行搜索。一般而言,分库分表属于水平分割,按照一定的规则将数据插入到不同的表中去。而分库则可以很方便的转移数据库的压力,比如将一个很大库的分别放在不同的服务器上。

下面是我写的一个分库分表的实现:

Config 类就做一个事情,根据配置文件,去拿到对应的库和表的链接配置,然后客户可以根据 dsn 去链接对应的数据库。对应的配置文件如下:

给出一个使用这个分库分表的例子:

下面这个例子展示了如何使用上述的 Model 类:

如果看官您有任何疑问或者有更好的实现,欢迎交流~

PHP 取一个整数的十位上的数值可能出现的 bug

问:如何取到一个整数的十位上的数值?

答:先除以 10 再对 10 取余。代码如下:

很简单是不是?但这段代码有 bug。你可以运行下面这段代码:

输出如下:

buggggggggggeeeeee

可以看到整数 9223372036854775807 的十位数上是 0 ,而我们确得到了 2 ,这是怎么一回事呢?

我们执行下如下代码:

输出为:

RTX截图未命名

可以看到, 9223372036854775807 不能被 10 整除,$a / 10 后得到的是一个浮点数,丢失了精度,这个时候再对 10 取模得到的值就不准了。

请时刻记住 PHP 变量的类型

我们都知道 PHP 变量类型是弱类型,这样很方便,当你需要一个数字时,变量就表现得像个 Int,当你需要字符串时,变量就表现得像个 String。但方便的同时,如果不注意也会带来坑,并且这种情况带来的 bug 还很不好排查。今天我就遇到了一个。

游戏新注册用户的时候需要生成一个 uid,一般来说,在数据量小且低并发的时候直接使用 MySQL 的自增 ID 就可以生成得到唯一的 uid,但如果数据量大且并发很高,使用 MySQL 的自增 ID 就不能满足需求了。我们使用了一个叫 ukg 的东西,该服务的功能就一个,每次调用它,就给你返回一个唯一的数字,嗯,很好,刚好能满足我们生成 uid 的需求。

代码写完后,测试的过程中发现了一个 bug,设备第一次登录的时候会登录失败,第二次及以后就登录成功。排查了近 1 个小时才找到问题所在,设备第一次登录的时候,我们访问 ukg 得到一个数字,当做玩家的 uid,并且存入 MySQL,后来发现我们从 ukg 返回得到的实际上是一个 数字+空格+回车 组成的一个字符串,而空格和回车都是不可见字符串,很容易就忽略了,存入 MySQL 时,MySQL 进行了转换将空格和回车去掉然后正确存进了数据库。这样就能解释为什么第一次登录失败,第二次及以后的请求就没问题了。

定位 bug 后,解决就很容易了,从 ukg 取到数据后,将其强制转换为 Int 就 OK 了。

虽然,PHP 的语法并不要求一个变量强制其数据类型,但我们在写代码的过程中,还是需要时刻注意变量的数据类型。常见的错误还有 0 == false 相等导致的逻辑出错。如果不嫌麻烦的话,所有 == 的地方都改成 === 就不会出现这类型的 bug 了。

PHP 使用 Redis 来做队列服务

为客户端开发 Api,采用 PHP,我们使用了一个叫做 Swoole_framework 的框架,同时使用了 swoole 扩展。在开发接口时使用了框架提供的模型类,但是,服务端除了接口,不可避免还有一些后台脚本,比如每日任务初始化,用户分数结算,后台脚本直接 require 相关配置文件,然后手动 update 数据库数据。这两天做需求的时候就发现,有些业务逻辑在服务端 api 层需要实现,在后台脚本里面也要实现,而这两处的代码不能复用,因此同样地逻辑处理了两遍,随着后续功能的增加,重复的代码还会增加。上周因为比较急就先采用这种方法完成了代码,实现了功能,这两天测试联调,总感觉同样地逻辑在多个地方出现,不仅写代码时麻烦,测试时也麻烦,下午灵光一现想到可以使用任务队列来完成这个工作。

简单来说,就是在原先逻辑处理的地方,只是简单增加一个任务,不再进行具体的业务逻辑,后台脚本和 api 都如此,然后再单独跑一个脚本,拉取任务,进行业务逻辑处理。这个的思路好处很明显,同样地逻辑归到了一处,开发以及调试找错时会清晰很多。

需要实现一个任务队列,使用 redis ,简单封装了一个队列:

队列的一个特点就是先进先出(FIFO),很显然,先产生的任务需要被先处理,redis 的 List 可以保证这一点。

晚上将代码重新组织,用任务队列加单独脚本的方式实现了需要的业务功能,顿时感觉浑身舒畅了许多。

nginx php-fpm 捕获记录 php 的错误日志

请确认 php-fpm.conf 的如下配置为:

catch_workers_output = yes # 捕获进程的输出,这个值默认为 no
error_log = log/php-fpm.log # 这个是默认值,指定 php-fpm 输出 log 的文件路径

确认 php.ini 的如下配置为:

log_errors = On # 开启记录错误日志,这个值默认为 Off
error_reporting=E_ALL&~E_NOTICE # 默认就是这个值,php 的 error_reporting 级别

参考网址:http://www.nginx.cn/666.html

腾讯 PHP 笔试题,写一个 is_writable 函数的替代函数

今年三月份去腾讯面试过。笔试题有一题是,PHP 中的 is_writable 函数不可信,请写一个函数来替代这个函数。

拿到这个题目,我便想起之前读 CI 框架实现的时候看到 CI 里面写了个 is_really_writable 函数,当时没有注意,只看这个函数的功能,至于 CI 为什么要自己实现一个 is_writable 函数的功能没有去深究。当时,我的解题思路是这样的,既然 is_writable 不可信,那么我就实实在在的尝试去那个文件夹里进行写入,如果可以写则返回 true,否则返回 false,思路是对的,但代码并不是完全无误,有瑕疵。

回到家后我便开始搜索,同时重新仔细查看 CI 的那个函数实现以及说明,原来是因为在 Windows 服务器上如果一个文件夹有只读属性,is_writable 这个函数还是会返回 true,而实际上 php 是不能进行写入的,另外一种情况是,在 unix 服务器上,如果 safe_mode 选项打开,那么 is_writable 函数也是不可信的。

这个事情提醒我,当遇到一个问题时,一定要追查到底,搞明白它的原理以及那样做的理由。

附上 CI 里面 is_really_writable 函数的实现: