PHP5.3升级到PHP7所遇到的问题

升级理由

php7相比老版本的php,性能提升非常大。

后端脚本测试

1
2
3
4
5
6
我们的许多产品,都是采用后端脚本的形式生成数据,经多个实际项目测试,升级php7后能够极大的减少脚本运行时间。以一个项目中的实际脚本为例:
脚本文件:/var/www/vis-free/applications/hk/scripts/cron/makeMarketDataHtml.php
功能:获取港股市场代码,并请求行情数据,生成html缓存文件(主要是数据处理和生成文件)
php5.3.6,CPU在90-92,内存在11.2左右,耗时37.76s
php7.0.10,CPU在80左右,内存在9.2左右,耗时12.83s
CPU、内存和耗时都有改善,其中脚本耗时提升非常明显,可以有效减少多脚本并行的情况,降低服务器负载。

php-fpm的cpu和内存测试

1
2
3
在正式服务器210.208上抓包,将请求在测试服务器205.235(8核4G)回放测试(100倍速度回放),限制20个php-fpm进程,统计数据如下:
php5.3.6:CPU在130%左右,内存占用16%左右
php7.0.10:CPU在80%左右,内存占用7.5%左右

安装PHP7遇到的问题

老系统安装php7的扩展,需要更新autoconf

1
2
3
4
报错:
configure:7611: error: possibly undefined macro: AS_CASE
If this token and others are legitimate, please use m4_pattern_allow.
See the Autoconf documentation.

重新编译安装2.63以上版本的autoconf即可

mongodb新扩展,需要高版本的mongo支持

经测试,线上的mongo版本2.6.3是支持最新扩展的API的;测试服务器205.233的mongo是2.0.8版本的,已经不支持新的API了,需要升级测试服务器的mongo。

PHP7不再支持handlersocket扩展

考虑到我们用到handlersocket扩展的应用,并发数都很小,直接将这部分改为普通的MySQL操作方式,舍弃掉handlersocket。

rc.local的启动项添加

需要注意rc.local 启动项添加,防止重启后没有能自动启动,文件:/etc/rc.d/rc.local

xhgui报错问题

很多项目中都会可能有检测xhgui时候存在,而PHP7不能支持该扩展,所以需要关闭 php.ini 里面的auto_prepend_file项

php-fpm的user、group问题

这里的 user 和 group 最好保持跟 老版本php的一致,防止生成的文件没有权限(当然,如果能找出所有生成的文件并修改其权限,也是妥妥滴)

语法兼容性问题

PHP7.0.12和PHP7.1.7差异

变量类型转化问题:
目前192.168.200.105为php7.1.7
7.0.12:$a = ;$a[‘key’] = ‘key’;正常;7.1.7:$a = ;$a[‘key’] = ‘key’;报警告,且$a的值为A;

cspp-jpgraph-画图问题

升级包:205.235-/usr/local/php7/lib/php/jpgraph/
这里加入了/usr/local/php7/lib/php/jpgraph/fonts/simsun.ttc
中文乱码问题:iconv转成GBK即可

php7从mongodb数据库中获取数据后,_id字段的存储变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
php7从mongodb数据库中获取数据格式,转化成数组格式后,例如:
Array
(
[_id] ## > MongoDB\BSON\ObjectID Object
(
[oid] ## > 580ec7771acc7a50f5350f09
)

[stock_code] ## > 000630
[buy_num] ## > 64
[sell_num] ## > 25
[time_region] ## > 1
[date] ## > 2016-10-25
[volume_id] ## > 4
[ocsy_id] ## > 1
)
其中_id未转化成数组格式,获取方式为$array['_id']->__toString()
(php7的MongoDb类引起)

php7升级之后,静态方法实例化对象导致内存增长问题

*以下代码每次调用会导致内存增长,由于脚本执行时间很长,积累下来最终导致服务器内存不足

