为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLock或Synchronized)进行互斥控制。在单机环境中,Java中提供了很多并发处理相关的API。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!
目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。
利用Memcached的add命令。此命令是原子性操作,只有在key不存在的情况下,才能add成功,也就意味着线程得到了锁。
和Memcached的方式类似,利用Redis的setnx命令。此命令同样是原子性操作,只有在key不存在的情况下,才能set成功。(setnx命令并不完善,指令本身是不支持传入超时时间的,幸好Redis 2.6.12以上版本为set指令增加了可选参数)
可参考:https://juejin.cn/post/6844903616667451399
Google公司实现的粗粒度分布式锁服务,底层利用了Paxos一致性算法。
可参考:https://www.w3cschool.cn/architectroad/architectroad-distributed-lock-2.html
利用Zookeeper的顺序临时节点,来实现分布式锁和等待队列。Zookeeper设计的初衷,就是为了实现分布式锁服务的。我们这篇文章主要将的是这种方式。
Items.java:
package com.huiq.springboot.domain;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table
public class Items {
@Id
private String id;
private String name;
private Integer counts;
// get和set方法省略。。。。。。
}
Orders.java:
package com.huiq.springboot.domain;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table
public class Orders {
@Id
private String id;
private String itemId;
// get和set方法省略。。。。。。
}
ItemsDao.java:
package com.huiq.springboot.dao;
import com.huiq.springboot.domain.Items;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ItemsDao extends JpaRepository<Items, String > {
}
OrdersDao.java:
package com.huiq.springboot.dao;
import com.huiq.springboot.domain.Orders;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrdersDao extends JpaRepository<Orders, String> {
}
ItemsService.java:
package com.huiq.springboot.service;
import com.huiq.springboot.dao.ItemsDao;
import com.huiq.springboot.domain.Items;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service
public class ItemsService {
@Autowired
private ItemsDao itemsDao;
public Items getItem(String itemId) {
return itemsDao.findById(itemId).orElse(null);
}
public void save(Items items) {
items.setId(UUID.randomUUID().toString());
itemsDao.save(items);
}
// 根据itemId获取库存量
public int getItemCounts(String itemId) {
return itemsDao.findById(itemId).orElse(null).getCounts();
}
// 调整库存
public void reduceCount(String itemId, int count) {
Items items = getItem(itemId);
items.setCounts(items.getCounts() - count);
itemsDao.save(items);
}
}
OrdersService.java:
package com.huiq.springboot.service;
import com.huiq.springboot.dao.OrdersDao;
import com.huiq.springboot.domain.Orders;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service
@Slf4j
public class OrdersService {
@Autowired
private OrdersDao ordersDao;
public boolean save(String itemId) {
try {
Orders orders = new Orders();
orders.setId(UUID.randomUUID().toString());
orders.setItemId(itemId);
ordersDao.save(orders);
log.info("订单创建成功。。。。。。");
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
PayService.java:
package com.huiq.springboot.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class PayService {
@Autowired
private OrdersService ordersService;
@Autowired
private ItemsService itemsService;
public boolean buy(String itemId) {
// 假设每次购买9个
int buyCount = 9;
// 第一步:是否有库存
int count = itemsService.getItemCounts(itemId);
if (count < 9) {
log.error("库存不足,下单失败。购买数{}件,库存只有{}件", buyCount, count);
return false;
}
// 第二步:创建订单
boolean flag = ordersService.save(itemId);
// 模拟高并发的场景
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 第三步:减库存
if (flag) {
itemsService.reduceCount(itemId, buyCount);
} else {
log.error("订单创建失败。。。。。。");
return false;
}
return true;
}
}
BuyController.java:
package com.huiq.springboot.controller;
import com.huiq.springboot.service.PayService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BuyController {
@Autowired
private PayService payService;
@GetMapping("/buy1")
@ResponseBody
public String buy1(String itemId) {
if (StringUtils.isNotBlank(itemId)) {
if (payService.buy(itemId)) {
return "订单创建成功。。。。。。";
} else {
return "订单创建失败。。。。。。";
}
} else {
return "条目id不能为空。。。。。。";
}
}
@GetMapping("/buy2")
@ResponseBody
public String buy2(String itemId) {
if (StringUtils.isNotBlank(itemId)) {
if (payService.buy(itemId)) {
return "订单创建成功。。。。。。";
} else {
return "订单创建失败。。。。。。";
}
} else {
return "条目id不能为空。。。。。。";
}
}
}
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.4.3version>
<relativePath/>
parent>
<groupId>com.huiqgroupId>
<artifactId>spring-boot-data-payartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>spring-boot-data-jdbcname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>commons-langgroupId>
<artifactId>commons-langartifactId>
<version>2.6version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
启动程序后插入相应的测试数据:
insert into items (id,counts,name) values('1',10,'三国演义');
insert into items (id,counts,name) values('2',6,'红楼梦');
insert into items (id,counts,name) values('3',2,'西游记');