Устойчивые микросервисы с помощью Quarkus и Kotlin — все нити вместе

Вторая часть этой серии была посвящена теории — мы подробно рассмотрели некоторые полезные паттерны проектирования устойчивости. Теперь пришло время действовать 💪. Давайте, наконец, соберем все нити воедино и посмотрим, как мы можем реализовать это с помощью Quarkus и Kotlin.

Вы все еще помните шокирующий сценарий всего этого? Мы обречены, потому что живем в районе с ядовитыми змеями и должны полагаться на собственную службу, чтобы вовремя предупредить об опасных животных.

Позвольте мне представить вам сценарий выполнения тревожного сигнала обнаружения змей, в котором мы собираемся справиться с этой проблемой устойчивым способом.

Представьте себе небольшие ящики с детекторами змей, которые могут магическим образом обнаруживать проходящих мимо змей. Иногда детекторы имеют ложные срабатывания с милыми и безобидными ящерицами. Чтобы справиться с этими ложными срабатываниями, детекторы змей могут также отправлять фотографию обнаруженного животного через конечную точку REST, чтобы ее можно было оценить, например, с помощью машинного обучения. Когда мы убедимся, что это змея, мы хотим отправить push-уведомление на все мобильные устройства в этом районе. К сожалению, для рассылки push-уведомлений нам придется использовать внешний сервис, а это довольно дорого. У нас есть два варианта: один сервис, который очень дорогой и на 100% надежный, но немного медленный, и другой сервис, который намного дешевле и быстрее, но совсем не такой надежный.

План состоит в том, чтобы построить надежный обходной путь от а) обнаружения «змеи или ящерицы» с помощью коробки до б) отправки предупреждающего push-уведомления всем в этом районе. В идеале push-уведомление рассылается быстро, и мы не тратим много денег на отправку этих push-уведомлений.

Давайте попробуем: блок детектора змей может сделать POST-запрос к SnakeAlarmEndpoint. Это может запустить SnakeAlarmRepository для отправки события в канал обмена сообщениями AMQP с именем snake-alarm-out. Затем мы подписываемся на эти события в другом канале snake-alarm-in, чтобы иметь возможность независимого масштабирования. На рисунке ниже вы видите наш SnakeAlarmProcessor, который может обрабатывать новые SnakeAlarms, поступающие из AMQP-канала snake-alarms-in.

Сам SnakeAlarmProcessor вызывает LizardCheck, чтобы убедиться, что к нам пришла именно змея, а не ящерица.
Время выполнения LizardCheck составляет от 0,2 с до 1,06 с. Измерив время выполнения, мы можем настроить вызов SnakeAlarmProcessor на LizardCheck с тайм-аутом в 1 с и максимум 3 повторными попытками, чтобы большинство вызовов были успешными за приемлемое время.

Наконец, мы вызываем службу push-уведомлений. Первичная служба по выбору будет опробована первой, но она терпит неудачу с вероятностью 50% и имеет среднее время ответа 1с. Мы даем ей максимум 2 повторных попытки. После этого мы вызываем вторичный сервис, который имеет вероятность ~100% и время отклика 3 с.

Образец вывода результата выполнения
Здесь представлен вывод конечного результата. Попробуйте увидеть, где у нас было ложное срабатывание, а где нет. Где мы использовали дорогой сервис push-уведомлений? Все это написано в логах.

