Як запустити команду в середньому 5 разів на секунду?


21

У мене є сценарій командного рядка, який виконує виклик API та оновлює базу даних з результатами.

У мене ліміт 5 дзвінків API в секунду у постачальника API. На виконання сценарію потрібно більше 0,2 секунди.

  • Якщо я запускаю команду послідовно, вона не буде працювати досить швидко, і я буду робити лише 1 або 2 виклики API в секунду.
  • Якщо я запускаю команду послідовно, але одночасно з декількох терміналів, я можу перевищити межу 5 викликів / секунду.

Якщо є спосіб упорядкувати потоки, щоб мій сценарій командного рядка виконувався майже рівно 5 разів на секунду?

Наприклад, щось, що працюватиме з 5 або 10 потоками, і жоден потік не виконує сценарій, якщо попередній потік виконав його менше ніж 200 мс тому.


Усі відповіді залежать від припущення, що ваш сценарій закінчиться в тому порядку, в якому він викликаний. Чи прийнятно це для вашого випадку використання, якщо вони закінчуються з ладу?
Коді Густафсон

@CodyGustafson Це цілком прийнятно, якщо вони закінчуються з ладу. Я не вірю, що принаймні у прийнятій відповіді є таке припущення?
Бенджамін

Що станеться, якщо ви перевищите кількість дзвінків в секунду? Якщо постачальник API замовчується, вам не потрібен жоден механізм в кінці ... чи не так?
Флоріс

@Floris Вони повернуть повідомлення про помилку, яке буде перекладено за винятком у SDK. По-перше, я сумніваюся, що постачальник API буде радий, якщо я генерую 50 дросельних повідомлень в секунду (ви повинні діяти відповідно до таких повідомлень), по-друге, я одночасно використовую API для інших цілей, тому я не хочуть досягти межі, яка насправді трохи вище.
Бенджамін

Відповіді:


25

У системі GNU, і якщо у вас є pv, ви можете зробити:

cmd='
   that command | to execute &&
     as shell code'

yes | pv -qL10 | xargs -n1 -P20 sh -c "$cmd" sh

-P20Це виконати не більше 20 $cmdодночасно.

-L10 обмежує швидкість до 10 байт в секунду, тому 5 рядків в секунду.

Якщо ваш $cmds стане двома повільними і спричинить досягнення межі 20, то xargsперестане читати, доки $cmdпринаймні один екземпляр не повернеться. pvпродовжуватиме писати на трубу з тією ж швидкістю, поки труба не заповниться (що для Linux з розміром труби за замовчуванням 64KiB займе майже 2 години).

У цей момент pvперестане писати. Але навіть тоді, коли xargsвідновить читання, pvспробує наздогнати та надіслати всі рядки, які він повинен був надіслати раніше, як можна швидше, щоб підтримувати загальну середню 5 рядків на секунду.

Це означає, що доки за допомогою 20 процесів можна виконати середню вимогу 5 запуску в секунду, вона це зробить. Однак при досягненні межі швидкість, з якою починаються нові процеси, визначається не таймером pv, а швидкістю, з якою повертаються попередні екземпляри cmd. Наприклад, якщо зараз працює 20 і пройшло протягом 10 секунд, і 10 з них вирішили закінчити всі одночасно, то 10 нових буде запущено відразу.

Приклад:

$ cmd='date +%T.%N; exec sleep 2'
$ yes | pv -qL10 | xargs -n1 -P20 sh -c "$cmd" sh
09:49:23.347013486
09:49:23.527446830
09:49:23.707591664
09:49:23.888182485
09:49:24.068257018
09:49:24.338570865
09:49:24.518963491
09:49:24.699206647
09:49:24.879722328
09:49:25.149988152
09:49:25.330095169

В середньому це буде 5 разів на секунду, навіть якщо затримка між двома пробіжками не завжди буде рівно 0,2 секунди.

З ksh93(або з, zshякщо ваша sleepкоманда підтримує дробові секунди):

typeset -F SECONDS=0
n=0; while true; do
  your-command &
  sleep "$((++n * 0.2 - SECONDS))"
done

Однак це не обмежує кількість одночасних your-commands.


Після трохи тестування pvкоманда, здається, саме те, що я шукала, не могла сподіватися на краще! Якраз у цьому рядку: yes | pv -qL10 | xargs -n1 -P20 sh -c "$cmd" shхіба не останнє shзайве?
Бенджамін

1
@Benjamin Ця секунда shдля $0вашого $cmdсценарію. Він також використовується в повідомленнях про помилки оболонкою. Без нього $0було б yз yes, так що ви отримаєте повідомлення про помилку , як y: cannot execute cmd... Ви також можете зробитиyes sh | pv -qL15 | xargs -n1 -P20 sh -c "$cmd"
Stéphane Chazelas

Я намагаюся розкласти всю справу на зрозумілі шматки, TBH! У вашому прикладі ви видалили це останнє sh; і в своїх тестах, коли я його знімаю, я не бачу різниці!
Бенджамін

@Benjamin. Це не критично. Це зробить інше, лише якщо ви все-таки $cmdвикористовуєте $0(навіщо це робити ?) Та повідомленнями про помилки. Спробуйте, наприклад, з cmd=/; без другого shви побачите щось подібне y: 1: y: /: Permission deniedзамістьsh: 1: sh: /: Permission denied
Стефан Шазелас

