58
Антон Довгаль 20 июля 2017 Модули PHP в Badoo

Как мы делаем модули PHP в Badoo – Антон Довгаль

Embed Size (px)

Citation preview

Page 1: Как мы делаем модули PHP в Badoo – Антон Довгаль

Антон Довгаль

20 июля 2017

Модули PHPв Badoo

Page 2: Как мы делаем модули PHP в Badoo – Антон Довгаль

● Свой первый модуль написал в 16 лет 2004 году - PECL/memcache

● Когда-то давно написал PECL/rar, PECL/sphinx, PECL/memtrack и ещё

какой-то трэш, по пути исправив сотни багов в PHP

● Успел 3 года поработать в Zend (в основном над Zend Server), в

процессе переписал заново PECL/oci8

● 10 лет в Badoo: поддержка/доработка PHP, разработка сервисов на C,

недорого

Про меня

Page 3: Как мы делаем модули PHP в Badoo – Антон Довгаль

● Крупнейший сервис знакомств в мире - 350 млн пользователей

● ~3 млн строк кода на PHP + ~1.5 млн строк тестов

● ~1000 серверов с PHP-FPM (разработан в Badoo, добавлен в PHP в 2010

году)

● ~600 серверов MySQL

● >200 инженеров

● 2 + 0.5 + 0.5 (=3?) датацентра

● 2 офиса разработки - Москва и Лондон

Про Badoo

Page 4: Как мы делаем модули PHP в Badoo – Антон Довгаль

Модули PHPони же - экстеншены

Page 5: Как мы делаем модули PHP в Badoo – Антон Довгаль

● Как работает PHP с модулями?

● Что представляют из себя модули PHP?

● Как и на чём их пишут?

● Зачем их (иногда) нужно писать?

● Как они работают?

● Как начать писать свой модуль PHP

● Пара примеров из жизни

У меня был план

Page 6: Как мы делаем модули PHP в Badoo – Антон Довгаль

Архитектура PHP в двух словах

Page 7: Как мы делаем модули PHP в Badoo – Антон Довгаль

● Пишутся на C/C++

● Статические модули - встроены в PHP, нельзя выгрузить

● Динамические - обычный .so/.dll, можно загрузить dl()

● Могут использовать сторонние библиотеки на C/C++

Модули PHP

Page 8: Как мы делаем модули PHP в Badoo – Антон Довгаль

● встроенные (standard, date, pcre, SPL, Reflection)

● собираемые по умолчанию (ctype, dom, hash, json etc.)

● поставляемые в дистрибуции

● модули из PECL - pecl.php.net

● сторонние модули - Github & Co.

Виды модулей PHP

Page 9: Как мы делаем модули PHP в Badoo – Антон Довгаль

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

Page 10: Как мы делаем модули PHP в Badoo – Антон Довгаль

● обычные модули

○ имплементируют функции, классы, методы

● Zend extension

○ имплементируют функции, классы, методы

○ вмешиваются в работу ядра (opcache, XDebug)

Типы модулей PHP

Page 11: Как мы делаем модули PHP в Badoo – Антон Довгаль

● blitz - движок шаблонов на C

● geoi - используется для гео-поиска

● gpbs - общий интерфейс к нашим сервисам на C/C++/Go

● handlersocketi - интерфейс к HS

● imatch - сравнение изображений, поиск дубликатов

● leptonica - работа с изображениями: ресайз, поворот, искажения для

капчи

● memtrack - следит за использованием памяти в PHP, ищет прожорливые

функции

● pinba - клиент для нашего сервиса статистики на базе MySQL

Нестандартные модули PHP в Badoo

Page 12: Как мы делаем модули PHP в Badoo – Антон Довгаль

● Нужен интерфейс к библиотеке на C/C++ (mysql, oci8)

● Есть задача, которая не решается на PHP (gpbs)

● Есть узкое место в коде, которое можно оптимизировать на C (blitz)

