在笔者工作中遇到的前后端交互的项目中,后端处理并返回数据,前端进行显示数据时,会有核心数据和非核心数据的区别。对于核心数据应该强保障,而对于非核心数据则弱保障即可。
那么核心数据和非核心数据的处理方式和逻辑编排则是不同。刚步入职场时,最得心应手的写法就是一把梭。不管数据核心与否,全部是同步调用和无异常处理。
一把梭的写法
public class ToBShowQueryTest {
//http的响应接口
public static void main(String[] args) throws InterruptedException {
String httpRes = getHttpRes();
System.out.println(httpRes);
}
//服务编排接口
public static String getHttpRes() throws InterruptedException {
String codeData = queryCodeData();
String nonCodeData1 = queryNonCodeData1();
String nonCodeData2 = queryNonCodeData2();
return codeData+"+"+nonCodeData1+"+"+nonCodeData2;
}
//原子能力查询接口
public static String queryCodeData() throws InterruptedException {
Thread.sleep(1500);
return "核心数据";
}
public static String queryNonCodeData1() throws InterruptedException {
Thread.sleep(3000);
return "非核心数据1";
}
public static String queryNonCodeData2() throws InterruptedException {
Thread.sleep(3000);
return "非核心数据2";
}
}
一把梭写法的时序图如下:
其中查询核心数据需要1.5s,查询非核心数据1和非核心数据2则都需要3s,故一次http请求则需要7.5s。如果查询核心数据失败(例如超时异常)那么http请求就会被阻断,符合预期;如果查询非核心数据1或者非核心数据2失败,那么http请求也会被阻断,当前逻辑是不符合正常预期的。对于非核心的数据,在页面上的显示维度应该具备一定的容错。
对于非核心数据的处理
查询非核心数据1失败也不会阻断整个http的请求
public class ToBShowQueryTest {
//http的响应接口
public static void main(String[] args) throws InterruptedException {
String httpRes = getHttpRes();
System.out.println(httpRes);
}
//服务编排接口
public static String getHttpRes() throws InterruptedException {
String codeData = queryCodeData();
String nonCodeData1 = "";
try {
nonCodeData1 = queryNonCodeData1();
}catch (Exception e){
Log.error("查询非核心数据1失败");
}
String nonCodeData2 = "";
try {
nonCodeData2 = queryNonCodeData2();
}catch (Exception e){
Log.error("查询非核心数据2失败");
}
return codeData+"+"+nonCodeData1+"+"+nonCodeData2;
}
//原子能力查询接口
public static String queryCodeData() throws InterruptedException {
Thread.sleep(1500);
return "核心数据";
}
public static String queryNonCodeData1() throws InterruptedException {
Thread.sleep(3000);
if (true){
throw new RuntimeException("抛出异常");
}
return "非核心数据1";
}
public static String queryNonCodeData2() throws InterruptedException {
Thread.sleep(3000);
return "非核心数据2";
}
}
页面的HTTP请求时间对于用户体验的影响通常不是孤立考虑的,而是结合整个页面加载过程中的各个阶段来评估。根据上述信息和业界公认的最佳实践:
在上述优化之后的一把梭写法中,http请求的显示已经排除了非核心数据的影响。但是查询性能依然没有任何提升,依然至少需要7.5s的查询时间。页面端查询理论上能够接受的最长时间也就1.5s,才处于用户的体验良好区,每新增1s,体验都会呈现指数下降。
根据上述的结论,非核心的数据在页面上的显示维度应该具备一定的容错。那么是否可以对非核心数据使用异步线程查询数据,即可大幅度降低查询时间。即http请求时间 = 核心数据查询时间 + 非核心数据查询的最大时间 = 1.5 + 3 = 4.5s
时序图如下
异步查询非核心数据
public class ToBShowQueryTest {
//http的响应接口
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
String httpRes = getHttpRes();
System.out.println(httpRes);
}
//服务编排接口
public static String getHttpRes() throws InterruptedException, ExecutionException, TimeoutException {
StringBuilder codeData = new StringBuilder(queryCodeData());
List<Future<String>> futureList = new ArrayList<>();
CompletableFuture<String> nonCodeData1Future = CompletableFuture.supplyAsync(() -> {
String nonCodeData1 = "";
try {
nonCodeData1 = queryNonCodeData1();
} catch (Exception e) {
Log.error("查询非核心数据1失败");
}
return nonCodeData1;
});
futureList.add(nonCodeData1Future);
CompletableFuture<String> nonCodeData2Future = CompletableFuture.supplyAsync(() -> {
String nonCodeData2 = "";
try {
nonCodeData2 = queryNonCodeData2();
} catch (Exception e) {
Log.error("查询非核心数据2失败");
}
return nonCodeData2;
});
futureList.add(nonCodeData2Future);
//此处获取结果有两种形式
//方法1:等待查询非核心数据1异步任务和查询非核心数据2异步任务都执行完成,然后获取数据
//当前获取方式的http请求时间 = 核心数据查询时间 + 非核心数据查询的最大时间 = 1.5 + 3 = 4.5s (最少需要4.5s,rpc请求有波动)
// for (Future<String> stringFuture : futureList) {
// String strRes = stringFuture.get();
// codeData.append("+").append(strRes);
// }
//方法2:查询非核心数据1异步任务和查询非核心数据2异步任务都等待3秒,然后获取数据
//当前获取方式的http请求时间 = 核心数据查询时间 + 非核心数据查询的等待结果时间 = 1.5 + 3 = 4.5s
for (Future<String> stringFuture : futureList) {
String strRes = "";
try {
strRes = stringFuture.get(3 , TimeUnit.SECONDS);
}catch (Exception e){
Log.error("非核心数据查询超时");
}
codeData.append("+").append(strRes);
}
return codeData.toString();
}
//原子能力查询接口
public static String queryCodeData() throws InterruptedException {
Thread.sleep(2000);
return "核心数据";
}
public static String queryNonCodeData1() throws InterruptedException {
//4s时间用于测试,等待时间超过限制等待时间(3s),
//stringFuture.get(3 , TimeUnit.SECONDS)方法会抛出异常,需要用异常捕获
Thread.sleep(4000);
//异常用于测试非核心数据查询异常捕获不影响主数据
// if (true){
// throw new RuntimeException("抛出异常");
// }
return "非核心数据1";
}
public static String queryNonCodeData2() throws InterruptedException {
Thread.sleep(3000);
return "非核心数据2";
}
}