
本書原始碼
https://github.com/huangwenyi10/spring-boot-book-v2
目錄
- 創建
- 秒殺系統
- 雛形
- 小結
- 詳情
- 高并發優化
- 流量削峰
創建


解決 Cannot resolve plugin org.apache.maven.plugins:maven-site-plugin:3.8.2:
https://ld246.com/article/1584103058009

秒殺系統
雛形
原始碼:
https://github.com/13407196713/SpringBoot2
小結
創建資料庫speed-kill-system
三個表 ay_product、ay_user、ay_user_kill_product
分別是用戶、商品、秒殺的商品
model/AyUser
model/AyProduct
model/AyUserKillProduct
model/KillStatus
mysql的依賴和設定
依賴:
application.properties配置:
repository/ProductRepository 商品的增刪改查 CRUD
repository/AyUserKillProductRepository 用戶秒殺商品記錄Repository 增刪查改 CRUD
service/ProductService
service/AyUserKillProductService
service/impl/ProductServiceImpl
service/impl/AyUserKillProductServiceImpl
controller/ProductController
前端依賴和配置
依賴:
application.properties配置:
templates/product_list.html
templates/success.html
templates/fail.html
詳情
創建資料庫speed-kill-system
三個表 ay_product、ay_user、ay_user_kill_product
分別是用戶、商品、秒殺的商品
model/AyUser
// 用戶
// 對物體注釋,任何 Hibernate 映射物件都要有這個注釋
@Entity
// 宣告此物件映射到資料庫的資料表,如果沒有則系統使用默認值(物體的短類名),
@Table(name = "ay_user")
public class AyUser implements Serializable {
//主鍵
@Id
private Integer id;
//用戶名
private String name;
//電話號碼
private String phoneNumber;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
model/AyProduct
// 商品
@Entity
@Table(name = "ay_product")
public class AyProduct implements Serializable {
/**
* 商品id
*/
@Id
private Integer id;
/**
* 商品圖片
*/
private String productImg;
/**
* 商品名稱
*/
private String name;
/**
* 商品數量
*/
private Integer number;
/**
* 秒殺開始時間
*/
private Date startTime;
/**
* 秒殺結束時間
*/
private Date endTime;
/**
* 創建時間
*/
private Date createTime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
public Date getStartTime() {
return startTime;
}
public void setStartTime(Date startTime) {
this.startTime = startTime;
}
public Date getEndTime() {
return endTime;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getProductImg() {
return productImg;
}
public void setProductImg(String productImg) {
this.productImg = productImg;
}
}
model/AyUserKillProduct
// 秒殺商品
@Entity
@Table(name = "ay_user_kill_product")
public class AyUserKillProduct implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
//指定主鍵的生成策略,有如下四個值 id是主鍵
// TABLE:使用表保存id值
// IDENTITY:由資料庫自動生成
// SEQUENCR :根據底層資料庫的序列來生成主鍵,條件是資料庫支持序列
// AUTO:主鍵由程式控制
private Integer id;
/**
* 商品id
*/
private Integer productId;
/**
* 用戶id
*/
private Integer userId;
/**
* 狀態,-1:無效;0:成功;1:已付款'
*/
private Integer state;
/**
* 創建時間
*/
private Date createTime;
public Integer getProductId() {
return productId;
}
public void setProductId(Integer productId) {
this.productId = productId;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
model/KillStatus
// 描述:秒殺狀態類
public enum KillStatus {
IN_VALID(-1, "無效"),
SUCCESS(0, "成功"),
PAY(1,"已付款");
private int code;
private String name;
KillStatus(){}
KillStatus(int code, String name){
this.code = code;
this.name = name;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
mysql的依賴和設定
依賴:
<!-- mysql start -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
application.properties配置:
### mysql連接資訊
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/speed-kill-system?serverTimezone=UTC
###用戶名
spring.datasource.username=root
###密碼
spring.datasource.password=123456
###驅動
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
repository/ProductRepository 商品的增刪改查 CRUD
// 商品的增刪改查 CRUD
public interface ProductRepository extends JpaRepository<AyProduct,Integer> {
}
repository/AyUserKillProductRepository 用戶秒殺商品記錄Repository 增刪查改 CRUD
// 描述:用戶秒殺商品記錄Repository 增刪查改 CRUD
public interface AyUserKillProductRepository extends JpaRepository<AyUserKillProduct,Integer> {
}
service/ProductService
public interface ProductService {
//查詢所有商品
List<AyProduct> findAll();
//查詢所有商品
// Collection<AyProduct> findAllCache();
// 秒殺商品
// @param productId 商品id
// @param userId 用戶id
AyProduct killProduct(Integer productId, Integer userId);
}
service/AyUserKillProductService
//描述:用戶秒殺商品記錄介面
public interface AyUserKillProductService {
//保存用戶秒殺商品記錄
AyUserKillProduct save(AyUserKillProduct killProduct);
}
service/impl/ProductServiceImpl
// 商品服務
@Service
public class ProductServiceImpl implements ProductService {
@Resource
private ProductRepository productRepository;
@Resource
private AyUserKillProductService ayUserKillProductService;
//日志
Logger logger = LoggerFactory.getLogger(ProductServiceImpl.class);
// 查詢所有商品
@Override
public List<AyProduct> findAll() {
try{
List<AyProduct> ayProducts = productRepository.findAll();
return ayProducts;
}catch (Exception e){
logger.error("ProductServiceImpl.findAll error", e);
return Collections.EMPTY_LIST;
}
}
// 秒殺商品
// @param productId 商品id
// @param userId 用戶id
@Override
public AyProduct killProduct(Integer productId, Integer userId) {
//查詢商品
AyProduct ayProduct = productRepository.findById(productId).get();
//判斷商品是否還有庫存
if(ayProduct.getNumber() < 0){
return null;
}
//設定商品的庫存:原庫存數量 - 1
ayProduct.setNumber(ayProduct.getNumber() - 1);
//更新商品庫存
ayProduct = productRepository.save(ayProduct);
//保存商品的秒殺記錄
AyUserKillProduct killProduct = new AyUserKillProduct();
killProduct.setCreateTime(new Date());
killProduct.setProductId(productId);
killProduct.setUserId(userId);
//設定秒殺狀態
killProduct.setState(KillStatus.SUCCESS.getCode());
//保存秒殺記錄詳細資訊
ayUserKillProductService.save(killProduct);
//商品秒殺成功后,更新快取中商品庫存數量
// redisTemplate.opsForHash().put(KILL_PRODUCT_LIST, killProduct.getProductId(),ayProduct);
return ayProduct;
}
}
service/impl/AyUserKillProductServiceImpl
@Service
public class AyUserKillProductServiceImpl implements AyUserKillProductService {
@Resource
private AyUserKillProductRepository ayUserKillProductRepository;
//保存用戶秒殺商品記錄
//@param killProduct
@Override
public AyUserKillProduct save(AyUserKillProduct killProduct) {
return ayUserKillProductRepository.save(killProduct);
}
}
controller/ProductController
@Controller
@RequestMapping("/products")
public class ProductController {
@Resource
private ProductService productService;
//查詢所有的商品
@RequestMapping("/all")
public String findAll(Model model){
List<AyProduct> products = productService.findAll();
model.addAttribute("products", products);
return "product_list";
}
// 秒殺商品
@RequestMapping("/{id}/kill")
public String killProduct(Model model,
@PathVariable("id") Integer productId,
@RequestParam("userId") Integer userId){
AyProduct ayProduct = productService.killProduct(productId, userId);
if(null != ayProduct){
return "success";
}
return "fail";
}
}
前端依賴和配置
依賴:
<!-- thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
application.properties配置:
#thymeleaf配置
#模板的模式,支持如:HTML、XML、TEXT、JAVASCRIPT等
spring.thymeleaf.mode=HTML5
#編碼,可不用配置
spring.thymeleaf.encoding=UTF-8
#內容類別,可不用配置
spring.thymeleaf.servlet.content-type=text/html
#開發配置為false,避免修改模板還要重啟服務器
spring.thymeleaf.cache=false
#配置模板路徑,默認就是templates,可不用配置
#spring.thymeleaf.prefix=classpath:/templates/
templates/product_list.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>hello</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- 引入 Bootstrap -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<!-- 頁面顯示部分-->
<div class="container">
<div class="panel panel-default">
<div class="panel-heading text-center">
<h1>秒殺活動</h1>
</div>
<div class="panel-body">
<table class="table table-hover">
<thead>
<tr>
<td>圖片</td>
<td>名稱</td>
<td>庫存</td>
<td>開始時間</td>
<td>結束時間</td>
<td>操作</td>
</tr>
</thead>
<tbody>
<tr th:each="product:${products}">
<td>
<img border="1px" width="100px" height="110px" th:src="@{${product.productImg}}"/>
</td>
<td th:text="${product.name}"></td>
<td th:text="${product.number}"></td>
<td th:text="${product.startTime}"></td>
<td th:text="${product.endTime}"></td>
<td>
<!-- 發起秒殺請求,用戶id先簡單寫死為 1 -->
<a class="btn btn-info" th:href="@{'/products/'+${product.id} + '/kill' + '?userId=1'}">秒殺</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
<!-- jQuery檔案,務必在bootstrap.min.js 之前引入 -->
<script src="https://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 檔案 -->
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</html>
templates/success.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>hello</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div align="center">
客官!!! 恭喜您,秒殺成功~~~
</div>
</body>
</html>
templates/fail.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>hello</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div align="center">
不要灰心,繼續加油~~~
</div>
</body>
</html>
啟動 SpringBoot2Application
進入 http://localhost:8080/products/all



高并發優化
原始碼
https://github.com/13407196713/SpringBoot2_2
引入redis快取
application.properties
### redis快取配置
### 默認redis資料庫為db0
spring.redis.database=0
### 服務器地址,默認為localhost
spring.redis.host=localhost
### 鏈接埠,默認為6379
spring.redis.port=6379
### redis密碼默認為空
spring.redis.password=
spring.redis.timeout=10000
service/ProductService
//查詢所有商品
Collection<AyProduct> findAllCache();
service/impl/ProductServiceImpl
//注入redisTemplate物件
@Resource
private RedisTemplate redisTemplate;
//定義快取key
private static final String KILL_PRODUCT_LIST = "kill_product_list";
//查詢商品資料(帶快取)
@Override
public Collection<AyProduct> findAllCache() {
try{
//從快取中查詢商品資料
Map<Integer, AyProduct> productMap = redisTemplate.opsForHash().entries(KILL_PRODUCT_LIST);
Collection<AyProduct> ayProducts = null;
//如果快取中查詢不到商品資料
if(CollectionUtils.isEmpty(productMap)){
//從資料庫中查詢商品資料
ayProducts = productRepository.findAll();
//將商品list轉換為商品map
productMap = convertToMap(ayProducts);
//將商品資料保存到快取中
redisTemplate.opsForHash().putAll(KILL_PRODUCT_LIST, productMap);
//設定快取資料的過期時間,這里設定10s,具體時間需要結合業務需求而定
//如果商品資料變化少,過期時間可以設定長一點;反之,過期時間可以設定短一點
redisTemplate.expire(KILL_PRODUCT_LIST,10000 , TimeUnit.MILLISECONDS);
return ayProducts;
}
ayProducts = productMap.values();
return ayProducts;
}catch (Exception e){
logger.error("ProductServiceImpl.findAllCache error", e);
return Collections.EMPTY_LIST;
}
}
//list轉換為map
private Map<Integer, AyProduct> convertToMap(Collection<AyProduct> ayProducts){
if(CollectionUtils.isEmpty(ayProducts)){
return Collections.EMPTY_MAP;
}
Map<Integer, AyProduct> productMap = new HashMap<>(ayProducts.size());
for(AyProduct product: ayProducts){
productMap.put(product.getId(), product);
}
return productMap;
}
controller/ProductController
// 查詢所有的商品(快取)
@RequestMapping("/all/cache")
public String findAllCache(Model model){
Collection<AyProduct> products = productService.findAllCache();
model.addAttribute("products", products);
return "product_list";
}
商品秒殺成功后,更新快取中商品庫存數量
service/impl/ProductServiceImpl
//商品秒殺成功后,更新快取中商品庫存數量
redisTemplate.opsForHash().put(KILL_PRODUCT_LIST, killProduct.getProductId(),ayProduct);
啟動 redis的redis-server.exe、redis-cli.exe
啟動程式

流量削峰
原始碼
https://github.com/13407196713/SpringBoot2_3
為了讓并發請求更平緩,我們需要流量削峰
用訊息佇列緩沖瞬時流量
ActiveMQ下載安裝
https://blog.csdn.net/weixin_38361347/article/details/83796570
ActiveMQ 依賴
<!-- activemq start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
配置
### activemq 配置
spring.activemq.broker-url=tcp://localhost:61616
spring.activemq.in-memory=true
spring.activemq.pool.enabled=false
spring.activemq.packages.trust-all=true
生產者
producer/AyProductKillProducer
// 生產者
@Service
public class AyProductKillProducer {
//日志
Logger logger = LoggerFactory.getLogger(ProductServiceImpl.class);
@Resource
private JmsMessagingTemplate jmsMessagingTemplate;
/**
* 描述:發送訊息
* @param destination 目標地址
* @param killProduct 描述商品
*/
public void sendMessage(Destination destination, final AyUserKillProduct killProduct) {
logger.info("AyProductKillProducer sendMessage , killProduct is" + killProduct);
jmsMessagingTemplate.convertAndSend(destination, killProduct);
}
}
消費者
consumer/AyProductKillConsumer
// 消費者
@Component
public class AyProductKillConsumer {
//日志
Logger logger = LoggerFactory.getLogger(ProductServiceImpl.class);
@Resource
private AyUserKillProductService ayUserKillProductService;
//消費訊息
@JmsListener(destination = "ay.queue.asyn.save")
public void receiveQueue(AyUserKillProduct killProduct){
//保存秒殺商品資料
ayUserKillProductService.save(killProduct);
//記錄日志
logger.info("ayUserKillProductService save, and killProduct: " + killProduct);
}
}
修改 service/impl/ProductServiceImpl
@Resource
private AyProductKillProducer ayProductKillProducer;
//佇列
private static Destination destination = new ActiveMQQueue("ay.queue.asyn.save");
/**
* 秒殺商品(引入MQ)
* @param productId 商品id
* @param userId 用戶id
*/
@Override
public AyProduct killProduct(Integer productId, Integer userId) {
//查詢商品
AyProduct ayProduct = productRepository.findById(productId).get();
//判斷商品是否還有庫存
if(ayProduct.getNumber() < 0){
return null;
}
//設定商品的庫存:原庫存數量 - 1
ayProduct.setNumber(ayProduct.getNumber() - 1);
//更新商品庫存
ayProduct = productRepository.save(ayProduct);
//保存商品的秒殺記錄
AyUserKillProduct killProduct = new AyUserKillProduct();
killProduct.setCreateTime(new Date());
killProduct.setProductId(productId);
killProduct.setUserId(userId);
//設定秒殺狀態
killProduct.setState(KillStatus.SUCCESS.getCode());
//保存秒殺記錄詳細資訊
//ayUserKillProductService.save(killProduct);
//異步保存商品的秒殺記錄
ayProductKillProducer.sendMessage(destination, killProduct);
//商品秒殺成功后,更新快取中商品庫存數量
redisTemplate.opsForHash().put(KILL_PRODUCT_LIST, killProduct.getProductId(),ayProduct);
return ayProduct;
}
啟動 redis的redis-server.exe、redis-cli.exe
啟動apache-activemq 的 bin/win64/activemq
啟動程式
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/300738.html
標籤:java
上一篇:cgb2107-day17
