前言
在单个进程中synchronized/lock锁是起作用的,但是在分布式环境中服务都是部署在不同的机器中的,此时synchronized/lock就没用了,这个时候就需要中间件来完成。
Zookeeper用于实现分布式锁优点在于对于节点有监听机制。
当我们一个线程拿到锁的时候,就创建一个节点,当其他节点也想创建该节点的时候就会报错,此时对该节点进行监听,一旦这个节点被释放,变会通知其他用户。Zookeeper实现分布式锁就是实现对节点的创建,监听,删除的操作。
一、非公平锁,公平锁,共享锁
-
非公平锁:
非公平锁就是所有等待的线程同时争抢一把锁,当这把锁被其中一个线程抢到了,剩余的线程都会监听这把锁,当这个锁被释放了,所有等待的线程都会去争抢它,但还是只有一个线程会抢到。当这种并发比较严重的时候,性能就会下降。其主要原因就是所有线程都会对同一个节点进行监听,当服务器检测到删除事件时,要通知所有的连接,所有连接同时收到事件,再次并发竞争,这就是“羊群效应”。这种枷锁方式就是非公平锁。 -
公平锁:
(1)请求进来直接在/lock节点下创建一个临时顺序节点
(2)判断自己是不是/lock节点下最小的节点,如果是最小节点,那就获取锁,如果不是那就对前面的节点进行监听。
(3)获取锁的请求,处理完释放锁,即delete节点,然后即下一个节点将收到通知,一次重复(2)(3)两步。Java代码:
创建一个springboot项目,启动两个端口服务,使用Nginx代理,使用jmeter进行压测。
大体截图:数据初始化
启动两个服务
Nginx配置代理连个服务,以此模拟分布式服务
此时使用jmeter压测一秒钟请求十次,会发现都成功了,再看下数据库中数量已经变为-5,那么就出现了问题。
当使用zookeeper分布式锁的时候,重启服务,还原数据库数据,重新执行jmeter
此时会发现只有五个线程能够执行成功,并且数据库数据也是从五减到零就不在减了。
备注:业务处理层
也就是不管有多少请求,都会往后排队,服务器每次通知获取锁都只通知后面紧邻的那个节点,所以不存并发在竞争获取锁。这种实现方式也满足了先来先获取锁,所以是公平锁。
- 共享锁
公平锁和非公平锁都是独占锁,即同一时间只能有一个线程占用,如果大量并发上来,性能会急剧下降。如果数据没有进行任何修改的话,是不需要加锁的,但是如果读数据的请求还没读完,这个时候来了一个写请求,怎么办呢?有人已经在读数据了,这个时候是不能写数据的,不然数据就不正确了。直到前面读锁全部释放掉以后,写请求才能执行,所以需要给这个读请求加一个标识(读锁),让写请求知道,这个时候是不能修改数据的。不然数据就不一致了。如果已经有人在写数据了,再来一个请求写数据,也是不允许的,这样也会导致数据
的不一致,所以所有的写请求,都需要加一个写锁,是为了避免同时对共享数据进行写操作。
二、Zookeeper注册中心
用把生产者注册到zookeeper上,消费者从zookeeper中拿到需要消费的服务。
使用的是SpringCloud的zookeeper注册中心。
服务提供方:
-
生产者的配置文件
-
生产者对外提供的方法
-
配置文件主要加上这个
服务消费方:
- 消费方配置文件:
- 消费方代码:
启动服务:
服务提供方启动两个服务,再把服务使用方启动
会发现zookeeper中注册了两个节点。
调用接口会发现轮询的方式 调用服务提供方的接口:
三、总结
Zookeeper主要作为分布式锁和注册中心来使用。Redis也可以使用setnx来实现分布式锁,两者各有千秋,具体选择还需要看具体需求。注册中心的话也有很多种 Eureka 、Nacos等 Zookeeper的注册中心也主要是配合Dubbo使用。