1 ZooKeeper实现分布式锁
Zookeeper的数据存储结构就像一棵树,这棵树由节点组成,这种节点叫做Znode。
Zookeeper中节点分为4种类型:
1.持久节点 (PERSISTENT)
默认的节点类型。创建节点的客户端与zookeeper断开连接后,该节点依旧存在
2.持久顺序节点(PERSISTENT_SEQUENTIAL)
所谓顺序节点,就是在创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号
3.临时节点(EPHEMERAL)
和持久节点相反,当创建节点的客户端与zookeeper断开连接后,临时节点会被删除
4.临时顺序节点(EPHEMERAL_SEQUENTIAL)
顾名思义,临时顺序节点结合和临时节点和顺序节点的特点:在创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号;当创建节点的客户端与zookeeper断开连接后,临时节点会被删除
Zookeeper实现分布式锁的原理是基于Zookeeper的临时顺序节点,如下图:
客户端A和客户端B争抢分布式锁,其实就是在/my_lock节点下创建临时顺序节点,这个顺序节点由zk内部自行维护一个节点序号,序号最小则表示对应客户端获得锁。
Apache Curator是一个比较完善的ZooKeeper客户端框架,通过封装的一套高级API 简化了ZooKeeper的操作,其中就包括分布式锁的实现。
2 JavaAPI具体操作过程如下
第一步:在pom.xml中导入maven坐标
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.10</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
第二步:编写配置类
package com.oldlu.config;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ZkConfig {
@Bean
public CuratorFramework curatorFramework(){
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("localhost:2181")
.sessionTimeoutMs(5000)
.connectionTimeoutMs(5000)
.retryPolicy(retryPolicy)
.build();
client.start();
return client;
}
}
第三步:改造StockController
@Autowired
private CuratorFramework curatorFramework;
@GetMapping("/stock")
public String stock() {
InterProcessMutex mutex = new InterProcessMutex(curatorFramework,"/mylock");
try {
//尝试获得锁
boolean locked = mutex.acquire(0, TimeUnit.SECONDS);
if(locked){
int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
if(stock > 0){
stock --;
redisTemplate.opsForValue().set("stock",stock+"");
System.out.println("库存扣减成功,剩余库存:" + stock);
}else {
System.out.println("库存不足!!!");
}
//释放锁
mutex.release();
}else{
System.out.println("没有获取锁,不能执行减库存操作!!!");
}
}catch (Exception ex){
System.out.println("出现异常!!!");
}
return "OK";
}
通过观察控制台输出可以看到,使用此种方式已经解决了线程并发问题。