Azkaban 3插件的安装与配置
Azkaban的插件分为2类,一种是web-server用的报表、展示类插件(viwer),一种是exec-server用的工作流类型插件。
2015年从 PHP5.3 升级到 PHP5.5 也测试过一次,速度提升比这个小多了,不过内存占用下降了很多。
我们在运行环境准备的时候,PHP已经发布到了7.0.14,使用的扩展(memcached、redis、xdebug、pthreads等)基本都有了至少一个稳定版本,除了第一次忘了用GCC 4.8+编译,在整个编译过程中也没有遇到问题。开发的扩展代码量很少,只是对几个常用处理过程用C进行了封装。参照wiki给出的指引,再结合编译过程中的错误提示,很快就解决了问题。
通过对比阅读 PHP7 和 PHP5 的源码,发现其之所以在保证兼容性的情况下还能达到很好的性能,主要的优化点在以下几个方面:
PHP7 对 zval 结构体改变很大,之前的处理方式是type表示值的类型,变量的值存储在 zvalue_value 联合体中,is_ref__gc 和 refcount__gc 表示是否引用和引用计数器。这种方式有2个比较大的缺点:
struct _zval_struct {
zvalue_value value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
};
typedef union _zvalue_value {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
} zvalue_value;
PHP7 的 zval 结构体包含 zend_value 用于存储变量的值(long or double)或者指针,另外还有 u1 和 u2 两个联合体,u1 是 zval 类型信息(共17种类型,见下zval.u1.type),u2 是辅助信息。另外,引用计数是在 zend_value 而不是 zval 上,变量之间的传递、赋值主要也是对 zend_value,新的做法显然更好一些。
struct _zval_struct {
zend_value value; /* value */
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, /* active type */
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved) /* call info for EX(This) */
} v;
uint32_t type_info;
} u1;
union {
uint32_t var_flags;
uint32_t next; /* hash collision chain */
uint32_t cache_slot; /* literal cache slot */
uint32_t lineno; /* line number (for ast nodes) */
uint32_t num_args; /* arguments number for EX(This) */
uint32_t fe_pos; /* foreach position */
uint32_t fe_iter_idx; /* foreach iterator index */
uint32_t access_flags; /* class constant access flags */
uint32_t property_guard; /* single property guard */
uint32_t extra; /* not further specified */
} u2;
};
typedef union _zend_value {
zend_long lval; /* long value */
double dval; /* double value */
zend_refcounted *counted;
zend_string *str;
zend_array *arr;
zend_object *obj;
zend_resource *res;
zend_reference *ref;
zend_ast_ref *ast;
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;
/* zval.u1.type */
/* regular data types */
#define IS_UNDEF 0
#define IS_NULL 1
#define IS_FALSE 2
#define IS_TRUE 3
#define IS_LONG 4
#define IS_DOUBLE 5
#define IS_STRING 6
#define IS_ARRAY 7
#define IS_OBJECT 8
#define IS_RESOURCE 9
#define IS_REFERENCE 10
/* constant expressions */
#define IS_CONSTANT 11
#define IS_CONSTANT_AST 12
/* fake types */
#define _IS_BOOL 13
#define IS_CALLABLE 14
/* internal types */
#define IS_INDIRECT 15
#define IS_PTR 17
PHP5 的 HashTable 结构体中,数据存储在 arBuckets 指针数组中,它是由 bucket 组成的双向链表,每个元素的值(zval结构)也存在这些 bucket 中,每个 bucket 中保存一个指向 zval 结构的指针,由于老的实现过于考虑通用性,所以不止需要一个指针,而是两个指针。这种结构下 bucket 和 zval 都需要分开分配,分配效率比较低。
typedef struct _hashtable {
uint nTableSize;
uint nTableMask;
uint nNumOfElements;
ulong nNextFreeElement;
Bucket *pInternalPointer;
Bucket *pListHead;
Bucket *pListTail;
Bucket **arBuckets;
dtor_func_t pDestructor;
zend_bool persistent;
unsigned char nApplyCount;
zend_bool bApplyProtection;
#if ZEND_DEBUG
int inconsistent;
#endif
} HashTable;
typedef struct bucket {
ulong h;
uint nKeyLength;
void *pData;
void *pDataPtr;
struct bucket *pListNext;
struct bucket *pListLast;
struct bucket *pNext;
struct bucket *pLast;
char *arKey;
} Bucket;
保证插入顺序的 bucket 实现
PHP7 的 HashTable 结构体中,bucket 是一个条目,zval是直接嵌入bucket结构体中,没有必要单独为他分配内存,也不会产生因内存分配引起的冗余信息,减少了空间浪费。
typedef struct _zend_array HashTable;
struct _zend_array {
zend_refcounted_h gc;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar flags,
zend_uchar nApplyCount,
zend_uchar nIteratorsCount,
zend_uchar consistency)
} v;
uint32_t flags;
} u;
uint32_t nTableMask;
Bucket *arData;
uint32_t nNumUsed;
uint32_t nNumOfElements;
uint32_t nTableSize;
uint32_t nInternalPointer;
zend_long nNextFreeElement;
dtor_func_t pDestructor;
};
typedef struct _Bucket {
zval val;
zend_ulong h; /* hash value (or numeric index) */
zend_string *key; /* string key or NULL for numerics */
} Bucket;
/*
* HashTable Data Layout
* =====================
*
* +=============================+
* | HT_HASH(ht, ht->nTableMask) |
* | ... |
* | HT_HASH(ht, -1) |
* +-----------------------------+
* ht->arData ---> | Bucket[0] |
* | ... |
* | Bucket[ht->nTableSize-1] |
* +=============================+
*/
以前的程序中使用了很多第三方库(smarty2、phpexcel、phpmailer、qrcode等),有一些用的版本比较老,升级PHP7的过程中也需要同步进行升级。为了将来的考虑,这次做的彻底一些,用Composer来管理第三方库,一些实在找不到替代品的只能自己处理了。现在再看这一步,真应该在升级5.6解决历史包袱的时候就应该处理掉,测试的工作量会少很多。
吃过上面亏,后面再动手前先看了看刚发布的 PHP7.1 迁移手册中废弃的特性一节,准备比较一下 openssl 和 mcrypt。
通过上面两篇文章的介绍,openssl 作为 mcrypt 替代者不论从支持的加密种类还是加密速度上都完爆后者。自己经过测试验证,确认无误。替换方式也很简单,这里以 DES-CBC 为例。
$pad = 8 - (strlen($text) % 8);
$text .= str_repeat(chr($pad), $pad);
base64_encode(mcrypt_encrypt(MCRYPT_DES, $key, $text, MCRYPT_MODE_CBC, $iv));
openssl_encrypt($text, 'DES-CBC', $key, OPENSSL_ZERO_PADDING, $iv);
另外最后提一下,特别是在跨平台时,填充方式可能有的区别,有兴趣的可以看看这里
set_exception_handler() 不再保证收到的一定是 Exception 对象
抛出 Error 对象时,如果 set_exception_handler() 里的异常处理代码声明了类型 Exception ,将会导致 fatal error。
想要异常处理器同时支持 PHP5 和 PHP7,应该删掉异常处理器里的类型声明。如果代码仅仅是升级到 PHP7,则可以把类型 Exception 替换成 Throwable。
// PHP 5 时代的代码将会出现问题
function handler(Exception $e) { ... }
set_exception_handler('handler');
// 兼容 PHP 5 和 7
function handler($e) { ... }
// 仅支持 PHP 7
function handler(Throwable $e) { ... }
//有变化
list()
//移除
call_user_method()
call_user_method_array()
ereg_replace() //ereg整个系列函数
$HTTP_RAW_POST_DATA
opcache.enable = 1
opcache.enable_cli = 1
opcache.interned_strings_buffer = 8
//php文件多可以设置的大一些
opcache.max_accelerated_files = 8000
opcache.memory_consumption = 256
//文件检查周期,追求性能的在生产环境可以关闭,关闭后修改文件必须重启php-fpm才能生效
opcache.revalidate_freq = 600
opcache.fast_shutdown = 1
//开启hugepages支持
opcache.huge_code_pages = 1
opcache.file_cache这个还属于实验性质,在生产环境没有启用。
开启系统的HugePages
sysctl vm.nr_hugepages=512
grep Huge /proc/meminfo
AnonHugePages: 391168 kB
HugePages_Total: 512
HugePages_Free: 477
HugePages_Rsvd: 103
HugePages_Surp: 0
Hugepagesize: 2048 kB
在第一次部署生产环境的时候还遇到了命令行下报错的情况。
/usr/local/php/bin/php -v
PHP Warning: Zend OPcache huge_code_pages: mmap(HUGETLB) failed: Cannot allocate memory (12) in Unknown on line 0
看了一下 text 段的占用,需要5个 HugePages,再看 HugePages_Free 发现只剩下4个了。调整参数到1024并重启 php-fpm,问题解决。出现这个问题主要是生产环境的 php-fpm 进程开的很多,导致 HugePages 不够用了。
size /usr/local/php/bin/php
text data bss dec hex filename
11092574 778968 149160 12020702 b76bde /usr/local/php/bin/php
分享一个查看进程 HugePages 占用的脚本
#进程hugepage占用,Redhat&CentOS首选
sudo grep -B 11 'KernelPageSize: 2048 kB' /proc/[pid]/smaps | grep "^Size:" | awk '{sum+=$2} END{print sum/1024}'
#!/usr/bin/perl
#查看huge_pages占用
sub counthugepages {
my $pid=$_[0];
open (NUMAMAPS, "/proc/$pid/numa_maps") || die "can't open numa_maps";
my $HUGEPAGECOUNT=0;
while (<NUMAMAPS>) {
if (/huge.*dirty=(\d+)/) {
$HUGEPAGECOUNT+=$1;
}
}
close NUMAMAPS;
return ($HUGEPAGECOUNT);
}
printf "%d huge pages\n",counthugepages($ARGV[0]);
从3月中旬第一次灰度上线,到7月12日下午最后一台服务器升级完成。整个集群CPU使用率平均下降15% - 20%,负载也有很大程度的下降,完全达到预期。
首先要感谢PHP社区各位成员的努力,让PHP的性能有了一次巨大提升,并且这次提升对于大多数应用领域的开发者来说,几乎可以算是透明的。
在这次漫长的升级过程中,各位小伙伴也为这次里程碑式的升级贡献了太多的智慧,付出了辛勤的劳动,感谢各位。
接下来还要推动公司内其它老项目的升级工作,希望其他小伙伴也能尽快享受到升级带来的实惠。
PHP’s new hashtable implementation
留下评论