How to Build a High‑Concurrency Flash‑Sale (SecKill) System in Java
This article explains how to design and implement a Java‑based flash‑sale (seckill) system that can handle tens of thousands of concurrent requests, covering entity modeling, DAO methods, service logic, controller handling, a concurrency simulation test, and an improved locking strategy to prevent overselling.
In e‑commerce flash‑sale (seckill) scenarios, massive concurrent requests can overwhelm the system. This article demonstrates a Java‑based solution, including entity design, DAO interfaces, service logic, controller implementation, a concurrency simulation test, and an improved locking strategy to avoid overselling.
Initial Scheme
Entity for the seckill product with initial stock:
@Entity
public class SecKillGoods implements Serializable {
@Id
private String id;
/** remaining stock */
private Integer remainNum;
/** product name */
private String goodsName;
}Entity for successful seckill orders:
@Entity
public class SecKillOrder implements Serializable {
@Id
@GenericGenerator(name = "PKUUID", strategy = "uuid2")
@GeneratedValue(generator = "PKUUID")
@Column(length = 36)
private String id;
// user name
private String consumer;
// product id
private String goodsId;
// purchase quantity
private Integer num;
}DAO for the product, providing a method to reduce stock:
public interface SecKillGoodsDao extends JpaRepository<SecKillGoods, String> {
@Query("update SecKillGoods g set g.remainNum = g.remainNum - ?2 where g.id=?1")
@Modifying(clearAutomatically = true)
@Transactional
int reduceStock(String id, Integer remainNum);
}Service that initializes data and saves orders:
@Service
public class SecKillService {
@Autowired
SecKillGoodsDao secKillGoodsDao;
@Autowired
SecKillOrderDao secKillOrderDao;
/**
* Initialize data at startup: clear tables and add a product with 10 units.
*/
@PostConstruct
public void initSecKillEntity() {
secKillGoodsDao.deleteAll();
secKillOrderDao.deleteAll();
SecKillGoods secKillGoods = new SecKillGoods();
secKillGoods.setId("123456");
secKillGoods.setGoodsName("秒杀产品");
secKillGoods.setRemainNum(10);
secKillGoodsDao.save(secKillGoods);
}
/** Save a successful order */
public void generateOrder(String consumer, String goodsId, Integer num) {
secKillOrderDao.save(new SecKillOrder(consumer, goodsId, num));
}
}Controller handling a seckill request (simplified version):
@Controller
public class SecKillController {
@Autowired
SecKillGoodsDao secKillGoodsDao;
@Autowired
SecKillService secKillService;
@RequestMapping("/seckill.html")
@ResponseBody
public String SecKill(String consumer, String goodsId, Integer num) throws InterruptedException {
SecKillGoods goods = secKillGoodsDao.findOne(goodsId);
if (goods.getRemainNum() >= num) {
// simulate network delay
Thread.sleep(1000);
// reduce stock
secKillGoodsDao.reduceStock(goodsId, num);
// save order
secKillService.generateOrder(consumer, goodsId, num);
return "购买成功";
}
return "购买失败,库存不足";
}
}Simulation controller that spawns multiple threads to mimic high concurrency:
@Controller
public class SecKillSimulationOpController {
final String takeOrderUrl = "http://127.0.0.1:8080/seckill.html";
@RequestMapping("/simulationCocurrentTakeOrder")
@ResponseBody
public String simulationCocurrentTakeOrder() {
SimpleClientHttpRequestFactory httpRequestFactory = new SimpleClientHttpRequestFactory();
for (int i = 0; i < 50; i++) {
final String consumerName = "consumer" + i;
new Thread(() -> {
try {
URI uri = new URI(takeOrderUrl + "?consumer=" + consumerName + "&goodsId=123456&num=1");
ClientHttpRequest request = httpRequestFactory.createRequest(uri, HttpMethod.POST);
InputStream body = request.execute().getBody();
BufferedReader br = new BufferedReader(new InputStreamReader(body));
String line, result = "";
while ((line = br.readLine()) != null) {
result += line;
}
System.out.println(consumerName + ":" + result);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
return "simulationCocurrentTakeOrder";
}
}After running the simulation, the order table should contain only 10 records because the product was initialized with 10 units. The screenshots below show the actual results.
Product table after initialization:
Analysis of Over‑Selling
Multiple threads read the stock simultaneously, each sees enough inventory, but because the stock reduction is delayed, they all proceed, causing dirty reads and overselling.
Improved Scheme
Apply row‑level locking in the database so that only one thread can modify a row at a time. The DAO method is changed to include a condition that the remaining stock must be greater than zero, and the method returns the number of affected rows.
public interface SecKillGoodsDao extends JpaRepository<SecKillGoods, String> {
@Query("update SecKillGoods g set g.remainNum = g.remainNum - ?2 where g.id=?1 and g.remainNum>0")
@Modifying(clearAutomatically = true)
@Transactional
int reduceStock(String id, Integer remainNum);
}The controller now checks the return value of reduceStock to decide whether the purchase succeeded:
@RequestMapping("/seckill.html")
@ResponseBody
public String SecKill(String consumer, String goodsId, Integer num) throws InterruptedException {
SecKillGoods goods = secKillGoodsDao.findOne(goodsId);
if (goods.getRemainNum() >= num) {
Thread.sleep(1000);
int affected = secKillGoodsDao.reduceStock(goodsId, num);
if (affected != 0) {
secKillService.generateOrder(consumer, goodsId, num);
return "购买成功";
} else {
return "购买失败,库存不足";
}
} else {
return "购买失败,库存不足";
}
}Running the improved version under the same high‑concurrency test shows that the stock never drops below zero and the order table contains exactly the initialized number of records, confirming that the row‑level lock prevents overselling even with network delays.
Thus, by leveraging database row‑level locking and checking the affected row count, the seckill system can safely handle massive concurrent requests without the risk of overselling.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
