分布式锁需要考虑的内容
1. 锁的获取与释放消耗低;
2. 锁的存在判断与获取操作保持原子性;
3. 程序崩溃时锁的释放;
redis相关特性
1. 关于消耗
下面是官方的bench-mark数据:
测试完成了50个并发执行100000个请求。
设置和获取的值是一个256字节字符串。
Linux box是运行Linux 2.6,这是X3320 Xeon 2.5 ghz。
文本执行使用loopback接口(127.0.0.1)。
结果:读的速度是110000次/s,写的速度是81000次/s 。
redis在单次读写的效率和系统消耗上面的数据值得肯定。
2. 原子性
redis单个操作都是原子的,但是通过exists和get两种中操作就不一定能保证了,目前有两种方式:
-
watch
WATCH key [key ...]
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
可用版本:
>= 2.2.0
时间复杂度:
O(1)。
返回值:
总是返回 OK 。
-
setnx
SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
可用版本:
>= 1.0.0
时间复杂度:
O(1)
返回值:
设置成功,返回 1 。
设置失败,返回 0 。
从代码编写成本上看,setnx更为方便,watch需要更多的代码完成原子操作。
3. 锁的释放
通过redis的expire设置key的时间,从而避免因为程序崩溃导致的无法获取锁的问题。
EXPIRE key seconds
为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。
4. 具体的代码实现
锁的接口声明及redis实现
package com.stg.lock;
/**
* @author stg
* 分布式锁接口
*/
public interface DistributeLock {
public boolean lock();
public boolean unlock();
}
package com.stg.lock.redis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import com.stg.lock.DistributeLock;
import com.stg.utils.RedisUtils;
public class RedisDistributeLock implements DistributeLock{
private static final Logger logger = LoggerFactory.getLogger(RedisDistributeLock.class);
/**
* 锁的key标识,根据业务逻辑调整
*/
private final String key;
/**
* DistributeLock非单例,Jedis切勿声明为static
*/
private Jedis jedis;
/**
* 超时时间,无限等待锁会造成系统崩溃
*/
private long timeout = 2 * 1000L;
public RedisDistributeLock(String key){
this.key = key;
jedis = RedisUtils.getJedis();
}
@Override
public boolean lock() {
long tryLocktime = System.currentTimeMillis();
while(System.currentTimeMillis() - tryLocktime < timeout) {
// value可随意设置,存储时间类信息便于问题发生后的时间定位
String value = String.valueOf(System.currentTimeMillis());
// 尝试写入key(上锁)
long result = jedis.setnx(key, value);
if(result == 1) {
// 获取成功,设置有效时间,单是秒
jedis.expire(key, 30);
logger.info("{} begin", Thread.currentThread().getName());
return true;
}
// 等待一段时间后尝试重获取key
try {
Thread.sleep(50);
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
return false;
}
@Override
public boolean unlock() {
long result = jedis.del(key);
if(result != 1){
logger.warn("unlock fail ,key -> " + key);
}
logger.info("{} end", Thread.currentThread().getName());
return result == 1;
}
}
工厂类
package com.stg.lock;
/**
* @author stg
* @param <T> 标识锁的key
*
* 工厂类,用于创建锁对象
*/
public interface DistributeLockFactory<T> {
public DistributeLock getLockBean(T key);
}
package com.stg.lock.redis;
import com.stg.lock.DistributeLock;
import com.stg.lock.DistributeLockFactory;
public class RedisDistributeLockFactory implements DistributeLockFactory<String> {
@Override
public synchronized DistributeLock getLockBean(String key) {
DistributeLock lockBean = new RedisDistributeLock(key);
return lockBean;
}
}
其他工具类
package com.stg.utils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisUtils {
private static JedisPool pool;
static {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxActive(1000);
config.setMaxIdle(100);
config.setMaxWait(100L);
config.setTestOnBorrow(true);
try{
pool = new JedisPool(config, "127.0.0.1", 6379, 3000);
} catch(Exception e) {
e.printStackTrace();
}
}
public static Jedis getJedis(){
Jedis jedis = pool.getResource();
return jedis;
}
}
测试类
package com.stg;
import java.io.Serializable;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.stg.lock.DistributeLock;
import com.stg.lock.DistributeLockFactory;
import com.stg.lock.redis.RedisDistributeLockFactory;
public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
private static DistributeLockFactory<String> factory;
public static void main(String[] args) {
lockOperation();
while(true) {
try {
Thread.sleep(30*1000L);
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
}
public static void lockOperation() {
factory = new RedisDistributeLockFactory();
int threadNum = 10;
Executor executor = Executors.newFixedThreadPool(threadNum);
for(int i = 0; i< threadNum; i++) {
executor.execute(new Runnable(){
@Override
public void run() {
User user = new User(10001, "zhangsan", 25);
DistributeLock lockBean = factory.getLockBean(String.valueOf(user.getId()));
try {
if (lockBean.lock()) {
// 业务逻辑处理
try {
/*
* ***注意***
* thread * sleeptime < 获取锁的超市时间
* 也就是说业务逻辑决定了锁超时时间的设定
*/
Thread.sleep(100L);
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
} else {
}
} finally {
lockBean.unlock();
}
}
});
}
}
}
class User implements Serializable {
private static final long serialVersionUID = -5937959261213855736L;
public User() {
}
public User(long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
private long id;
private String name;
private int age;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
输出结果:
pool-1-thread-4 begin
pool-1-thread-4 end
pool-1-thread-5 begin
pool-1-thread-5 end
pool-1-thread-1 begin
pool-1-thread-1 end
pool-1-thread-6 begin
pool-1-thread-6 end
pool-1-thread-7 begin
pool-1-thread-7 end
pool-1-thread-2 begin
pool-1-thread-2 end
pool-1-thread-3 begin
pool-1-thread-3 end
pool-1-thread-10 begin
pool-1-thread-10 end
pool-1-thread-9 begin
pool-1-thread-9 end
pool-1-thread-8 begin
pool-1-thread-8 end