● Нужно добавить функционал в сам PHP (xdebug)

● Хочется посмотреть “что у ней внутре”

Причины написания своего модуля

Page 13: Как мы делаем модули PHP в Badoo – Антон Довгаль

Пишем модуль PHPcrash course

Page 14: Как мы делаем модули PHP в Badoo – Антон Довгаль

> 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

Page 15: Как мы делаем модули PHP в Badoo – Антон Довгаль

> ./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

Page 16: Как мы делаем модули PHP в Badoo – Антон Довгаль

config.m4 - опции configure и проверки библиотек, хидеров и пр.

config.w32 - используется для сборки под Windows

CREDITS - информация об авторах

EXPERIMENTAL - файл-метка, показывающий статус модуля

.gitignore - игнорируем мусор от сборки

php_example.h - заголовок модуля

example.c - исходник модуля

example.php - небольшой скрипт для демонстрации работы

tests/ - ??? какие тесты ???

Пишем модуль PHP

Page 17: Как мы делаем модули PHP в Badoo – Антон Довгаль

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

Page 18: Как мы делаем модули PHP в Badoo – Антон Довгаль

● Функции-обработчики:

○ MINIT - при загрузке/старт PHP

○ MSHUTDOWN - при выгрузке/окончание работы PHP

○ MINFO - вывод в phpinfo()

○ RINIT - начало запроса, на каждый запрос

○ RSHUTDOWN - конец запроса, на каждый запрос

● Функции, методы классов

● Список функций модуля - zend_function_entry[]

● Структура модуля - zend_module_entry

Структура модуля PHP

Page 19: Как мы делаем модули PHP в Badoo – Антон Довгаль

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

Page 20: Как мы делаем модули PHP в Badoo – Антон Довгаль

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

Page 21: Как мы делаем модули PHP в Badoo – Антон Довгаль

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

Page 22: Как мы делаем модули PHP в Badoo – Антон Довгаль

БЕЗ ПАНИКИ!всё это можно потом найти тут:

github.com/tony2001/example

Page 23: Как мы делаем модули PHP в Badoo – Антон Довгаль

example.c:…/* {{{ proto string confirm_example_compiled(string arg) Return a string to confirm that the module is compiled in */

/* }}} */…

Пишем модуль PHP

Page 24: Как мы делаем модули PHP в Badoo – Антон Довгаль

example.c:…

PHP_FUNCTION(confirm_example_compiled){

RETURN_STR(strg);}…

Пишем модуль PHP

Page 25: Как мы делаем модули PHP в Badoo – Антон Довгаль

#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 + макросы = ❤�

Page 26: Как мы делаем модули PHP в Badoo – Антон Довгаль

#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 + макросы = ❤�

Page 27: Как мы делаем модули PHP в Badoo – Антон Довгаль

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

Page 28: Как мы делаем модули PHP в Badoo – Антон Довгаль

ZVAL в PHP7

Page 29: Как мы делаем модули PHP в Badoo – Антон Довгаль

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

Page 30: Как мы делаем модули PHP в Badoo – Антон Довгаль

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

Page 31: Как мы делаем модули PHP в Badoo – Антон Довгаль

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

Page 32: Как мы делаем модули PHP в Badoo – Антон Довгаль

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

Page 33: Как мы делаем модули PHP в Badoo – Антон Довгаль

Нам понадобится phpize и заголовки PHP.

Обычно они есть в пакетах:

● php7-devel

● или php-devel

● или php-dev

Пишем Собираем модуль PHP

Page 34: Как мы делаем модули PHP в Badoo – Антон Довгаль

> 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

Page 35: Как мы делаем модули PHP в Badoo – Антон Довгаль

<?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.

Page 36: Как мы делаем модули PHP в Badoo – Антон Довгаль

Cсылка на исходники

github.com/tony2001/example

Page 37: Как мы делаем модули PHP в Badoo – Антон Довгаль

Пример из жизни #1совсем простой

