做性能優化時,遇到一些跨不過的坎,如果能夠繞過問題本身來解決該問題也是不錯的選擇。本人在做 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 官方繼續優化聯合查詢性能。
如果大家有更好的解決方案,歡迎交流。