简要
记录一个小项目,通过ZooKeeper加锁实现并发控制。
config层
@Configuration
public class CuratorConfig {
@Autowired
private Environment env;
@Bean
public CuratorFramework curatorFramework() {
CuratorFramework curatorFramework =
CuratorFrameworkFactory.builder().
connectString(env.getProperty("zk.host")).
namespace(env.getProperty("zk.namespace")).
retryPolicy(new RetryNTimes(5, 1000)).build();
curatorFramework.start();
return curatorFramework;
}
}
controller层
@RestController
public class BookRobController {
//定义日志
private static final Logger log= LoggerFactory.getLogger(BookRobController.class);
//定义请求前缀
private static final String prefix="book/rob";
//定义核心逻辑处理器服务类 不加锁
@Autowired
private BookRobService bookRobService;
@RequestMapping(value=prefix+"/request",method= RequestMethod.GET)
public BaseResponse takeMoney(BookRobDto dto){
//校验参数的合法性
if(Strings.isNullOrEmpty(dto.getBookNo())|| dto.getUserId()==null||dto.getUserId()<=0){
return new BaseResponse(StatusCode.InvalidParams);
}
BaseResponse baseResponse=new BaseResponse(StatusCode.Success);
try {
bookRobService.robWithZKLock(dto);
} catch (Exception e) {
e.printStackTrace();
}
return baseResponse;
}
}
dto层
@Data
@ToString
public class BookRobDto implements Serializable {
private Integer userId;
private String bookNo;
}
mapper和model层
使用mybatis生成代码
service层
@Service
public class BookRobService {
//定义日志实例
private static final Logger log = LoggerFactory.getLogger(BookRobService.class);
//定义书籍库存实体操作接口Mapper实例
@Autowired
private BookStockMapper bookStockMapper;
//定义书籍抢购实体操作接口Mapper实例
@Autowired
private BookRobMapper bookRobMapper;
//定义Zookeeper客户端CuratorFramework实例
@Autowired
private CuratorFramework client;
//Zookeeper分布式锁的实现原则是由ZNode节点的创建,删除与监听器构成的
//而ZNode节点将对应一个具体的路径-根Unix文件路径类似-需要以/开头
private static final String pathPrefix = "/zkLock/";
/**
* 处理书籍抢购逻辑-加Zookeeper分布式锁
*
* @param dto
*/
@Transactional(rollbackFor = Exception.class)
public void robWithZKLock(BookRobDto dto) throws Exception {
InterProcessMutex interProcessMutex = new InterProcessMutex(client, pathPrefix + dto.getBookNo() + dto.getUserId() + "---lock");
//获取锁
try {
if (interProcessMutex.acquire(20, TimeUnit.SECONDS)) {
//看此书是否还存在
BookStock bs = bookStockMapper.selectByBookNo(dto.getBookNo());
//此人是否已经抢过了
int total = bookRobMapper.countByBookNoUserId(dto.getUserId(), dto.getBookNo());
if (bs != null && bs.getStock() > 0 && total <= 0) {
int res = bookStockMapper.updateStock(dto.getBookNo());
if (res > 0) {
//增加用户抢购记录
BookRob rob = new BookRob();
BeanUtils.copyProperties(dto, rob);
rob.setRobTime(new Date());
bookRobMapper.insertSelective(rob);
}
}
} else {
log.error("--------获取Zookeeper分布式锁失败!!");
throw new RuntimeException("获取Zookeeper分布式锁失败!");
}
} catch (Exception e) {
log.error("--------获取Zookeeper分布式锁失败!!{}----------", e.getMessage());
} finally {
//不管发生何种情况 在处理完核心业务逻辑之后,需要释放该分布式锁
interProcessMutex.release();
}
}
}
util
public class BaseResponse<T> {
//状态码
private Integer code;
//描述信息
private String msg;
//响应数据-采用泛型表示可以接受通用的数据类型
private T data;
//重载的构造方法一
public BaseResponse(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
//重载的构造方法二
public BaseResponse(StatusCode statusCode) {
this.code = statusCode.getCode();
this.msg = statusCode.getMsg();
}
//重载的构造方法三
public BaseResponse(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
public enum StatusCode {
//以下是暂时设定的几种状态码类
Success(0,"成功"),
Fail(-1,"失败"),
InvalidParams(201,"非法的参数!"),
InvalidGrantType(202,"非法的授权类型");
//状态码
private Integer code;
//描述信息
private String msg;
//重载的构造方法
StatusCode(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
过程
- 开启ZooKeeper
- 开启spring
- 使用jmeter高并发请求
user.csv文件内容:
现在6个人抢购10本书。
未加锁:
加锁: