zoukankan      html  css  js  c++  java
  • 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; } }
  • 相关阅读:
    超过经理收入的员工
    搜索插入位置
    整数反转
    俩数之和
    tar 命令参数解释
    【记录】Transaction rolled back because it has been marked as rollback-only
    【转载】BIO、NIO、AIO
    【转载】JDK自带的log工具
    【转载】java8中的Calendar日期对象(LocalDateTime)
    【对象属性复制】BeanUtils.copyProperties(obj1, obj2);
  • 原文地址:https://www.cnblogs.com/t96fxi/p/12548801.html
Copyright © 2011-2022 走看看