Upload
badoo-development
View
284
Download
15
Embed Size (px)
Citation preview
Антон Довгаль
20 июля 2017
Модули PHPв Badoo
● Свой первый модуль написал в 16 лет 2004 году - PECL/memcache
● Когда-то давно написал PECL/rar, PECL/sphinx, PECL/memtrack и ещё
какой-то трэш, по пути исправив сотни багов в PHP
● Успел 3 года поработать в Zend (в основном над Zend Server), в
процессе переписал заново PECL/oci8
● 10 лет в Badoo: поддержка/доработка PHP, разработка сервисов на C,
недорого
Про меня
● Крупнейший сервис знакомств в мире - 350 млн пользователей
● ~3 млн строк кода на PHP + ~1.5 млн строк тестов
● ~1000 серверов с PHP-FPM (разработан в Badoo, добавлен в PHP в 2010
году)
● ~600 серверов MySQL
● >200 инженеров
● 2 + 0.5 + 0.5 (=3?) датацентра
● 2 офиса разработки - Москва и Лондон
Про Badoo
Модули PHPони же - экстеншены
● Как работает PHP с модулями?
● Что представляют из себя модули PHP?
● Как и на чём их пишут?
● Зачем их (иногда) нужно писать?
● Как они работают?
● Как начать писать свой модуль PHP
● Пара примеров из жизни
У меня был план
Архитектура PHP в двух словах
● Пишутся на C/C++
● Статические модули - встроены в PHP, нельзя выгрузить
● Динамические - обычный .so/.dll, можно загрузить dl()
● Могут использовать сторонние библиотеки на C/C++
Модули PHP
● встроенные (standard, date, pcre, SPL, Reflection)
● собираемые по умолчанию (ctype, dom, hash, json etc.)
● поставляемые в дистрибуции
● модули из PECL - pecl.php.net
● сторонние модули - Github & Co.
Виды модулей PHP
bcmath
bz2
calendar
com_dotnet
ctype
curl
date
dba
dom
enchant
exif
fileinfo
filter
Модули в дистрибуцииftp
gd
gettext
gmp
hash
iconv
imap
interbase
intl
json
ldap
libxml
mbstring
mysqli
mysqlnd
oci8
odbc
opcache
openssl
pcntl
pcre
pdo
pdo_dblib
pdo_firebird
pdo_mysql
pdo_oci
pdo_odbc
pdo_pgsql
pdo_sqlite
pgsql
phar
posix
pspell
readline
recode
reflection
session
shmop
simplexml
skeleton
snmp
soap
sockets
spl
sqlite3
standard
sysvmsg
sysvsem
sysvshm
tidy
tokenizer
wddx
xml
xmlreader
xmlrpc
xmlwriter
xsl
zend_test
zip
zlib
● обычные модули
○ имплементируют функции, классы, методы
● Zend extension
○ имплементируют функции, классы, методы
○ вмешиваются в работу ядра (opcache, XDebug)
Типы модулей PHP
● blitz - движок шаблонов на C
● geoi - используется для гео-поиска
● gpbs - общий интерфейс к нашим сервисам на C/C++/Go
● handlersocketi - интерфейс к HS
● imatch - сравнение изображений, поиск дубликатов
● leptonica - работа с изображениями: ресайз, поворот, искажения для
капчи
● memtrack - следит за использованием памяти в PHP, ищет прожорливые
функции
● pinba - клиент для нашего сервиса статистики на базе MySQL
Нестандартные модули PHP в Badoo
● Нужен интерфейс к библиотеке на C/C++ (mysql, oci8)
● Есть задача, которая не решается на PHP (gpbs)
● Есть узкое место в коде, которое можно оптимизировать на C (blitz)
● Нужно добавить функционал в сам PHP (xdebug)
● Хочется посмотреть “что у ней внутре”
Причины написания своего модуля
Пишем модуль PHPcrash course
> git clone https://github.com/php/php-src
> cd php-src/ext
> ./ext_skel
./ext_skel --extname=module [--proto=file] [--stubs=file] [--xml[=file]] [--skel=dir]
[--full-xml] [--no-help]
--extname=module module is the name of your extension
--proto=file file contains prototypes of functions to create
--stubs=file generate only function stubs in file
--xml generate xml documentation to be added to phpdoc-svn
--skel=dir path to the skeleton directory
--full-xml generate xml documentation for a self-contained extension (not yet
implemented)
--no-help don't try to be nice and create comments in the code and helper functions
to test if the module compiled
Пишем модуль PHP
> ./ext_skel --extname=example
Creating directory example
Creating basic files: config.m4 config.w32 .gitignore example.c
php_example.h CREDITS EXPERIMENTAL tests/001.phpt example.php [done].
Пишем модуль PHP
config.m4 - опции configure и проверки библиотек, хидеров и пр.
config.w32 - используется для сборки под Windows
CREDITS - информация об авторах
EXPERIMENTAL - файл-метка, показывающий статус модуля
.gitignore - игнорируем мусор от сборки
php_example.h - заголовок модуля
example.c - исходник модуля
example.php - небольшой скрипт для демонстрации работы
tests/ - ??? какие тесты ???
Пишем модуль PHP
config.m4:
PHP_ARG_ENABLE(example, whether to enable example support,[ --enable-example Enable example support])
if test "$PHP_EXAMPLE" != "no"; then PHP_NEW_EXTENSION(example, example.c, $ext_shared)fi
Пишем модуль PHP
● Функции-обработчики:
○ MINIT - при загрузке/старт PHP
○ MSHUTDOWN - при выгрузке/окончание работы PHP
○ MINFO - вывод в phpinfo()
○ RINIT - начало запроса, на каждый запрос
○ RSHUTDOWN - конец запроса, на каждый запрос
● Функции, методы классов
● Список функций модуля - zend_function_entry[]
● Структура модуля - zend_module_entry
Структура модуля PHP
php_example.h:
#ifndef PHP_EXAMPLE_H#define PHP_EXAMPLE_H
extern zend_module_entry example_module_entry;#define phpext_example_ptr &example_module_entry
#define PHP_EXAMPLE_VERSION "0.1.0"
#endif /* PHP_EXAMPLE_H */
Пишем модуль PHP
example.c:
#ifdef HAVE_CONFIG_H#include "config.h"#endif
#include "php.h"#include "php_ini.h"#include "ext/standard/info.h"#include "php_example.h"
…
Пишем модуль PHP
example.c:…/* {{{ proto string confirm_example_compiled(string arg) Return a string to confirm that the module is compiled in */PHP_FUNCTION(confirm_example_compiled){ char *arg = NULL; size_t arg_len, len; zend_string *strg;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) { return; }
strg = strpprintf(0, "Congratulations! You have successfully modified ext/%s/config.m4. Module %s is now compiled into PHP.", "example", arg);
RETURN_STR(strg);}/* }}} */…
Пишем модуль PHP
БЕЗ ПАНИКИ!всё это можно потом найти тут:
github.com/tony2001/example
example.c:…/* {{{ proto string confirm_example_compiled(string arg) Return a string to confirm that the module is compiled in */
/* }}} */…
Пишем модуль PHP
example.c:…
PHP_FUNCTION(confirm_example_compiled){
RETURN_STR(strg);}…
Пишем модуль PHP
#define PHP_FUNCTION(name) ZEND_FUNCTION(name)
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name))
#define ZEND_FN(name) zif_##name
#define ZEND_NAMED_FUNCTION(name) name(INTERNAL_FUNCTION_PARAMETERS)
#define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value
PHP_FUNCTION(name) => zif_name(zend_execute_data *execute_data, zval *return_value)
PHP + макросы = ❤�
#define RETURN_STR(s) { RETVAL_STR(s); return; }
#define RETVAL_STR(s) ZVAL_STR(return_value, s)
#define ZVAL_STR(z, s) do { \ zval *__z = (z); \ zend_string *__s = (s); \ Z_STR_P(__z) = __s; \ /* interned strings support */ \ Z_TYPE_INFO_P(__z) = ZSTR_IS_INTERNED(__s) ? \ IS_INTERNED_STRING_EX : \ IS_STRING_EX; \ } while (0)
PHP + макросы = ❤�
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;
Переменные в PHP. ZVAL
ZVAL в PHP7
example.c:…
char *arg = NULL; size_t arg_len, len; zend_string *strg;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) { return; }
…
Пишем модуль PHP
example.c:…
strg = strpprintf(0, "Congratulations! You have successfully modified ext/%s/config.m4. Module %s is now compiled into PHP.", "example", arg);
RETURN_STR(strg);}/* }}} */…
Пишем модуль PHP
example.c:…
PHP_MINFO_FUNCTION(example){ php_info_print_table_start(); php_info_print_table_header(2, "example support", "enabled"); php_info_print_table_end();}
…
Пишем модуль PHP
example.c:…const zend_function_entry example_functions[] = { PHP_FE(confirm_example_compiled, NULL) PHP_FE_END};
zend_module_entry example_module_entry = { STANDARD_MODULE_HEADER, "example", example_functions, NULL, //MINIT NULL, //MSHUTDOWN NULL, //RINIT NULL, //RSHUTDOWN PHP_MINFO(example), PHP_EXAMPLE_VERSION, STANDARD_MODULE_PROPERTIES};
Пишем модуль PHP
Нам понадобится phpize и заголовки PHP.
Обычно они есть в пакетах:
● php7-devel
● или php-devel
● или php-dev
Пишем Собираем модуль PHP
> phpizeConfiguring for:PHP Api Version: 20160303Zend Module Api No: 20160303Zend Extension Api No: 320160303
> ./configure --with-php-config=/path/to/php/php-configconfigure: loading site script /usr/share/site/x86_64-unknown-linux-gnuchecking for grep that handles long lines and -e... /usr/bin/grep……
> make installInstalling shared extensions: /path/to/php/lib64/extensions/debug-non-zts-20160303/
Пишем Собираем модуль PHP
<?php
dl('example.so');
echo confirm_example_compiled(“example”);
?>
Используем модуль PHP
Congratulations! You have successfully modified ext/example/config.m4. Module “example” is now compiled into PHP.
Cсылка на исходники
github.com/tony2001/example
Пример из жизни #1совсем простой
Код для “шифрования” ID пользователя:
function map_canonical($n, $d = true) { $seed = 3358638055; $map1 = array(31, 27, 23, 19, 15, 11, 7, 3, 28, 24, 20, 16, 12, 8, 4, 0, 29, 25, 21, 17, 13, 9, 5, 1, 30, 26, 22, 18, 14, 10, 6, 2); $map2 = array(15, 23, 31, 7, 14, 22, 30, 6, 13, 21, 29, 5, 12, 20, 28, 4, 11, 19, 27, 3, 10, 18, 26, 2, 9, 17, 25, 1, 8, 16, 24, 0);
$m = 0; $n = $d ? $n : ($n ^ $seed);
foreach ($d ? $map1 : $map2 as $i => $l) { $k = $n & (1 << $i); if ($k) $m |= 1 << $l; } return $d ? $m ^ $seed : $m; }
Пример #1. Оригинал на PHP
Вызовем 100 млн раз:
# time php5.3 /tmp/original.php
real 9m9.008suser 9m8.904ssys 0m0.028s
~180 тысяч вызовов в секунду
Пример #1. Оригинал на PHP
static PHP_FUNCTION(bi_map_encode) /* {{{ */{ long n; unsigned int result = 0; unsigned int number;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &n) == FAILURE) { return; }
number = (unsigned int)n;#define K(i) (number & (1 << i))#define ITER(i, n) result += (n > i) ? (K(i) << (n - i)) : (K(i) >> (i - n));
ITER(0, 31); ITER(1, 27); ITER(2, 23); ITER(3, 19);…
Пример #1. Жалкая пародия на C
Вызовем 100 млн раз:
# time php5.3 /tmp/copy.php
real 0m9.427suser 0m9.417ssys 0m0.008s
~11 млн вызовов в секундут.е. в 60 раз быстрее
Пример #1. Жалкая пародия на C
5.3
PHP - ~550 секундМодуль - ~9 секунд
Мини-бенчмарк PHP 5.3 vs PHP 7.1
7.1
PHP - ~100 секундыМодуль - ~4 секунды
Обновляйте PHP!Мораль:
Пример из жизни #2чуть посложнее
<?php
$connect = memcache_connect(“service.host”, 3113);
$result = memcache_laccess_update($connect, $anketa_id, $user_id,
$mode, $command, $partner_id);
C:
модуль - snprintf(buf, sizeof(buf), "laccess_update v17 %u %u %c %c %u
%s %u", aid, user_id, mode, command, partner_id, ip, country_id);
сервис - sscanf(buf, "%u %ld %ld %c %f %f %c %*s %*s %ld %*s %*s %ld
%*s %*s %*s %*s %ld"....); >_<
Пример #2. Текстовый протокол
Минусы:
○ приходится постоянно менять модуль PHP вместе с API сервисов
○ неэкономно расходуется сеть
○ сложный разбор аргументов методов
Плюсы:
○ человеко-читабельность протокола
Пример #2. Текстовый протокол
● Google Protobuf: формат сериализации, бинарная
альтернатива XML, JSON и пр.
● Google Protocol Buffers Service - RPC на базе GPB
● Формат данных задаётся proto-файлом
● Из proto можно генерировать код на C, C++, PHP, Go,
Java, Python и прочих языках
● gRPC - официальный RPC от Google 2015 года, но у нас
всё работает c 2011 года
Пример #2. GPBS
Пример #2. GPBS
package badoo.cityd2;
message city { required uint32 id = 1; required string name = 2; required uint32 lang_id = 3; required uint32 user_cnt = 4; optional float lat = 5; optional float lon = 6;}
message request_find_city { required string name = 1; optional uint32 limit = 2; repeated uint32 lang_ids = 3;}
message response_cities { repeated city cities = 1;}
- с помощью protobuf-c генерируем из proto-файла код на C и
дескриптор (описывает все структуры в proto a-la reflection)
- собираем код в .so, добавляя дескриптор
- подгружаем .so в PHP (dlopen, dlsym)
- используем для кодирования/декодирования пакетов Protobuf в модуле
Пример #2. GPBS
<?php
$module = gpbs_import(“cityd.so”);
$connect = gpbs_connect(“cityd.host”, 1331, $module);
$call_result = gpbs_call(“find_city”, array(“name” => ”mos”));
//добавляем новый аргумент
$call_result = gpbs_call(“find_city”, array(“name” => ”mos”,
“country_id” => 1));
Пример #2. GPBS
Разница между GPB и JSON:
<?php
//грузим .so, создаём объект модуля со всеми методами из дескриптора
$so = gpbs_import(“cityd.so");
//сериализуем запрос в Protobuf
$j = gpbs_serialize($so->request_find_city, array("name" => "mos",
"limit"=>10, "lang_ids"=>array(1,2,3)));
var_dump($j); // string(13). Тот же самый запрос в JSON - 44 байта!
Пример #2. GPBS
Плюсы:
● все сервисы унифицированы, используют общую базу кода
● бинарный протокол - меньше потребление сети
● при изменениях в API сервиса нужно пересобрать .so из proto-файла,
в остальном меняется только PHP-код клиентской части
Пример #2. RPC на Google Protobufs
Минусы:
● бинарный протокол приходится разбирать Wireshark
Плюсы:
● все сервисы унифицированы, используют общую базу кода
● бинарный протокол - меньше потребление сети
● при изменениях в API сервиса нужно пересобрать .so из proto-файла,
в остальном меняется только PHP-код клиентской части
Пример #2. RPC на Google Protobufs
Ещё плюсы:
● можно генерить JSON!
PHP:
● PHP at the Core: A Hacker's Guide php.net/manual/en/internals2.php
● Extending and Embedding PHP, Sara Golemon 2006 (местами устарела).
● Читайте исходники PHP и других модулей!
Google Protobuf:
● github.com/google/protobuf
● github.com/grpc/grpc
Badoo:
● techblog.badoo.com
● habrahabr.ru/company/badoo
Полезные ссылки
Спасибо!github.com/tony2001