Документация на тему архитектуры языка PHP скудна и разрозненна, несмотря на то что тема интересна многим. В моем докладе я постараюсь заполнить этот пробел и рассказать о модулях PHP: как они работают, зачем и как их пишут. В процессе мы рассмотрим опыт Badoo в этой сфере на примерах двух модулей. И еще напишем очень небольшой собственный модуль.
— Что такое модули PHP, как они работают
— Как начать писать свой модуль PHP
— Скелет модуля — Функции, классы, методы
— Разбор параметров функции
— Сборка модуля
— Подгрузка модуля
— Простой пример модуля из Badoo
— Сложный пример модуля из Badoo
2. ● Свой первый модуль написал в 16 лет 2004 году - PECL/memcache
● Когда-то давно написал PECL/rar, PECL/sphinx, PECL/memtrack и ещё
какой-то трэш, по пути исправив сотни багов в PHP
● Успел 3 года поработать в Zend (в основном над Zend Server), в
процессе переписал заново PECL/oci8
● 10 лет в Badoo: поддержка/доработка PHP, разработка сервисов на C,
недорого
Про меня
3. ● Крупнейший сервис знакомств в мире - 350 млн пользователей
● ~3 млн строк кода на PHP + ~1.5 млн строк тестов
● ~1000 серверов с PHP-FPM (разработан в Badoo, добавлен в PHP в 2010
году)
● ~600 серверов MySQL
● >200 инженеров
● 2 + 0.5 + 0.5 (=3?) датацентра
● 2 офиса разработки - Москва и Лондон
Про Badoo
5. ● Как работает PHP с модулями?
● Что представляют из себя модули PHP?
● Как и на чём их пишут?
● Зачем их (иногда) нужно писать?
● Как они работают?
● Как начать писать свой модуль PHP
● Пара примеров из жизни
У меня был план
7. ● Пишутся на C/C++
● Статические модули - встроены в PHP, нельзя выгрузить
● Динамические - обычный .so/.dll, можно загрузить dl()
● Могут использовать сторонние библиотеки на C/C++
Модули PHP
8. ● встроенные (standard, date, pcre, SPL, Reflection)
● собираемые по умолчанию (ctype, dom, hash, json etc.)
● поставляемые в дистрибуции
● модули из PECL - pecl.php.net
● сторонние модули - Github & Co.
Виды модулей PHP
10. ● обычные модули
○ имплементируют функции, классы, методы
● Zend extension
○ имплементируют функции, классы, методы
○ вмешиваются в работу ядра (opcache, XDebug)
Типы модулей PHP
11. ● blitz - движок шаблонов на C
● geoi - используется для гео-поиска
● gpbs - общий интерфейс к нашим сервисам на C/C++/Go
● handlersocketi - интерфейс к HS
● imatch - сравнение изображений, поиск дубликатов
● leptonica - работа с изображениями: ресайз, поворот, искажения для
капчи
● memtrack - следит за использованием памяти в PHP, ищет прожорливые
функции
● pinba - клиент для нашего сервиса статистики на базе MySQL
Нестандартные модули PHP в Badoo
12. ● Нужен интерфейс к библиотеке на C/C++ (mysql, oci8)
● Есть задача, которая не решается на PHP (gpbs)
● Есть узкое место в коде, которое можно оптимизировать на C (blitz)
● Нужно добавить функционал в сам PHP (xdebug)
● Хочется посмотреть “что у ней внутре”
Причины написания своего модуля
14. > 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
16. config.m4 - опции configure и проверки библиотек, хидеров и пр.
config.w32 - используется для сборки под Windows
CREDITS - информация об авторах
EXPERIMENTAL - файл-метка, показывающий статус модуля
.gitignore - игнорируем мусор от сборки
php_example.h - заголовок модуля
example.c - исходник модуля
example.php - небольшой скрипт для демонстрации работы
tests/ - ??? какие тесты ???
Пишем модуль PHP
17. 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
18. ● Функции-обработчики:
○ MINIT - при загрузке/старт PHP
○ MSHUTDOWN - при выгрузке/окончание работы PHP
○ MINFO - вывод в phpinfo()
○ RINIT - начало запроса, на каждый запрос
○ RSHUTDOWN - конец запроса, на каждый запрос
● Функции, методы классов
● Список функций модуля - zend_function_entry[]
● Структура модуля - zend_module_entry
Структура модуля PHP
23. example.c:
…
/* {{{ proto string confirm_example_compiled(string arg)
Return a string to confirm that the module is compiled in */
/* }}} */
…
Пишем модуль PHP
39. Вызовем 100 млн раз:
# time php5.3 /tmp/original.php
real 9m9.008s
user 9m8.904s
sys 0m0.028s
~180 тысяч вызовов в секунду
Пример #1. Оригинал на PHP
40. 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
41. Вызовем 100 млн раз:
# time php5.3 /tmp/copy.php
real 0m9.427s
user 0m9.417s
sys 0m0.008s
~11 млн вызовов в секунду
т.е. в 60 раз быстрее
Пример #1. Жалкая пародия на C
47. Минусы:
○ приходится постоянно менять модуль PHP вместе с API сервисов
○ неэкономно расходуется сеть
○ сложный разбор аргументов методов
Плюсы:
○ человеко-читабельность протокола
Пример #2. Текстовый протокол
48.
49. ● 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
50. Пример #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;
}
51. - с помощью protobuf-c генерируем из proto-файла код на C и
дескриптор (описывает все структуры в proto a-la reflection)
- собираем код в .so, добавляя дескриптор
- подгружаем .so в PHP (dlopen, dlsym)
- используем для кодирования/декодирования пакетов Protobuf в модуле
Пример #2. GPBS
53. Разница между 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
54. Плюсы:
● все сервисы унифицированы, используют общую базу кода
● бинарный протокол - меньше потребление сети
● при изменениях в API сервиса нужно пересобрать .so из proto-файла,
в остальном меняется только PHP-код клиентской части
Пример #2. RPC на Google Protobufs
Минусы:
● бинарный протокол приходится разбирать Wireshark
55. Плюсы:
● все сервисы унифицированы, используют общую базу кода
● бинарный протокол - меньше потребление сети
● при изменениях в API сервиса нужно пересобрать .so из proto-файла,
в остальном меняется только PHP-код клиентской части
Пример #2. RPC на Google Protobufs
Ещё плюсы:
● можно генерить JSON!
56.
57. 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
Полезные ссылки