缓存穿透后的并发数据库查询优化

现象

当缓存中没有数据的时候,需要到数据库中查询,俗称缓存穿透。

在高并发情况下,多个线程同时穿透去查询同一条数据的时候,对数据库压力很大,事实上也造成了多余无效的数据库查询。

分析

那么希望一种方法,可以只让一个线程去数据库查询,其他线程等待这个线程的查询结果,这样就可以极大的减轻数据库压力。

解决

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;


public class ConcurrentDbQueryService {

  private static final int THREAD_POOL_SIZE = 20;

  private final String threadPrefix = "conc-db-query-";

  private final AtomicLong threadCount = new AtomicLong();

  private final ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE, new ThreadFactory() {
    @Override
    public Thread newThread(Runnable r) {
      return new Thread(r, threadPrefix + threadCount.incrementAndGet());
    }
  });

  private final Map<String, FutureTask<String>> runningTaskMap = new ConcurrentHashMap<>();

  public String getByKey(String key) {

    FutureTask<String> runningTask = runningTaskMap.get(key);
    if (runningTask != null) {
      try {
        // 发现同一个 key 有其他线程在查询,直接等待它的结果
        return runningTask.get();
      } catch (InterruptedException e) {
        e.printStackTrace();
      } catch (ExecutionException e) {
        e.printStackTrace();
      }
    }

    FutureTask<String> task = new FutureTask<>(() -> {
      try {
        // TODO 从 db 中获取数据
        return null;
      } finally {
        // 任务完成后,把自己从 runningTaskMap 中拿掉
        runningTaskMap.remove(key);
      }
    });

    runningTask = runningTaskMap.putIfAbsent(key, task);
    if (runningTask == null) {
      // 说明没有其他线程做相同操作
      runningTask = task;
      threadPool.submit(runningTask);
    }

    try {
      return runningTask.get();
    } catch (InterruptedException e) {
      e.printStackTrace();
    } catch (ExecutionException e) {
      e.printStackTrace();
    }
  }
}

版权

评论