关于分布式锁的概念网上太多了,这里就不罗嗦了。对于开发者来说,最关心的应该是什么情况下使用分布式锁。
使用分布式锁,一般要满足以下几个条件:
· 分布式系统(关键是分布式)
· 共享资源(各系统访问同一资源,资源的载体可能是传统关系型数据库或者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会不断尝试去获取锁,直到超时。也就是说,如果长时间获取不到,就会获取锁失败,相当于没加锁!具体的超时时间设置为多长,有待后期验证,再做优化。
原文:http://my.oschina.net/91jason/blog/517996