redis工具类锁-分布式锁
2021-10-15 本文已影响0人
Raral
redis解决高并发场景
限时在一个时间点抢购一个限量的商品
正常实现(没有锁)
- 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 "调用成功!";
}
}
- 通过jemter并发测试,0秒触发100次接口,出现的问题
[图片上传失败...(image-265ed3-1634277425216)]
超卖了88个
[图片上传失败...(image-165906-1634277425216)]
多产生了88个记录
分布式锁
- 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 "调用成功!";
}
}
- 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";
}
- 通过jemter并发测试,0秒触发100次接口,出现的问题
[图片上传失败...(image-ded16d-1634277425216)]
超卖了52个
[图片上传失败...(image-ec8d-1634277425216)]
多产生了52记录