redis分布式锁原理

redis分布式锁原理

  1. 为什么要在分布式系统中加分布式锁
  2. 分布式锁的类型
  3. redis怎么实现分布式锁的

1、为什么要在分布式系统中加分布式锁:
这篇文章介绍的比较全面:https://www.jianshu.com/p/4b39c071557f
原生加的锁只对属于自己JVM里面的线程有效,对于其他JVM的线程是无效的。因此,这里的问题是:Java提供的原生锁机制在多机部署场景下失效了,这是因为两台机器加的锁不是同一个锁(两个锁在不同的JVM里面)。要保证两台机器加的锁是同一个锁,即多台机器用一个全局锁
要保证的是
1、分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上 的一个线程执行。
2、这把锁要是一把可重入锁,另外还要具备锁失效机制(避免死锁)
3、这把锁最好是一把阻塞锁(根据业务需求考虑要不要这条)
4、有高可用的获取锁和释放锁功能
5、获取锁和释放锁的性能要好

2、分布式锁的类型
数据库实现:利用主键唯一规则(或者利用Mysql行锁的特性)

首先我们利用主键唯一规则,在争抢锁的时候向DB中写一条记录,这条记录主要包含锁的id、当前占用锁的线程名、重入的次数和创建时间等,如果插入成功表示当前线程获取到了锁,如果插入失败那么证明锁被其他人占用,等待一会儿继续争抢,直到争抢到或者超时为止。

重入主要实现思路是,在每次获取锁之前去取当前锁的信息,如果锁的线程是当前线程,那么更新锁的count+1,并且执行锁之后的逻辑。如果不是当前锁,那么进行重试。释放的时候也要进行count-1,最后减到0时,删除锁标识释放锁。

优点:实现简单

缺点:没有超时保护机制,mysql存在单点,并发量大的时候请求量太大、没有线程唤醒机制,用异常去控制逻辑多少优点恶心。

因为是基于数据库实现的,数据库的可用性和性能将直接影响分布式锁的可用性及性能,所以,数据库需要双机部署、数据同步、主备切换;
没有锁失效机制,因为有可能出现成功插入数据后,服务器宕机了,对应的数据没有被删除,当服务恢复后一直获取不到锁,所以,需要在表中新增一列,用于记录失效时间,并且需要有定时任务清除这些失效的数据;
不具备阻塞锁特性,获取不到锁直接返回失败,所以需要优化获取逻辑,循环多次去获取。
zookeeper实现(我没用过,只是把他拿过来知道有这种方式)
zookeeper是一个分布式一致性协调框架,主要可以实现选主、配置管理和分布式锁等常用功能,因为Zookeeper的写入都是顺序的,在一个节点创建之后,其他请求再次创建便会失败,同时可以对这个节点进行Watch,如果节点删除会通知其他节点抢占锁。步骤:

创建一个目录mylock;
线程A想获取锁就在mylock目录下创建临时顺序节点;
获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
  Curator是Netflix开源的一套ZooKeeper客户端框架,curator-recipes库里面集成了很多zookeeper的应用场景,因此,需要使用zookeeper的分布式锁功能,可以使用curator-recipes库。

优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。

缓存分布式锁:基于redis实现
  缓存实现分布式锁还是比较常见的,因为缓存比较轻量,并且缓存的响应快、吞吐高。最重要的是还有自动失效的机制来保证锁一定能释放。

缓存的分布式锁主要通过Redis实现,setNX是Redis提供的一个原子操作,如果指定key存在,那么setNX失败,如果不存在会进行Set操作并返回成功。我们可以利用这个来实现一个分布式的锁,主要思路就是,set成功表示获取锁,set失败表示获取失败,失败后需要重试。

优点:实现简单,吞吐量十分客观,对于高并发情况应付自如,自带超时保护,对于网络抖动的情况也可以利用超时删除策略保证不会阻塞所有流程。

缺点:单点问题、没有线程唤醒机制、网络抖动可能会引起锁删除失败。

3、实例

/** * 获取锁 * * @param lockName 锁名称 * @param expire 过期时间 * @param expireUnit 过期时间单位 * @return */ private static final Logger LOGGER = LoggerFactory.getLogger(RedisLockService.class); //RedisLockService当前类名 public boolean tryLock(final String lockName, final long expire, final TimeUnit expireUnit) { LOGGER.debug("Entering tryLock, lockName={}, expire={}, expireUnit={}", lockName, expire, expireUnit); return stringRedisTemplate.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { RedisSerializer<String> serializer = stringRedisTemplate.getStringSerializer(); byte[] lockBytes = serializer.serialize(lockName); boolean locked = connection.setNX(lockBytes, lockBytes); LOGGER.debug("Leaving tryLock, locked={}, lockame={}", locked, lockName); if (locked) { stringRedisTemplate.expire(lockName, expire, expireUnit); return true; } return false; } }); }
stringRedisTemplate是package org.springframework.data.redis.core包下把springboot 作为父依赖,就直接可以引进来了 但是需要在某个@Configuration的配置类下面注入bean
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值