分布式锁
第一种分布式锁:redis自带一个分布式锁,set ex nx
第二种分布式锁:redisson框架
题外话
// 为了防止缓存穿透将,null或者空字符串值设置给redis
jedis.setex("sku:"+skuId+":info",60*3,JSON.toJSONString(""));
//防止缓存穿透
redisTemplate.boundValueOps("sku:"+skuId+":item").set("",1,TimeUnit.MINUTES);
//两种写法效果一样
工具类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* <p>
* 取自于网络
* 选择合适的变量作为key值,这个变量可以允许同一时间只有一个线程执行某段业务代码
* 相对于key的value如何定义?
* 这里我们设定一个requestId作为value值,这个值可以在实际编程中使用UUID来生成,这样做的好处是可以保证加锁和解锁的是同一线程
* 如何防止死锁?
* 考虑到Redis的命令可以设置生命周期,我们最好的办法就是为每个加锁的业务都要求使用者根据业务设定合理的过期时间,业务处理完之后尽可能快的释放锁
* 在高并发的场景下,同一时间只能有一个线程成功加锁,如何实现?
* 自然我们想到Redis的setNx命令
* </p>
* @since 2019/10/26
* @author ygg
*
*/
@Component
public class RedisLockHandler {
public static final String LOCK_PREFIX = "redis_lock:";
@Resource
private RedisTemplate<String, Object> redisTemplate;
public boolean lock(String key, String requestId, long expire) {
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
Boolean result = connection.set((LOCK_PREFIX+key).getBytes(), requestId.getBytes(), Expiration.from(expire, TimeUnit.SECONDS), RedisStringCommands.SetOption.SET_IF_ABSENT);
return result;
});
}
public boolean releaseLock(String key, String requestId) {
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Boolean result = connection.eval(script.getBytes(), ReturnType.BOOLEAN, 1, (LOCK_PREFIX+key).getBytes(), requestId.getBytes());
return result;
});
}
}
2.redisUtil
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisUtil {
private JedisPool jedisPool;
public void initPool(String host,int port ,int database){
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(200);
poolConfig.setMaxIdle(30);
poolConfig.setBlockWhenExhausted(true);
poolConfig.setMaxWaitMillis(10*1000);
poolConfig.setTestOnBorrow(true);
jedisPool=new JedisPool(poolConfig,host,port,20*1000);
}
public Jedis getJedis(){
Jedis jedis = jedisPool.getResource();
return jedis;
}
}
举个例子
使用.redisTemplate
/**
* <p>
* 库存单元表 服务实现类
* </p>
*
* @author 披着床单的小王子
* @since 2019-10-23
*/
@Service
public class PmsSkuInfoServiceImpl extends ServiceImpl<PmsSkuInfoMapper, PmsSkuInfo> implements PmsSkuInfoService {
@Autowired
PmsSkuImageService pmsSkuImageService;
@Autowired
PmsSkuAttrValueService pmsSkuAttrValueService;
@Autowired
PmsSkuSaleAttrValueService pmsSkuSaleAttrValueService;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Autowired
CommonRedisHelper redisHelper;
@Autowired
RedisLockHandler redisLockHandler;
/**
* 查看item详情
* @param skuId
* @return
*/
@Override
public PmsSkuInfoVo getSkuGroupById(String skuId) {
//获取缓存
String s = redisTemplate.boundValueOps("sku:" + skuId + ":item").get();
if (StringUtils.isEmpty(s)){
//获取分布式锁,方法1
//boolean lock = redisHelper.lock("sku:" + skuId + ":item");
//获取分布式锁,方法1
String requestId = UUID.randomUUID().toString();
boolean lock = redisLockHandler.lock("sku:" + skuId + ":item", requestId, 3000);
if(lock){
PmsSkuInfoVo vo = new PmsSkuInfoVo();
PmsSkuInfo pmsSkuInfo = baseMapper.selectById(skuId);
if (pmsSkuInfo==null){
//redisHelper.delete("sku:" + skuId + ":lock");
redisLockHandler.releaseLock("sku:" + skuId + ":item",requestId);
//防止缓存穿透
redisTemplate.boundValueOps("sku:"+skuId+":item").set("",1,TimeUnit.MINUTES);
return vo;
}
BeanUtils.copyProperties(pmsSkuInfo,vo);
List<PmsSkuImage> list = pmsSkuImageService.list(new QueryWrapper<PmsSkuImage>()
.lambda().eq(PmsSkuImage::getSkuId, skuId));
vo.setSkuImageList(list);
//添加到缓存
redisTemplate.boundValueOps("sku:"+skuId+":item")
.set(JSON.toJSONString(vo),5,TimeUnit.MINUTES);
//释放分布式锁
//redisHelper.delete("sku:" + skuId + ":item");
redisLockHandler.releaseLock("sku:" + skuId + ":item",requestId);
return vo;
}else{
throw new RuntimeException("网络错误, 请稍等再试");
}
}
PmsSkuInfoVo pmsSkuInfoVo = JSON.parseObject(s, PmsSkuInfoVo.class);
return pmsSkuInfoVo;
}
使用 jedis
@Service
public class SkuServiceImpl implements SkuService {
@Autowired
PmsSkuInfoMapper pmsSkuInfoMapper;
@Autowired
PmsSkuAttrValueMapper pmsSkuAttrValueMapper;
@Autowired
PmsSkuSaleAttrValueMapper pmsSkuSaleAttrValueMapper;
@Autowired
PmsSkuImageMapper pmsSkuImageMapper;
@Autowired
RedisUtil redisUtil;
/**
* 查看item详情
* @param skuId
* @return
*/
@Override
public PmsSkuInfo getSkuById(String skuId,String ip) {
System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"进入的商品详情的请求");
PmsSkuInfo pmsSkuInfo = new PmsSkuInfo();
// 链接缓存
Jedis jedis = redisUtil.getJedis();
// 查询缓存
String skuKey = "sku:"+skuId+":info";
String skuJson = jedis.get(skuKey);
if(StringUtils.isNotBlank(skuJson)){//if(skuJson!=null&&!skuJson.equals(""))
System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"从缓存中获取商品详情");
pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
}else{
// 如果缓存中没有,查询mysql
System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"发现缓存中没有,申请缓存的分布式锁:"+"sku:" + skuId + ":lock");
// 设置分布式锁
String token = UUID.randomUUID().toString();
String OK = jedis.set("sku:" + skuId + ":lock", token, "nx", "px", 10*1000);// 拿到锁的线程有10秒的过期时间
if(StringUtils.isNotBlank(OK)&&OK.equals("OK")){
// 设置成功,有权在10秒的过期时间内访问数据库
System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"有权在10秒的过期时间内访问数据库:"+"sku:" + skuId + ":lock");
pmsSkuInfo = getSkuByIdFromDb(skuId);
if(pmsSkuInfo!=null){
// mysql查询结果存入redis
jedis.set("sku:"+skuId+":info",JSON.toJSONString(pmsSkuInfo));
}else{
// 数据库中不存在该sku
// 为了防止缓存穿透将,null或者空字符串值设置给redis
jedis.setex("sku:"+skuId+":info",60*3,JSON.toJSONString(""));
}
// 在访问mysql后,将mysql的分布锁释放
System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"使用完毕,将锁归还:"+"sku:" + skuId + ":lock");
String lockToken = jedis.get("sku:" + skuId + ":lock");
if(StringUtils.isNotBlank(lockToken)&&lockToken.equals(token)){
//jedis.eval("lua");可与用lua脚本,在查询到key的同时删除该key,防止高并发下的意外的发生
jedis.del("sku:" + skuId + ":lock");// 用token确认删除的是自己的sku的锁
}
}else{
// 设置失败,自旋(该线程在睡眠几秒后,重新尝试访问本方法)
System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"没有拿到锁,开始自旋");
return getSkuById(skuId,ip);
}
}
jedis.close();
return pmsSkuInfo;
}