

offer风火轮:redis分布式锁与zk分布式锁区别?
source link: https://mp.weixin.qq.com/s?__biz=MzA4MTc4NTUxNQ%3D%3D&%3Bmid=2650522895&%3Bidx=1&%3Bsn=8b1eac72d138f5c45507303cea0e3b22
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

为码农谋福利,替名媛定终身。offer风火轮 系列开启,xjjdog将挑选典型高价值题目进行分享。 版权声明: 本文为群友讨论整理所得,只允许公众号转载,未经授权拒绝拷贝至其他平台。
标签:【高级】【Redis】【ZooKeeper】
1. 问
redis分布式锁与zk分布式锁区别?
2. 解析
这个问题对面试者要求较高,它不仅要了解实现方法,还要对原理有所掌握。所以问题回答起来,分为很多层次。
众所周知,Redis标榜的是轻量级,直观上分布式锁是比较好实现的,比如使用setnx,但一旦加入高可用这个属性,Redis锁的实现难度就会爆炸式上升。
再加上锁的其他几个属性:乐观悲观、读写锁等,事情会更加的复杂。
如果你全都知晓,聊一天都聊不完。
3. 答
先来一个,比较浅显、入门的回答:
-
redis的分布式锁,可以基于setnx指令实现(但其实更建议使用带nx参数的set指令)
-
zk的分布式锁,是基于临时节点的有序性和节点的监听机制完成的
这种回答方式,直接把自己给绕进去了,因为这涉及到非常多的细节。别人只是问区别,为什么把自己往源码级别绕呢?
建议回答:
-
Redis,使用redisson封装的RedLock
-
Zk,使用curator封装的InterProcessMutex
对比:
-
实现难度上:Zookeeper >= redis
-
服务端性能:redis > Zookeeper
-
客户端性能:Zookeeper > redis
-
可靠性:Zookeeper > redis
细聊:
3.1 实现难度
对于直接操纵底层API来说,实现难度都是差不多的,都需要考虑很多边界场景。但由于Zk的ZNode天然具有锁的属性,所以直接上手撸的话,很简单。
Redis需要考虑太多异常场景,比如锁超时、锁的高可用等,实现难度较大。
3.2 服务端性能
Zk基于Zab协议,需要一半的节点ACK,才算写入成功,吞吐量较低。如果频繁加锁、释放锁,服务端集群压力会很大。
Redis基于内存,只写Master就算成功,吞吐量高,Redis服务器压力小。
3.3 客户端性能
Zk由于有通知机制,获取锁的过程,添加一个监听器就可以了。避免了轮询,性能消耗较小。
Redis并没有通知机制,它只能使用类似CAS的轮询方式去争抢锁,较多空转,会对客户端造成压力。
3.4 可靠性
这个就很明显了。Zookeeper就是为协调而生的,有严格的Zab协议控制数据的一致性,锁模型健壮。
Redis追求吞吐,可靠性上稍逊一筹。即使使用了Redlock,也无法保证100%的健壮性,但一般的应用不会遇到极端场景,所以也被常用。
4. 扩展
Zk的分布式锁样代码样例:
import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.locks.InterProcessMutex; import java.util.concurrent.TimeUnit; public class ExampleClientThatLocks { private final InterProcessMutex lock; private final FakeLimitedResource resource; private final String clientName; public ExampleClientThatLocks(CuratorFramework client, String lockPath, FakeLimitedResource resource, String clientName) { this.resource = resource; this.clientName = clientName; lock = new InterProcessMutex(client, lockPath); } public void doWork(long time, TimeUnit unit) throws Exception { if ( !lock.acquire(time, unit) ) { throw new IllegalStateException(clientName + " could not acquire the lock"); } try { System.out.println(clientName + " has the lock"); resource.use(); } finally { System.out.println(clientName + " releasing the lock"); lock.release(); // always release the lock in a finally block } } }
RedLock的分布式锁使用样例:
String resourceKey = "goodgirl"; RLock lock = redisson.getLock(resourceKey); try { lock.lock(5, TimeUnit.SECONDS); //真正的业务 Thread.sleep(100); } catch (Exception ex) { ex.printStackTrace(); } finally { if (lock.isLocked()) { lock.unlock(); } }
再附一段RedLock的内部lock和unlock的代码实现,以便对你对其复杂度有一定的了解。
@Override <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) { internalLockLeaseTime = unit.toMillis(leaseTime); return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command, "local mode = redis.call('hget', KEYS[1], 'mode'); " + "if (mode == false) then " + "redis.call('hset', KEYS[1], 'mode', 'read'); " + "redis.call('hset', KEYS[1], ARGV[2], 1); " + "redis.call('set', KEYS[2] .. ':1', 1); " + "redis.call('pexpire', KEYS[2] .. ':1', ARGV[1]); " + "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; " + "if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1) then " + "local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1); " + "local key = KEYS[2] .. ':' .. ind;" + "redis.call('set', key, 1); " + "redis.call('pexpire', key, ARGV[1]); " + "local remainTime = redis.call('pttl', KEYS[1]); " + "redis.call('pexpire', KEYS[1], math.max(remainTime, ARGV[1])); " + "return nil; " + "end;" + "return redis.call('pttl', KEYS[1]);", Arrays.<Object>asList(getName(), getReadWriteTimeoutNamePrefix(threadId)), internalLockLeaseTime, getLockName(threadId), getWriteLockName(threadId)); } @Override protected RFuture<Boolean> unlockInnerAsync(long threadId) { String timeoutPrefix = getReadWriteTimeoutNamePrefix(threadId); String keyPrefix = getKeyPrefix(threadId, timeoutPrefix); return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "local mode = redis.call('hget', KEYS[1], 'mode'); " + "if (mode == false) then " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1; " + "end; " + "local lockExists = redis.call('hexists', KEYS[1], ARGV[2]); " + "if (lockExists == 0) then " + "return nil;" + "end; " + "local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1); " + "if (counter == 0) then " + "redis.call('hdel', KEYS[1], ARGV[2]); " + "end;" + "redis.call('del', KEYS[3] .. ':' .. (counter+1)); " + "if (redis.call('hlen', KEYS[1]) > 1) then " + "local maxRemainTime = -3; " + "local keys = redis.call('hkeys', KEYS[1]); " + "for n, key in ipairs(keys) do " + "counter = tonumber(redis.call('hget', KEYS[1], key)); " + "if type(counter) == 'number' then " + "for i=counter, 1, -1 do " + "local remainTime = redis.call('pttl', KEYS[4] .. ':' .. key .. ':rwlock_timeout:' .. i); " + "maxRemainTime = math.max(remainTime, maxRemainTime);" + "end; " + "end; " + "end; " + "if maxRemainTime > 0 then " + "redis.call('pexpire', KEYS[1], maxRemainTime); " + "return 0; " + "end;" + "if mode == 'write' then " + "return 0;" + "end; " + "end; " + "redis.call('del', KEYS[1]); " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1; ", Arrays.<Object>asList(getName(), getChannelName(), timeoutPrefix, keyPrefix), LockPubSub.UNLOCK_MESSAGE, getLockName(threadId)); }
所以,建议使用已经封装好的组件。如果你非要使用setnx或者set指令去做这些事,xjjdog只能说是想被虐。基本原理我们可以做到了解,这些细节,不下点功夫是理不清的。
说了这半天,我们选型的时候,该如何做呢?这要看你的基础设施。如果你的应用用到了zk,而且集群性能很强劲,优选zk。如果你只有redis,不想为了个分布式锁,引入臃肿的zk,那就用redis。
作者简介: 小姐姐味道 (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,进一步交流。
http://xjjdog.cn 对200+原创文章进行了细致的分类,阅读更流畅,欢迎收藏。
推荐阅读:
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK