商品秒杀的业务场景中一般用到锁来解决,但当一线程释放锁时各线程争抢资源会出现羊群效应。
所以下面我使用Zookeeper分布式锁来解决,解决方案及原理如下:
zookeeper客户端的java api没有用curator,用的是原始的没被封装好的zookeeper客户端的依赖jar包:
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.6.0version>
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。
想请教下是哪里出问题了?
是不是光\只解决了并发,但是库存那边需要设置,当最后一件商品被扣完的时候,整个秒杀状态需要停止.