Як запустити команду в середньому 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.