1
2
3
4
5
6
7
8
9
public static function factory($type, $encoding ##  'utf-8')
{
if (in_array($type, self::$_validators)) {
require_once 'Hexin/XmlValidator/' . $type . '.php';
$className ## 'Hexin_XmlValidator_' . $type;
return new $className($encoding);
}
throw new Exception('Unknown xml validator type ' . $type);
}

继承方法中的参数必须保持一致(指定类型也必须一致)

最优参数中继承关系太多,以下仅举例:
Warning: Declaration of Domain_ActiveRecord_OptimalTradeSignal::save($date) should be compatible with ActiveRecord::save($date = NULL) in /var/www/zycs/applications/optimalparam/models/Domain/ActiveRecord/OptimalTradeSignal.php on line 76
Warning: Declaration of Domain_ActiveRecord_OptimalTradeSignal::_getTableName($date) should be compatible with ActiveRecord::_getTableName() in /var/www/zycs/applications/optimalparam/models/Domain/ActiveRecord/OptimalTradeSignal.php on line 76

stdClass对象未定义直接调用

Warning: Creating default object from empty value in /var/www/zycs/models/Parameter/DataDeal/Table/Optimal.php on line 1168

stdClass对象赋值中出现的Notice

Notice: Array to string conversion in /var/www/zycs/models/Parameter/DataDeal/Table/Optimal.php on line 1162
Notice: Undefined property: stdClass::$Param in /var/www/zycs/models/Parameter/DataDeal/Table/OptimalParam.php on line 43

静态方法的严格检查

1
2
Strict Standards: Static function Business_Quote_Abstract::get() should not be abstract in /var/www/cso/applications/platform/models/Business/Quote/Abstract.php on line 4
PHP Strict Standards: Static function Business_Quote_Abstract::update() should not be abstract in /var/www/cso/applications/platform/models/Business/Quote/Abstract.php on line 9
1
2
3
4
5
对象有两种,一种是实例对象,一种是静态对象
一个类可以有多个实例对象,但只有一个静态对象.也就是类本身
类是可以被继承的.但他的静态对象只有一个,不能被继承,他的静态方法就更不可能被继承.
所以,抽象方法是需要继承来实现,而抽象静态方法根本不能被继承,两者互相矛盾.
所以,根本就不存在抽象静态方法.

动态函数结果不能直接作为函数参数

Only variables should be passed by reference
$apiName = strtolower(array_pop(explode(‘_’, $requestName)));

子类和父类的传参必须保持一致

对于数组判断需先判断该key值是否存在

如,is_bool($params[‘index’])?$params[‘index’]:false;当index不存在时会报错
需先 is_set($params[‘index’])进行判断

过期函数

*iconv_set_encoding(): Use of iconv.internal_encoding is deprecated

1
2
3
4
5
if (PHP_VERSION_ID < 50600) {
iconv_set_encoding('internal_encoding', $origenc);
} else {
ini_set('default_charset', $origenc);
}

PHP Warning: preg_replace(): The /e modifier is no longer supported, use preg_replace_callback instead

php7不再支持mysql

用到原始mysql函数操作数据库的,得改为mysqld。
高级诊股的后台升级前没考虑到这点,导致升级后不能使用了,幸好只是后台,没有影响前端用户的使用。

php7默认的charset明确指定为了utf8

php7之前,默认是没有指定编码的,这样请求返回的content-type中没有写明编码,浏览器会根据网页HTML中的charset来选择编码。

1
2
3
以5.3.6为例,php.ini中没有设置default_charset:
[root@preview-webtest stockname]# php -i|grep charset
default_charset => no value => no value

php7默认会将字符编码设置为utf8,这样请求返回的content-type中会写明编码,浏览器会根据响应头的编码来设置页面展示的编码类型;如果你的页面是gbk的,升级后就会乱码。

1
2
3
以php7为例,php.ini中没有设置default_charset:
[thsweb@hm_vis_frontend_200_206 logs]$ php -i|grep charset
default_charset => UTF-8 => UTF-8

htmlspecialchars从php5.4开始不支持GBK

现象:
sp的0.244通过htmlspecialchars防止前端样式问题
php7升级之后,0.244的mall支付失败,现象是html上的中文部分为空
htmlspecialchars对GBK编码的中文不再支持(5.4开始)

解决:
调用htmlspecialchars时指定编码
在 PHP 5.4 之前的版本,无法被识别的字符集将被忽略并由 ISO-8859-1 替代。自 PHP 5.4 起,无法被识别的字符集将被忽略并由 UTF-8 替代。

测试方法

测试前,先开启PHP的报错,保证即使是Notice也会显示出来。

针对用户触发的请求

可以通过tcpcopy,将线上请求导入线下环境进行测试。
不方便采用这种方式的,也可以搜集线上的Nginx日志,逐个请求测试,不过效率很低。

针对后端定时脚本

需要查看每个脚本的执行是否会报错,另外请务必验证脚本生成的数据的正确性。

老项目编码的坑

很多老项目的编码都是gbk、gb2312,最好是找一些老项目去点点看,会不会出现乱码

可能存在svn上不存在的文件

可能存在svn上不存在的文件

经验教训

半自动化思路

api管理平台二期增加自动检测接口
zycs的PHP7测试,我先从zycs.access.log.1匹配出接收到的请求
然后手动执行请求
这块能否程序化实现,从nginx日志中解析出请求,自动保存起来(post请求需要人工添加参数,不过比起全部手动操作,这个量应该不大)
这里还需要根据Zend路由规则进行去重
然后可以批量执行请求,已方便测试
正式升级PHP7之后,再执行一遍,做回归测试

梦想与现实的差距

服务器升级方面的操作,不要太过理想化,因为我们服务器的环境差异,总是会出现各种问题,比预期的要困难很多。
所以,先做好心理准备和时间准备。

没有考虑到线上环境的系统差异

线上服务器的系统各异,从老的CentOS5.6到6.6都有,而我们测试环境是CentOS6.6的,因此真正线上升级,在老版本系统的服务器上,会遇到很多软件安装的问题。
因此升级前,一定要给运维人员留出充裕的时间。

部署文档不够详细

运维有自己的部署规范,但是具体操作细节和顺序、以及各软件包的获取方式,最好还是详细说明一下。
另外程序方面的内容,需要我们自己确认,比如PHP安装后如何迁移公共包的位置、PHP配置文件的一些特殊配置要求(比如include_path)等等。

关于升级失败/遇到问题的回滚方案

1.将需要备份的文件做好说明
2.确认快速回退的方案,或者哪些情况可以放弃本次升级等等

php逻辑较少的应用,升级php7带来的提升很小

比如模拟炒股的0.92服务器(模拟炒股mysql备库及接口服务器),接口的逻辑都是通过pg的存储过程来实现的,php中的逻辑非常少,因此升级前后基本看不出什么提升。
第一个红框为升级前,第二个为升级后,都是交易日。
1

升级日志

  • 2016.10.19:升级云计算和资讯的4台服务器
    ** 200.186:类库安装耗时很大,注意看下预编译日志,其实预编译已经报错了
    ** 200.186:出现make失败,修改参数后重新编译前,需要make clean一次
    ** 200.186:zmq之前安装成功的,本次在zmq的安装上耗时很大,可以使用configure –with
    ** 以上两点,耗时3个小时以上,其余3台升级+测试,3个小时不到
    ** 后续,下午4点30分,即可联系运维进行php7的独立安装
  • 2016.10.25:升级模拟炒股服务器
    ** 下午发给运维的时间有点晚了,保险起见,可以今天只安装php7,明天再替换和测试
    ** 后来运维回复邮件,所有操作推迟到明天进行

升级脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#!/bin/sh
#安装PHP7的脚本
SOURCE_DIR="/home/zhangsan/src"
PHP_VERSION="php-7.0.12"


#安装PHP7
function install_php()
{
cd $SOURCE_DIR
tar -xvzf $PHP_VERSION".tar.gz"
cd $PHP_VERSION
./configure --prefix=/usr/local/php7 --with-config-file-path=/usr/local/php7/lib --enable-fpm --with-fpm-user=10jqka --with-fpm-group=10jqka --with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd --with-iconv-dir --with-freetype-dir=/usr/local/freetype --with-jpeg-dir --with-png-dir --with-zlib --with-libxml-dir=/usr --enable-xml --disable-rpath --enable-bcmath --enable-shmop --enable-sysvsem --enable-inline-optimization --with-curl --enable-mbregex --enable-mbstring --with-mcrypt --with-gd --enable-gd-native-ttf --with-openssl --with-mhash --enable-pcntl --enable-sockets --with-xmlrpc --enable-zip --enable-soap --with-gettext --disable-fileinfo --enable-opcache
make && make install
ln -s /usr/local/php7/bin/php /usr/bin/php7
cp /etc/php.ini /usr/local/php7/lib/php.ini
cat >>/usr/local/php7/lib/php.ini<<EOF
date.timezone = Asia/Shanghai
;opcache
[Zend Opcache]
zend_extension=opcache.so
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1
;opcache end
EOF
cat >/usr/local/php7/etc/php-fpm.conf<<EOF
[global]
pid = /usr/local/php/var/run/php7-fpm.pid
error_log = /data/logs/php7-fpm.log
log_level = notice

[www]
listen = /memdisk/phpfpm7.socket
listen.backlog = -1
listen.allowed_clients = 127.0.0.1
listen.owner = 10jqka
listen.group = 10jqka
listen.mode = 0666
user = 10jqka
group = 10jqka
pm = dynamic
pm.max_children = 1024
pm.start_servers = 50
pm.min_spare_servers = 50
pm.max_spare_servers = 1024
request_terminate_timeout = 100
request_slowlog_timeout = 0
pm.status_path = /phpfpm_status
slowlog = /data/logs/php7.slow.log
EOF
echo "请手动删除/usr/local/php7/lib/php.ini中多余的配置,并修正extension_dir的路径\n"
}

function configure_php_module()
{
module_name=$1
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make && make install
echo "extension=$module_name.so" >> /usr/local/php7/lib/php.ini
}

#注意:部分高版本的扩展(比如mongodb),已经不支持.configure编译安装了,而是采用pecl安装,因此我们需要先安装好pecl
function install_php_pecl()
{
cd "cd /usr/local/php7/bin/"
curl -o go-pear.php "http://pear.php.net/go-pear.phar"
/usr/local/php7/bin/php go-pear.php
#后面输入下回车
}

#安装php的mongodb扩展
#如果不能联网,得找个以前的源码包来自己安装
function install_php_module_mongodb()
{
/usr/local/php7/bin/pecl install mongodb
echo "extension=mongodb.so" >> /usr/local/php7/lib/php.ini
}

#安装php的redis扩展
function install_php_module_redis()
{
cd $SOURCE_DIR
tar -xvzf "redis-3.0.0.tgz"
cd "redis-3.0.0"
configure_php_module "redis"
}

#安装php的memcache扩展
function install_php_module_memcache()
{
cd $SOURCE_DIR
unzip "pecl-memcache-NON_BLOCKING_IO_php7.zip"
cd "pecl-memcache-NON_BLOCKING_IO_php7"
configure_php_module "memcache"
}

#安装php的zmq扩展
function install_php_module_zeromq()
{
cd $SOURCE_DIR
unzip "php-zmq-master.zip"
cd "php-zmq-master"
configure_php_module "zmq"
}
install_php
install_php_pecl
install_php_module_mongodb
install_php_module_memcache
install_php_module_zeromq

性能对比

采用200.105服务器的数据进行对比,这台服务器上的业务基本上都是“后端脚本跑数据->前端请求读文件”的方式。

CPU的Load Average

第一个红框为升级前的负载数据,第二、三个红框为升级后的数据,都是交易日,可以看到负载下降到了原来的50%,提升非常明显!
2

可用内存

第一个红框为升级前的负载数据,第二个红框为升级后的数据,可用内存多了大约6%,提升幅度一般。
3