У мене виникає проблема з вашим рішенням: він працює чудово протягом декількох годин, потім в якийсь момент він просто виходить, без жодної помилки. Чи може це бути пов’язано з повним наповненням труб, які мають несподівані побічні ефекти?
Бенджамін

4

Простіше кажучи, якщо ваша команда триває менше 1 секунди, ви можете просто запустити 5 команд щосекунди. Очевидно, це дуже бурхливо.

while sleep 1
do    for i in {1..5}
      do mycmd &
      done
done

Якщо ваша команда може зайняти більше 1 секунди, і ви хочете розкласти команди, ви можете спробувати

while :
do    for i in {0..4}
      do  sleep .$((i*2))
          mycmd &
      done
      sleep 1 &
      wait
done

Крім того, ви можете мати 5 окремих циклів, які працюють незалежно, мінімум 1 секунду.

for i in {1..5}
do    while :
      do   sleep 1 &
           mycmd &
           wait
      done &
      sleep .2
done

Дуже приємне рішення також. Мені подобається те, що він простий і рівно 5 разів на секунду, але у нього є недолік запускати 5 команд одночасно (замість кожні 200 мс), і, можливо, не вистачає гарантії того, що не більше ніж n потоків працює одночасно !
Бенджамін

@Benjamin Я додав сон у 200 мс у циклі другої версії. Ця друга версія не може мати більше 5 смс за один раз, тому що ми тільки кожного запуску 5, тоді чекаємо їх усіх.
meuh

Проблема полягає в тому, що у вас не може бути більше 5 за секунду; якщо для всіх сценаріїв раптом потрібно більше 1 секунди для виконання, то ви далеко не досягнете межі API. Плюс, якщо ви дочекаєтесь їх усіх, один сценарій блокування заблокував би всі інші?
Бенджамін

@Benjamin Отже, ви можете запустити 5 незалежних циклів, кожна з яких має мінімум 1 секунду, див. 3-ю версію.
meuh

2

За допомогою програми C

Наприклад, ви можете використовувати нитку, яка спить протягом 0,2 секунди

#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>

pthread_t tid;

void* doSomeThing() {
    While(1){
         //execute my command
         sleep(0.2)
     } 
}

int main(void)
{
    int i = 0;
    int err;


    err = pthread_create(&(tid), NULL, &doSomeThing, NULL);
    if (err != 0)
        printf("\ncan't create thread :[%s]", strerror(err));
    else
        printf("\n Thread created successfully\n");



    return 0;
}

використовуйте його, щоб знати, як створити нитку: створіть нитку (це посилання, яке я використовував для вставки цього коду)


Дякую за вашу відповідь, хоча я в ідеалі шукав щось, що не передбачало б програмування на С, а лише використовуючи існуючі інструменти Unix!
Бенджамін

Так, відповідь stackoverflow на це може бути, наприклад, використання відра токена, поділеного між декількома робочими потоками, але запит на Unix.SE пропонує більше підходу "Power user", а не "програміста" :-) Все-таки ccє існуючий інструмент Unix, і це не багато коду!
Стів Джессоп

1

За допомогою node.js ви можете запустити один потік, який виконує скрипт bash кожні 200 мілісекунд, незалежно від того, скільки часу потрібно, щоб відповідь повернулася, оскільки відповідь надходить через функцію зворотного виклику .

var util = require('util')
exec = require('child_process').exec

setInterval(function(){
        child  = exec('fullpath to bash script',
                function (error, stdout, stderr) {
                console.log('stdout: ' + stdout);
                console.log('stderr: ' + stderr);
                if (error !== null) {
                        console.log('exec error: ' + error);
                }
        });
},200);

Цей javascript працює кожні 200 мілісекунд, і відповідь отримується через функцію зворотного дзвінка function (error, stdout, stderr).

Таким чином ви можете контролювати, щоб він ніколи не перевищував 5 дзвінків в секунду, незалежно від того, наскільки повільним або швидким є виконання команди, і скільки потрібно чекати відповіді.


Мені подобається таке рішення: воно запускає рівно 5 команд в секунду, через рівні проміжки часу. Єдиний недолік, який я можу бачити, - це те, що йому не вистачає гарантії, щоб мати не більше ніж n процесів за один раз! Якщо це те, що ви можете легко включити? Я не знайомий з node.js.
Бенджамін

0

Я pvдеякий час використовував базування Стефана Шазеласа , але виявив, що він вийшов випадковим чином (і мовчки) через деякий час, десь від декількох хвилин до декількох годин. - Редагувати: Причина полягала в тому, що мій скрипт PHP періодично помирав через перевищення максимального часу виконання, виходячи зі статусом 255.

Тому я вирішив написати простий інструмент командного рядка, який робить саме те, що мені потрібно.

Досягнення моєї первісної мети так само просто, як:

./parallel.phar 5 20 ./my-command-line-script

Він запускає майже рівно 5 команд в секунду, якщо тільки немає вже 20 одночасних процесів, і в цьому випадку він пропускає наступне виконання, поки слот не стане доступним.

Цей інструмент не чутливий до виходу статусу 255.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.