限速PHP功能

I have a php function which gets called when someone visits POST www.example.com/webhook. However, the external service which I cannot control, sometimes calls this url twice in rapid succession, messing with my logic since the webhook persists stuff in the database which takes a few ms to complete.

In other words, when the second request comes in (which can not be ignored), the first request is likely not completed yet however I need this to be completed in the order it came in.

So I've created a little hack in Laravel which should "throttle" the execution with 5 seconds in between. It seems to work most of the time. However an error in my code or some other oversight, does not make this solution work everytime.

function myWebhook() {
    // Check if cache value (defaults to 0) and compare with current time.
    while(Cache::get('g2a_webhook_timestamp', 0) + 5 > Carbon::now()->timestamp) {
        // Postpone execution.
        sleep(1);
    }
    // Create a cache value (file storage) that stores the current 
    Cache::put('g2a_webhook_timestamp', Carbon::now()->timestamp, 1);
    // Execute rest of code ...
}

Anyone perhaps got a watertight solution for this issue?

You have essentially designed your own simplified queue system which is the right approach but you can make use of the native Laravel queue to have a more robust solution to your problem.

  1. Define a job, e.g: ProcessWebhook
  2. When a POST request is received to /webhook queue the job

The laravel queue worker will process one job at a time[1] in the order they're received, ensuring that no matter how many requests are received, they'll be processed one by one and in order.

The implementation of this would look something like this:

  1. Create a new Job, e.g: php artisan make:job ProcessWebhook
  2. Move your webhook processing code into the handle method of the job, e.g:

    public function __construct($data)
    {
        $this->data = $data;
    }
    
    public function handle()
    {
        Model::where('x', 'y')->update([
            'field' => $this->data->newValue
        ]);
    }
    
  3. Modify your Webhook controller to dispatch a new job when a POST request is received, e.g:

    public function webhook(Request $request)
    {
        $data = $request->getContent();
        ProcessWebhook::dispatch($data);
    }
    
  4. Start your queue worker, php artisan queue:work, which will run in the background processing jobs in the order they arrive, one at a time.

That's it, a maintainable solution to processing webhooks in order, one-by-one. You can read the Queue documentation to find out more about the functionality available, including retrying failed jobs which can be very useful.

[1] Laravel will process one job at a time per worker. You can add more workers to improve queue throughput for other use cases but in this situation you'd just want to use one worker.