如何实现分布式锁?

转载请注明:http://blog.csdn.net/HEL_WOR/article/details/51661591

从本质上说。
单机锁,锁本地的一个共享资源。
分布式锁,锁一个所有应用访问到的共享资源。

不管是redis锁,还是zookeeper锁,锁的都是共享资源。
在redis上,通过判断一个key是否存在存在作为锁,此时这个key就是共享资源,以key是否存在作为锁的标志,表现在代码中就是setnx是否返回1.
在zookeeper上,通过判断一个node是否存在作为锁,此时这个node就是共享资源,以node是否存在作为锁的标志,表现在代码中就是是否能create这个结点而不报出结点已存在的异常。

只是在这个逻辑可能有一些优化,比如curator框架中内置的interProcessLock,其也是通过判断能否成功在指定路径上能否创建结点完成加锁。但其作为一个客户端通过一个threadlocal保存已获得锁的线程完成了锁的可重入,并且避免每次获取锁都创建一次结点,因为它是一个客户端,因此threadlocal在这里使用是本地的操作,从设计来讲,这样的实现,比将threadlocal放在zookeeper内每次传送当前线程的id完成锁的可重入会完美许多。

举个redis锁的实现:
有一个计算流量的逻辑,需要每隔一段时间将单机中的流量缓存同步到Redis库中,现在就出现了这个问题,一个服务被部署到多服务器上,当需要同时写同一台redis数据库,就需要使用分布式锁。

这里使用了Redis来实现分布式锁,实现的逻辑类似单机中使用的Lock或者synchronized,由于每个对象都拥有一个锁,只有获得了对象锁的线程才能对对象进行操作(在synchronized中使用了锁计数来完成,即通过判断是否非0来表示是否上锁)。
这里把锁对象变成锁KV对。

如果redis库中有特定的KV对,就表示上锁。
如果redis库中没有特定的KV对,就表示未上锁。
锁使用完毕后需要删除redis中的特定KV对。

实现代码:

/**
 * 利用redis实现分布式锁
 *
 * @author zhenghao:
 * @version 1.0 2016-6-7
 */
@Component
public class RedLock {

    private static final Log LOG = LogFactory.getLog(RedLock.class);
    private static final String REDLOCK_KEY = "RedLock";
    private static final Long REDLOCK_EXPIRES = 5L;
    private static final int LOCK_TIMES_LIMIT = 5;
    private static volatile boolean locked = false;
    private static String redLockValue = String.valueOf(Math.random());
    private static AtomicInteger lockTimes = new AtomicInteger(0);

    @Autowired
    private CacheService cacheService;

    /**
     * 锁状态
     */
    public boolean isLocked(){
        return locked;
    }

    /**
     * 加锁
     * 通过Redis的setNx函数返回值判断加锁是否成功(1:成功, 0:失败)。
     * 如若不成功,说明锁已被占用,休眠REDLOCK_EXPIRES后重新获取锁
     * 加入对获取锁操作技术,超过一定范围说明锁竞争激烈,需要调整,否则会比较明显的降低程序的性能
     */
    public void lock() {
        while (cacheService.setRedLock(REDLOCK_KEY, redLockValue, REDLOCK_EXPIRES) == 0) {
            try {
                if (lockTimes.incrementAndGet() > LOCK_TIMES_LIMIT) {
                    LOG.info("SYSTEM_LOCK_CONTENTION: 锁竞争激烈");
                }

                Thread.sleep(REDLOCK_EXPIRES * 1000);
            } catch (InterruptedException e) {
                LOG.info("SYSTEM_INTERRUPTED_EXCEPTION: 内部异常中断错误");
            }
        }

        locked = true;
    }

    /**
     * 解除Redis锁
     * 判断逻辑加入的原因在于:考虑一种情况,单机A获得了Redis锁,但A由于处理超时5秒后KEY超时,此条记录已被Redis移除,单机A被迫释放锁。
     * 此时单机B进入已获得了Redis锁。
     * 如若单机A中调用了Unlock逻辑,执行了removeRedLock操作,则会删除单机B正常获取到的锁。
     * 判断单机B是否正常获取到了锁,通过比对保存的随机数完成。
     * isLocked不置为false仅当A超时B拿到锁的情况下.
     */
    public void unlock() {
        String oldRedLockValue = cacheService.getRedLock(REDLOCK_KEY);
        if (oldRedLockValue == null) {
            locked = false;
        } else if (oldRedLockValue.equals(redLockValue)) {
            cacheService.removeRedLock(REDLOCK_KEY);
            locked = false;
        }

        lockTimes.set(0);
    }
}

update:
1.基于setnx函数实现的redis锁属于悲观锁,线程抢不抢得到锁只能看运气,在做了休眠操作可以减缓锁竞争的冲突,但当此应用横向扩展部署十多台以应对活动带来的高并发,线程用在在获取锁的时间将大大拉长,先不说是否会有线程一直拿不到锁而饿死,获取锁的时间将大大拉长导致平均响应时间的拉长,根据吞吐量的计算公式,系统的吞吐量将降低,原本横向扩展带来的吞吐量提升因为锁的争抢,提升效果将下降。
如果因为响应时间的拉长影响到了用户,想想我们平时上网网页开的慢或者半天打不开会干嘛,Ctrl+F5,更多的重复请求到达。更多的线程开启,更多的内存占用,更多的上下文切换时间,要么连接耗尽,要么系统拖死,挂了一台,剩下的就是雪崩效应。

2.使用消息队列,强行将原本的多线程变成单线程,再多线程监听消息。如果处理速度跟不上消费速度,内存会被吃掉,最后吃完。另外还要确保发送消息时不要出现诸如EOF等的错误,导致用户的请求并没有进入消息队列。

3.使用乐观锁,redis自带watch。消耗cpu。

具体场景使用具体的方法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值