升级理由 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中的逻辑非常少,因此升级前后基本看不出什么提升。 第一个红框为升级前,第二个为升级后,都是交易日。
升级日志
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%,提升非常明显!
可用内存 第一个红框为升级前的负载数据,第二个红框为升级后的数据,可用内存多了大约6%,提升幅度一般。