美文网首页
redis工具类锁-分布式锁

redis工具类锁-分布式锁

作者: Raral | 来源:发表于2021-10-15 13:58 被阅读0次

    redis解决高并发场景

    限时在一个时间点抢购一个限量的商品

    正常实现(没有锁)

    1. controller
    @RestController
    @RequestMapping("/api")
    public class RedisTest {
    
        @Autowired
        private IAccUserService iAccUserService;
    
        @Autowired
        private SkuMapper skuMapper;
    
    
        @Autowired
        private IAppDiseaseService iAppDiseaseService;
    
        @PostMapping("/addAccUser")
        public String addUserInfo() {
            AccUser accUser = new AccUser();
            accUser.setId(IdWorker.getId());
            accUser.setUsername("test" + IdWorker.getId());
            try {
                //查询商品的库存
                Sku sku = skuMapper.selectById("222");
                Integer stock = sku.getStock();
                if(stock > 0) {
                    //插入一条记录
                    boolean save = iAccUserService.save(accUser);
                    if(save) {
                        //更新库存
                        skuMapper.updateStock2("222", 1);
                    System.out.println("当前库存:" + stock);
                    }
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            }
            return "调用成功!";
        }
    }
    
    1. 通过jemter并发测试,0秒触发100次接口,出现的问题
      [图片上传失败...(image-265ed3-1634277425216)]
      超卖了88个
      [图片上传失败...(image-165906-1634277425216)]
      多产生了88个记录

    分布式锁

    1. controller
    @RestController
    @RequestMapping("/api")
    public class RedisTest {
    
        @Autowired
        private IAccUserService iAccUserService;
    
        @Autowired
        private SkuMapper skuMapper;
    
        @Resource
        private RedisLock redisLock;
    
        @Resource
        private RedisOperator redisOperator;
    
        @PostMapping("/addAccUser")
        public String addUserInfo() {
            //模拟每次进来的用户
            long uid = IdWorker.getId();
            if(!redisLock.lock(String.valueOf(uid), RedisConst.LOCK_KEY_TPYE_SYNC_ISSUER)){
                ThreadUtil.safeSleep(300);
            }
    
            try {
                AccUser accUser = new AccUser();
                accUser.setId(uid);
                accUser.setUsername("test" + uid);
                //查询商品的库存
                Sku sku = skuMapper.selectById("222");
                Integer stock = sku.getStock();
                if(stock > 0) {
                    //插入一条记录
                    boolean save = iAccUserService.save(accUser);
                    if(save) {
                        //更新库存
                        skuMapper.updateStock2("222", 1);
                        System.out.println("当前库存:" + stock);
                    }
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                //在释放锁时候,保证是当前线程id加的锁
                if(String.valueOf(uid).equals(redisOperator.get(String.valueOf(uid)))) {
                    //假如这个卡顿一下,可能会删除 下一个线程2加的锁
                    //释放当前业务完成后释放锁
                    redisLock.releaseLock(String.valueOf(uid), RedisConst.LOCK_KEY_TPYE_SYNC_ISSUER);
                }
            }
            return "调用成功!";
        }
    }
    
    
    1. RedisLock
    @Slf4j
    @Component
    public class RedisLock {
        @Autowired
        private RedisTemplate redisTemplate;
    
        /**
         * 加锁(自动重试)
         *
         * @param key
         * @param lockKeyType
         * @return
         */
        public boolean tryLock(String key, String lockKeyType) {
            boolean flag = false;
            try {
                key = RedisConst.REDIS_LOCK_KEY_PREFIX + lockKeyType + key;
                log.info("加锁请求数据,key:{}", key);
    
                long lockTimeout = 500 * 161;
                long sleepTimeout = 500;
    
                for (int i = 1; i <= 160; i++) {
                    flag = this.lockOnce(key, lockTimeout);
                    if (flag) {
                        log.info("{}加锁第{}次,成功", key, i);
                        break;
                    } else {
                        // log.info("{}加锁第{}次,失败", key, i);
                        Thread.sleep(sleepTimeout);
                    }
                }
                if (flag) {
                    log.info("{}加锁成功", key);
                } else {
                    log.info("{}加锁失败", key);
                }
            } catch (Exception e) {
                e.printStackTrace();
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
            return flag;
        }
    
        /**
         * 加锁
         *
         * @param key
         * @param lockKeyType
         * @return
         */
        public boolean lock(String key, String lockKeyType) {
            key = RedisConst.REDIS_LOCK_KEY_PREFIX + lockKeyType + key;
            log.info("加锁请求数据,key:{}", key);
            long lockTimeout = 1 * 1000;
            boolean flag = this.lockOnce(key, lockTimeout);
            if (flag) {
                log.info("{}加锁成功", key);
            } else {
                log.info("{}加锁失败", key);
            }
            return flag;
        }
    
        /**
         * 解锁
         *
         * @param key
         * @param lockKeyType
         */
        public void releaseLock(String key, String lockKeyType) {
            key = RedisConst.REDIS_LOCK_KEY_PREFIX + lockKeyType + key;
            log.info("解锁请求数据,key:{}", key);
            try {
                redisTemplate.delete(key);
                log.info("{}解锁成功", key);
            } catch (Exception e) {
                log.error(StrUtil.format("{}解锁失败", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
        }
    
        private boolean lockOnce(String key, long timeout) {
            String value = key + "_" + System.currentTimeMillis();
            boolean flag = false;
            try {
                flag = redisTemplate.opsForValue().setIfAbsent(key, value,
                        timeout, TimeUnit.MILLISECONDS);
            } catch (Exception e) {
                e.printStackTrace();
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
            return flag;
        }
    }
    
    
    @Slf4j
    @Component
    public class RedisOperator {
        private static final String PREFIX="gzsz-cs-api:";
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        // Key(键),简单的key-value操作
    
        /**
         * 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
         *
         * @param key
         * @return
         */
        public long ttl(String key) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            long rest = 0;
            try {
                rest = redisTemplate.getExpire(fullKey);
            } catch (Exception e) {
                e.printStackTrace();
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
            return rest;
        }
    
        /**
         * 实现命令:expire 设置过期时间,单位秒
         *
         * @param key
         * @return
         */
        public void expire(String key, long timeout) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            try {
                redisTemplate.expire(fullKey, timeout, TimeUnit.SECONDS);
            } catch (Exception e) {
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
        }
    
        /**
         * 实现命令:INCR key,增加key一次
         *
         * @param key
         * @return
         */
        public long incr(String key, long delta) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            long num = 0;
            try {
                num = redisTemplate.opsForValue().increment(fullKey, delta);
            } catch (Exception e) {
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
            return num;
        }
    
        public long incrBy(String key) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            long andIncrement = 0;
            try {
                RedisAtomicLong entityIdCounter = new RedisAtomicLong(fullKey, redisTemplate.getConnectionFactory());
                andIncrement = entityIdCounter.getAndIncrement();
            } catch (Exception e) {
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
            return andIncrement;
        }
    
    
    
        /**
         * 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key
         */
        public Set<String> keys(String pattern) {
            Set<String> set = null;
            try {
                set = redisTemplate.keys(pattern);
            } catch (Exception e) {
                log.error("redis处理异常", e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
            return set;
        }
    
        /**
         * 实现命令:DEL key,删除一个key
         *
         * @param key
         */
        public void del(String key) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            try {
                redisTemplate.delete(fullKey);
            } catch (Exception e) {
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
        }
    
        // String(字符串)
    
        /**
         * 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
         *
         * @param key
         * @param value
         */
        public void set(String key, Object value) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            try {
                redisTemplate.opsForValue().set(fullKey, value);
            } catch (Exception e) {
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
        }
    
        /**
         * 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
         *
         * @param key
         * @param value
         * @param timeout
         *            (以秒为单位)
         */
        public void set(String key, String value, long timeout) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            try {
                redisTemplate.opsForValue().set(fullKey, value, timeout, TimeUnit.SECONDS);
            } catch (Exception e) {
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
        }
    
        /**
         * 实现命令:GET key,返回 key所关联的字符串值。
         *
         * @param key
         * @return value
         */
        public String get(String key) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            String value = null;
            try {
                value = (String)redisTemplate.opsForValue().get(fullKey);
            } catch (Exception e) {
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
            return value;
        }
    
        /**
         * 实现命令:GET key,返回 key所关联的字符串值。
         *
         * @param key
         * @return value
         */
        public Object getObject(String key) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            Object value = null;
            try {
                value = redisTemplate.opsForValue().get(fullKey);
            } catch (Exception e) {
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
            return value;
        }
    
        // Hash(哈希表)
    
        /**
         * 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value
         *
         * @param key
         * @param field
         * @param value
         */
        public void hset(String key, String field, Object value) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            try {
                redisTemplate.opsForHash().put(fullKey, field, value);
            } catch (Exception e) {
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
        }
    
        /**
         * 实现命令:HGET key field,返回哈希表 key中给定域 field的值
         *
         * @param key
         * @param field
         * @return
         */
        public String hget(String key, String field) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            String value = null;
            try {
                value = (String) redisTemplate.opsForHash().get(fullKey, field);
            } catch (Exception e) {
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
            return value;
        }
    
        /**
         * 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
         *
         * @param key
         * @param fields
         */
        public void hdel(String key, Object... fields) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            try {
                redisTemplate.opsForHash().delete(fullKey, fields);
            } catch (Exception e) {
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
        }
    
        /**
         * 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。
         *
         * @param key
         * @return
         */
        public Map<Object, Object> hgetall(String key) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            Map<Object, Object> map = null;
            try {
                map = redisTemplate.opsForHash().entries(fullKey);
            } catch (Exception e) {
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
            return map;
        }
    
        // List(列表)
    
        /**
         * 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头
         *
         * @param key
         * @param value
         * @return 执行 LPUSH命令后,列表的长度。
         */
        public long lpush(String key, String value) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            long len = 0;
            try {
                len = redisTemplate.opsForList().leftPush(fullKey, value);
            } catch (Exception e) {
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
            return len;
        }
    
        /**
         * 实现命令:LPOP key,移除并返回列表 key的头元素。
         *
         * @param key
         * @return 列表key的头元素。
         */
        public String lpop(String key) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            String value = null;
            try {
                value = (String)redisTemplate.opsForList().leftPop(fullKey);
            } catch (Exception e) {
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
            return value;
        }
    
        /**
         * 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。
         *
         * @param key
         * @param value
         * @return 执行 LPUSH命令后,列表的长度。
         */
        public long rpush(String key, String value) {
            String fullKey =new StringBuffer(PREFIX).append(key).toString();
            long len = 0;
            try {
                len = redisTemplate.opsForList().rightPush(fullKey, value);
            } catch (Exception e) {
                log.error(StrUtil.format("redis处理异常, key:{}", key), e);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
            return len;
        }
    }
    
    
    @Component
    public class RedisConst {
        public static final String REDIS_LOCK_KEY_PREFIX = "GZSZ_CS_API_LOCK_";
        /**
         * 同步商户key
         */
        public static final String LOCK_KEY_TPYE_SYNC_ISSUER = "SYNC_ISSUER_";
        public static final String LOCK_KEY_TPYE_MINI = "MINI_";
        public static final String CNYNO = "CNYNO";
        public static final String ACTNO = "ACTNO";
        public static final String STYNO = "STYNO";
        //商户号
        public static final String GMNO = "GMNO";
    }
    
    
    1. 通过jemter并发测试,0秒触发100次接口,出现的问题
      [图片上传失败...(image-ded16d-1634277425216)]
      超卖了52个
      [图片上传失败...(image-ec8d-1634277425216)]
      多产生了52记录

    库存放入redis

    相关文章

      网友评论

          本文标题:redis工具类锁-分布式锁

          本文链接:https://www.haomeiwen.com/subject/fkrmoltx.html