做性能优化时,遇到一些跨不过的坎,如果能够绕过问题本身来解决该问题也是不错的选择。本人在做 MongoDB 联合查询时,遇到了性能瓶颈,使用 MongoDB 内部联合查询时 Mongo竟然不会使用索引,效率低下,不得采用数据库外部联合查询来解决。那么,问题来了,两种方式优势与劣势同样明显,若要同时运用两种查询方式的优势,避其短处,本人采用了竞争式查询。在不同的场景下,两者同时执行,谁最快执行完成,就用谁,保证执行时间最短。这种方式也是典型的用资源或者空间换时间的做法了。
多线程竞争代码如下:
package com.zhaoxj_2017.tools;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
public class CompetitiveExecutor {
@Test
public void execute() throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
long startTime = System.currentTimeMillis();
List<FutureTask<Integer>> tasks = new ArrayList<>();
Signal signal = new Signal();
CompetitiveCallable callable1 = createCompetitiveCallable(signal);
CompetitiveCallable callable2 = createCompetitiveCallable(signal);
CompetitiveCallable callable3 = createCompetitiveCallable(signal);
CompetitiveCallable callable4 = createCompetitiveCallable(signal);
FutureTask<Integer> task1 = new FutureTask<>(callable1);
FutureTask<Integer> task2 = new FutureTask<>(callable2);
FutureTask<Integer> task3 = new FutureTask<>(callable3);
FutureTask<Integer> task4 = new FutureTask<>(callable4);
tasks.add(task1);
tasks.add(task2);
tasks.add(task3);
tasks.add(task4);
callable1.setCompetitors(tasks);
callable1.setCurrentTask(task1);
callable2.setCompetitors(tasks);
callable2.setCurrentTask(task2);
callable3.setCompetitors(tasks);
callable3.setCurrentTask(task3);
callable4.setCompetitors(tasks);
callable4.setCurrentTask(task4);
executorService.submit(task1);
executorService.submit(task2);
executorService.submit(task3);
executorService.submit(task4);
Integer result = -1;
for (FutureTask<Integer> task : tasks) {
try {
result = task.get();
} catch (Exception e) {
continue;
}
break;
}
System.out.println("Result is : " + result);
System.out.println("Total execution time is " + (System.currentTimeMillis() - startTime));
TimeUnit.SECONDS.sleep(10);
executorService.shutdown();
}
private CompetitiveCallable createCompetitiveCallable(Signal signal) {
return new CompetitiveCallable(new Random().nextInt(5) + 2, signal);
}
class Signal {
private boolean state = true;
synchronized boolean get() {
if(state) {
state = false;
return true;
}
return false;
}
}
class CompetitiveCallable implements Callable<Integer> {
private List<FutureTask<Integer>> competitors = Collections.emptyList();
private FutureTask currentTask;
private Signal signal;
private int randomResource;
CompetitiveCallable(int randomResource, Signal signal) {
this.randomResource = randomResource;
this.signal = signal;
}
void setCompetitors(List<FutureTask<Integer>> competitors) {
this.competitors = new ArrayList<>(competitors);
}
void setCurrentTask(FutureTask currentTask) {
this.currentTask = currentTask;
}
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + ": running, need cost " + randomResource);
TimeUnit.SECONDS.sleep(randomResource);
System.out.println(Thread.currentThread().getName() + ": completing, and cost " + randomResource);
if(signal.get()) {
System.out.println(Thread.currentThread().getName() + ": Obtain final execution rights.");
competitors.remove(currentTask);
competitors.forEach(futureTask -> {
try {
futureTask.cancel(true);
} catch (Exception e) {
// Ignore.
}
});
}
return randomResource;
}
}
}
该方式的执行时间就是执行最快的线程执行所需要的时间,与此同时,未执行完成的线程也会被取消执行。
通过多种方式竞争,充分利用各种算法的优点,算是勉强解决了本人所遇问题,但是从根本上解决 MongoDB 联合查询的性能问题,仍需要 Mongo 官方继续优化联合查询性能。
如果大家有更好的解决方案,欢迎交流。