2022-07-24 16:03:53,936 INFO  [de.fay.sna.SnakeAlarmProcessor] (vert.x-eventloop-thread-0) Received an alarm for snake alarm fulfillment: [SnakeItems(id=1, imageASCIIArtString=e--==--==--)], False positive - Successfully verified a lizard
2022-07-24 16:03:53,994 DEBUG [io.sma.rea.mes.amqp] (vert.x-eventloop-thread-2) SRMSG16211: Sending AMQP message to address `snake-alarms`
2022-07-24 16:03:54,000 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Disposition{role=RECEIVER, first=1, last=1, settled=true, state=Accepted{}, batchable=false}
2022-07-24 16:03:54,001 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Transfer{handle=0, deliveryId=1, deliveryTag=x00, messageFormat=0, settled=false, more=false, rcvSettleMode=null, state=null, resume=false, aborted=false, batchable=false}[x00Ssxd0x00x00x00)x00x00x00x07@@xa1x0csnake-alarms@@@xa3x10application/jsonx00Suxa0x9c{"snakes":[{"id":"1","imageASCIIArtString":"e--==--==--"}],"snakeVerification":"Snake could also be a lizard :-)","estimatedPushNotificationTimestamp":null}]
2022-07-24 16:03:54,460 DEBUG [io.sma.rea.mes.amqp] (vert.x-eventloop-thread-2) SRMSG16211: Sending AMQP message to address `snake-alarms`
2022-07-24 16:03:54,463 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Transfer{handle=0, deliveryId=2, deliveryTag=x01, messageFormat=0, settled=false, more=false, rcvSettleMode=null, state=null, resume=false, aborted=false, batchable=false}[x00Ssxd0x00x00x00)x00x00x00x07@@xa1x0csnake-alarms@@@xa3x10application/jsonx00Suxa0x9c{"snakes":[{"id":"1","imageASCIIArtString":"e--==--==--"}],"snakeVerification":"Snake could also be a lizard :-)","estimatedPushNotificationTimestamp":null}]
2022-07-24 16:03:54,464 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Disposition{role=RECEIVER, first=2, last=2, settled=true, state=Accepted{}, batchable=false}
2022-07-24 16:03:54,579 INFO  [de.fay.sna.SnakeAlarmProcessor] (vert.x-eventloop-thread-0) Received an alarm for snake alarm fulfillment: [SnakeItems(id=1, imageASCIIArtString=e--==--==--)], False positive - Successfully verified a lizard
2022-07-24 16:03:54,937 INFO  [de.fay.sna.SnakeAlarmProcessor] (vert.x-eventloop-thread-0) Received an alarm for snake alarm fulfillment: [SnakeItems(id=1, imageASCIIArtString=e--==--==--)], False positive - Successfully verified a lizard
2022-07-24 16:03:56,180 DEBUG [io.sma.rea.mes.amqp] (vert.x-eventloop-thread-2) SRMSG16211: Sending AMQP message to address `snake-alarms`
2022-07-24 16:03:56,185 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Disposition{role=RECEIVER, first=3, last=3, settled=true, state=Accepted{}, batchable=false}
2022-07-24 16:03:56,186 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Transfer{handle=0, deliveryId=3, deliveryTag=x00, messageFormat=0, settled=false, more=false, rcvSettleMode=null, state=null, resume=false, aborted=false, batchable=false}[x00Ssxd0x00x00x00)x00x00x00x07@@xa1x0csnake-alarms@@@xa3x10application/jsonx00Suxa0x9c{"snakes":[{"id":"1","imageASCIIArtString":"e--==--==--"}],"snakeVerification":"Snake could also be a lizard :-)","estimatedPushNotificationTimestamp":null}]
2022-07-24 16:03:56,618 DEBUG [io.sma.rea.mes.amqp] (vert.x-eventloop-thread-2) SRMSG16211: Sending AMQP message to address `snake-alarms`
2022-07-24 16:03:56,622 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Transfer{handle=0, deliveryId=4, deliveryTag=x01, messageFormat=0, settled=false, more=false, rcvSettleMode=null, state=null, resume=false, aborted=false, batchable=false}[x00Ssxd0x00x00x00)x00x00x00x07@@xa1x0csnake-alarms@@@xa3x10application/jsonx00Suxa0x9c{"snakes":[{"id":"1","imageASCIIArtString":"e--==--==--"}],"snakeVerification":"Snake could also be a lizard :-)","estimatedPushNotificationTimestamp":null}]
2022-07-24 16:03:56,625 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Disposition{role=RECEIVER, first=4, last=4, settled=true, state=Accepted{}, batchable=false}
2022-07-24 16:03:57,105 DEBUG [io.sma.rea.mes.amqp] (vert.x-eventloop-thread-2) SRMSG16211: Sending AMQP message to address `snake-alarms`
2022-07-24 16:03:57,110 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Disposition{role=RECEIVER, first=5, last=5, settled=true, state=Accepted{}, batchable=false}
2022-07-24 16:03:57,111 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Transfer{handle=0, deliveryId=5, deliveryTag=x02, messageFormat=0, settled=false, more=false, rcvSettleMode=null, state=null, resume=false, aborted=false, batchable=false}[x00Ssxd0x00x00x00)x00x00x00x07@@xa1x0csnake-alarms@@@xa3x10application/jsonx00Suxa0x9c{"snakes":[{"id":"1","imageASCIIArtString":"e--==--==--"}],"snakeVerification":"Snake could also be a lizard :-)","estimatedPushNotificationTimestamp":null}]
2022-07-24 16:03:57,219 INFO  [de.fay.sna.SnakeAlarmProcessor] (vert.x-eventloop-thread-0) Received an alarm for snake alarm fulfillment: [SnakeItems(id=1, imageASCIIArtString=e--==--==--)], False positive - Successfully verified a lizard
2022-07-24 16:03:57,534 DEBUG [io.sma.rea.mes.amqp] (vert.x-eventloop-thread-2) SRMSG16211: Sending AMQP message to address `snake-alarms`
2022-07-24 16:03:57,538 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Disposition{role=RECEIVER, first=6, last=6, settled=true, state=Accepted{}, batchable=false}
2022-07-24 16:03:57,539 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Transfer{handle=0, deliveryId=6, deliveryTag=x00, messageFormat=0, settled=false, more=false, rcvSettleMode=null, state=null, resume=false, aborted=false, batchable=false}[x00Ssxd0x00x00x00)x00x00x00x07@@xa1x0csnake-alarms@@@xa3x10application/jsonx00Suxa0x9c{"snakes":[{"id":"1","imageASCIIArtString":"e--==--==--"}],"snakeVerification":"Snake could also be a lizard :-)","estimatedPushNotificationTimestamp":null}]
2022-07-24 16:03:58,039 INFO  [de.fay.sna.SnakeAlarmProcessor] (vert.x-eventloop-thread-0) Received an alarm for snake alarm fulfillment: [SnakeItems(id=1, imageASCIIArtString=e--==--==--)], False positive - Successfully verified a lizard
2022-07-24 16:03:58,041 INFO  [de.fay.sna.SnakeAlarmProcessor] (vert.x-eventloop-thread-0) Received an alarm for snake alarm fulfillment: [SnakeItems(id=1, imageASCIIArtString=e--==--==--)], Danger - Successfully verified a snake
2022-07-24 16:03:58,052 INFO  [de.fay.sna.PushNotificationService] (vert.x-eventloop-thread-0) Push notification estimated in 3 sec
2022-07-24 16:03:58,420 INFO  [de.fay.sna.SnakeAlarmProcessor] (vert.x-eventloop-thread-0) Received an alarm for snake alarm fulfillment: [SnakeItems(id=1, imageASCIIArtString=e--==--==--)], False positive - Successfully verified a lizard
Вход в полноэкранный режим Выход из полноэкранного режима

