redis分布式锁

情景:秒杀减库存操作,先从数据库查库存,然后再扣减

方案一:

用数据库的行锁,select * from xxx for update,将这行锁住,

缺点: 慢,很多数据会放在内存或redis中

方案二:

synchronized关键字 增加方法锁,

缺点:1慢,2而且无法做到细颗粒控制,比如有多个商品减库存会互相影响。3只适合单点应用,如果有集群不同用户就不能同步

@Service
public class KillService {
    @Autowired
    RedisLock redisLock;
    static Map<String, Integer> products;
    static Map<String, Integer> stock;
    static Map<String, String> orders;
    private static final long TIMEOUT = 1000*10;
    static {
        products = new HashMap<>();
        stock = new HashMap<>();
        orders = new HashMap<>();
        products.put("123456", 100000);
        stock.put("123456", 100000);
    }
    
    public String query(String prodId) {
        return "限量"+products.get(prodId)+"份,还剩:"+stock.get(prodId)
               +"份,下单数目:"+orders.size();
    }
    
    public synchronized void kill(String prodId) {
        // 加锁
        long time = System.currentTimeMillis()+TIMEOUT;
        if(!redisLock.lockNew(prodId, String.valueOf(Thread.currentThread()), 3)) {
            throw new SellException(101, "人太多,稍候再试");
        }
        // 查库存
        int stockNum = stock.get(prodId);
        if(stockNum==0) {
            throw new SellException(1000,"库存不足");
        }
        // 下单
        orders.put(KeyUtil.generateKey(), prodId);
        // 减库存
        stockNum = stockNum -1;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        stock.put(prodId, stockNum);
        // 解锁
        redisLock.unlockNew(prodId, String.valueOf(Thread.currentThread()));
    }
}
@Component
public class RedisLock {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    long TIMEOUT = 1000*10;
    
    // 1.以客户端ID为value保证只有该客户端能解锁。2.设值和超时是同一个命令,原子性,防止中途崩溃,永远不超时
    //
    public boolean lockNew(String key, String requestId, long timeout) {
        return redisTemplate.opsForValue().setIfAbsent(key, requestId, 10, TimeUnit.SECONDS);
    }
    // lua代码当成一个命令执行,eval()方法可以确保原子性
    public boolean unlockNew(String key, String requestId) {
        String scriptText = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> getRedisScript = new DefaultRedisScript<Long>();
        getRedisScript.setResultType(Long.class);
        getRedisScript.setScriptText(scriptText);
        Long result = redisTemplate.opsForValue().getOperations().execute(getRedisScript, Collections.singletonList(key), requestId);
        logger.debug("----------redis:"+ result);
        // TODO
        return (result==1);
    }
 
// 以prodId为key, 超时时间为value, 通过判断value来是否超时获取锁
public boolean lock(String key, String value) { // 如果不存在则设置,存在则不处理 if(redisTemplate.opsForValue().setIfAbsent(key, value)) { return true; } // 两个线程同时到这里,他们的key和value一样,在下面设置时需要判断是否已经被另一个线程赋值了 String currentValue = redisTemplate.opsForValue().get(key); // 如果已经过期 if(!StringUtils.isEmpty(currentValue) && Long.valueOf(currentValue) < System.currentTimeMillis()) { // 设置并获取旧的值, 有问题!!!! 可能会覆盖别人的锁,get之后有人设了锁,不是原子性的 // 还有个问题是没有标识客户端无法,解锁时无法识别时不是本人 String oldValue = redisTemplate.opsForValue().getAndSet(key, value); // 没有被其他线程赋值,则返回成功 if(currentValue.equals(oldValue)) { return true; } } return false; } public boolean unlock(String key, String value) { try { String currentValue = redisTemplate.opsForValue().get(key); // 非原子性,中间若已经超时被别人加锁,会删掉别人的锁 if(value.equals(currentValue)) { return redisTemplate.opsForValue().getOperations().delete(key); } } catch (Exception e) { logger.error("【redis分布式解锁】异常,{}", e); } return false; } }
原文地址:https://www.cnblogs.com/t96fxi/p/12548801.html