[Redis 实现缓存,缓存雪崩解决方法,缓存穿透.......]

目录

前言:  

场景描述:

Java语言实现Redis缓存复杂流程的示例代码

 示例代码中实现了以下功能:

缓存雪崩解决方案:

实现缓存穿透保护

使用布隆过滤器进行缓存穿透保护:

使用空值缓存进行缓存穿透保护:

实现限流措施

使用令牌桶算法进行限流控制

使用Semaphore进行限流控制


前言:  

      给粉丝的小demo 笔记

场景描述:

作为Key-Value形态的数据格式,Redis 最先会被想到的应用场景便是作为数据缓存。 ,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力,而使用 Redis 缓存数据也非常简单,只需要通过string类型将序列化后的对象存起来即可,不过也有一些需要注意的地方:

  • 必须保证不同对象的 key 不会重复,并且使 key 尽量短,一般使用类名(表名)加主键拼接而成。
  • 选择一个优秀的序列化方式也很重要,目的是提高序列化的效率和减少内存占用。
  • 缓存内容与数据库的一致性,这里一般有两种做法:

①只在数据库查询后将对象放入缓存,如果对象发生了修改或删除操作,直接清除对应缓存(或设为过期)。
②在数据库新增和查询后将对象放入缓存,修改后更新缓存,删除后清除对应缓存(或设为过期)。
 

Java语言实现Redis缓存复杂流程的示例代码

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.List;
import java.util.Map;

public class RedisCacheDemo {
    private JedisPool jedisPool;

    public RedisCacheDemo(String host, int port, String password) {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(100);
        jedisPoolConfig.setMaxIdle(10);
        jedisPoolConfig.setMinIdle(1);
        jedisPoolConfig.setMaxWaitMillis(10000);

        this.jedisPool = new JedisPool(jedisPoolConfig, host, port, 10000, password);
    }

    public void set(String key, String value) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.set(key, value);
        }
    }

    public String get(String key) {
        try (Jedis jedis = jedisPool.getResource()) {
            return jedis.get(key);
        }
    }

    public void setHash(String key, Map map) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.hmset(key, map);
        }
    }

    public Map getHash(String key) {
        try (Jedis jedis = jedisPool.getResource()) {
            return jedis.hgetAll(key);
        }
    }

    public void setList(String key, List list) {
        try (Jedis jedis = jedisPool.getResource()) {
            for (String value : list) {
                jedis.rpush(key, value);
            }
        }
    }

    public List getList(String key) {
        try (Jedis jedis = jedisPool.getResource()) {
            return jedis.lrange(key, 0, -1);
        }
    }

    public void setExpire(String key, int seconds) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.expire(key, seconds);
        }
    }

    public void delete(String key) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.del(key);
        }
    }

    public void flushAll() {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.flushAll();
        }
    }
}
  • JedisPool.getResource()方法的作用是从连接池中获取一个Jedis实例。如果连接池中有空闲的Jedis实例,则返回一个空闲的Jedis实例;如果连接池中没有空闲的Jedis实例,则创建一个新的Jedis实例并返回。在获取Jedis实例后,需要使用try-with-resources语句,以确保Jedis实例的资源被正确关闭。

 示例代码中实现了以下功能:

  • set方法:设置缓存数据。
  • get方法:获取缓存数据。
  • setHash方法:设置哈希表缓存数据。
  • getHash方法:获取哈希表缓存数据。
  • setList方法:设置列表缓存数据。
  • getList方法:获取列表缓存数据。
  • setExpire方法:设置缓存过期时间。
  • delete方法:删除缓存数据。
  • flushAll方法:清空所有缓存数据。

在使用Redis缓存时,需要注意缓存的过期时间和缓存的命名规范,以避免缓存穿透和缓存雪崩等问题


缓存雪崩解决方案:

  • 设置缓存数据的过期时间随机化
  • 缓存数据的过期时间可以设置为一个随机值,避免大量缓存数据在同一时间失效。例如,可以在缓存数据的过期时间上加上一个随机值,使得缓存数据的过期时间分散在不同的时间点。
  • 使用多级缓存
  • 多级缓存可以避免缓存服务器宕机导致的缓存雪崩。例如,可以使用本地缓存和分布式缓存相结合的方式,将热点数据存储在本地缓存中,将冷数据存储在分布式缓存中。
  • 实现缓存预热
  • 缓存预热可以避免缓存失效导致的缓存雪崩。例如,在应用程序启动时,可以将热点数据加载到缓存中,以提前缓存数据,避免缓存失效导致的缓存雪崩。
  • 实现缓存穿透保护
  • 缓存穿透是指查询一个不存在的数据,导致大量请求直接访问数据库,从而导致数据库压力过大,甚至崩溃。为了避免缓存穿透,可以使用布隆过滤器等技术进行缓存穿透保护。
  • 实现限流措施
  • 限流可以避免大量请求直接访问数据库,从而导致数据库压力过大,甚至崩溃。例如,可以使用令牌桶算法等技术进行限流控制,限制请求的访问频率和并发数。

实现缓存穿透保护

使用布隆过滤器进行缓存穿透保护:

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Random;

public class RedisCacheDemo {
    private JedisPool jedisPool;
    private BloomFilter bloomFilter;

