关于分布式锁的概念网上太多了,这里就不罗嗦了。对于开发者来说,最关心的应该是什么情况下使用分布式锁。
使用分布式锁,一般要满足以下几个条件:
· 分布式系统(关键是分布式)
· 共享资源(各系统访问同一资源,资源的载体可能是传统关系型数据库或者NoSQL)
· 同步访问(没有同步访问,谁管你资源竞争不竞争)
场景案例
· 光说不练假把式,上点干货。管理后台的部署架构(多台tomcat服务器+redis+mysql)就满足使用分布式锁的条件。多台服务器要访问redis全局缓存的资源,如果不使用分布式锁就会出现问题。 看如下伪代码:
long N=0L;
//N从redis获取值
if(N<5){
N++;
//N写回redis
}
· 从redis获取值N,对数值N进行边界检查,自加1,然后N写回redis中。 这种应用场景很常见,像秒杀,全局递增ID、IP访问限制等。以IP访问限制来说,恶意攻击者可能发起无限次访问,并发量比较大,分布式环境下对N的边界检查就不可靠,因为从redis读的N可能已经是脏数据。传统的加锁的做法(如java的synchronized和Lock)也没用,因为这是分布式环境,这个同步问题的救火队员也束手无策。在这危急存亡之秋,分布式锁终于有用武之地了。
所谓知己知彼,百战百胜。要想用好他,首先你要了解他。分布式锁可以基于很多种方式实现,比如zookeeper、redis...。不管哪种方式,他的基本原理是不变的:用一个状态值表示锁,对锁的占用和释放通过状态值来标识。
这里主要讲如何用redis实现分布式锁。
实现原理
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。redis的SETNX命令可以方便的实现分布式锁。
SETNX命令(SET if Not eXists) 语法: SETNX key value 功能: 当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。 有了以上Redis知识了,写个简单的分布式锁就不是什么难事。
直接贴java代码!
public class RedisLock implements Lock {
@Autowired
protected StringRedisTemplate redisTemplate;
private static final Logger logger = Logger.getLogger(RedisLock.class);
// lock flag stored in redis
private static final String LOCKED = "TRUE";
// timeout(ms)
private static final long TIME_OUT = 30000;
// lock expire time(s)
public static final int EXPIRE = 60;
// private Jedis jedis;
private String key;
// state flag
private volatile boolean locked = false;
private static ConcurrentMap<String, RedisLock> map = Maps.newConcurrentMap();
public RedisLock(String key) {
this.key = "_LOCK_" + key;
redisTemplate = (StringRedisTemplate) ApplicationContextHolder.getBean("redisTemplate");
}
public static RedisLock getInstance(String key) {
return map.getOrDefault(key, new RedisLock(key));
}
public void lock(long timeout) {
long nano = System.nanoTime();
timeout *= 1000000;
final Random r = new Random();
try {
while ((System.nanoTime() - nano) < timeout) {
if (redisTemplate.getConnectionFactory().getConnection().setNX(key.getBytes(), LOCKED.getBytes())) {
redisTemplate.expire(key, EXPIRE, TimeUnit.SECONDS);
locked = true;
logger.debug("add RedisLock[" + key + "].");
break;
}
Thread.sleep(3, r.nextInt(500));
}
} catch (Exception e) {
}
}
@Override
public void unlock() {
if (locked) {
logger.debug("release RedisLock[" + key + "].");
redisTemplate.delete(key);
}
}
@Override
public void lock() {
lock(TIME_OUT);
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public Condition newCondition() {
return null;
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
}
基准测试
饭已OK,快来MIXI啦。
~~~等等,还得让评审来评价下这饭做得好不好吃。
写一段测试代码来对比测试下。
public void concurrentTest() {
final Printer outer = new Printer();
new Thread(new Runnable() {
@Override
public void run() {
outer.output("I am a boy.");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
outer.output("You are a girl.");
}
}).start();
}
?
不加锁的Printer:
?
class Printer {
public void output(String name) {
for (int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
?
?使用java内置的锁ReentrantLock:
Lock lock=new ReentrantLock();class Printer {
public void output(String name) {
lock.lock();
for (int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.unlock();
}
}
?
?使用分布式锁RedisLock:
Lock lock=new ReentrantLock();class Printer {
public void output(String name) {
Lock lock = new RedisLock("lock1");
lock.lock();
for (int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.unlock();
}
}
对比测试结果如下:
项目 |
不加锁 |
java内置锁ReentrantLock |
加分布式锁RedisLock |
测试结果 |
IY oau m aar eb oa yg.irl. |
I am a boy.You are a girl. |
You are a girl.I am a boy. |
写在后面
话说RedisLock能够正常使用了,也达到了预期效果。是不是这个分布式锁就万无一失呢?
这是一个悲观锁,RedisLock会不断尝试去获取锁,直到超时。也就是说,如果长时间获取不到,就会获取锁失败,相当于没加锁!具体的超时时间设置为多长,有待后期验证,再做优化。
来源:oschina
链接:https://my.oschina.net/u/567296/blog/517996