原文链接:
Redis缓存穿透,缓存击穿,缓存雪崩实战代码总结
package com.macro.mall.portal.service.impl;
import com.macro.mall.common.utils.JsonUtils;
import com.macro.mall.common.utils.RedisUtil;
import com.macro.mall.model.PmsProduct;
import com.macro.mall.portal.dao.PmsProductDao;
import com.macro.mall.portal.service.PmsProductService;
import com.mysql.cj.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import javax.annotation.Resource;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 商品管理Service实现类
* Created by macro on 2018/4/26.
*/
@Service
public class PmsProductServiceImpl implements PmsProductService {
private static final Logger LOGGER = LoggerFactory.getLogger(PmsProductServiceImpl.class);
@Autowired
private PmsProductDao pmsProductDao;
@Autowired
private RedisUtil redisUtil;
@Resource
private RedisTemplate<String, Object> redisTemplate;
private static final String KEY_PREFIX = "sku:info:id:";
private static final String KEY = "sku:id:";
//锁名称前缀
public static final String LOCK_PREFIX = "redis:lock:id:";
/**
* 查询商品详情
*
* @param id
* @return
*/
@Override
public PmsProduct list(Long id,String ip) {
System.out.println("当前" + ip + "正在访问");
//商品详情的key
String key = KEY_PREFIX;
String lockKey = LOCK_PREFIX + id;
String infoKey = KEY + id;
// 创建布隆过滤器对象,防止缓存击穿
/* BloomFilter<Integer> filter = BloomFilter.create(
Funnels.integerFunnel(),
15000,
0.01);
//添加数据到布隆过滤器
String ids = id.toString();
filter.putAll(Integer.valueOf(ids));*/
//查询缓存中是否存在
if (redisUtil.hasKey(key)) {
//查询缓存
return (PmsProduct) redisUtil.get(key);
} else {
System.out.println("ip为" + ip + "在缓存中没有查到,申请分布式锁" + lockKey);
//设置分布式锁,防止缓存击穿
String token = UUID.randomUUID().toString();
//redisTemplate设置分布式锁
//boolean lock = redisUtil.lock(lockKey);
Jedis jedis = new Jedis("localhost", 6379);
//防止删除别人的锁
String lock = jedis.set(lockKey, token, "nx", "px", 10 * 1000);
if (null != lock && "OK".equals(lock)) {
//设置成功,有权在锁有效时间内查询数据库
PmsProduct list = pmsProductDao.list(id);
//防止某用户抢不到锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list != null) {
BoundHashOperations<String, Object, Object> stringObjectObjectBoundHashOperations = redisTemplate.boundHashOps(key);
//放入redis
stringObjectObjectBoundHashOperations.put(infoKey, JsonUtils.serialize(list));
Random r = new Random();
//设置key的随机过期时间,防止缓存雪崩
stringObjectObjectBoundHashOperations.expire(r.nextInt(123), TimeUnit.SECONDS);
return list;
} else {
//防止缓存穿透,设置为null的key以及过期时间
BoundHashOperations<String, Object, Object> hashOperations = redisTemplate.boundHashOps(key);
hashOperations.put(key, "");
hashOperations.expire(60 * 3, TimeUnit.SECONDS);
}
//判断是否是删除自己的锁
String localToken = jedis.get(lockKey);
if (null != localToken && token.equals(localToken)) {
String script = "if redis.call('get',KEYS[1] == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Long eval = (Long) jedis.eval(script, Integer.parseInt(lockKey),localToken);
if (eval != null && eval != 0) {
//使用lua脚本在高并发情况下,获取key的同时删除key
jedis.del(lockKey);
}
System.out.println("ip" + ip + "申请分布式锁成功,并已删除");
}
//redisTemplate释放分布式锁
//redisUtil.deleteLock(key);
} else {
System.out.println("ip为" + ip + "申请分布式锁失败" + lockKey);
//设置自旋,重新访问
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//直接return,防止产生未执行线程
return list(id, ip);
}
}
return null;
}
/**
* 获得锁
*
* @param lock
* @return
*/
public boolean lock(String lock) {
return (boolean) redisTemplate.execute((RedisCallback) connection -> {
//获取时间毫秒值
long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
//获取锁
Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
if (acquire) {
return true;
} else {
byte[] bytes = connection.get(lock.getBytes());
//非空判断
if (Objects.nonNull(bytes) && bytes.length > 0) {
long expireTime = Long.parseLong(new String(bytes));
// 如果锁已经过期
if (expireTime < System.currentTimeMillis()) {
// 重新加锁,防止死锁
byte[] set = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1).getBytes());
return Long.parseLong(new String(set)) < System.currentTimeMillis();
}
}
}
return false;
});
}
/**
* 删除锁
*
* @param key
*/
public void deleteLock(String key) {
redisTemplate.delete(key);
}