---
title: Микросервисы
date: 2016-05-06T10:00
tags: [backend]
---

Микросервисы в современной веб-разработке это [архитектурный подход](https://habrahabr.ru/post/249183/) по разделению изначального монолитного приложения на независимые системные (linux) процессы. Необходимость в таком разделении возникает когда монолит становится **слишком медленным** для одного синхронного процесса, когда код тянет **слишком много зависимостей** и когда **повышается риск** что-то сломать в этой длинной цепочке обработки данных.

Микросервисы — не панацея и тоже усложняют всё приложение в плане транзактивности, логов, и обработки ошибок, управления конфигураций, версионирования, деплоя, возникает дублирование bootstrap-кода из-за изоляции сервисов. Поэтому стоит осторожно подходить к тому, что вы хотите выделить в микросервис и что он даст по **характеристикам** приложения (performance, стабильность, масштабирование, разделение нагрузки). Как правило, веб-приложения создаются [сначала монолитом](https://martinfowler.com/bliki/MonolithFirst.html), а потом разделяются на сервисы — так эффективней и проще эволюционировать.

<iframe width="934" height="350" src="https://www.youtube.com/embed/2yko4TbC8cI" title="Martin Fowler  –  Microservices" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

### Паттерны взаимодействия

Общение между сервисами можно делать по-разному:

1. Синхронно — сервис вызывают явно как веб-сервис по HTTP/REST, делая работу **синхронно** по-старинке, подобно веб-серверу, тогда как клиент ждёт ответа (подобно ajax)

2. Асинхронно — cервис вызывают явно по HTTP/REST, регистрируется задача и клиент тут же получает job ID. Клиент обязан сам проверить состояние задачи, периодически спрашивая у сервиса (polling, aws transcoder)

3. Асинхронно — сервис как работник (демон) без публичного доступа. Слушает какой-то ресурс и генерирует новые вызовы. Это может быть

- единая шина сообщений ([RabbitMQ](https://www.rabbitmq.com/),[Gearman](http://gearman.org/), [0MQ](http://zeromq.org/), Kafka, AWS SQS, AWS Kinesis)>
- хранилище с локами (файлы, память, Memcache, Redis)
- другие ресурсы (процессы, сеть, железо)

4. Асинхронно, не используя MQ, регистрирует слушателей в себе и сам знает к кому куда стучать

Главное что-бы подход был единый, с предсказуемым форматом данных ([JSON](http://json.org/), [protobuf](https://github.com/google/protobuf), [thrift](http://thrift.apache.org/), [messagepack](http://msgpack.org/)).Для удобной конфигурации и дружбы между сервисами, надо ставить [Consul](https://www.consul.io/), [ETCD](https://github.com/coreos/etcd) или [ZooKeeper](https://zookeeper.apache.org/) — они позволят абстрагироваться от конкретных IP адресов и PID процессов.

### Пример

Например пользователь приложения имеет возможность **загружать файлы**.Обработка файла очень тяжёлая по CPU, генерирует несколько результатов и занимает много времени из-за долгих внутренних сетевых запросов по загрузке готовых обработанных файлов в хранилище. Допустим эта синхронная операция upload+resize+store занимает 20 секунд в монолитном приложении. В микросервисном приложении, вы решаете создать сервис **обработки** картинки, а для очередей сначала создаёте табличку в БД. Отлично, это даёт возможность сервис положить на другую машину и не нагружать основной сайт. Теперь возникают практические вопросы — как это всё сделать? Практически, если раньше в монолите разные слои приложения проверяли всё за вас, то теперь чуть ли не все значимые классы надо будет выделять отдельные сервисы авторизацию, логирование, менеджер картинок, менеджер транзакций и сам ресайз картинок.

### Веб-сервис на PHP

Если ваш сервис должен иметь публичный веб-интерфейс, проще всего запустить php в качестве сервера под конкретную папку и она будет по запросу дёргаться. Тут пригодятся микро-фреймворки типа [Silex](http://silex.sensiolabs.org/), [Slim](https://www.slimframework.com/) или [Lumen](https://lumen.laravel.com/)

```
#run me as:
#php -S 0.0.0.0:80 -t /var/www/silex

#install me with:
#composer require silex/silex:~1.3

$loader = require __DIR__.'/vendor/autoload.php';

$app = new Silex\Application();

#синхронная работа
$app->post('/resizeImage', function(Request $request) use($app) {
    #загрузить imagemagick, сделать resize, сохранить
    
    return "{success:true}";
});

#асинхронная работа
$app->post('/resizeImageAsync', function(Request $request) use($app) {
    #зарегистрировать новую работу и передать её демону через RabbitMQ, см. ниже
    #клиента оповестим автоматом через websocketы
    
    return "{success:true}";
});
```

### Демон-работник на PHP

Хотя php изначально разрабатывался как интерпретатор html файлов, современный php может работать в постоянном режиме как сервис если его запустить в CLI режиме. В реальной жизни, php демоны как правило нестабильны из-за утечек памяти и не так эффективны по скорости как сервисы на nodejs или go. Запустить демон можно из коммандной строки..

```
php -f /var/www/app/daemon/myImageProcessor.php

#let it live after terminal exit:
nohup php -f myImageProcessor.php &
```

С демонами есть несколько особенностей

- Работая в бесконечном цикле надо чистить ресурсы (DB-соединения, file handles)
- Надо мониторить рост своей памяти что-бы не умереть от fatal error
- Надо поддерживать graceful stop при получении SIGTERM и прочих сигналов что-бы не повредить данные на случай если кто-то остановит демона из консоли или перезапустит сервис
- Надо ловить все исключения, делать альтернативную функциональность бэкапов и откатов, формировать красивый log
- Перезапуск сервера должен запускать демоны заново — через крон или через регистрацию демона в виде системного сервиса

У вас может быть несколько экземпляров (instance) одного и того же демона в виде разных процессов. Тогда очень важно не попасть в проблемы многопоточности — дедлоки транзакций, запись в один log файл параллельно и обработка одного и того же задания. Обо всём этом чуть позже..

А пока, вот простой пример с бесконечным циклом и псевдо-кодом:

```
//myImageProcessor.php
//set custom process name
cli_set_process_title('php - daemon - myImageProcessor.php');

//enable process interruption handling
declare(ticks = 1);
$interrupted = false;
function handleSignal(){
    $interrupted = true;
}

pcntl_signal(SIGTERM, "handleSignal");
pcntl_signal(SIGHUP, "handleSignal");
pcntl_signal(SIGUSR1, "handleSignal");
pcntl_signal(SIGINT, "handleSignal");

while(true){
    $db->connect();
    
    $db->query("SELECT * FROM images WHERE processed = 0");
    
    //do some work here with resize & upload
    
    $db->disconnect();
    
    //memory cleanup & check
    gc_collect_cycles();
    const MAX_MEMORY_EXTRA = 10 * 1024 * 1024;
    if (convertToBytes(ini_get('memory_limit')) < (memory_get_usage(true) + MAX_MEMORY_EXTRA)) {
        exit();
    }
    
    //graceful shutdown
    if($interrupted){
        exit();
    }
    
    //wait for some time to decrease load on DB above
    sleep(10);
}
```

## Управление процессами

Проверку на УЖЕ запущенного демона можно сделать как на уровне bash, так и на уровне самого php с использованием экзотических флагов в базе, флагов во временных файлах или же используя такой же доступ к shell. С такой проверкой, комманду можно смело класть в cron и демон будет запускаться автоматом (раз в минуту)

### Bash

```
#check if daemon is not running yet
#add logs
[ "$(ps -ef| grep -v grep | grep myImageProcessor|wc -l)" -eq 0 ] && php -f myImageProcessor.php > /var/www/logs/myImageProcessor.log

#up to 5 instances!
[ "$(ps -ef| grep -v grep | grep imageResizer|wc -l)" -lt 5 ] && php myImageProcessor.php > /var/logs/myImageProcessor.log
```

Остановить демона можно так же из шелла..

```
sudo /usr/bin/kill -9 `ps aux | grep 'myImageProcessor' | grep -v grep | awk '{print $2}'`
```

### PM2

Для более могучего nodejs, существует отличный process manager, который может управлять и php-демонами! С его помощью можно сразу видеть **только** процессы демонов. Кроме того:

- перезапускать демона сразу после его падения
- следить за изменениями файла что-бы перезапустить демон (watch)
- следить за памятью и CPU и перезапускать при их превышениях

Менеджеры попроще — [forever](https://github.com/foreverjs/forever) и [guvnor](https://github.com/tableflip/guvnor)

### Очереди и RabbitMQ

Как только вы начали использовать демонов, вероятней всего вы станете использовать и паттерн Command, по которому микросервисы выполняют роль работников (workers). MQ-сервер выступает в роли брокера и шины, связывающей асинхронные процессы воедино. Он отвечает за надёжность передачи сообщения сервису и за то что несколько сервисов не получат одну и ту же комманду. Реализовывать это на стандартной БД было бы сложней

Для PHP-демонов, подключение rabbitMQ делается с помощью [php-amqplib](https://github.com/php-amqplib/php-amqplib) библиотеки и бесконечный цикл в этом случае начинает зависеть от неё..

```
$callback = function($msg){
    //actual work here
    $data = json_decode($msg->body, true);
    
    //Ack message that everything is done
    $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
}


//technical details..
$connection = new AMQPStreamConnection('localhost', 5672, 'root', 'pass');
$channel    = $connection->channel();

//new queue if it was missing
$channel->queue_declare($queue, false, true, false, false);

//one entry at a time
$channel->basic_qos(null, 1, null);
$channel->basic_consume($queue, $receiverName, false, false, false, false, $callback);

//almost infinite cycle
while (count($channel->callbacks)) {
	$channel->wait(null);
}

$channel->close();
$connection->close();
```

Понятно что один сервис хорошо, но архитектура ценна именно тем что сервисы взаимодействуют между собой. Поэтому сообщения можно генерировать другому сервису. Например если обработалось изображение, значит надо оповестить пользователя в реальном времени и обновить UI.

```
$queue = 'datasync';
$message = ['imageID'=>34, 'status'=>'processed'];
$message = json_encode($message);

$connection = new AMQPStreamConnection($server, $port, $login, $pass);
$channel    = $connection->channel();
$channel->queue_declare($queue, false, true, false, false);

$channel->basic_publish(new AMQPMessage($message, ['delivery_mode' => 2]), '', $routeKey);
$channel->close();
$connection->close();
```

### Перспективы

Более серьёзные сервисы пишутся на nodejs, а лучше на Go.

Грубо говоря, если прогнать [нагрузочный тест](https://github.com/tsenart/vegeta), то с PHP вы получите 300 req/s, тогда как с node 7000 req/s, а с go 9000 req/s.

С Go можно копать в сторону [go-micro](https://github.com/micro/go-micro)

См. также

- [Демоны — Основы](http://kamashev.name/2011/05/daemons-base/)
- [Демоны — Сигналы](http://kamashev.name/2011/06/daemons-signals/)
- [Create Daemons in PHP](http://kvz.io/blog/2009/01/09/create-daemons-in-php/)
- [PHP and RabbitMQ : Advanced examples](https://www.sitepoint.com/php-rabbitmq-advanced-examples/)
- [RabbitMQ introduction](https://www.rabbitmq.com/tutorials/tutorial-one-php.html)
- [How to Install and Manage RabbitMQ](https://www.digitalocean.com/community/tutorials/how-to-install-and-manage-rabbitmq)
- [Failing at Microservices](https://rclayton.silvrback.com/failing-at-microservices)

<iframe width="467" height="260" src="https://www.youtube.com/embed/6yy4vyZFTsM" title="Maxim Sidorenko - &quot;Microservices architecture in Online Card Processing&quot;" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

<iframe width="467" height="260" src="https://www.youtube.com/embed/hcHKOqMGd-c" title="Микросервисы: первая кровь: видео доклада (Dev Labs 2016)" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

<iframe width="467" height="260" src="https://www.youtube.com/embed/tJHVM198suE" title="Виталий Баум — Практические примеры создания Микросервисов" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

<iframe width="467" height="260" src="https://www.youtube.com/embed/PFQnNFe27kU" title="Principles Of Microservices by Sam Newman" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## Related

- [Alter sequence](/ru/blog/tech/backend/DB/postgres/alter-sequence/)
- [Amazon S3 direct upload](/ru/blog/tech/backend/amazon-s3-direct-upload/)
- [CUDA](/ru/blog/tech/backend/gpu/cuda/)
