简体   繁体   中英

How do I ensure that there's a minimum time before a response in WebFlux but do not add a fixed delay?

I have an operation that executes relatively quick, but it is still high in CPU. So I want to delay the response so that there's a minimum of 100ms

I know I can simply do this

  @PostMapping(
      path = "/Echo/echo",
      consumes = MediaType.APPLICATION_JSON_VALUE,
      produces = MediaType.APPLICATION_JSON_VALUE)
  public Mono<EchoResponse> echo(@RequestBody EchoRequest request) {

    return Mono.just(
            EchoResponse.builder().message(request.getMessage()).timestamp(Instant.now()).build())
        .delayElement(Duration.ofMillis(100L));
  }

But that would always add 100ms what I'd want is to something like

  • original processing time is 10ms, then the response should take 100ms
  • original processing time is 100ms, then the response should take 100ms
  • original processing time is 120ms, then the response should take 120ms

I am thinking I should be using delayUntil (now that I wrote this)

OffTopic

There is something terrible wrong with your consumer. A fast response should not result in a retry.

I would never built some throttling in a production-application.

OnTopic

delayUntil() expects a function which doesn't fit well to your use-case.
I see only one way: Measure the timings.

Here is an example.
Caution #1: Written by hand, there are some errors.
Caution #2: Spring-Magic is not part of the method. Serialization and Spring-Filters cannot be measured.

public Mono<EchoResponse> echo(@RequestBody EchoRequest request) {

    var startTime = LocalDateTime.now();

    var hardWorkedObject = EchoResponse.builder()
        .message(request.getMessage())
        .timestamp(Instant.now())
        .build())

    var workedTime = Duration.between(startTime, LocalDateTime.now());
    
    if (workedTime.getMillis() < 100) {
       return Observable.timer(100 - workedTime.getMillies())
          .take(1)
          .map(ignored -> hardWorkedObject)
          .toMono();
    } else {
       return Mono.just(hardWorkedObject);
    }
}

First thing is to store the start time. The value would be stored in the context using the following function.

    private Context writeStartTimeToContext(Context ctx) {
        return ctx.put("startTime", System.currentTimeMillis());
    }

Next there needs to be a method that takes in some source data and returns a Mono that is delayed for delayUntil to use.

  /**
   * Provide a function to apply a minimum operation time
   *
   * @param i input type ignored
   * @return a mono that is delayed until a target time.
   * @param <T> input type that is ignored
   */
  private <T> Mono<?> applyMinimumOperationTime(T i) {

    return Mono.deferContextual(
            ctx ->
                Mono.just(
                    authProperties.getMinimumOperationTimeInMillis()
                        + (long) ctx.get("startTime")
                        - System.currentTimeMillis()))
        .filter(delayTime -> delayTime > 0)
        .map(Duration::ofMillis)
        .flatMap(delayTime -> Mono.just(i).delayElement(delayTime));
  }

This can then be applied in the call chain near the end as

someOperationThatCouldBeVeryQuickThatProvidesAMono
  ...
  .delayUntil(this::applyMinimumOperationTime)
  .contextWrite(this::writeStartTimeToContext)
  ...;

Example of how this is used .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM