大数跨境
0
0

从缓存穿透到热点优化:构建高效缓存架构的实战技巧

从缓存穿透到热点优化:构建高效缓存架构的实战技巧 二进制跳动
2025-11-22
2
导读:从缓存穿透到热点优化:构建高效缓存架构的实战技巧

高性能缓存架构是为了提高系统的响应速度和降低数据库的压力,适用于大规模的分布式系统,尤其是需要快速响应的大型电商平台、社交网络等。

1. 缓存穿透

问题:
缓存穿透的现象发生在当查询的数据不在缓存中,也不在数据库中时,导致查询直接打到数据库。比如,当用户查询的商品不存在时,系统会去查询数据库,浪费资源且增加了数据库的压力。

为什么会出现:

  • 用户请求的数据本身在数据库中不存在,缓存没有数据返回。

  • 查询请求直接击穿缓存,增加了数据库负载。

场景:
假设你有一个电商平台,当用户查询某个商品信息时,如果该商品不存在,系统直接查询数据库,这样每次查询不存在的商品时都会增加数据库压力。

解决方案:

  • 使用缓存空对象:当查询到数据为空时,将“空对象”缓存。这样即使查询到的是空值,缓存也能避免频繁查询数据库。

为什么使用空对象缓存:

  • 这样做的好处是避免了相同的数据再次查询数据库,提高了效率。

  • 适用于查询频繁且大部分为空的数据场景。

代码示例:

import redis.clients.jedis.Jedis;
public class CachePenetrationExample {
    private Jedis jedis = new Jedis("localhost");
    public String getUser(int userId) {        String key = "user:" + userId;        String user = jedis.get(key);
        if (user == null) {            // 从数据库查询            user = queryDatabase(userId);            if (user != null) {                jedis.set(key, user);  // 缓存数据            } else {                jedis.set(key, "null");  // 缓存空对象,防止下次查询数据库            }        }        return user;    }
    private String queryDatabase(int userId) {        // 假设这个方法从数据库查询用户信息        return null;  // 模拟查询到的结果为空    }}

2. 缓存雪崩

问题:
缓存雪崩是指大量缓存同时过期,导致请求同时访问数据库,增加数据库的压力。

为什么会出现:

  • 当多个缓存同时过期时,所有的请求都直接访问数据库,造成数据库性能瓶颈。

场景:
电商平台的商品信息缓存和用户会话缓存都有相同的过期时间,导致某一时刻大量缓存失效,系统压力骤增。

解决方案:

  • 设置缓存过期时间差:通过不同的过期时间避免缓存的同步失效。

  • 后台更新机制:提前更新缓存,避免突然的缓存失效。


import redis.clients.jedis.Jedis;
public class CacheAvalancheExample {
    private Jedis jedis = new Jedis("localhost");
    public String getProduct(int productId) {        String key = "product:" + productId;        String product = jedis.get(key);
        if (product == null) {            // 从数据库查询            product = queryProductFromDatabase(productId);            if (product != null) {                // 设置不同的过期时间,避免雪崩                int expiryTime = (int) (Math.random() * (900 - 300 + 1)) + 300// 5-15分钟随机                jedis.setex(key, expiryTime, product);            }        }        return product;    }
    private String queryProductFromDatabase(int productId) {        // 假设这个方法从数据库查询商品信息        return "Product Info";  // 返回商品信息    }}

3. 缓存热点

问题:
缓存热点是指某些数据被频繁访问,导致该数据的缓存压力过大。

为什么会出现:

  • 热点数据频繁被访问,但缓存更新不及时或缓存存储不均衡,导致性能瓶颈。

场景:
一个新闻网站的热点新闻被大量访问,缓存系统无法处理这些请求,导致数据库压力增大。

解决方案:

  • 设置多级缓存:将热点数据分布到不同层级的缓存中,减少单一缓存系统的压力。


import redis.clients.jedis.Jedis;
public class CacheHotspotExample {
    private Jedis redis = new Jedis("localhost");    private Cache localCache = new Cache();
    public String getArticle(int articleId) {        String key = "article:" + articleId;
        // 检查本地缓存        String article = localCache.get(key);        if (article == null) {            // 本地缓存未命中,再查Redis缓存            article = redis.get(key);            if (article == null) {                // 从数据库获取                article = queryArticleFromDatabase(articleId);                // 设置Redis缓存                redis.setex(key, 600, article);  // 10分钟过期            }            // 更新本地缓存            localCache.put(key, article);        }        return article;    }
    private String queryArticleFromDatabase(int articleId) {        // 假设这个方法从数据库查询文章        return "Hot Article Content";  // 返回文章内容    }
    // 简单的本地缓存实现    class Cache {        private java.util.Map<StringString> cache = new java.util.HashMap<>();
        public String get(String key) {            return cache.get(key);        }
        public void put(String key, String value) {            cache.put(key, value);        }    }}

4. 实现方式:程序代码实现的中间层方式

问题:
为了提高缓存系统的可扩展性和灵活性,需要通过中间层来管理缓存。

为什么需要缓存中间层:

  • 缓存中间层将缓存操作与业务逻辑解耦,可以灵活地进行缓存策略的设置,增强系统的可维护性。

场景:
一个电商平台,用户信息和商品信息需要分别缓存,使用中间层来管理不同类型的缓存,使得缓存操作更灵活。

import redis.clients.jedis.Jedis;
public class CacheMiddleLayerExample {
    private Jedis jedis = new Jedis("localhost");
    // 缓存数据    public void cacheData(String key, String value, int ttl) {        jedis.setex(key, ttl, value);  // 设置缓存    }
    // 获取缓存数据    public String getCachedData(String key) {        String data = jedis.get(key);        if (data == null) {            return null;  // 缓存未命中        }        return data;    }
    public static void main(String[] args) {        CacheMiddleLayerExample cache = new CacheMiddleLayerExample();        cache.cacheData("user:1001""User Data"3600);  // 缓存用户数据,TTL为1小时        String userData = cache.getCachedData("user:1001");        System.out.println("User Data: " + userData);    }}

5. 存储数据不存储

问题:
如果没有合理管理缓存的数据,可能导致内存浪费或存储不一致,影响缓存性能。

为什么会出现:

  • 如果缓存的数据没有被及时清理,或者清理策略不当,可能导致内存被占满,影响其他缓存的命中率。

解决方案:

  • 合理设置内存限制和清理策略:使用LRU(最近最少使用)算法来清理不常用的数据。

# Redis 配置示例:设置最大内存,并使用LRU算法清理过期数据maxmemory 256mbmaxmemory-policy allkeys-lru


【声明】内容源于网络
0
0
二进制跳动
15 年 + 技术老兵 架构师|技术总监|科技创业技术合伙人 曾任职苏宁科技、电讯盈科、联想云 专注架构设计与技术落地
内容 739
粉丝 0
二进制跳动 15 年 + 技术老兵 架构师|技术总监|科技创业技术合伙人 曾任职苏宁科技、电讯盈科、联想云 专注架构设计与技术落地
总阅读62
粉丝0
内容739