Page 38: Как мы делаем модули PHP в Badoo – Антон Довгаль

Код для “шифрования” 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

Page 39: Как мы делаем модули PHP в Badoo – Антон Довгаль

Вызовем 100 млн раз:

# time php5.3 /tmp/original.php

real 9m9.008suser 9m8.904ssys 0m0.028s

~180 тысяч вызовов в секунду

Пример #1. Оригинал на PHP

Page 40: Как мы делаем модули PHP в Badoo – Антон Довгаль

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

Page 41: Как мы делаем модули PHP в Badoo – Антон Довгаль

Вызовем 100 млн раз:

# time php5.3 /tmp/copy.php

real 0m9.427suser 0m9.417ssys 0m0.008s

~11 млн вызовов в секундут.е. в 60 раз быстрее

Пример #1. Жалкая пародия на C

Page 42: Как мы делаем модули PHP в Badoo – Антон Довгаль

5.3

PHP - ~550 секундМодуль - ~9 секунд

Мини-бенчмарк PHP 5.3 vs PHP 7.1

7.1

PHP - ~100 секундыМодуль - ~4 секунды

Page 43: Как мы делаем модули PHP в Badoo – Антон Довгаль

Обновляйте PHP!Мораль:

Page 44: Как мы делаем модули PHP в Badoo – Антон Довгаль

Пример из жизни #2чуть посложнее

Page 45: Как мы делаем модули PHP в Badoo – Антон Довгаль
Page 46: Как мы делаем модули PHP в Badoo – Антон Довгаль

<?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. Текстовый протокол

Page 47: Как мы делаем модули PHP в Badoo – Антон Довгаль

Минусы:

○ приходится постоянно менять модуль PHP вместе с API сервисов

○ неэкономно расходуется сеть

○ сложный разбор аргументов методов

Плюсы:

○ человеко-читабельность протокола

Пример #2. Текстовый протокол

Page 48: Как мы делаем модули PHP в Badoo – Антон Довгаль
Page 49: Как мы делаем модули PHP в Badoo – Антон Довгаль

● 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

Page 50: Как мы делаем модули PHP в Badoo – Антон Довгаль

Пример #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;}

Page 51: Как мы делаем модули PHP в Badoo – Антон Довгаль

- с помощью protobuf-c генерируем из proto-файла код на C и

дескриптор (описывает все структуры в proto a-la reflection)

- собираем код в .so, добавляя дескриптор

- подгружаем .so в PHP (dlopen, dlsym)

- используем для кодирования/декодирования пакетов Protobuf в модуле

Пример #2. GPBS

Page 52: Как мы делаем модули PHP в Badoo – Антон Довгаль

<?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

Page 53: Как мы делаем модули PHP в Badoo – Антон Довгаль

Разница между 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

Page 54: Как мы делаем модули PHP в Badoo – Антон Довгаль

Плюсы:

● все сервисы унифицированы, используют общую базу кода

● бинарный протокол - меньше потребление сети

● при изменениях в API сервиса нужно пересобрать .so из proto-файла,

в остальном меняется только PHP-код клиентской части

Пример #2. RPC на Google Protobufs

Минусы:

● бинарный протокол приходится разбирать Wireshark

Page 55: Как мы делаем модули PHP в Badoo – Антон Довгаль

Плюсы:

● все сервисы унифицированы, используют общую базу кода

● бинарный протокол - меньше потребление сети

● при изменениях в API сервиса нужно пересобрать .so из proto-файла,

в остальном меняется только PHP-код клиентской части

Пример #2. RPC на Google Protobufs

Ещё плюсы:

● можно генерить JSON!

Page 56: Как мы делаем модули PHP в Badoo – Антон Довгаль
Page 57: Как мы делаем модули PHP в Badoo – Антон Довгаль

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

Полезные ссылки

Page 58: Как мы делаем модули PHP в Badoo – Антон Довгаль

Спасибо!github.com/tony2001

[email protected]