Bagaimana cara menjalankan perintah dengan rata-rata 5 kali per detik?

21

Saya memiliki skrip baris perintah yang melakukan panggilan API dan memperbarui database dengan hasilnya.

Saya memiliki batas 5 panggilan API per detik dengan penyedia API. Script membutuhkan waktu lebih dari 0,2 detik untuk dieksekusi.

  • Jika saya menjalankan perintah secara berurutan, itu tidak akan berjalan cukup cepat dan saya hanya akan membuat 1 atau 2 panggilan API per detik.
  • Jika saya menjalankan perintah secara berurutan, tetapi secara bersamaan dari beberapa terminal, saya mungkin melebihi batas 5 panggilan / detik.

Jika ada cara untuk mengatur utas sehingga skrip baris perintah saya dijalankan hampir tepat 5 kali per detik?

Misalnya sesuatu yang akan berjalan dengan 5 atau 10 utas, dan tidak ada utas yang akan menjalankan skrip jika utas sebelumnya telah mengeksekusinya kurang dari 200 ms yang lalu.

Benjamin
sumber
Semua jawaban tergantung pada asumsi bahwa skrip Anda akan selesai sesuai urutan pemanggilannya. Apakah dapat diterima untuk kasus penggunaan Anda jika sudah rusak?
Cody Gustafson
@CodyGustafson Sangat bisa diterima jika mereka rusak. Saya tidak percaya ada asumsi seperti itu dalam jawaban yang diterima, paling tidak?
Benjamin
Apa yang terjadi jika Anda melebihi jumlah panggilan per detik? Jika penyedia API mencekik, Anda tidak memerlukan mekanisme apa pun pada akhirnya ... bukan?
Floris
@ Floris Mereka akan mengembalikan pesan kesalahan yang akan menerjemahkan dalam pengecualian di SDK. Pertama-tama saya ragu penyedia API akan senang jika saya menghasilkan 50 pesan throttle per detik (Anda seharusnya bertindak berdasarkan pesan tersebut sesuai), dan kedua saya menggunakan API untuk tujuan lain pada saat yang sama, jadi saya tidak ingin mencapai batas yang sebenarnya sedikit lebih tinggi.
Benjamin

Jawaban:

25

Pada sistem GNU dan jika sudah pv, Anda dapat melakukannya:

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

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

The -P20adalah untuk mengeksekusi paling 20 $cmdpada waktu yang sama.

-L10 membatasi laju hingga 10 byte per detik, jadi 5 baris per detik.

Jika Anda $cmdmenjadi dua lambat dan menyebabkan batas 20 tercapai, maka xargsakan berhenti membaca sampai satu $cmdcontoh setidaknya kembali. pvmasih akan terus menulis ke pipa pada kecepatan yang sama, sampai pipa menjadi penuh (yang pada Linux dengan ukuran pipa default 64KiB akan memakan waktu hampir 2 jam).

Pada saat itu, pvakan berhenti menulis. Tetapi meskipun demikian, ketika xargsresume membaca, pvakan mencoba dan mengejar dan mengirim semua baris yang seharusnya telah dikirim sebelumnya secepat mungkin sehingga mempertahankan rata-rata 5 baris per detik secara keseluruhan.

Apa itu artinya bahwa selama mungkin dengan 20 proses untuk memenuhi 5 run per detik pada persyaratan rata-rata, itu akan melakukannya. Namun ketika batas tercapai, laju di mana proses baru dimulai tidak akan didorong oleh timer pv tetapi oleh tingkat di mana contoh cmd sebelumnya kembali. Misalnya, jika 20 saat ini sedang berjalan dan telah selama 10 detik, dan 10 dari mereka memutuskan untuk menyelesaikan semuanya pada saat yang sama, maka 10 yang baru akan dimulai sekaligus.

Contoh:

$ 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

Rata-rata, itu akan menjadi 5 kali per detik bahkan jika penundaan antara dua berjalan tidak selalu tepat 0,2 detik.

Dengan ksh93(atau dengan zshjika sleepperintah Anda mendukung pecahan detik):

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

Itu tidak membatasi jumlah konkuren your-command.

Stéphane Chazelas
sumber
Setelah sedikit pengujian, pvperintahnya sepertinya persis seperti yang saya cari, tidak bisa berharap lebih baik! Hanya di baris ini:, yes | pv -qL10 | xargs -n1 -P20 sh -c "$cmd" shbukankah itu shberlebihan?
Benjamin
1
@ Benjamin Yang kedua shadalah untuk skrip $0Anda $cmd. Ini juga digunakan dalam pesan kesalahan oleh shell. Tanpa itu, $0akan ydari yes, jadi Anda akan mendapatkan pesan kesalahan seperti y: cannot execute cmd... Anda juga bisa melakukannyayes sh | pv -qL15 | xargs -n1 -P20 sh -c "$cmd"
Stéphane Chazelas
Saya berjuang untuk menguraikan semuanya menjadi bagian yang dapat dimengerti, TBH! Dalam contoh Anda, Anda telah menghapus yang terakhir ini sh; dan dalam pengujian saya, ketika saya menghapusnya, saya tidak dapat melihat perbedaan!
Benjamin
@Benjamin. Itu tidak kritis. Itu hanya akan membuat perbedaan jika Anda $cmdmemang menggunakan $0(mengapa?) Dan untuk pesan kesalahan. Coba misalnya dengan cmd=/; tanpa yang kedua sh, Anda akan melihat sesuatu seperti y: 1: y: /: Permission deniedbukannyash: 1: sh: /: Permission denied
Stéphane Chazelas
Saya mengalami masalah dengan solusi Anda: itu berfungsi dengan baik selama beberapa jam, kemudian pada beberapa titik itu keluar, tanpa kesalahan. Mungkinkah ini terkait dengan pipa menjadi penuh, memiliki beberapa efek samping yang tidak terduga?
Benjamin
4

