Redis(80)如何解决Redis的缓存穿透问题?

解决Redis缓存穿透问题的方法有多种,具体的解决方案可以根据实际情况选择。以下是几种常见的解决方法及详细的代码示例,包括缓存空结果、使用布隆过滤器以及参数校验。

1. 缓存空结果

当查询数据库返回空结果时,也将其缓存起来,并设置一个较短的过期时间,比如5分钟。这样即使请求数据不存在,也不需要每次都访问数据库。

示例代码:

java 复制代码
import redis.clients.jedis.Jedis;
import com.fasterxml.jackson.databind.ObjectMapper;

public class CachePenetrationExample {
    private Jedis jedis;
    private static final ObjectMapper objectMapper = new ObjectMapper();

    public CachePenetrationExample(Jedis jedis) {
        this.jedis = jedis;
    }

    public <T> T getCachedData(String key, Class<T> clazz, DataProvider<T> provider, int cacheTime) {
        try {
            String cacheValue = jedis.get(key);
            if (cacheValue != null) {
                if (cacheValue.equals("null")) {
                    return null;
                }
                return objectMapper.readValue(cacheValue, clazz);
            }

            T data = provider.getData();
            if (data == null) {
                jedis.setex(key, 300, "null"); // 缓存空结果5分钟
            } else {
                jedis.setex(key, cacheTime, objectMapper.writeValueAsString(data));
            }
            return data;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public interface DataProvider<T> {
        T getData();
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        CachePenetrationExample cache = new CachePenetrationExample(jedis);

        String userId = "nonexistent";
        String cacheKey = "user:" + userId;
        int cacheTime = 3600; // 缓存 1 小时

        User user = cache.getCachedData(cacheKey, User.class, () -> {
            // 模拟数据库查询,返回null表示数据不存在
            return null;
        }, cacheTime);

        if (user == null) {
            System.out.println("User not found.");
        } else {
            System.out.println("User: " + user);
        }

        jedis.close();
    }

    static class User {
        private String id;
        private String name;
        private String email;

        // Getters and Setters

        @Override
        public String toString() {
            return "User{id='" + id + "', name='" + name + "', email='" + email + "'}";
        }
    }
}

2. 布隆过滤器

使用布隆过滤器快速判断数据是否存在。布隆过滤器是一种概率型数据结构,它能以较低的内存消耗判断一个元素是否存在某个集合中。虽然存在误判的可能,但不会漏判。

示例代码:

java 复制代码
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import redis.clients.jedis.Jedis;
import java.nio.charset.Charset;

public class BloomFilterExample {
    private Jedis jedis;
    private BloomFilter<String> bloomFilter;

    public BloomFilterExample(Jedis jedis, int expectedInsertions, double falsePositiveProbability) {
        this.jedis = jedis;
        this.bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, falsePositiveProbability);
        // 初始化布隆过滤器,通常从数据库加载已有数据
        initializeBloomFilter();
    }

    private void initializeBloomFilter() {
        // 模拟加载数据
        bloomFilter.put("existingUser1");
        bloomFilter.put("existingUser2");
        // 真实环境中应从数据库加载
    }

    public <T> T getCachedData(String key, Class<T> clazz, DataProvider<T> provider, int cacheTime) {
        try {
            if (!bloomFilter.mightContain(key)) {
                return null;
            }

            String cacheValue = jedis.get(key);
            if (cacheValue != null) {
                if (cacheValue.equals("null")) {
                    return null;
                }
                return objectMapper.readValue(cacheValue, clazz);
            }

            T data = provider.getData();
            if (data == null) {
                jedis.setex(key, 300, "null"); // 缓存空结果5分钟
            } else {
                jedis.setex(key, cacheTime, objectMapper.writeValueAsString(data));
            }
            return data;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public interface DataProvider<T> {
        T getData();
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        BloomFilterExample cache = new BloomFilterExample(jedis, 10000, 0.01);

        String userId = "nonexistent"; // 修改这个值来测试存在和不存在的情况
        String cacheKey = "user:" + userId;
        int cacheTime = 3600; // 缓存 1 小时

        User user = cache.getCachedData(cacheKey, User.class, () -> {
            // 模拟数据库查询,返回null表示数据不存在
            return null;
        }, cacheTime);

        if (user == null) {
            System.out.println("User not found.");
        } else {
            System.out.println("User: " + user);
        }

        jedis.close();
    }

    static class User {
        private String id;
        private String name;
        private String email;

        // Getters and Setters

        @Override
        public String toString() {
            return "User{id='" + id + "', name='" + name + "', email='" + email + "'}";
        }
    }
}

3. 参数校验

对请求参数进行严格校验,过滤掉明显无效的请求。例如,用户ID应为正整数,可以在程序中对用户ID进行校验,过滤掉不符合规则的请求。

示例代码:

java 复制代码
import redis.clients.jedis.Jedis;

public class ParameterValidationExample {
    private Jedis jedis;

    public ParameterValidationExample(Jedis jedis) {
        this.jedis = jedis;
    }

    public <T> T getCachedData(String key, Class<T> clazz, DataProvider<T> provider, int cacheTime) {
        try {
            if (!isValidKey(key)) {
                return null;
            }

            String cacheValue = jedis.get(key);
            if (cacheValue != null) {
                if (cacheValue.equals("null")) {
                    return null;
                }
                return objectMapper.readValue(cacheValue, clazz);
            }

            T data = provider.getData();
            if (data == null) {
                jedis.setex(key, 300, "null"); // 缓存空结果5分钟
            } else {
                jedis.setex(key, cacheTime, objectMapper.writeValueAsString(data));
            }
            return data;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private boolean isValidKey(String key) {
        // 仅示例性校验,实际中应视具体情况进行校验
        return key != null && key.matches("^user:\\d+$");
    }

    public interface DataProvider<T> {
        T getData();
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        ParameterValidationExample cache = new ParameterValidationExample(jedis);

        String userId = "123"; // 正确的用户ID
        String cacheKey = "user:" + userId;
        int cacheTime = 3600; // 缓存 1 小时

        User user = cache.getCachedData(cacheKey, User.class, () -> {
            // 模拟数据库查询
            return getUserFromDatabase(userId);
        }, cacheTime);

        if (user == null) {
            System.out.println("User not found.");
        } else {
            System.out.println("User: " + user);
        }

        jedis.close();
    }

    private static User getUserFromDatabase(String userId) {
        // 模拟数据库查询
        User user = new User();
        user.setId(userId);
        user.setName("John Doe");
        user.setEmail("john.doe@example.com");
        return user;
    }

    static class User {
        private String id;
        private String name;
        private String email;

        // Getters and Setters

        @Override
        public String toString() {
            return "User{id='" + id + "', name='" + name + "', email='" + email + "'}";
        }
    }
}

总结

通过以上示例代码,您可以分别使用缓存空结果、布隆过滤器和参数校验的方法来解决Redis的缓存穿透问题。选择合适的方法可以有效地减少对数据库的无效访问,提高系统的性能和稳定性。

相关推荐
徐子童1 天前
网络协议---TCP协议
网络·网络协议·tcp/ip·面试题·1024程序员节
扫地的小何尚3 天前
NVIDIA RTX PC开源AI工具升级:加速LLM和扩散模型的性能革命
人工智能·python·算法·开源·nvidia·1024程序员节
数据皮皮侠AI4 天前
上市公司股票名称相似度(1990-2025)
大数据·人工智能·笔记·区块链·能源·1024程序员节
开开心心就好4 天前
系统清理工具清理缓存日志,启动卸载管理
linux·运维·服务器·神经网络·cnn·pdf·1024程序员节
Evan东少7 天前
[踩坑]笔记本Ubuntu20.04+NvidiaRTX5060驱动+cuda+Pytorch+ROS/Python实现人脸追踪(环境准备)
1024程序员节
不爱编程的小陈8 天前
C/C++每日面试题
面试·职场和发展·1024程序员节
开开心心就好8 天前
右键菜单管理工具,添加程序自定义名称位置
linux·运维·服务器·ci/cd·docker·pdf·1024程序员节
码农三叔9 天前
(4-2-05)Python SDK仓库:MCP服务器端(5)Streamable HTTP传输+Streamable HTTP传输
开发语言·python·http·大模型·1024程序员节·mcp·mcp sdk
西幻凌云13 天前
初始——正则表达式
c++·正则表达式·1024程序员节
启芯硬件13 天前
电源XL6009E1的dieshot细节分析-芯片设计干货
大数据·经验分享·硬件工程·1024程序员节