一般情况下,如果是单结点服务,直接用进程内的锁(一般情况下都是使用可重入锁),比如synchronized、基于AQS的ReentrantLock等。甚至于一个线程安全的容器,比如ConcurrentHashMap都可以进行资源的锁定。但是,在分布式环境中,这些方法是没有办法 跨jvm进程来做并发控制的,这时就需要分布式锁。
这就需要提到select for update了。这种是强制锁定,其他线程在锁释放前都无法操作,如果获取锁的线程有大量耗时的操作,会导致很多其他等待锁的线程超时,因为 这种锁也是不可重入的。
数据库的乐观锁一般是使用一个版本号字段来实现,或者直接使用时间戳代替。有点类似于CAS操作,但是没有CAS的重试。这种方式会在比对版本号不一致时导致一些线程 的操作直接失败。所以这种方式适用于并发量小,读操作比较多的场景。比如只有少量的几个用户或线程有修改数据的权限时。
特点:锁不可重入,同一个线程在没有释放锁之前无法获得锁。而且锁不会自动失效,需要手动释放。
- 获取锁(unique_value可以是UUID等)SET resource_name unique_value NX PX 30000- 释放锁(lua脚本中,一定要比较value,防止误解锁)if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1])else return 0end
这种方式的最大缺点是,加锁时只作用在一个redis节点上,当redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况。一个线程在Redis的master节点上拿到了锁,但是这个加锁的key还没有同步到slave节点,如果这时master故障,发生故障转移,slave节点升级为master节点就会导致锁丢失。
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
redlock是让获取锁的客户端获取当前的unix时间,尝试从n个redis实例中用同一个key和val为资源唯一标识获取锁。并且设置有获取锁的超时时间。当且仅当超过n/2 + 1的节点都获取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。如果锁获取失败,应该去所有的redis实例上进行解锁。
具体的redlock实现参考redisson的RLock。
参考:https://mp.weixin.qq.com/s?_biz=MzI1NDU0MTE1NA==&mid=2247483862&idx=1&sn=914d7912c7313123897ef479f0bd7f80&chksm=e9c2eddbdeb564cd58923f395441996332fb9332d8c53fc6368a96a19b6e59297020e6f47537&scene=21&xtrack=1&key=e3977f8a79490c63bdcb6c58fa99576e80eb3c7db7fe393f6039d192362c395566c47cd13a582277c96064472e8271f63879aadb0c8c0de6f81dcc876c06e46d50130699ad56ba62693f29e202a34258&ascene=1&uin=MjI4MTc0ODEwOQ==&devicetype=Windows%207&version=62060719&lang=zhCN&passticket=HBqvqAg8/E8F0PgXE4QY26oVnramk4zVwRSUT2QD%20BrblglnRt6GwnH4nA%20OLM//#wechatredirect
因为 etcd 使用 Raft 算法保持了数据的强一致性,某次操作存储到集群中的值必然是全局一致的,所以很容易实现分布式锁。etcd有一套实现了CAS的API,可以保持独占。etcd还有一套自动创建有序键的API,可以 控制获取锁的时序,所有试图获取锁的用户都会进入等待队列,保证获得锁的顺序全局唯一。另外etcd可以进行节点的事件监听和节点的主动释放及超时自动释放等,都是它能实现分布式锁的前提条件。参考:https://segmentfault.com/a/1190000014297365
比如memcache的方式:memcache实现了CAS,也是它能来做分布式锁的前提条件,参考:https://www.zhihu.com/question/25489362
目前主流使用的分布式锁以redis居多,高可用环境下的redis一般RedLock使用比较多。