Secara sederhana, jika perintah Anda bertahan kurang dari 1 detik, Anda bisa memulai 5 perintah setiap detik. Jelas, ini sangat meledak.

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

Jika perintah Anda mungkin membutuhkan waktu lebih dari 1 detik dan Anda ingin menyebarkan perintah yang dapat Anda coba

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

Atau, Anda dapat memiliki 5 loop terpisah yang berjalan secara independen, dengan minimum 1 detik.

for i in {1..5}
do    while :
      do   sleep 1 &
           mycmd &
           wait
      done &
      sleep .2
done
meuh
sumber
Solusi yang cukup bagus juga. Saya suka fakta bahwa itu sederhana dan tepat 5 kali per detik, tetapi memiliki kelemahan memulai 5 perintah pada saat yang sama (bukan setiap 200 ms), dan mungkin tidak memiliki perlindungan paling banyak n menjalankan thread pada satu waktu !
Benjamin
@Benjamin saya menambahkan tidur 200 ms di loop dari versi kedua. Versi kedua ini tidak dapat menjalankan lebih dari 5 cmds sekaligus karena kami hanya memulai setiap 5, lalu menunggu semuanya.
meuh
Masalahnya adalah, Anda tidak dapat memulai lebih dari 5 per detik; jika semua skrip tiba-tiba membutuhkan lebih dari 1 untuk dieksekusi, maka Anda jauh dari mencapai batas API. Plus, jika Anda menunggu semuanya, satu skrip pemblokiran akan memblokir yang lainnya?
Benjamin
@Benjamin Agar Anda dapat menjalankan 5 loop independen, masing-masing dengan tidur minimal 1 detik, lihat versi ke-3.
meuh
2

Dengan program C,

Misalnya Anda dapat menggunakan utas yang tidur selama 0,2 detik sebentar

#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;
}

gunakan untuk mengetahui cara membuat utas: buat utas (ini adalah tautan yang saya gunakan untuk menempelkan kode ini)

Couim
sumber
Terima kasih atas jawaban Anda, walaupun saya idealnya mencari sesuatu yang tidak akan melibatkan pemrograman C, tetapi hanya menggunakan alat Unix yang ada!
Benjamin
Ya, jawaban stackoverflow untuk ini mungkin misalnya menggunakan token bucket yang dibagikan di antara banyak utas pekerja, tetapi menanyakan Unix.SE menyarankan lebih dari "Power user" daripada pendekatan "programmer" yang dicari :-) Namun, ccmasih alat Unix yang ada, dan ini bukan banyak kode!
Steve Jessop
1

Dengan menggunakan node.js Anda dapat memulai utas tunggal yang mengeksekusi skrip bash setiap 200 milidetik tidak peduli berapa lama respons yang diperlukan untuk kembali karena respons datang melalui fungsi callback .

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 ini berjalan setiap 200 milidetik dan responsnya didapat melalui fungsi panggilan balik function (error, stdout, stderr).

Dengan cara ini Anda dapat mengontrol bahwa itu tidak pernah melebihi 5 panggilan per detik terlepas dari seberapa lambat atau cepat eksekusi perintah atau berapa banyak harus menunggu jawaban.

jcbermu
sumber
Saya suka solusi ini: dimulai tepat 5 perintah per detik, secara berkala. Satu-satunya kelemahan yang bisa saya lihat adalah bahwa ia tidak memiliki perlindungan untuk memiliki paling banyak dan proses berjalan pada satu waktu! Jika ini sesuatu yang bisa Anda sertakan dengan mudah? Saya tidak terbiasa dengan node.js.
Benjamin
0

Saya telah menggunakan pvsolusi berbasis Stéphane Chazelas untuk beberapa waktu, tetapi menemukan bahwa itu keluar secara acak (dan diam-diam) setelah beberapa waktu, di mana saja dari beberapa menit hingga beberapa jam. - Sunting: Alasannya adalah skrip PHP saya kadang-kadang mati karena waktu eksekusi maksimal terlampaui, keluar dengan status 255.

Jadi saya memutuskan untuk menulis alat baris perintah sederhana yang melakukan apa yang saya butuhkan.

Mencapai tujuan awal saya semudah:

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

Ini memulai hampir persis 5 perintah per detik, kecuali sudah ada 20 proses bersamaan, dalam hal ini ia melewatkan eksekusi berikutnya hingga slot tersedia.

Alat ini tidak sensitif terhadap status 255.

Benjamin
sumber