目录
前言:
场景描述:
Java语言实现Redis缓存复杂流程的示例代码
示例代码中实现了以下功能:
缓存雪崩解决方案:
实现缓存穿透保护
使用布隆过滤器进行缓存穿透保护:
使用空值缓存进行缓存穿透保护:
实现限流措施
使用令牌桶算法进行限流控制
使用Semaphore进行限流控制
给粉丝的小demo 笔记
作为Key-Value形态的数据格式,Redis 最先会被想到的应用场景便是作为数据缓存。 ,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力,而使用 Redis 缓存数据也非常简单,只需要通过string类型将序列化后的对象存起来即可,不过也有一些需要注意的地方:
①只在数据库查询后将对象放入缓存,如果对象发生了修改或删除操作,直接清除对应缓存(或设为过期)。
②在数据库新增和查询后将对象放入缓存,修改后更新缓存,删除后清除对应缓存(或设为过期)。
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();
}
}
}
在使用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
- 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。
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。 具体业务逻辑需要看着修改
待持续更新