分布式锁
针对单体应用来说,一个JVM运行的进程中,要解决资源共享的情况,可以使用java中的 lock进行加锁解锁、使用synchronized修饰方法,修饰变量,也可以使用volatile来保证原子性等,来保证多线程安全问题。但随着为了保证高并发或者系统容错,一个jar包运行在多台jvm上面的时候,nginx分流后,再去考虑资源共享就要引入分布式锁,分布式锁简单来说就是找到一个多个jvm都去访问的点,以保证多个环境下的互斥。
常见的分布式锁实现:数据库实现、缓存实现、zokeeper实现
数据库实现:
实现相对来说,比较容易,就是直接在库中创建一张表,这张表包含方法名 id、method_name、create_time字段,当有线程抢占资源时候,表中insert一条数据,保存访问的哪个方法,相当于这个资源方法被加锁,其他线程去抢占这个锁的时候,会去访问这个表,如果有该方法的一条数据,就表明该锁已经被占有。
常见的几个问题是,不可重入、锁没有失效时间、数据库服务器宕机锁失效等。对于没有失效时间,可以在代码中加入轮询根据创建时间去扫描清除过期的锁。
缓存实现
以redis为例,同数据库类似,其实redis分布式锁可以考虑使用使用 Redisson 框架,官网:https://redisson.org/, 简单的代码如下,更多学习可以参考官网
RLock lock = redisson.getLock("lock");
lock.lock();
lock.unlock();
而且redis可以部署高可用主从集群,放置master宕机,同时redis锁可以保证锁互斥,可重入加锁机制,自动释放锁(reidss expire设置失效时间)等机制。
zokeeper实现
zookeeper是google的一个开源分布式应用协同器,用于管理协同集群,监事节点,并通过 文件系统、 通知系统。
文件系统 :由各种子目录(ZNode)构成,Znode可以类似于 文件 那样的增加和删除,znode包含四种类型,
1 持久化目录节点(客户端与zk断开后,节点仍然存在)
2 持久化顺序编号目录节点(同持久化目录节点,zk增加了节点的顺序编号)
3 临时目录节点(客户端与zk断开后,节点被删除)
4 临时顺序编号目录节点(同临时目录节点,zk增加节点顺序编号)
通知机制:客户端注册监听目录节点,被监听节点更改(增加字目录,删除,数据修改),zk会通知客户端。
zookeeper除了做分布式锁之外,还可以做dubbo的服务注册中心。用于做分布式锁利用其文件系统,将zk的znode作为锁,多个客户端去抢占锁资源就成了多个客户端去尝试在znode下创建子目录,最后最先创建子目录临时节点的客户端获得锁。具体如下:
以两个客户端 client_a,client_b,zk的节点znode_0
1.client_a 访问znode0,去创建临时子目录节点,zk创建znode0的子目录节点为znode_a...001,然后client_a 访问znode_0的子目录节点,发现子目录为[znode_a...001],及子目录数组中第一个节点为自己创建的临时目录节点,则代表自己获取到锁
2.client_b 访问znode0,去创建临时子目录节点,zk创建znode0的子目录节点为znode_b...002,然后client_b 访问znode0的子目录节点,发现子目录为[znode_a...001,znode_b...002],发现znode0的第一个子目录节点及子目录节点数组[0]的节点不是自己所创建的节点,则代表自己没有争取到锁
3、client_b没有获取到锁时候,会在znode0的子目录数组[0]及znode_a...001处增加注册监听,加入client_a释放锁,及znode_a...001被删除,则会通知client_b,client_b回去zk那里访问znode0的第一个子目录临时节点也就是client_b...001,如果是则获取锁,否则就继续监听znode_0的第一个子目录临时节点 ... ...
相对来说,zk的实现比较复杂