商品秒杀的业务场景中一般用到锁来解决,但当一线程释放锁时各线程争抢资源会出现羊群效应。
所以下面我使用Zookeeper分布式锁来解决,解决方案及原理如下:

- 所有请求进来,在/lock下创建 临时顺序节点 ,放心,zookeeper会帮你编号排序
- 判断自己是不是/lock下最小的节点
①. 是,获得锁(创建节点)
②. 否,对前面小我一级的节点进行监听 - 获得锁请求,处理完业务逻辑,释放锁(删除节点),后一个节点得到通知(比你年轻的死了,你
成为最嫩的了) - 重复步骤2
zookeeper客户端的java api没有用curator,用的是原始的没被封装好的zookeeper客户端的依赖jar包:
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
</dependency>
下面是spring代码实现:
分布式锁主要在Controller层控制:
@Controller
public class OrderController {
@Autowired
private OrderService orderService;
// 集群ip
static String connectStr = "192.168.1.148,192.168.1.146,192.168.1.188";
// session连接超时限制
static private int sessionTimeout = 60000;
@GetMapping("/buy")
@ResponseBody
public Object buy(String id) throws Exception { //id为商品id
// 创建监听器
Watcher watcher = watchedEvent -> {
if (Watcher.Event.EventType.NodeDeleted.equals(watchedEvent.getType())) {
try {
orderService.buy(id);
} catch (Exception e) {
e.printStackTrace();
}
}
};
ZooKeeper zooKeeperClient = new ZooKeeper(connectStr,sessionTimeout,watcher);
System.out.println(zooKeeperClient);
String s = zooKeeperClient.create("/lock/q", id.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
List<String> children = zooKeeperClient.getChildren(s, true);
String delPath = s;
if (children != null) {
if (children.size() > 0) {
children.sort((o1, o2) -> {
String substring1 = o1.substring(o1.length() - 5);
String substring2 = o2.substring(o2.length() - 5);
return Integer.parseInt(substring1) - Integer.parseInt(substring2);
});
delPath = children.get(0);
}
}
zooKeeperClient.delete(delPath,0);
return "ok";
}
}
service主要实现业务,减库存,里面有逻辑判断库存小于0就抛异常,没有则新增订单:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private ProductMapper productMapper;
@Override
public void buy(String productId) throws Exception{
Product product = productMapper.selectProductById(Integer.parseInt(productId));
Thread.sleep(1000); //模拟大量业务处理的延迟
if (product.getStock() <= 0) {
throw new Exception("商品已抢光");
}
int affectRows = productMapper.reduceProduct(Integer.parseInt(productId));
if (affectRows == 1) {
Order order = new Order();
order.setId(UUID.randomUUID().toString());
order.setPid(Integer.parseInt(productId));
order.setUserid(101); //为测试方便,userid字段随便写死的
orderMapper.create(order);
} else {
throw new Exception("新增订单失败!");
}
}
}
测试过程:在三台zookeeper服务器都正常启动的情况下,在数据库某商品的库存设置成5,然后用jmeter并发请求,1秒请求50次,最后结果是控制台有打印商品已抢光的Excetion栈信息,但数据库该商品的库存变成负数-45。
想请教下是哪里出问题了?