MST
星途 面试题库

面试题:缓存设计之Guava Cache高级特性

Guava Cache的LoadingCache接口在缓存数据加载方面有哪些优势?在高并发场景下,如何利用LoadingCache避免缓存击穿问题?请结合代码示例阐述。
14.4万 热度难度
后端开发缓存设计

知识考点

AI 面试

面试题答案

一键面试

Guava Cache的LoadingCache接口在缓存数据加载方面的优势

  1. 自动加载:LoadingCache接口实现了CacheLoader,当调用get(key)方法且缓存中不存在对应key时,会自动调用CacheLoaderload方法加载数据并放入缓存,极大简化了缓存加载逻辑。
  2. 批量加载CacheLoader还提供了loadAll方法,当需要加载多个键值对时,可以一次性加载,提升效率并减少资源开销。
  3. 可配置性:可以通过CacheBuilder对缓存进行各种配置,如设置过期时间、最大缓存容量等,灵活满足不同应用场景需求。

在高并发场景下利用LoadingCache避免缓存击穿问题

缓存击穿指的是大量并发请求同时访问一个过期的缓存键,导致所有请求都直接访问后端数据库等数据源,对后端造成巨大压力。LoadingCache在高并发场景下避免缓存击穿的原理是,当一个key过期或不存在时,只有一个线程会去真正加载数据,其他线程会等待这个线程加载完成并将数据放入缓存后,直接从缓存获取数据。

以下是代码示例:

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class LoadingCacheExample {
    // 模拟数据库查询
    private static int getDataFromDB(String key) {
        System.out.println("从数据库获取数据: " + key);
        return Integer.parseInt(key);
    }

    public static void main(String[] args) {
        LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
               .expireAfterWrite(10, TimeUnit.SECONDS)
               .build(
                        new CacheLoader<String, Integer>() {
                            @Override
                            public Integer load(String key) throws Exception {
                                return getDataFromDB(key);
                            }
                        }
                );

        // 模拟高并发请求
        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            final String key = String.valueOf(i);
            threads[i] = new Thread(() -> {
                try {
                    System.out.println("线程 " + Thread.currentThread().getName() + " 获取到数据: " + cache.get(key));
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            });
            threads[i].start();
        }

        // 等待所有线程执行完毕
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在上述代码中,LoadingCache在获取缓存值时,如果缓存中不存在该值,会调用CacheLoaderload方法从数据库(这里用getDataFromDB模拟)加载数据。在高并发场景下,只有一个线程会执行load方法,其他线程会等待这个线程加载完成并将数据放入缓存,从而避免了缓存击穿问题。同时,通过CacheBuilder设置了缓存的过期时间为10秒,在过期后同样能保证只有一个线程去重新加载数据。