Sylius订单更新并发问题

问题描述 投票:0回答:1

在高负载下(进行负载测试以测试结帐流程),看来我们的自定义订单控制器在付款时会引发异常。这大约发生在9个订单中的1个:

RaceConditionException: "Operated entity was previously modified."

我们已按如下方式覆盖了sylius_shop_checkout_complete路线

sylius_shop_checkout_complete:
path: /complete
methods: [GET, PUT]
defaults:
    _controller: sylius.controller.order:completeSectionAction
    _sylius:
        event: complete
        flash: false
        template: "@SyliusShop/Checkout/complete.html.twig"
        repository:
            method: findCartForSummary
            arguments:
                - "expr:service('sylius.context.cart').getCart().getId()"
        state_machine:
            graph: sylius_order_checkout
            transition: complete
        redirect:
            route: sylius_shop_order_pay
            parameters:
                tokenValue: resource.tokenValue
        form:
            type: Sylius\Bundle\CoreBundle\Form\Type\Checkout\CompleteType
            options:
                validation_groups: 'sylius_checkout_complete'

还有我们自己的支票付款状态路线,PaymentGateway会在付款后重定向到该路线,如下所示:

sylius_shop_checkout_status:
path: /check-status
methods: [GET, PUT]
defaults:
    _controller: sylius.controller.order:checkStatusAction
    _sylius:
        event: complete
        flash: false
        template: "@SyliusShop/Checkout/complete.html.twig"
        repository:
            method: findCartForSummary
            arguments:
                - "expr:service('sylius.context.cart').getCart().getId()"
        state_machine:
            graph: sylius_order_checkout
            transition: complete
        redirect:
            route: sylius_shop_order_pay
            parameters:
                tokenValue: resource.tokenValue
        form:
            type: Sylius\Bundle\CoreBundle\Form\Type\Checkout\CompleteType
            options:
                validation_groups: 'sylius_checkout_complete'

在checkStatusAction函数中,我们得到以下异常

request.CRITICAL: Uncaught PHP Exception Sylius\Component\Resource\Exception\RaceConditionException: "Operated entity was previously modified." at /srv/sylius/vendor/sylius/sylius/src/Sylius/Bundle/CoreBundle/Doctrine/ORM/Handler/ResourceUpdateHandler.php line 46 {"exception":"[object] (Sylius\\Component\\Resource\\Exception\\RaceConditionException(code: 0): Operated entity was previously modified. at /srv/sylius/vendor/sylius/sylius/src/Sylius/Bundle/CoreBundle/Doctrine/ORM/Handler/ResourceUpdateHandler.php:46, Doctrine\\ORM\\OptimisticLockException(code: 0): The optimistic lock on an entity failed. at /srv/sylius/vendor/doctrine/orm/lib/Doctrine/ORM/OptimisticLockException.php:64)"} []

这是我们的checkStatus函数:

public function checkStatusAction(Request $request, LoggerInterface $logger): Response
{
    $configuration = $this->requestConfigurationFactory->create($this->metadata, $request);

    $this->isGrantedOr403($configuration, ResourceActions::UPDATE);
    $resource = $this->findOr404($configuration);

    $form = $this->resourceFormFactory->create($configuration, $resource);

    $payment = $resource->getLastPayment();
    $paymentMethod = $payment->getMethod();
    $paymentConfig = $paymentMethod->getGatewayConfig()->getConfig();
    $paymentFactoryName = $paymentMethod->getGatewayConfig()->getFactoryName();

    $response = $this->checkPaymentStatus($request->get('resourcePath'), $resource, $paymentConfig);
    $decodedResponse = json_decode($response, true);

    if(substr($decodedResponse['result']['code'], 0, 4 ) === '000.') {

        // If need to run scheduler
        $tcResponse = null;
        if ($paymentFactoryName === 'tp_payment_subscription' || $paymentFactoryName === 'tp_bank_transfer') {

            $amount = $paymentFactoryName === 'tp_bank_transfer' ?  0.00 : number_format($resource->getTotal() / 100, 2, '.', '');
            $tcResponse = $this->createSubscription($resource, $response, $amount, $paymentConfig);
        }

        // Store payment details
        $paymentDetails = [
            'PayOn' => $response,
            'TotalControl' => $tcResponse
        ];
        $payment->setDetails($paymentDetails);
        $payment->setState(Payment::STATE_COMPLETED);

        try {
            $this->resourceUpdateHandler->handle($resource, $configuration, $this->manager);
        } catch (UpdateHandlingException $exception) {

            if (!$configuration->isHtmlRequest()) {
                return $this->viewHandler->handle(
                    $configuration,
                    View::create($form, $exception->getApiResponseCode())
                );
            }

            $this->flashHelper->addErrorFlash($configuration, $exception->getFlash());

            return $this->redirectHandler->redirectToReferer($configuration);
        }

        // if ($configuration->isHtmlRequest()) {
        //     $this->flashHelper->addSuccessFlash($configuration, ResourceActions::UPDATE, $resource);
        // }

        $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $configuration, $resource);

        return $this->redirectHandler->redirectToResource($configuration, $resource);
    }

    $this->flashHelper->addErrorFlash($configuration, 'something_went_wrong_error');

    $flashes = $request->getSession()->getBag('flashes');
    $flashes->add('error', [
        'message' => 'sylius.totalp.payment_failure',
        'parameters' => ['hello' => 'hi']
    ]);

    return $this->redirectHandler->redirectToReferer($configuration, $resource);
}

该错误特别发生在以下行:

$this->resourceUpdateHandler->handle($resource, $configuration, $this->manager);

仅当网站同时执行许多订单时才会发生此问题,有人知道如何解决此问题吗?

symfony sylius
1个回答
0
投票

我已经设法解决了这个问题,如果有人遇到此问题,Sylius订单号生成器似乎并不是为繁重的并发负载而构建的。

特别是SequentialOrderNumberGenerator.php。

我们更改了以下内容(为了清楚起见,我已保留在注释的代码中)

/**
 * {@inheritdoc}
 */
public function generate(OrderInterface $order): string
{
    //$sequence = $this->getSequence();

    //$this->sequenceManager->lock($sequence, LockMode::OPTIMISTIC, $sequence->getVersion());

    $number = $this->generateNumber($order);
    //$sequence->incrementIndex();

    return $number;
}

private function generateNumber($order): string
{
    //$number = $this->startNumber + $index;

    return str_pad((string) $order->getId(), $this->numberLength, '0', \STR_PAD_LEFT);
}
© www.soinside.com 2019 - 2024. All rights reserved.