【问题标题】:Sylius order update concurrency issueSylius 订单更新并发问题
【发布时间】:2020-05-31 12:11:48
【问题描述】:

在高负载下(进行负载测试以测试结帐过程)时,我们的自定义订单控制器似乎在付款时抛出异常。这发生在大约九分之一的订单上:

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'

还有我们自己的check payment status route,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】:

    我已经设法解决了这个问题,如果有人遇到这个问题,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);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-06-05
      • 1970-01-01
      • 2012-11-28
      • 2015-07-02
      • 2021-11-15
      • 2016-04-27
      • 1970-01-01
      相关资源
      最近更新 更多