使用场景:
比较经典的就是库存场景,比如一个商品库存为1,此时两个线程同时查到库存为1,就都把商品卖出去了。
那么就出现了超卖的情况,这种情况下就可以考虑使用分布式锁来解决这个问题。
Ps:乐观锁也行,加上版本号去做,具体看业务的使用场景以及是否合适
这边简单介绍几种实现方式,以及之间的比较
一.分布式锁的几种实现及其原理
Database:
Database:用数据库实现分布式锁,因为数据库表的唯一键的形式。如果同一个时刻,多个线程同时向一个表中插入同样的记录,由于唯一键的原因,只能有一个线程插入成功。获取锁期间要记录锁过期时间字段,拿锁过期时间跟现在时间比,加锁过程要用乐观锁去更新记录(防止在更新前数据被其他线程更新了)。
Zookeeper:
Zookeeper:实现分布式锁的原理就是多个节点同时在一个指定的节点下面创建临时目录顺序节点,谁创建的节点序号最小,谁就获得了锁,并且其他节点就会监听序号比自己小的节点,一旦序号比自己小的节点被删除了,其他节点就会得到相应的事件,然后查看自己是否为序号最小的节点,如果是,则获取锁。
Redis:
单机:
上锁 setnx + lua脚本 (解锁)
1.要这个用set命令,用这个,带过期时间的
RedisClient.getJedisCluster().set(key,value,"NX","EX",1L);
2.value要具有唯一性
3.解锁要检查value,不能误解锁
注意点:
1.锁要加失效时间,防止执行到一般,服务挂掉,锁永不过期
2.验证value唯一性是因为(假如A过期时间10秒,线程阻塞了,到12秒执行解锁操作,这期间,线程B加上了锁,A会释放掉B的锁,所以解锁前要验证value)
3.用lua脚本解锁是因为,A和B线程都加了锁,在A准备解锁并且下一步要解锁的时候,突然A的锁过过期了,这时候还是会解B线程的锁,用lua脚本保证原子性
4. 定时刷新,保证锁过期时间大于业务执行时间(设置volite变量为true,在业务要过期时候定时刷新,解锁时候把变量置为false)
Redis集群:
如果加锁只作用于一个redis节点上。即使通过sentinel保证高可用,如果这个节点发生了主从切换,就会出现锁丢失
redis在master拿到了锁
这时候还没同步到slave节点
Master故障,发生故障转移,slave发生了故障转移,salve升级为master节点,锁丢失
建议:使用Redisson
Redisson对redlock算法进行了封装,加锁起码(n/2 + 1),解锁去所有节点解锁
二.几种方式优缺点比较
Zookeeper:
zookeeper分布式锁实现简单,集群自己来保证数据一致性,但是会存在建立无用节点且多节点之间需要同步数据的问题,因此一般适合于并发量小的场景。
Redis:
redis分布式锁(非redlock)由于redis自己的高性能(纯内存操作,数据结构简单,单线程无上下文切换)原因,会有很好的性能,因此适用于高并发的场景。
数据库:
database分布式锁由于数据库本身的限制:性能不高且不满足高可用(即是存在备份,也会导致数据不一致),因此,工作中很难见到真正使用数据库来作为分布式锁的解决方案,这里使用数据库实现主要是为了理解分布式锁的实现原理。
网友评论