引言:分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
1、分布式锁代码设计
zk实现简单的分布式锁的思路,主要是抓住一下三点:
(1)当一个客户端成功创建一个节点,另外一个客户端是无法创建同名的节点(达到互斥的效果)
(2)我们注册该节点的监听时间,当节点删除,会通知其他的客户端,这个时候其他的客户端可以重新去创建该节点(可以认为时拿到锁的客户端释放锁,其他的客户端可以抢锁)
(3)创建的节点应该是临时节点,这样保证我们在已经拿到锁的客户端挂掉了会自动释放锁
2、基于Curator的ZK代码设计
Apache Curator是一个比较完善的ZooKeeper客户端框架,通过封装的一套高级API 简化了ZooKeeper的操作。通过查看官方文档,可以发现Curator主要解决了三类问题:
- 封装ZooKeeper client与ZooKeeper server之间的连接处理
- 提供了一套Fluent风格的操作API
- 提供ZooKeeper各种应用场景(recipe, 比如:分布式锁服务、集群领导选举、共享计数器、缓存机制、分布式队列等)的抽象封装
Curator主要从以下几个方面降低了zk使用的复杂性:
重试机制:提供可插拔的重试机制, 它将给捕获所有可恢复的异常配置一个重试策略,并且内部也提供了几种标准的重试策略(比如指数补偿)
连接状态监控: Curator初始化之后会一直对zk连接进行监听,一旦发现连接状态发生变化将会作出相应的处理
zk客户端实例管理: Curator会对zk客户端到server集群的连接进行管理,并在需要的时候重建zk实例,保证与zk集群连接的可靠性
各种使用场景支持: Curator实现了zk支持的大部分使用场景(甚至包括zk自身不支持的场景),这些实现都遵循了zk的最佳实践,并考虑了各种极端情况
引入依赖:
<!-- zookeeper 分布式锁、注意zookeeper版本 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.1</version>
</dependency>
分布式锁ZkLockUtil:
public class ZkLockUtil{
//ZooKeeper服务地址
private static String address = "192.168.174.100:2181";
//创建连接实例
public static CuratorFramework client;
static{
//1、重试策略:初试时间为1s 重试3次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
//2、通过工厂创建连接
client = CuratorFrameworkFactory.newClient(address, retryPolicy);
//3、开启连接
client.start();
}
/**
* 私有的默认构造子,保证外界无法直接实例化
*/
private ZkLockUtil(){};
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
* 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
*/
private static class SingletonHolder{
/**
* 静态初始化器,由JVM来保证线程安全
*/
private static InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock");
}
public static InterProcessMutex getMutex(){
return SingletonHolder.mutex;
}
//获得了锁
public static boolean acquire(long time, TimeUnit unit){
try {
return getMutex().acquire(time,unit);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
//释放锁
public static void release(){
try {
getMutex().release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
以秒杀商品为例,controller层设计:
@RestController
@RequestMapping("/seckillDistributed")
public class SeckillDistributedController {
private final static Logger LOGGER = LoggerFactory.getLogger(SeckillDistributedController.class);
private static int corePoolSize = Runtime.getRuntime().availableProcessors();
//调整队列数 拒绝服务
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, corePoolSize+1, 10l, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(10000));
@Autowired
private ISeckillService seckillService;
@Autowired
private ISeckillDistributedService seckillDistributedService;
@PostMapping("/startZkLock")
public Result startZkLock(long seckillId){
seckillService.deleteSeckill(seckillId);
final long killId = seckillId;
LOGGER.info("开始秒杀");
for(int i=0;i<10000;i++){
final long userId = i;
Runnable task = new Runnable() {
@Override
public void run() {
Result result = seckillDistributedService.startSeckilZksLock(killId, userId);
LOGGER.info("用户:{}{}",userId,result.get("msg"));
}
};
executor.execute(task);
}
try {
Thread.sleep(10000);
Long seckillCount = seckillService.getSeckillCount(seckillId);
LOGGER.info("一共秒杀出{}件商品",seckillCount);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Result.ok();
}
}
service服务层设计:
@Service
public class SeckillDistributedServiceImpl implements ISeckillDistributedService {
@Transactional
public Result startSeckilZksLock(long seckillId, long userId) {
boolean res=false;
try {
res = ZkLockUtil.acquire(3,TimeUnit.SECONDS);//zk获取锁
if(res){
String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
Long number = ((Number) object).longValue();
if(number>0){
SuccessKilled killed = new SuccessKilled();
killed.setSeckillId(seckillId);
killed.setUserId(userId);
killed.setState((short)0);
killed.setCreateTime(new Timestamp(new Date().getTime()));
dynamicQuery.save(killed);
nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=? AND number>0";
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
}else{
return Result.error(SeckillStatEnum.END);
}
}else{
return Result.error(SeckillStatEnum.MUCH);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
if(res){
ZkLockUtil.release(); //zk释放锁
}
}
return Result.ok(SeckillStatEnum.SUCCESS);
}
}