    public RedisCacheDemo(String host, int port, String password) {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(100);
        jedisPoolConfig.setMaxIdle(10);
        jedisPoolConfig.setMinIdle(1);
        jedisPoolConfig.setMaxWaitMillis(10000);

        this.jedisPool = new JedisPool(jedisPoolConfig, host, port, 10000, password);

        // 初始化布隆过滤器
        this.bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), 1000000, 0.01);
    }

    public void set(String key, String value) {
        try (Jedis jedis = jedisPool.getResource()) {
            Random random = new Random();
            int expireTime = random.nextInt(60) + 60; // 设置缓存过期时间为60~120秒之间的随机值
            jedis.setex(key, expireTime, value);
            bloomFilter.put(key); // 将key添加到布隆过滤器中
        }
    }

    public String get(String key) {
        if (!bloomFilter.mightContain(key)) { // 如果布隆过滤器中不存在该key,则直接返回null
            return null;
        }

        try (Jedis jedis = jedisPool.getResource()) {
            return jedis.get(key);
        }
    }
}
使用Google Guava库中的BloomFilter实现了缓存穿透保护。在设置缓存数据时,将key添加到布隆过滤器中;在获取缓存数据时,先判断key是否存在于布隆过滤器中,如果不存在,则直接返回null,避免大量请求直接访问数据库。

使用空值缓存进行缓存穿透保护:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.List;
import java.util.Map;
import java.util.Random;

public class RedisCacheDemo {
    private JedisPool jedisPool;

    public RedisCacheDemo(String host, int port, String password) {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(100);
        jedisPoolConfig.setMaxIdle(10);
        jedisPoolConfig.setMinIdle(1);
        jedisPoolConfig.setMaxWaitMillis(10000);

        this.jedisPool = new JedisPool(jedisPoolConfig, host, port, 10000, password);
    }

    public void set(String key, String value) {
        try (Jedis jedis = jedisPool.getResource()) {
            Random random = new Random();
            int expireTime = random.nextInt(60) + 60; // 设置缓存过期时间为60~120秒之间的随机值
            jedis.setex(key, expireTime, value);
        }
    }

    public String get(String key) {
        String value = null;

        try (Jedis jedis = jedisPool.getResource()) {
            value = jedis.get(key);
        }

        if (value == null) { // 如果缓存中不存在该key,则将空值缓存到Redis中,并设置较短的过期时间
            try (Jedis jedis = jedisPool.getResource()) {
                jedis.setex(key, 60, "");
            }
        }

        return value;
    }
}

依赖:


    redis.clients
    jedis
    3.6.0

 

  1. Google Guava依赖:


    com.google.guava
    guava
    30.1.1-jre

 


实现限流措施

可以采取限流措施,限制请求的访问频率和并发数。以下是使用Java语言实现限流措施的示例代码:

使用令牌桶算法进行限流控制

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.concurrent.TimeUnit;

public class RedisRateLimiter {
    private JedisPool jedisPool;
    private int capacity; // 令牌桶容量
    private int rate; // 令牌发放速率,单位:令牌/秒
    private String key; // Redis键名
    private long lastRefillTime; // 上次令牌桶填充时间
    private double tokens; // 令牌桶中当前令牌数量

    public RedisRateLimiter(String host, int port, String password, int capacity, int rate, String key) {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(100);
        jedisPoolConfig.setMaxIdle(10);
        jedisPoolConfig.setMinIdle(1);
        jedisPoolConfig.setMaxWaitMillis(10000);

        this.jedisPool = new JedisPool(jedisPoolConfig, host, port, 10000, password);
        this.capacity = capacity;
        this.rate = rate;
        this.key = key;
        this.lastRefillTime = System.currentTimeMillis();
        this.tokens = capacity;
    }

    public boolean tryAcquire() {
        try (Jedis jedis = jedisPool.getResource()) {
            long now = System.currentTimeMillis();
            long elapsedTime = now - lastRefillTime;
            lastRefillTime = now;

            // 计算令牌桶中当前令牌数量
            tokens += elapsedTime * rate / 1000.0;
            if (tokens > capacity) {
                tokens = capacity;
            }

            // 尝试获取令牌
            if (tokens >= 1) {
                tokens -= 1;
                jedis.setex(key, 1, ""); // 设置Redis键的过期时间为1秒,避免占用过多内存
                return true;
            } else {
                return false;
            }
        }
    }
}
上面的示例代码中,使用令牌桶算法实现了限流控制。在每次请求到达时,先计算令牌桶中当前令牌数量,然后尝试获取令牌,如果令牌桶中有足够的令牌,则返回true,否则返回false。

使用Semaphore进行限流控制

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class RedisRateLimiter {
    private JedisPool jedisPool;
    private Semaphore semaphore; // 信号量
    private String key; // Redis键名

    public RedisRateLimiter(String host, int port, String password, int permits, String key) {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(100);
        jedisPoolConfig.setMaxIdle(10);
        jedisPoolConfig.setMinIdle(1);
        jedisPoolConfig.setMaxWaitMillis(10000);

        this.jedisPool = new JedisPool(jedisPoolConfig, host, port, 10000, password);
        this.semaphore = new Semaphore(permits);
        this.key = key;
    }

    public boolean tryAcquire() {
        try (Jedis jedis = jedisPool.getResource()) {
            if (semaphore.tryAcquire()) {
                jedis.setex(key, 1, ""); // 设置Redis键的过期时间为1秒,避免占用过多内存
                return true;
            } else {
                return false;
            }
        }
    }
}

示例代码中,使用Semaphore实现了限流控制。在每次请求到达时,先尝试获取信号量,如果获取成功,则返回true,否则返回false。  具体业务逻辑需要看着修改

待持续更新

你可能感兴趣的:(粉丝栏,Redis专栏,缓存,redis,数据库)