一.概念
资源竞争:多个用户同时访问同一个资源。
eg:理想情况下,顾客A去买车票,提交订单并付款,付款成功后修改该票状态,由待出售改为已出售,这时其他用户买票,系统查询待出售票就不会查到该票。
如果系统中用户较多,多个用户同时购买同一张待出售的票,如果不做处理,就会出现,多个用户都付款成功。但资源只有一个,这显然是有问题的
加锁:给资源加锁,并发变成串行,多个用户不能同时访问,只有等一个用户访问完了下一个用户才能进入。
结论:
1.资源竞争问题和用系统用户量有关,用户量较大才可能出现,测试阶段不会发现。
2.所有涉及到资源竞争 的地方都必须加锁
二.redis setnx解决方案
原理:调用redis setnx方法,该方法具有原子性,可以保证串行。
功能:多个用户同时访问同一个资源,使用redis底层提供的setnx方法,将并发改为串行。程序对第一个抢到该资源的用户加锁,加锁后拦截其他用户获得该资源,并且当前用户可以正常访问。加锁后如果用户一直不付款,设置30分钟后释放锁
/*
*固定字符串+资源id作为key,用户id作为值,设置过期时间为30分钟(RedisUtil封装的setnx()方法,已贴出)
*redis中key存在,返回false;否则,true
*用户id作为值,避免当前用户也被锁住
*/
boolean flag = RedisUtil.setnx(WebConstatVar.IP_RESOURCE_ID_ + splitIds[i], userId.toString(), 30 * 60);
if(!flag) {
String _userId = RedisUtil.get(WebConstatVar.IP_RESOURCE_ID_ + splitIds[i]);
//key取值,和userId比较,判断资源是否被当前用户占用。
if(StringUtils.isNotEmpty(_userId)&&(!_userId.equals(userId.toString()))){
IpResources ipResources = ipResourcesService.findIpResources(Integer.valueOf(splitIds[i]));
String usedIp = ipResources.getIntranetIp() + "-" + ipResources.getInternetIp();
responseData.setStatus(ResponseData.STATUS_ERROR);
responseData.setMessage("支付失败,资源为:" + usedIp + "的ip已被其他用户先申请");
return responseData;
}
}
/*RedisUtils类中封装redis提供的setnx方法
*seconds:缓存过期时间
*key存在,返回false;key不存在,返回true
*/
public static boolean setnx(String key,String value, int seconds) {
ShardedJedis shardedJedis = null;
try {
shardedJedis = getShardedJedisPool();
//setnx的含义就是SET if Not Exists,其主要有两个参数 setnx(key, value)。
//该方法是原子的,如果key不存在,则设置当前key成功,返回1;如果当前key已经存在,则设置当前key失败,返回0。
long result = shardedJedis.setnx(key, value);
shardedJedis.expire(key, seconds);
return result == 1;
} catch (JedisConnectionException e) {
try {
releaseBroken(shardedJedis);
} catch (Exception ex) {
log.error(ex.toString());
}
} catch (Exception e) {
log.error(e.toString());
} finally {
try {
release(shardedJedis);
} catch (Exception e) {
log.error(e.toString());
}
}
return false;
}