redis工具类锁-分布式锁

2021-10-15  本文已影响0人  Raral

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

上一篇下一篇

猜你喜欢

热点阅读