0

I have a Spring Boot application, implementing Websocket as well as Redis stream.

The flow is, Subscriber (who subscribes Redis stream) upon receiving message will then send that information into Websocket (using STOMP protocol and AmazonMQ - ActiveMQ as an external message broker).

Example of 1 consumer group

    public Subscription fExecutionResponse(RedisTemplate<String, Object> redisTemplate) {
        try {
            String groupName = FStreamName.fExecutionConsumerGroup;
            String streamKey = FStreamName.fExecutionStream;
            createConsumerGroup(streamKey, groupName, redisTemplate);
            val listenerContainer = listenerContainer(redisTemplate, FTradeDataRedisEvent.class);

            val subscription = listenerContainer.receiveAutoAck(
                    Consumer.from(groupName, FStreamName.fExecutionConsumer),
                    StreamOffset.create(streamKey, ReadOffset.lastConsumed()),
                    message -> {
                        log.info("[Subscription F_EXECUTION]: {}", message.getValue());
                        FTradeDataRedisEvent mTradeEvent = message.getValue();
                        try {
                            if (ExternalConfiguration.futureTradeDataSource.equals(ExecutionSource.ALL) || ExternalConfiguration.futureTradeDataSource.equals(mTradeEvent.getSource())) {
                                futureProductService.updateProductByDummy(mTradeEvent);
                                futureExecutionToTickHistoricalService.transform(mTradeEvent);
                            }
                        } catch (Exception ex) {
                            log.error("[fTradeEventResponse] error: {}", ex.getMessage());
                        }
                        redisTemplate.opsForStream().acknowledge(streamKey, groupName, message.getId());
                        redisTemplate.opsForStream().delete(streamKey, message.getId());
                    });

            listenerContainer.start();
            log.info("A stream key `{}` had been successfully set up in the group `{}`", streamKey, groupName);
            return subscription;
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        }
        return null;
    }

futureExecutionToTickHistoricalService.transform will send the data to web socket using SimpMessageSendingOperations

    public void transform(FTradeDataRedisEvent tradeData) {
        if (lastUpdateTickHistoricalDataTime == 0L) {
            Calendar calendar = Calendar.getInstance();
            this.lastUpdateTickHistoricalDataTime = calendar.getTimeInMillis();
        }

        List<FTickHistorical> res = separateFTickHistorical(tradeData);
        res.forEach(tickHistorical -> {
            List<KlineResponse> klineResponses = new ArrayList<>();
            klineResponses.add(new KlineResponse(tickHistorical));

            messageTemplate.convertAndSend(
                    PUBLIC_TOPIC_PREDIX + Constants.F_PRODUCTS_DESTINATION + "/" + tickHistorical.getProductId() + "/klines" + "_" + tickHistorical.getResolution().getCode(),
                    new HistoryResponse(klineResponses)
            );
        });
    }

There are two problems with this setup, I have resolved one of them.

  • The Redis stream subscriber is started up before the connection to the external message broker is ready. Solved (listen to BrokerAvailabilityEvent and only then start Redis subscriptions)
  • When redeploy or shutdown application on IDE (like Intellij). The connection to the broker is again destroyed first (before the Redis stream subscribers), at the same time, there are still some data sending to the socket. This cause error: Message broker not active

I don't know how to configure the Spring boot application, so that when the application is stopped, it first stops consuming messages from Redis stream, process all of the pending messages, then close the broker connection.

This is the error log when the application is destroyed.

14.756  INFO 41184 --- [extShutdownHook] c.i.c.foapi.SpotStreamSubscriber         : Broker not available
2022-10-09 14:14:14.757  INFO 41184 --- [extShutdownHook] c.i.c.f.config.RedisSubConfiguration     : Broker not available
2022-10-09 14:14:14.781 ERROR 41184 --- [cTaskExecutor-1] c.i.c.foapi.consumer.SpotStreamConsumer  : [executionResponse] error: Message broker not active. Consider subscribing to receive BrokerAvailabilityEvent's from an ApplicationListener Spring bean.; value of message: ExecutionRedisEvent(id=426665086, eventTime=2022-10-09T14:13:45.056809, productId=2, price=277.16, quantity=0.08, buyerId=2, sellerId=3, createdDate=2022-10-09T14:13:45.056844, takerSide=SELL, orderBuyId=815776680, orderSellId=815776680, symbol=bnbusdt, source=BINANCE)
2022-10-09 14:14:14.785 ERROR 41184 --- [cTaskExecutor-1] c.i.c.foapi.consumer.SpotStreamConsumer  : [dummyOrderBookResponse] error: Message broker not active. Consider subscribing to receive BrokerAvailabilityEvent's from an ApplicationListener Spring bean.; value of message: DummyOrderBookEvent(productId=1, bids=[DummyOrderBook(price=1941.6, qty=0), DummyOrderBook(price=18827.3, qty=0.013), DummyOrderBook(price=18938.8, qty=5.004), DummyOrderBook(price=18940.3, qty=22.196), DummyOrderBook(price=18982.5, qty=20.99), DummyOrderBook(price=19027.2, qty=0.33), DummyOrderBook(price=19045.8, qty=8.432)

This is the code of SpotStreamSubscriber

@Component
@RequiredArgsConstructor
@Slf4j
public class SpotStreamSubscriber implements ApplicationListener<BrokerAvailabilityEvent> {
    private final SpotStreamConsumer spotStreamConsumer;

    @Override
    public void onApplicationEvent(BrokerAvailabilityEvent event) {
        if (event.isBrokerAvailable()) {
            log.info("Broker ready");
            spotStreamConsumer.subscribe();
        } else {
            log.info("Broker not available");
        }
    }
}

As you can see, the message broker is destroyed before the pending Redis messages have a chance to be processed.

Current architecture, We use external message broker so that we can scale horizontally the api.

enter image description here

Loc Truong
  • 359
  • 5
  • 22

0 Answers0