需求
有一个业务场景如下:对外提供一个查询接口,该接口接收一个指定的超时时间参数 timeout,接口并发执行两个方法,方法一为查询缓存,方法二为http接口查询,http接口的超时时间为timeout。如果在timeout时间内方法二有返回结果,则接口返回方法二的查询结果。如果方法二在指定的超时内没有返回结果,则查询接口返回方法一的执行结果。同时如果方法二如果超时了,需要异步等待300ms,尽可能获取方法二的执行结果,如果有结果就写一条日志,否则丢弃中断http请求。
需求分析
- 接口接收超时参数: 接口定义一个timeout参数。
- 并发执行查询: 同时执行查询缓存(cacheQuery)和HTTP接口查询(httpQuery)。
- 优先返回HTTP查询结果: 如果在timeout时间内httpQuery有结果,返回该结果。
- 超时返回缓存结果: 如果httpQuery超时,返回cacheQuery结果。
- 记录日志: 如果httpQuery超时,异步等待300ms,记录结果或中断请求。
技术实现
Future 实现方式
Controller
java
@RestController
public class QueryController {
@Autowired
private QueryService queryService;
@RequestMapping(value = "/query", method = RequestMethod.GET)
public ResponseEntity<?> query(@RequestParam("timeout") long timeout) {
try {
String result = queryService.queryWithTimeout(timeout);
return ResponseEntity.ok(result);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
}
Service
java
@Service
public class QueryService {
@Autowired
private CacheQueryComponent cacheQueryComponent;
@Autowired
private HttpQueryComponent httpQueryComponent;
public String queryWithTimeout(long timeout) throws InterruptedException, ExecutionException, TimeoutException {
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
// 提交缓存查询任务
Future<String> cacheFuture = executor.submit(() -> cacheQueryComponent.query());
// 提交HTTP查询任务,带有超时设置
Future<String> httpFuture = executor.submit(() -> httpQueryComponent.query(timeout));
try {
// 优先获取HTTP查询结果
return httpFuture.get(timeout, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
// HTTP查询超时,返回缓存查询结果
return cacheFuture.get();
} finally {
// 如果HTTP查询超时,异步等待300ms
if (!httpFuture.isDone()) {
executor.submit(() -> {
try {
String result = httpFuture.get(300, TimeUnit.MILLISECONDS);
// 记录日志
logResult(result);
} catch (Exception ex) {
httpFuture.cancel(true); // 中断HTTP请求
}
});
}
}
} finally {
executor.shutdown();
}
}
private void logResult(String result) {
// 日志记录逻辑
System.out.println("Delayed HTTP query result: " + result);
}
}
缓存查询组件
java
@Component
public class CacheQueryComponent {
public String query() {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
printLog("cache result");
return "cache result";
}
}
HTTP查询组件
java
@Component
public class HttpQueryComponent {
public String query() {
try {
Thread.sleep(400);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
printLog("cache result");
return "cache result";
}
}
CompletableFuture 实现
Service
java
@Service
public class QueryService {
@Autowired
private CacheQueryComponent cacheQueryComponent;
@Autowired
private HttpQueryComponent httpQueryComponent;
public String queryWithTimeout(long timeout) throws InterruptedException, ExecutionException, TimeoutException {
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
// 创建一个异步任务,从缓存中查询数据
CompletableFuture<String> cacheTask = CompletableFuture.supplyAsync(() -> {
printLog("queryFromCache");
return cacheQueryComponent.query();
});
// 创建一个异步任务,从HTTP中查询数据
CompletableFuture<String> httpTask = CompletableFuture.supplyAsync(() -> {
printLog("queryFromHttp");
return httpQueryComponent.query();
});
// 如果HTTP任务在200ms内没有完成,就取消任务
executor.schedule(() -> {
if (!httpTask.isDone()) {
try {
String httpResult = httpTask.get(300, TimeUnit.MILLISECONDS);
printLog("HTTP task completed after 200ms: " + httpResult);
} catch (Exception e) {
printLog("HTTP task cancel");
httpTask.cancel(true);
}
}
}, 200, TimeUnit.MILLISECONDS);
// 创建一个延迟200ms的任务
CompletableFuture<Void> delay = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
// 如果HTTP任务完成,就返回HTTP任务的结果,否则返回缓存任务的结果
return delay.thenApply(v -> {
if (httpTask.isDone()) {
return httpTask.join();
} else {
return cacheTask.join();
}
}).join();
} finally {
executor.shutdown();
}
}
private void logResult(String result) {
// 日志记录逻辑
System.out.println("Delayed HTTP query result: " + result);
}
}