【转载】Redis 分布式锁进化史

按:系统架构经过多年演进,现在越来越多的系统采用微服务架构,而说到微服务架构必然牵涉到分布式,以前单体应用加锁是很简单的,但现在分布式系统下加锁就比较难了,我之前曾简单写过一篇文章,关于分布式锁的实现,但有一次发现实现的分布式锁是有问题的,因为出问题的概率很低,所以当时也没在意,前几天和朋友聊这个问题,想起来看过一篇文章,写的不错,今天特转载过来,希望能让更多的人看到,同时也加深一下记忆。原文链接是:http://tech.dianwoda.com/2018/04/11/redisfen-bu-shi-suo-jin-hua-shi/ 以下为原文: 近两年来微服务变得越来越热门,越来越多的应用部署在分布式环境中,在分布式环境中,数据一致性是一直以来需要关注并且去解决的问题,分布式锁也就成为了一种广泛使用的技术,常用的分布式实现方式为Redis,Zookeeper,其中基于Redis的分布式锁的使用更加广泛。 但是在工作和网络上看到过各个版本的Redis分布式锁实现,每种实现都有一些不严谨的地方,甚至有可能是错误的实现,包括在代码中,如果不能正确的使用分布式锁,可能造成严重的生产环境故障,本文主要对目前遇到的各种分布式锁以及其缺陷做了一个整理,并对如何选择合适的Redis分布式锁给出建议。 一. 各个版本的Redis分布式锁 V1.0 tryLock() { SETNX Key 1 EXPIRE Key Seconds } release() { DELETE Key } 这个版本应该是最简单的版本,也是出现频率很高的一个版本,首先给锁加一个过期时间操作是为了避免应用在服务重启或者异常导致锁无法释放后,不会出现锁一直无法被释放的情况。 这个方案的一个问题在于每次提交一个Redis请求,如果执行完第一条命令后应用异常或者重启,锁将无法过期,一种改善方案就是使用Lua脚本(包含SETNX和EXPIRE两条命令),但是如果Redis仅执行了一条命令后crash或者发生主从切换,依然会出现锁没有过期时间,最终导致无法释放。 另外一个问题在于,很多同学在释放分布式锁的过程中,无论锁是否获取成功,都在finally中释放锁,这样是一个锁的错误使用,这个问题将在后续的V3.0版本中解决。 针对锁无法释放问题的一个解决方案基于GETSET命令来实现 V1.1 基于GETSET tryLock() { NewExpireTime = CurrentTimestamp + ExpireSeconds if (SETNX Key NewExpireTime Seconds) { oldExpireTime = GET(Key) if (oldExpireTime < CurrentTimestamp) { NewExpireTime = CurrentTimestamp+ExpireSeconds CurrentExpireTime = GETSET(Key,NewExpireTime) if (CurrentExpireTime == oldExpireTime) { return 1; } else { return 0; } } } } release() { DELETE key } 思路: ...

October 14, 2018 · 1 min · 212 words · Bridge Li

Redis实现分布式锁

大家都知道Redis是NoSQL的一种,目前在互联网公司中在作为缓存广泛的使用者,其实利用Redis的setnx还可以快速实现一个分布式锁,公司的业务就需要使用分布式锁保证数据的唯一性,经检索在网上发现已经有活雷锋分享了一套,本着不在重新发明轮子的想法,测试了一下好像没有问题,几乎不用对原代码进行修改,就能使用,下面就分享在这里,供需要的朋友参考。原文里面还有对实现的原理进行解释,所以本文就不再赘述,详情请通过参考资料进行访问。 package cn.bridgeli.distributedlock; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.Transaction; public class RedisDistributedLock { private static final Logger LOG = LoggerFactory.getLogger(RedisDistributedLock.class); private static final String redisHost = "127.0.0.1"; private static final int port = 6381; private static JedisPoolConfig config; private static JedisPool pool; private static ExecutorService service; private static int ThLeng = 10; private static CountDownLatch latch; private static AtomicInteger Countor = new AtomicInteger(0); private static int count = 0; private static String LockName = "mylock_test10"; static { // 利用Redis连接池,保证多个线程利用多个连接,充分模拟并发性 config = new JedisPoolConfig(); config.setMaxIdle(10); config.setMaxWaitMillis(1000); config.setMaxTotal(30); pool = new JedisPool(config, redisHost, port); // 利用ExecutorService 管理线程 service = Executors.newFixedThreadPool(ThLeng); // CountDownLatch保证主线程在全部线程结束之后退出 latch = new CountDownLatch(ThLeng); } /** * 获取锁 tips:生成一个UUID,作为Key的标识,不断轮询lockName,直到set成功,表示成功获取锁。 * 其他的线程在set此lockName时被阻塞直到超时。 * * @param pool * @param lockName * @param timeouts * @return 锁标志 */ public static String getLock(JedisPool pool, String lockName, long timeouts) { Jedis client = pool.getResource(); try { String value = UUID.randomUUID().toString(); long timeWait = System.currentTimeMillis() + timeouts * 1000; while (System.currentTimeMillis() < timeWait) { if (client.setnx(lockName, value) == 1) { // 防止key卡死,一直不释放锁,所以1800s过期 client.expire(lockName, 1800); LOG.info("lock geted"); return value; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } LOG.info("get lock timeouts"); } finally { // pool.returnBrokenResource(client); pool.returnResource(client); } return null; } /** * 释放锁 tips:对lockName做watch,开启一个事务,删除以LockName为key的锁,删除后,此锁对于其他线程为可争抢的。 * * @param pool * @param lockName * @param value */ public static void relaseLock(JedisPool pool, String lockName, String value) { Jedis client = pool.getResource(); try { while (true) { client.watch(lockName); if (client.get(lockName).equals(value)) { Transaction tx = client.multi(); tx.del(lockName); tx.exec(); return; } client.unwatch(); } } finally { // pool.returnBrokenResource(client); pool.returnResource(client); } } public static void main(String args[]) { for (int i = 0; i < ThLeng; i++) { String tName = "thread-" + i; Thread t = new Thread(new SubAddThread(pool, tName)); LOG.info(tName + "inited&#8230;"); service.submit(t); } service.shutdown(); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } LOG.info(Countor.get() + ""); LOG.info(count + ""); } public static class SubAddThread implements Runnable { private String name; private JedisPool pool; public SubAddThread(JedisPool pool, String uname) { this.pool = pool; this.name = uname; } @Override public void run() { for (int i = 0; i < 100; i++) { LOG.info(name + " starting&#8230;"); String valuse = getLock(pool, LockName, 50); LOG.info(name + " get Lock " + valuse); count++; relaseLock(pool, LockName, valuse); Countor.incrementAndGet(); LOG.info(name + " " + count); } latch.countDown(); LOG.info(name + " complated"); } } } 参考资料:http://www.jianshu.com/p/c1f5d26cb1c9

January 15, 2017 · 2 min · 401 words · Bridge Li