Curator实现zk分布式锁的确很简单,比zkclient便捷太多了。
部分内容参考 stwen 阿甘正专,向原作者致谢。
环境:SpringBoot,redis集群,zookeeper3.4.14,Curator4.2,jmeter测试。
相关依赖:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
<exclusions>
<exclusion>
<artifactId>zookeeper</artifactId>
<groupId>org.apache.zookeeper</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.7</version>
</dependency>
<!-- lombok包 @Data :注解在类上;提供类所有属性的 getting 和 setting 方法,此外还提供了equals、canEqual、hashCode、toString 方法
同时可以使用@Slf4j注解-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<!-- springboot的配置文件解析依赖,可以通过注释@ConfigurationProperties生成元数据文件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.1.7.RELEASE</version>
</dependency>
配置文件 application.properties
#重试次数
curator.retryCount=5
#重试间隔时间
curator.elapsedTimeMs=5000
#zookeeper 地址
curator.connectString=192.168.37.134:2181
#session超时时间
curator.sessionTimeoutMs=60000
#连接超时时间
curator.connectionTimeoutMs=5000
spring.redis.cluster.nodes=192.168.37.134:7001,192.168.37.134:7002,192.168.37.134:7003,192.168.37.134:7004,192.168.37.134:7005,192.168.37.134:7006
Configuration配置类
import lombok.Data;
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.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "curator")
@Data
public class CuratorConfig {
private int retryCount; //连接重试次数
private int elapsedTimeMs; //重试的间隔时间
private String connectString; //zookeeper连接地址
private int sessionTimeoutMs; //session超时时间
private int connectionTimeoutMs; //连接超时时间
@Bean(initMethod = "start")
public CuratorFramework getCuratorFramework(){
RetryPolicy retryPolicy=new ExponentialBackoffRetry(elapsedTimeMs,retryCount);
CuratorFramework curatorFramework= CuratorFrameworkFactory.builder()
.connectString(connectString)
.sessionTimeoutMs(sessionTimeoutMs)
.retryPolicy(retryPolicy)
.build();
return curatorFramework;
}
}
测试业务类 ,实现分布式锁
取得可重入互斥锁InterProcessMutex,以lock.acquire()加锁,lock.release()解锁。
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.Date;
@Slf4j
@Service
public class OrderImp {
@Autowired
private CuratorFramework curatorFramework;
@Autowired
private RedisTemplate<String,String> rt;
private static int sellnum=0; //售出数量
private final String path="/distribute/lock"; //zk临时节点位置
/*redis现有库存 store=100*/
public void generateOrder()
{
InterProcessMutex rlock=new InterProcessMutex(curatorFramework,path);
try {
rlock.acquire();
log.info(Thread.currentThread().getName()+"取锁成功");
int store=Integer.valueOf(rt.opsForValue().get("store"));
if(0<store--){
rt.opsForValue().set("store",String.valueOf(store));
String ordernum=this.orderNumber("miphone");
rt.opsForList().leftPush("order",ordernum);
//System.out.printf("售出%d个,订单号为:%s\n",sellnum,ordernum);
log.info("售出{}个,订单号为:{}",sellnum,ordernum);
}else
log.info("已售完,下次早些来吧");
} catch (Exception e) {
log.error("some wrong");
e.printStackTrace();
} finally {
try {
rlock.release();
log.info(Thread.currentThread().getName()+"解锁");
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 生成订单编号,设编号格式为productID/MM-dd-HH:mm:ss,sellnum
public String orderNumber(String productID){
//当前时间
SimpleDateFormat sdate=new SimpleDateFormat("MM-dd-HH:mm:ss");
String datestring=sdate.format(new Date());
return productID+"/"+datestring+(++sellnum);
}
}
测试Controller
jmeter测试
500线程,重复2次访问。
结果正常