spring boot - mapping correct response to request, for multi asynchronous calls

问题: I wrote a spring-boot application that recieves an object named Calc from the user, which contains two parameters, and returns an answer that consists of a complex calculat...

问题:

I wrote a spring-boot application that recieves an object named Calc from the user, which contains two parameters, and returns an answer that consists of a complex calculation (the calculation itself is not relevant to the question). Because the system may be busy, each object is entered into the queue, and there is a scheduler that passes by order on the queue, and preforms the calculation.

My problem is how to return the result of the item's calculation to the correct request.

I've included the code I wrote:

controller:

@RestController
public class CalcController {

    @Autowired
    private CalculateService calculateService;

    @RequestMapping("/")
    public int calculate(@RequestBody Calc calc) {
        return calculateService.calculate(calc);
    }
}

Calc Object:

@Data
public class Calc {
    private int paramA;
    private int paramB;
}

CalculateService:

@Service
public class CalculateService {
    private BlockingQueue<Calc> calcQueue;

    @PostConstruct
    private void init() {
        calcQueue = new LinkedBlockingDeque<>();
    }

    public int calculate(Calc calc) {
        calcQueue.add(calc);

        // TODO: Return calculation result.
        return 0;
    }

    @Scheduled(fixedRate = 2000)
    public void calculateQueue() throws InterruptedException {
        while (!calcQueue.isEmpty()) {
            Calc calc = calcQueue.take();
            int result = Calculator.calculate(calc);
            // TODO: Return calculation result to the right request.
        }
    }
}

Thanks


回答1:

You can use an ExecutorService which essentially maintains an internal queue to dispatch your work requests to a number of threads.

class Service {
    // use 4 threads; optimal is the amount of processor cores available
    private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(4);

    public int calculate(int input) {
        Future<Integer> future = EXECUTOR.submit(() -> Calculator.calculate(input));
        return future.get(); // this actually thrown an Exception you have to catch
    }
}

The submit will call Calculator.calculate() when the executor has a thread available, and future.get() will extract the actual result of that call.

Note that this code does block until the result has been calculated, only the calculation itself will be parallel. If you do want to return immediately and provide the result later, that's a different story, but it doesn't really fit the REST controller concept.

You can also make this code simpler by using CompletableFuture

class Service {
    public int calculate(int input) {
        return CompletableFuture.supplyAsync(() -> Calculator.calculate(input)).get();
    }
}

回答2:

You can use Spring's @Async ability

  1. Create a Thread pool
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@EnableAsync
@Configuration
public class ThreadConfig {
    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        return new ThreadPoolTaskExecutor();
    }
}
  1. Update calculateService, you do not need to store objects in Queue, it will be handled by Spring's async utility
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;

@Service
public class CalculateService {
    @Async("threadPoolTaskExecutor")
    public CompletableFuture<Integer> calculate(Calc calc) {
        int result = Calculator.calculate(calc);
        return CompletableFuture.completedFuture(result);
    }
}
  1. Update Controller method to
import com.example.service.CalculateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

@RestController
public class CalcController {

    @Autowired
    private CalculateService calculateService;

    @RequestMapping("/")
    public int calculate(@RequestBody Calc calc) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> resultFut = calculateService.calculate(calc);
        // some other calls/logic
        return resultFut.get();
    }
}

If you want to store sessions per request, refer this SO post

  • 发表于 2019-03-19 01:51
  • 阅读 ( 182 )
  • 分类:sof

条评论

请先 登录 后评论
不写代码的码农
小编

篇文章

作家榜 »

  1. 小编 文章
返回顶部
部分文章转自于网络,若有侵权请联系我们删除