我们都知道在现在这个互联网的产品中,总会有需要锁处理的场景:比如秒杀。但是大部分的应用都是基于数据库来实现,比如添加行锁,表锁。但是我们不得不考虑,当锁定的该表拿到锁之后,在操作数据库(增,删,改)的操作中,事务发生回滚或者错误,那么在释放锁之前,可能就会发生死锁,这样对于该表或者该库的请求产生堆积,越来越多,轻则数据库压力变大,产生死锁,重则数据库崩溃;
所以在上面的场景中我们需要考虑使用其他形式的锁:redis分布式锁;redis分布式锁采用单进程单线程的处理模式,将每一个请求进行串行的访问,并且redis可以确保多个对客户端的访问不产生竞争关系。
redis分布式锁主要是基于SetNx和GET函数:
▶ SETNX命令(SET if Not eXists)
语法:
SETNX key value
功能:
当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。
▶ GET命令
语法:
GET key
功能:
返回 key 所关联的字符串值,如果 key 不存在那么返回特殊值 nil 。
下面是使用redisLock的代码实例:
public class RedisBillLockHandler implements IBatchBillLockHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisBillLockHandler.class);
private static final int DEFAULT_SINGLE_EXPIRE_TIME = 3;
private static final int DEFAULT_BATCH_EXPIRE_TIME = 6;
private final JedisPool jedisPool;
/**
* 构造
*/
public RedisBillLockHandler(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
* 获取锁 如果锁可用 立即返回true, 否则返回false
* @param billIdentify
* @return
*/
public boolean tryLock(IBillIdentify billIdentify) {
return tryLock(billIdentify, 0L, null);
}
/**
* 锁在给定的等待时间内空闲,则获取锁成功 返回true, 否则返回false
* @param billIdentify
* @param timeout
* @param unit
* @return
*/
public boolean tryLock(IBillIdentify billIdentify, long timeout, TimeUnit unit) {
String key = (String) billIdentify.uniqueIdentify();
Jedis jedis = null;
try {
jedis = getResource();
long nano = System.nanoTime();
do {
LOGGER.debug("try lock key: " + key);
Long i = jedis.setnx(key, key);
if (i == 1) {
jedis.expire(key, DEFAULT_SINGLE_EXPIRE_TIME);
LOGGER.debug("get lock, key: " + key + " , expire in " + DEFAULT_SINGLE_EXPIRE_TIME + " seconds.");
return Boolean.TRUE;
} else { // 存在锁
if (LOGGER.isDebugEnabled()) {
String desc = jedis.get(key);
LOGGER.debug("key: " + key + " locked by another business:" + desc);
}
}
if (timeout == 0) {
break;
}
Thread.sleep(300);
} while ((System.nanoTime() - nano) < unit.toNanos(timeout));
return Boolean.FALSE;
} catch (JedisConnectionException je) {
LOGGER.error(je.getMessage(), je);
returnBrokenResource(jedis);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
} finally {
returnResource(jedis);
}
return Boolean.FALSE;
}
/**
* 如果锁空闲立即返回 获取失败 一直等待
* @param billIdentify
*/
public void lock(IBillIdentify billIdentify) {
String key = (String) billIdentify.uniqueIdentify();
Jedis jedis = null;
try {
jedis = getResource();
do {
LOGGER.debug("lock key: " + key);
Long i = jedis.setnx(key, key);
if (i == 1) {
jedis.expire(key, DEFAULT_SINGLE_EXPIRE_TIME);
LOGGER.debug("get lock, key: " + key + " , expire in " + DEFAULT_SINGLE_EXPIRE_TIME + " seconds.");
return;
} else {
if (LOGGER.isDebugEnabled()) {
String desc = jedis.get(key);
LOGGER.debug("key: " + key + " locked by another business:" + desc);
}
}
Thread.sleep(300);
} while (true);
} catch (JedisConnectionException je) {
LOGGER.error(je.getMessage(), je);
returnBrokenResource(jedis);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
} finally {
returnResource(jedis);
}
}
/**
* 释放锁
* @param billIdentify
*/
public void unLock(IBillIdentify billIdentify) {
List<IBillIdentify> list = new ArrayList<IBillIdentify>();
list.add(billIdentify);
unLock(list);
}
/**
* 批量获取锁 如果全部获取 立即返回true, 部分获取失败 返回false
* @date 2013-7-22 下午10:27:44
* @param billIdentifyList
* @return
*/
public boolean tryLock(List<IBillIdentify> billIdentifyList) {
return tryLock(billIdentifyList, 0L, null);
}
/**
* 锁在给定的等待时间内空闲,则获取锁成功 返回true, 否则返回false
* @param billIdentifyList
* @param timeout
* @param unit
* @return
*/
public boolean tryLock(List<IBillIdentify> billIdentifyList, long timeout, TimeUnit unit) {
Jedis jedis = null;
try {
List<String> needLocking = new CopyOnWriteArrayList<String>();
List<String> locked = new CopyOnWriteArrayList<String>();
jedis = getResource();
long nano = System.nanoTime();
do {
// 构建pipeline,批量提交
Pipeline pipeline = jedis.pipelined();
for (IBillIdentify identify : billIdentifyList) {
String key = (String) identify.uniqueIdentify();
needLocking.add(key);
pipeline.setnx(key, key);
}
LOGGER.debug("try lock keys: " + needLocking);
// 提交redis执行计数
List<Object> results = pipeline.syncAndReturnAll();
for (int i = 0; i < results.size(); ++i) {
Long result = (Long) results.get(i);
String key = needLocking.get(i);
if (result == 1) { // setnx成功,获得锁
jedis.expire(key, DEFAULT_BATCH_EXPIRE_TIME);
locked.add(key);
}
}
needLocking.removeAll(locked); // 已锁定资源去除
if (CollectionUtils.isEmpty(needLocking)) {
return true;
} else {
// 部分资源未能锁住
LOGGER.debug("keys: " + needLocking + " locked by another business:");
}
if (timeout == 0) {
break;
}
Thread.sleep(500);
} while ((System.nanoTime() - nano) < unit.toNanos(timeout));
// 得不到锁,释放锁定的部分对象,并返回失败
if (!CollectionUtils.isEmpty(locked)) {
jedis.del(locked.toArray(new String[0]));
}
return false;
} catch (JedisConnectionException je) {
LOGGER.error(je.getMessage(), je);
returnBrokenResource(jedis);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
} finally {
returnResource(jedis);
}
return true;
}
/**
* 批量释放锁
* @param billIdentifyList
*/
public void unLock(List<IBillIdentify> billIdentifyList) {
List<String> keys = new CopyOnWriteArrayList<String>();
for (IBillIdentify identify : billIdentifyList) {
String key = (String) identify.uniqueIdentify();
keys.add(key);
}
Jedis jedis = null;
try {
jedis = getResource();
jedis.del(keys.toArray(new String[0]));
LOGGER.debug("release lock, keys :" + keys);
} catch (JedisConnectionException je) {
LOGGER.error(je.getMessage(), je);
returnBrokenResource(jedis);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
} finally {
returnResource(jedis);
}
}
/**
* @date 2013-7-22 下午9:33:45
* @return
*/
private Jedis getResource() {
return jedisPool.getResource();
}
/**
* 销毁连接
* @param jedis
*/
private void returnBrokenResource(Jedis jedis) {
if (jedis == null) {
return;
}
try {
//容错
jedisPool.returnBrokenResource(jedis);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
/**
* @param jedis
*/
private void returnResource(Jedis jedis) {
if (jedis == null) {
return;
}
try {
jedisPool.returnResource(jedis);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
}