【redis】分布式锁

分布式锁

第一种分布式锁: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;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值