Postman
Это примерный POST-запрос, который мы будем использовать для запуска наших сервисов. Если мы правильно составим запрос, мы должны получить ответ 202 Accepted. Вы найдете его в виде коллекции Postman в исходных текстах на Github.

Конечная точка сигнализации Snake
Наша конечная точка вызывает хранилище SnakeAlarmRepository, которое обо всем позаботится.

@Path("snake-alarm")
class SnakeAlarmEndpoint(
    private val snakeAlarmRepository: SnakeAlarmRepository
) {
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.TEXT_PLAIN)
    suspend fun snakeAlarm(request: SnakeAlarmRequest): Response {
        return when (snakeAlarmRepository.alarm(request.snakes)) {
            true  -> Response.accepted().build()
            false -> Response.serverError().build()
        }
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Хранилище SnakeAlarmRepository отправляет сигнал тревоги без проверки, используя канал обмена сообщениями AMQP с именем snake-alarms-out.

@ApplicationScoped
class SnakeAlarmRepository(
    private val pushNotificationService: PushNotificationService,
    @Channel("snake-alarms-out") private val snakeAlarmEmitter: Emitter<SnakeAlarm>,
    private val logger: Logger
) {
    suspend fun alarm(items: List<SnakeItems>): Boolean {
        val snakeAlarm = SnakeAlarm(
            snakes = items,
            snakeVerification = "Snake could also be a lizard :-)",
            estimatedPushNotificationTimestamp = null
        )
        return try {
            snakeAlarmEmitter.send(snakeAlarm).await()
            true
        } catch (e: Exception) {
            logger.log(Level.SEVERE, "Error while sending snake alarm", e)
            false
        }
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Свойства приложения
Здесь нам нужно настроить наши слабо связанные каналы обмена сообщениями AMPQ, которые будут запускаться с помощью образа Docker (пример файла Docker предоставлен в исходных текстах на Github).

quarkus.log.level=DEBUG
quarkus.log.category."org.jboss.resteasy".min-level=DEBUG
# The AMQP broker location and credentials
amqp-host=localhost
amqp-port=5672
amqp-username=quarkus
amqp-password=quarkus
# Configure snake alarm messaging
mp.messaging.incoming.snake-alarms-in.connector=smallrye-amqp
mp.messaging.incoming.snake-alarms-in.address=snake-alarms
mp.messaging.outgoing.snake-alarms-out.connector=smallrye-amqp
mp.messaging.outgoing.snake-alarms-out.address=snake-alarms
Войти в полноэкранный режим Выход из полноэкранного режима

Когда мы получаем сообщение от этого канала, SnakeAlarmProcessor проверяет, действительно ли это змея, используя LizardCheck.

@ApplicationScoped
class SnakeAlarmProcessor(
    private val pushNotificationService: PushNotificationService,
    private val lizardCheck: LizardCheck,
    private val logger: Logger
) {
    @Incoming("snake-alarms-in")
    suspend fun processSnakeAlarm(snakeAlarm: SnakeAlarm) {
        val isVerifiedSnake = lizardCheck.checkSnakeAlarmValid(snakeAlarm)
        val verificationResultText = if (isVerifiedSnake) "Danger - Successfully verified a snake" else "False positive - Successfully verified a lizard"

        logger.info { "Received an alarm for snake alarm fulfillment: ${snakeAlarm.snakes}, $verificationResultText" }
        if(isVerifiedSnake) pushNotificationService.announcePrimaryPushNotificationService()
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

LizardCheck часто имеет таймаут. Поэтому мы настроили опции таймаута и повторной попытки с помощью аннотаций.

@ApplicationScoped
class LizardCheck {
    @Timeout(1, unit = ChronoUnit.SECONDS)
    @Retry(maxRetries = 3)
    suspend fun checkSnakeAlarmValid(): Boolean {
        if (Random.nextBoolean()) {
            delay(Random.nextLong(200, 1060))
            return false
        }
        return true
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

PushNotificationServices имеет метод для объявления каждой службы уведомлений. У первого метода есть второй метод, у второго — обратный. Если используется fallback, то максимальное количество повторных попыток составляет две.

@ApplicationScoped
class PushNotificationService(
    private val logger: Logger
) {
    @Fallback(fallbackMethod = "announceSecondaryPushNotificationService")
    fun announcePrimaryPushNotificationService(): Date {
        val fail = Random.nextBoolean()
        if (fail) {
            error("Can't reach primary push notification service")
        }
        logger.info { "Push notification estimated in 1 sec" }
        return Calendar.getInstance().apply {
            add(Calendar.SECOND, 1)
        }.time
    }

    @Retry(maxRetries = 2)
    fun announceSecondaryPushNotificationService(): Date {
        logger.info { "Push notification estimated in 3 sec" }
        return Calendar.getInstance().apply {
            add(Calendar.SECOND, 3)
        }.time
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте еще раз проверим журналы:

2022-07-24 16:03:53,936 INFO  [de.fay.sna.SnakeAlarmProcessor] (vert.x-eventloop-thread-0) Received an alarm for snake alarm fulfillment: [SnakeItems(id=1, imageASCIIArtString=e--==--==--)], False positive - Successfully verified a lizard
2022-07-24 16:03:53,994 DEBUG [io.sma.rea.mes.amqp] (vert.x-eventloop-thread-2) SRMSG16211: Sending AMQP message to address `snake-alarms`
2022-07-24 16:03:54,000 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Disposition{role=RECEIVER, first=1, last=1, settled=true, state=Accepted{}, batchable=false}
2022-07-24 16:03:54,001 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Transfer{handle=0, deliveryId=1, deliveryTag=x00, messageFormat=0, settled=false, more=false, rcvSettleMode=null, state=null, resume=false, aborted=false, batchable=false}[x00Ssxd0x00x00x00)x00x00x00x07@@xa1x0csnake-alarms@@@xa3x10application/jsonx00Suxa0x9c{"snakes":[{"id":"1","imageASCIIArtString":"e--==--==--"}],"snakeVerification":"Snake could also be a lizard :-)","estimatedPushNotificationTimestamp":null}]
2022-07-24 16:03:54,460 DEBUG [io.sma.rea.mes.amqp] (vert.x-eventloop-thread-2) SRMSG16211: Sending AMQP message to address `snake-alarms`
2022-07-24 16:03:54,463 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Transfer{handle=0, deliveryId=2, deliveryTag=x01, messageFormat=0, settled=false, more=false, rcvSettleMode=null, state=null, resume=false, aborted=false, batchable=false}[x00Ssxd0x00x00x00)x00x00x00x07@@xa1x0csnake-alarms@@@xa3x10application/jsonx00Suxa0x9c{"snakes":[{"id":"1","imageASCIIArtString":"e--==--==--"}],"snakeVerification":"Snake could also be a lizard :-)","estimatedPushNotificationTimestamp":null}]
2022-07-24 16:03:54,464 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Disposition{role=RECEIVER, first=2, last=2, settled=true, state=Accepted{}, batchable=false}
2022-07-24 16:03:54,579 INFO  [de.fay.sna.SnakeAlarmProcessor] (vert.x-eventloop-thread-0) Received an alarm for snake alarm fulfillment: [SnakeItems(id=1, imageASCIIArtString=e--==--==--)], False positive - Successfully verified a lizard
2022-07-24 16:03:54,937 INFO  [de.fay.sna.SnakeAlarmProcessor] (vert.x-eventloop-thread-0) Received an alarm for snake alarm fulfillment: [SnakeItems(id=1, imageASCIIArtString=e--==--==--)], False positive - Successfully verified a lizard
2022-07-24 16:03:56,180 DEBUG [io.sma.rea.mes.amqp] (vert.x-eventloop-thread-2) SRMSG16211: Sending AMQP message to address `snake-alarms`
2022-07-24 16:03:56,185 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Disposition{role=RECEIVER, first=3, last=3, settled=true, state=Accepted{}, batchable=false}
2022-07-24 16:03:56,186 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Transfer{handle=0, deliveryId=3, deliveryTag=x00, messageFormat=0, settled=false, more=false, rcvSettleMode=null, state=null, resume=false, aborted=false, batchable=false}[x00Ssxd0x00x00x00)x00x00x00x07@@xa1x0csnake-alarms@@@xa3x10application/jsonx00Suxa0x9c{"snakes":[{"id":"1","imageASCIIArtString":"e--==--==--"}],"snakeVerification":"Snake could also be a lizard :-)","estimatedPushNotificationTimestamp":null}]
2022-07-24 16:03:56,618 DEBUG [io.sma.rea.mes.amqp] (vert.x-eventloop-thread-2) SRMSG16211: Sending AMQP message to address `snake-alarms`
2022-07-24 16:03:56,622 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Transfer{handle=0, deliveryId=4, deliveryTag=x01, messageFormat=0, settled=false, more=false, rcvSettleMode=null, state=null, resume=false, aborted=false, batchable=false}[x00Ssxd0x00x00x00)x00x00x00x07@@xa1x0csnake-alarms@@@xa3x10application/jsonx00Suxa0x9c{"snakes":[{"id":"1","imageASCIIArtString":"e--==--==--"}],"snakeVerification":"Snake could also be a lizard :-)","estimatedPushNotificationTimestamp":null}]
2022-07-24 16:03:56,625 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Disposition{role=RECEIVER, first=4, last=4, settled=true, state=Accepted{}, batchable=false}
2022-07-24 16:03:57,105 DEBUG [io.sma.rea.mes.amqp] (vert.x-eventloop-thread-2) SRMSG16211: Sending AMQP message to address `snake-alarms`
2022-07-24 16:03:57,110 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Disposition{role=RECEIVER, first=5, last=5, settled=true, state=Accepted{}, batchable=false}
2022-07-24 16:03:57,111 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Transfer{handle=0, deliveryId=5, deliveryTag=x02, messageFormat=0, settled=false, more=false, rcvSettleMode=null, state=null, resume=false, aborted=false, batchable=false}[x00Ssxd0x00x00x00)x00x00x00x07@@xa1x0csnake-alarms@@@xa3x10application/jsonx00Suxa0x9c{"snakes":[{"id":"1","imageASCIIArtString":"e--==--==--"}],"snakeVerification":"Snake could also be a lizard :-)","estimatedPushNotificationTimestamp":null}]
2022-07-24 16:03:57,219 INFO  [de.fay.sna.SnakeAlarmProcessor] (vert.x-eventloop-thread-0) Received an alarm for snake alarm fulfillment: [SnakeItems(id=1, imageASCIIArtString=e--==--==--)], False positive - Successfully verified a lizard
2022-07-24 16:03:57,534 DEBUG [io.sma.rea.mes.amqp] (vert.x-eventloop-thread-2) SRMSG16211: Sending AMQP message to address `snake-alarms`
2022-07-24 16:03:57,538 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Disposition{role=RECEIVER, first=6, last=6, settled=true, state=Accepted{}, batchable=false}
2022-07-24 16:03:57,539 FINE  [pro.trace] (vert.x-eventloop-thread-0) IN: CH[0] : Transfer{handle=0, deliveryId=6, deliveryTag=x00, messageFormat=0, settled=false, more=false, rcvSettleMode=null, state=null, resume=false, aborted=false, batchable=false}[x00Ssxd0x00x00x00)x00x00x00x07@@xa1x0csnake-alarms@@@xa3x10application/jsonx00Suxa0x9c{"snakes":[{"id":"1","imageASCIIArtString":"e--==--==--"}],"snakeVerification":"Snake could also be a lizard :-)","estimatedPushNotificationTimestamp":null}]
2022-07-24 16:03:58,039 INFO  [de.fay.sna.SnakeAlarmProcessor] (vert.x-eventloop-thread-0) Received an alarm for snake alarm fulfillment: [SnakeItems(id=1, imageASCIIArtString=e--==--==--)], False positive - Successfully verified a lizard
2022-07-24 16:03:58,041 INFO  [de.fay.sna.SnakeAlarmProcessor] (vert.x-eventloop-thread-0) Received an alarm for snake alarm fulfillment: [SnakeItems(id=1, imageASCIIArtString=e--==--==--)], Danger - Successfully verified a snake
2022-07-24 16:03:58,052 INFO  [de.fay.sna.PushNotificationService] (vert.x-eventloop-thread-0) Push notification estimated in 3 sec
2022-07-24 16:03:58,420 INFO  [de.fay.sna.SnakeAlarmProcessor] (vert.x-eventloop-thread-0) Received an alarm for snake alarm fulfillment: [SnakeItems(id=1, imageASCIIArtString=e--==--==--)], False positive - Successfully verified a lizard
Вход в полноэкранный режим Выход из полноэкранного режима

Если в журнале появляется сообщение «Опасность — успешно проверена змея» или «Ложное срабатывание — успешно проверена ящерица»: мы всегда знаем, проверили ли мы змею. Время отправки push-уведомлений составляет 1 секунду для более быстрой и менее надежной первичной версии и 3 секунды в случае, если вторичная резервная версия преуспеет в течение двух повторных попыток.

Добавить устойчивость в Quarkus с помощью Kotlin довольно просто, потому что большая часть этого может быть сделана с помощью аннотаций и свободной связи.

Если вы хотите попробовать пример устойчивости, приведенный выше, вы можете найти его на Github. Удачи вам и оставайтесь любознательными.

Эпилог
Змеи — классные животные. Детектор змей — это просто забавная выдумка, не имеющая целью поддержать страх перед дикими животными. В следующий раз я выберу сценарий, помогающий животным 🙂 — потому что мы должны защищать их всех. Купить способ: Тардиграды и муравьи — хорошие примеры очень живучих животных!

Оцените статью
devanswers.ru
Добавить комментарий