From 23d9e22082d5ecc90649210c342aabdd6ad57438 Mon Sep 17 00:00:00 2001 From: kuro Date: Wed, 24 Sep 2025 18:42:37 +0300 Subject: [PATCH 1/4] Test 1: version 0/0/1 --- .../ru/quipy/payments/logic/PaymentService.kt | 14 +++++++++++--- src/main/resources/application.properties | 3 ++- test-local-run.http | 4 ++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentService.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentService.kt index 255db77dd..3bf6cdbb5 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentService.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentService.kt @@ -32,13 +32,21 @@ interface PaymentExternalSystemAdapter { data class PaymentAccountProperties( val serviceName: String, val accountName: String, - val parallelRequests: Int, - val rateLimitPerSec: Int, - val price: Int, + val parallelRequests: Int, // 30 + val rateLimitPerSec: Int, // 10 + val price: Int, // 30 val averageProcessingTime: Duration = Duration.ofSeconds(11), val enabled: Boolean, ) +/* +#- parallelRequests=5 - означает, что провайдер разрешает вам в любой момент времени иметь не более 5 одновременных запросов от вас к нему для этого аккаунта +#- rateLimitPerSec=5 - означает, что провайдер разрешает вам каждую секунду отправлять к нему не более 5 запросов по этому аккаунту +#- price=30 - означает, что провайдер оплаты будет взымать за каждый успешный или неуспешный вызов 30 денежных единиц с вашего магазина. +#- averageProcessingTime=PTO.05S - провайдер оплаты сообщает вам, что в среднем время обработки одного запроса по этому аккаунту будет составлять около 50ms. + + */ + /** * Describes response from external service. */ diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 33d51a58b..661d0c947 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -26,5 +26,6 @@ management.endpoints.web.exposure.include=info,health,prometheus,metrics payment.service-name=${PAYMENT_SERVICE_NAME} payment.token=${PAYMENT_TOKEN} -payment.accounts=${PAYMENT_ACCOUNTS:acc-12,acc-20} +#payment.accounts=${PAYMENT_ACCOUNTS:acc-12,acc-20} +payment.accounts=${PAYMENT_ACCOUNTS:acc-3} payment.hostPort=${PAYMENT_HOST:localhost}:${PAYMENT_PORT:1234} \ No newline at end of file diff --git a/test-local-run.http b/test-local-run.http index 7be0e4f73..644edf067 100644 --- a/test-local-run.http +++ b/test-local-run.http @@ -5,8 +5,8 @@ Content-Type: application/json { "serviceName": "{{serviceName}}", "token": "{{token}}", - "ratePerSecond": 1, - "testCount": 100, + "ratePerSecond": 11, + "testCount": 1200, "processingTimeMillis": 80000 } From 64be8f4612887aed438626721cfdd7d5e8619b67 Mon Sep 17 00:00:00 2001 From: kuro Date: Wed, 24 Sep 2025 19:14:38 +0300 Subject: [PATCH 2/4] Test 1: version 0/0/2 --- .../ru/quipy/apigateway/APIController.kt | 3 +- .../ru/quipy/payments/logic/OrderPayer.kt | 50 +++++++++++++------ 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/ru/quipy/apigateway/APIController.kt b/src/main/kotlin/ru/quipy/apigateway/APIController.kt index 6f23fa18d..2bdf631a6 100644 --- a/src/main/kotlin/ru/quipy/apigateway/APIController.kt +++ b/src/main/kotlin/ru/quipy/apigateway/APIController.kt @@ -55,14 +55,13 @@ class APIController { } @PostMapping("/orders/{orderId}/payment") - fun payOrder(@PathVariable orderId: UUID, @RequestParam deadline: Long): PaymentSubmissionDto { + suspend fun payOrder(@PathVariable orderId: UUID, @RequestParam deadline: Long): PaymentSubmissionDto { val paymentId = UUID.randomUUID() val order = orderRepository.findById(orderId)?.let { orderRepository.save(it.copy(status = OrderStatus.PAYMENT_IN_PROGRESS)) it } ?: throw IllegalArgumentException("No such order $orderId") - val createdAt = orderPayer.processPayment(orderId, order.price, paymentId, deadline) return PaymentSubmissionDto(createdAt, paymentId) } diff --git a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt index a5909b85b..4ccb7b018 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt @@ -1,13 +1,17 @@ package ru.quipy.payments.logic +import kotlinx.coroutines.delay +import kotlinx.coroutines.sync.Semaphore import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service import ru.quipy.common.utils.CallerBlockingRejectedExecutionHandler import ru.quipy.common.utils.NamedThreadFactory +import ru.quipy.common.utils.SlidingWindowRateLimiter import ru.quipy.core.EventSourcingService import ru.quipy.payments.api.PaymentAggregate +import java.time.Duration import java.util.* import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.ThreadPoolExecutor @@ -27,29 +31,47 @@ class OrderPayer { private lateinit var paymentService: PaymentService private val paymentExecutor = ThreadPoolExecutor( - 16, - 16, + 30, + 30, 0L, TimeUnit.MILLISECONDS, - LinkedBlockingQueue(8_000), + LinkedBlockingQueue(150), NamedThreadFactory("payment-submission-executor"), CallerBlockingRejectedExecutionHandler() ) - fun processPayment(orderId: UUID, amount: Int, paymentId: UUID, deadline: Long): Long { + private val rateLimit = SlidingWindowRateLimiter( + rate = 10, + window = Duration.ofSeconds(1), + ) + + private val parallelLimiter = Semaphore(30) + + suspend fun processPayment(orderId: UUID, amount: Int, paymentId: UUID, deadline: Long): Long { val createdAt = System.currentTimeMillis() - paymentExecutor.submit { - val createdEvent = paymentESService.create { - it.create( - paymentId, - orderId, - amount - ) + + parallelLimiter.acquire() + + return try { + while(!rateLimit.tick()) { + delay(10) } - logger.trace("Payment ${createdEvent.paymentId} for order $orderId created.") - paymentService.submitPaymentRequest(paymentId, amount, createdAt, deadline) + paymentExecutor.submit { + try { + val createdEvent = paymentESService.create { + it.create(paymentId, orderId, amount) + } + logger.trace("Payment ${createdEvent.paymentId} for order $orderId created.") + paymentService.submitPaymentRequest(paymentId, amount, createdAt, deadline) + } finally { + parallelLimiter.release() + } + } + createdAt + } catch (e: Exception) { + parallelLimiter.release() + throw e } - return createdAt } } \ No newline at end of file From aceea3a41bda9a990ec5ee0c1f3f340ff7450942 Mon Sep 17 00:00:00 2001 From: kuro Date: Thu, 25 Sep 2025 16:42:36 +0300 Subject: [PATCH 3/4] Test 2: version 0/0/1 --- .../ru/quipy/payments/logic/OrderPayer.kt | 46 ++++++++++++------- .../logic/PaymentExternalServiceImpl.kt | 4 ++ .../ru/quipy/payments/logic/PaymentService.kt | 2 + src/main/resources/application.properties | 5 +- test-local-run.http | 28 +++++++++-- 5 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt index 4ccb7b018..9c7b43d57 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt @@ -30,22 +30,34 @@ class OrderPayer { @Autowired private lateinit var paymentService: PaymentService - private val paymentExecutor = ThreadPoolExecutor( - 30, - 30, - 0L, - TimeUnit.MILLISECONDS, - LinkedBlockingQueue(150), - NamedThreadFactory("payment-submission-executor"), - CallerBlockingRejectedExecutionHandler() - ) + @Autowired + private lateinit var accountAdapters: List + + private val accountProperties: PaymentAccountProperties by lazy { + accountAdapters.firstOrNull()?.getAccountProperties() + ?: throw IllegalStateException("No payment accounts configured") + } - private val rateLimit = SlidingWindowRateLimiter( - rate = 10, - window = Duration.ofSeconds(1), - ) + private val paymentExecutor: ThreadPoolExecutor by lazy { + ThreadPoolExecutor( + accountProperties.parallelRequests, + accountProperties.parallelRequests, + 0L, + TimeUnit.MILLISECONDS, + LinkedBlockingQueue(accountProperties.parallelRequests * 10), + NamedThreadFactory("payment-submission-executor"), + CallerBlockingRejectedExecutionHandler() + ) + } + + private val rateLimit: SlidingWindowRateLimiter by lazy { + SlidingWindowRateLimiter( + rate = accountProperties.rateLimitPerSec.toLong(), + window = Duration.ofSeconds(1), + ) + } - private val parallelLimiter = Semaphore(30) + private val parallelLimiter = Semaphore(5) suspend fun processPayment(orderId: UUID, amount: Int, paymentId: UUID, deadline: Long): Long { val createdAt = System.currentTimeMillis() @@ -53,8 +65,8 @@ class OrderPayer { parallelLimiter.acquire() return try { - while(!rateLimit.tick()) { - delay(10) + while (!rateLimit.tick()) { + delay(100) } paymentExecutor.submit { @@ -62,7 +74,7 @@ class OrderPayer { val createdEvent = paymentESService.create { it.create(paymentId, orderId, amount) } - logger.trace("Payment ${createdEvent.paymentId} for order $orderId created.") + logger.trace("Payment {} for order {} created.", createdEvent.paymentId, orderId) paymentService.submitPaymentRequest(paymentId, amount, createdAt, deadline) } finally { parallelLimiter.release() diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt index 5cb12106a..905b3088b 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt @@ -36,6 +36,10 @@ class PaymentExternalSystemAdapterImpl( private val client = OkHttpClient.Builder().build() + override fun getAccountProperties(): PaymentAccountProperties { + return properties + } + override fun performPaymentAsync(paymentId: UUID, amount: Int, paymentStartedAt: Long, deadline: Long) { logger.warn("[$accountName] Submitting payment request for payment $paymentId") diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentService.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentService.kt index 3bf6cdbb5..2907c7706 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentService.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentService.kt @@ -17,6 +17,8 @@ interface PaymentService { */ interface PaymentExternalSystemAdapter { + fun getAccountProperties(): PaymentAccountProperties + fun performPaymentAsync(paymentId: UUID, amount: Int, paymentStartedAt: Long, deadline: Long) fun name(): String diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 661d0c947..4e3ebfbc4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -26,6 +26,7 @@ management.endpoints.web.exposure.include=info,health,prometheus,metrics payment.service-name=${PAYMENT_SERVICE_NAME} payment.token=${PAYMENT_TOKEN} -#payment.accounts=${PAYMENT_ACCOUNTS:acc-12,acc-20} -payment.accounts=${PAYMENT_ACCOUNTS:acc-3} +# payment.accounts=${PAYMENT_ACCOUNTS:acc-12,acc-20} +# payment.accounts=${PAYMENT_ACCOUNTS:acc-3} +payment.accounts=${PAYMENT_ACCOUNTS:acc-5} payment.hostPort=${PAYMENT_HOST:localhost}:${PAYMENT_PORT:1234} \ No newline at end of file diff --git a/test-local-run.http b/test-local-run.http index 644edf067..cabd61099 100644 --- a/test-local-run.http +++ b/test-local-run.http @@ -5,11 +5,31 @@ Content-Type: application/json { "serviceName": "{{serviceName}}", "token": "{{token}}", - "ratePerSecond": 11, - "testCount": 1200, - "processingTimeMillis": 80000 + "ratePerSecond": 2, + "testCount": 100, + "processingTimeMillis": 60000 } ### Stop running test to save time and resources # @timeout 120 -POST http://localhost:1234/test/stop/{{serviceName}} \ No newline at end of file +POST http://localhost:1234/test/stop/{{serviceName}} + +# PaymentAccountProperties( +# serviceName=cas-m3404-05, +# accountName=acc-3, +# parallelRequests=30, +# rateLimitPerSec=10, +# price=30, +# averageProcessingTime=PT1S, +# enabled=true +# ) + +# PaymentAccountProperties( +# serviceName=cas-m3404-05, +# accountName=acc-5, +# parallelRequests=5, +# rateLimitPerSec=3, +# price=30, +# averageProcessingTime=PT4.9S, +# enabled=true +#) From 52997a2ca8ef71176acd903851b0f1a484f55200 Mon Sep 17 00:00:00 2001 From: kuro Date: Thu, 25 Sep 2025 18:05:34 +0300 Subject: [PATCH 4/4] Test 2: version 0/0/2 --- src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt index 9c7b43d57..efac11f4d 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt @@ -1,7 +1,6 @@ package ru.quipy.payments.logic import kotlinx.coroutines.delay -import kotlinx.coroutines.sync.Semaphore import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired @@ -14,6 +13,7 @@ import ru.quipy.payments.api.PaymentAggregate import java.time.Duration import java.util.* import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.Semaphore import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit