一、前端实现
前端项目初始化
首先,我们需要创建一个新的 Vue 3 项目。你可以使用 Vue CLI 来快速搭建项目。
安装 Vue CLI(如果尚未安装)
bash
bash
npm install -g @vue/cli
创建 Vue 项目
bash
bash
vue create seckill-frontend
cd seckill-frontend
在创建过程中选择默认的 Vue 3 预设或其他你需要的配置。
安装 Axios
Axios 是一个常用的 HTTP 客户端,用于发送请求到后端 API。
bash
bash
npm install axios
实现前端功能
1. 商品列表页面
创建一个 ProductList.vue
组件来显示商品列表,并提供秒杀按钮。
html
html
<template>
<div class="product-list">
<h1>Available Products</h1>
<ul>
<li v-for="product in products" :key="product.id" class="product-item">
<h2>{{ product.name }}</h2>
<p>Price: ${{ product.price }}</p>
<p>Stock: {{ product.stock }}</p>
<button @click="seckill(product.id)" :disabled="product.stock <= 0 || isSeckilling">
{{ isSeckilling ? 'Secilling...' : 'Seckill Now' }}
</button>
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
products: [],
isSeckilling: false,
};
},
created() {
this.fetchProducts();
},
methods: {
async fetchProducts() {
try {
const response = await axios.get('http://localhost:8080/api/products?page=0&size=10');
this.products = response.data;
} catch (error) {
console.error('Error fetching products:', error);
}
},
async seckill(productId) {
if (this.isSeckilling) return;
this.isSeckilling = true;
try {
const response = await axios.post(`http://localhost:8080/api/products/seckill/${productId}/1/1`);
alert(response.data);
} catch (error) {
console.error('Seckill failed:', error);
alert('Seckill failed! Please try again.');
} finally {
this.isSeckilling = false;
this.fetchProducts(); // Refresh product list after attempting seckill
}
},
},
};
</script>
<style scoped>
.product-list {
padding: 20px;
}
.product-item {
border-bottom: 1px solid #ccc;
padding: 10px 0;
}
.product-item button {
margin-top: 10px;
padding: 5px 10px;
cursor: pointer;
}
</style>
2. 主页路由配置
修改 src/router/index.js
文件以包含 ProductList
组件。
js
javascript
import { createRouter, createWebHistory } from 'vue-router';
import ProductList from '../components/ProductList.vue';
const routes = [
{
path: '/',
name: 'ProductList',
component: ProductList,
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
3. 修改 App.vue
确保 App.vue
正确引入并使用路由。
html
html
<template>
<router-view></router-view>
</template>
<script>
export default {
name: 'App',
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
运行前端应用
确保你的后端服务正在运行,然后启动前端应用:
bash
bash
npm run serve
打开浏览器访问 http://localhost:8081
(假设前端服务运行在 8081 端口),你应该能看到商品列表和秒杀按钮。
总结
通过以上步骤,我们成功创建了一个简单的前端应用使用 Vue 3.0 来与后端秒杀系统进行交互。以下是整个前端项目的概览图:
二、后端实现
实现一个高并发秒杀系统是一个复杂的任务,需要仔细设计和优化以确保系统的稳定性和性能。以下是一个详细的步骤指南,帮助你构建一个基于微服务架构的高并发秒杀系统。
1. 技术选型
选择合适的技术栈对于提高系统的性能和可维护性至关重要。以下是一些推荐的技术:
- 框架: Spring Boot
- 数据库: MySQL 或 PostgreSQL
- 缓存: Redis
- 消息队列: RabbitMQ 或 Kafka(如果需要异步处理)
- 负载均衡: Nginx
- 容器化: Docker
- 部署: Kubernetes
- 分布式锁: Redisson 或 Zookeeper
2. 架构设计
2.1 微服务架构
将应用分解为多个微服务,每个微服务负责特定的功能。例如:
- User Service: 用户管理
- Product Service: 商品管理
- Order Service: 订单管理
- Seckill Service: 秒杀活动管理
- Inventory Service: 库存管理
- Gateway Service: API Gateway
2.2 API Gateway
使用 Spring Cloud Gateway 或 Netflix Zuul 作为 API Gateway,负责路由请求到相应的微服务,并提供统一的身份验证和日志记录。
2.3 数据库设计
设计合理的数据库模式,确保查询高效。可以使用关系型数据库(如 MySQL)或 NoSQL 数据库(如 MongoDB),具体取决于需求。
2.4 缓存机制
使用 Redis 进行缓存,减少对数据库的直接访问,提高响应速度。
2.5 异步处理
使用消息队列(如 RabbitMQ 或 Kafka)进行异步处理,提高系统的吞吐量和响应速度。
2.6 分布式锁
使用 Redisson 或 Zookeeper 实现分布式锁,确保库存扣减的原子性。
3. 实施步骤
3.1 创建 Spring Boot 项目
使用 Spring Initializr 创建一个新的 Spring Boot 项目。
bash
https://start.spring.io/
选择以下依赖:
- Spring Web
- Spring Data JPA
- MySQL Driver
- Lombok
- Spring Security (如果需要身份验证)
- Spring Cloud Gateway
- Spring Cloud Eureka (服务注册与发现)
- Spring AMQP (RabbitMQ)
- Redis
- Resilience4j (容错)
3.2 配置数据库连接
在 application.properties
中配置数据库连接:
bash
spring.datasource.url=jdbc:mysql://localhost:3306/seckill_system?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
3.3 创建实体类
创建实体类来表示数据库中的表。例如,创建 Product
和 Order
实体类:
java
java
package com.seckillsystem.model;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Data
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double price;
private Integer stock;
}
java
package com.seckillsystem.model;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Data
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long productId;
private Long userId;
private Integer quantity;
}
3.4 创建 Repository
创建 Repository 接口来操作数据库。例如,创建 ProductRepository
和 OrderRepository
:
java
java
package com.seckillsystem.repository;
import com.seckillsystem.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
}
java
package com.seckillsystem.repository;
import com.seckillsystem.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderRepository extends JpaRepository<Order, Long> {
}
3.5 创建 Service 层
创建 Service 层来处理业务逻辑。例如,创建 ProductService
和 OrderService
:
java
java
package com.seckillsystem.service;
import com.seckillsystem.model.Product;
import com.seckillsystem.repository.ProductRepository;
import org.redisson.api.RedissonClient;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Autowired
private RedissonClient redissonClient;
public Optional<Product> getProductById(Long id) {
return productRepository.findById(id);
}
public boolean reduceStock(Long productId) {
RLock lock = redissonClient.getLock("product:" + productId + ":lock");
try {
if (lock.tryLock()) {
Product product = productRepository.findById(productId).orElse(null);
if (product != null && product.getStock() > 0) {
product.setStock(product.getStock() - 1);
productRepository.save(product);
return true;
}
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return false;
}
}
java
package com.seckillsystem.service;
import com.seckillsystem.model.Order;
import com.seckillsystem.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public Order createOrder(Order order) {
return orderRepository.save(order);
}
}
3.6 创建 Controller 层
创建 Controller 层来处理 HTTP 请求。例如,创建 ProductController
和 OrderController
:
java
java
package com.seckillsystem.controller;
import com.seckillsystem.model.Product;
import com.seckillsystem.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/products")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping
public List<Product> getAllProducts() {
return productService.getAllProducts();
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
return productService.getProductById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping("/seckill/{productId}/{userId}/{quantity}")
public ResponseEntity<String> seckill(@PathVariable Long productId, @PathVariable Long userId, @PathVariable Integer quantity) {
if (productService.reduceStock(productId)) {
Order order = new Order();
order.setProductId(productId);
order.setUserId(userId);
order.setQuantity(quantity);
productService.createOrder(order);
return ResponseEntity.ok("Seckill successful!");
} else {
return ResponseEntity.badRequest().body("Seckill failed! Out of stock.");
}
}
}
java
package com.seckillsystem.controller;
import com.seckillsystem.model.Order;
import com.seckillsystem.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public Order createOrder(@RequestBody Order order) {
return orderService.createOrder(order);
}
}
3.7 配置 Redis 缓存
在 pom.xml
中添加 Redis 依赖:
xml
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.8</version>
</dependency>
在 application.properties
中配置 Redis 连接:
XML
spring.redis.host=localhost
spring.redis.port=6379
# Redisson configuration
spring.redisson.config=classpath:redisson-config.yml
创建 redisson-config.yml
文件:
XML
singleServerConfig:
address: "redis://127.0.0.1:6379"
threads: 0
nettyThreads: 0
codec:
class: "org.redisson.codec.JsonJacksonCodec"
transportMode: "NIO"
3.8 使用 Swagger 文档
添加 Swagger 依赖以生成 API 文档:
在 pom.xml
中添加 Swagger 依赖:
xml
XML
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
创建 Swagger 配置类:
java
java
package com.seckillsystem.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.seckillsystem.controller"))
.paths(PathSelectors.any())
.build();
}
}
3.9 容器化和部署
创建 Dockerfile
来容器化应用:
为每个微服务创建一个 Dockerfile
。这里以 Product Service
为例:
Dockerfile
java
# Use an official OpenJDK runtime as a parent image
FROM openjdk:17-jdk-slim
# Set the working directory in the container
WORKDIR /app
# Copy the current directory contents into the container at /app
COPY target/product-service.jar /app/product-service.jar
# Make port 8080 available to the world outside this container
EXPOSE 8080
# Run the application
CMD ["java", "-jar", "product-service.jar"]
假设其他微服务也有类似的结构,你可以分别为它们创建相应的 Dockerfile
。
3.9.2 构建 Docker 镜像
在每个微服务目录中运行以下命令来构建 Docker 镜像:
bash
bash
mvn clean package
docker build -t product-service .
重复上述步骤为其他微服务构建镜像。
3.9.3 使用 Docker Compose 进行本地开发
为了简化多容器应用的开发、测试和部署,可以使用 Docker Compose。
创建 docker-compose.yml
文件:
Yaml
bash
version: '3.8'
services:
product-service:
image: product-service
ports:
- "8081:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/seckill_system?useSSL=false&serverTimezone=UTC
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: password
depends_on:
- db
- redis
order-service:
image: order-service
ports:
- "8082:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/seckill_system?useSSL=false&serverTimezone=UTC
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: password
depends_on:
- db
- redis
- rabbitmq
seckill-service:
image: seckill-service
ports:
- "8083:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/seckill_system?useSSL=false&serverTimezone=UTC
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: password
depends_on:
- db
- redis
- rabbitmq
gateway-service:
image: gateway-service
ports:
- "8080:8080"
depends_on:
- product-service
- order-service
- seckill-service
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: seckill_system
volumes:
- db_data:/var/lib/mysql
redis:
image: redis:latest
rabbitmq:
image: rabbitmq:management
volumes:
db_data:
启动所有服务:
bash
bash
docker-compose up --build
3.9.4 使用 Kubernetes 进行生产部署
为了在生产环境中部署,我们可以使用 Kubernetes。首先,确保你已经安装了 Kubernetes 和 kubectl 工具。
3.9.4.1 创建 Kubernetes 部署文件
为每个微服务创建一个 Kubernetes 部署文件。这里以 Product Service
为例:
Yaml
bash
apiVersion: apps/v1
kind: Deployment
metadata:
name: product-service
spec:
replicas: 3
selector:
matchLabels:
app: product-service
template:
metadata:
labels:
app: product-service
spec:
containers:
- name: product-service
image: product-service:latest
ports:
- containerPort: 8080
env:
- name: SPRING_DATASOURCE_URL
value: jdbc:mysql://mysql:3306/seckill_system?useSSL=false&serverTimezone=UTC
- name: SPRING_DATASOURCE_USERNAME
value: root
- name: SPRING_DATASOURCE_PASSWORD
value: password
---
apiVersion: v1
kind: Service
metadata:
name: product-service
spec:
type: ClusterIP
selector:
app: product-service
ports:
- protocol: TCP
port: 80
targetPort: 8080
为其他微服务创建类似的部署文件。
3.9.4.2 创建 ConfigMap 和 Secret
创建 ConfigMap 和 Secret 来管理配置和敏感信息。
Yaml
bash
apiVersion: v1
kind: ConfigMap
metadata:
name: database-config
data:
spring.datasource.url: jdbc:mysql://mysql:3306/seckill_system?useSSL=false&serverTimezone=UTC
spring.datasource.username: root
---
apiVersion: v1
kind: Secret
metadata:
name: database-secret
type: Opaque
data:
spring.datasource.password: cGFzc3dvcmQ= # base64 encoded 'password'
3.9.4.3 创建 MySQL 和 Redis 的 StatefulSet 和 Headless Service
Yaml
bash
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: "mysql"
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: database-secret
key: spring.datasource.password
- name: MYSQL_DATABASE
valueFrom:
configMapKeyRef:
name: database-config
key: spring.datasource.database
ports:
- containerPort: 3306
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: mysql-persistent-storage
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
ports:
- port: 3306
clusterIP: None
selector:
app: mysql
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
spec:
serviceName: "redis"
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:latest
ports:
- containerPort: 6379
volumeMounts:
- name: redis-persistent-storage
mountPath: /data
volumeClaimTemplates:
- metadata:
name: redis-persistent-storage
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: Service
metadata:
name: redis
spec:
ports:
- port: 6379
clusterIP: None
selector:
app: redis
3.9.4.4 创建 RabbitMQ 的 StatefulSet 和 Headless Service
Yaml
bash
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: rabbitmq
spec:
serviceName: "rabbitmq"
replicas: 1
selector:
matchLabels:
app: rabbitmq
template:
metadata:
labels:
app: rabbitmq
spec:
containers:
- name: rabbitmq
image: rabbitmq:management
ports:
- containerPort: 5672
- containerPort: 15672
volumeMounts:
- name: rabbitmq-persistent-storage
mountPath: /var/lib/rabbitmq
volumeClaimTemplates:
- metadata:
name: rabbitmq-persistent-storage
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: Service
metadata:
name: rabbitmq
spec:
ports:
- port: 5672
- port: 15672
clusterIP: None
selector:
app: rabbitmq
3.9.4.5 应用 Kubernetes 配置
将所有 YAML 文件保存到一个目录中,然后应用这些配置:
bash
bash
kubectl apply -f .
4. 性能优化
4.1 数据库索引
确保数据库表上有适当的索引,以加速查询。例如,在 Product
表的 id
和 stock
字段上创建索引。
sql
sql
CREATE INDEX idx_product_id ON product(id);
CREATE INDEX idx_product_stock ON product(stock);
4.2 分页查询
对于大量数据的查询,使用分页查询来减少每次查询的数据量。在 ProductService
中添加分页查询方法:
java
java
package com.seckillsystem.service;
import com.seckillsystem.model.Product;
import com.seckillsystem.repository.ProductRepository;
import org.redisson.api.RedissonClient;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Autowired
private RedissonClient redissonClient;
public Optional<Product> getProductById(Long id) {
return productRepository.findById(id);
}
public boolean reduceStock(Long productId) {
RLock lock = redissonClient.getLock("product:" + productId + ":lock");
try {
if (lock.tryLock()) {
Product product = productRepository.findById(productId).orElse(null);
if (product != null && product.getStock() > 0) {
product.setStock(product.getStock() - 1);
productRepository.save(product);
return true;
}
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return false;
}
public Page<Product> getAllProducts(Pageable pageable) {
return productRepository.findAll(pageable);
}
}
4.3 响应式编程
使用 Spring WebFlux 进行响应式编程,提高非阻塞 I/O 的性能。首先,将项目转换为 Spring WebFlux 项目。
在 pom.xml
中添加 Spring WebFlux 依赖:
xml
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
修改 ProductController
使用 WebFlux:
java
java
package com.seckillsystem.controller;
import com.seckillsystem.model.Product;
import com.seckillsystem.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/api/products")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public Flux<Product> getAllProducts(@RequestParam int page, @RequestParam int size) {
return Flux.fromIterable(productService.getAllProducts(PageRequest.of(page, size)).getContent());
}
@GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<Product> getProductById(@PathVariable Long id) {
return Mono.justOrEmpty(productService.getProductById(id));
}
@PostMapping(value = "/seckill/{productId}/{userId}/{quantity}", produces = MediaType.TEXT_PLAIN_VALUE)
public Mono<String> seckill(@PathVariable Long productId, @PathVariable Long userId, @PathVariable Integer quantity) {
if (productService.reduceStock(productId)) {
Order order = new Order();
order.setProductId(productId);
order.setUserId(userId);
order.setQuantity(quantity);
productService.createOrder(order);
return Mono.just("Seckill successful!");
} else {
return Mono.just("Seckill failed! Out of stock.");
}
}
}
4.4 日志监控
使用 ELK Stack (Elasticsearch, Logstash, Kibana) 或 Prometheus + Grafana 进行日志收集和监控。
4.4.1 配置 ELK Stack
在 Kubernetes 中部署 Elasticsearch、Logstash 和 Kibana。
Elasticsearch Deployment YAML:
Yaml
java
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: elasticsearch
spec:
serviceName: "elasticsearch"
replicas: 1
selector:
matchLabels:
app: elasticsearch
template:
metadata:
labels:
app: elasticsearch
spec:
containers:
- name: elasticsearch
image: docker.elastic.co/elasticsearch/elasticsearch:7.10.1
ports:
- containerPort: 9200
- containerPort: 9300
env:
- name: discovery.type
value: single-node
volumeMounts:
- name: elasticsearch-persistent-storage
mountPath: /usr/share/elasticsearch/data
volumeClaimTemplates:
- metadata:
name: elasticsearch-persistent-storage
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: elasticsearch
spec:
ports:
- port: 9200
clusterIP: None
selector:
app: elasticsearch
Logstash Deployment YAML:
Yaml
java
apiVersion: apps/v1
kind: Deployment
metadata:
name: logstash
spec:
replicas: 1
selector:
matchLabels:
app: logstash
template:
metadata:
labels:
app: logstash
spec:
containers:
- name: logstash
image: docker.elastic.co/logstash/logstash:7.10.1
ports:
- containerPort: 5044
volumeMounts:
- name: logstash-config
mountPath: /usr/share/logstash/pipeline/
volumes:
- name: logstash-config
configMap:
name: logstash-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: logstash-config
data:
logstash.conf: |
input {
beats {
port => 5044
}
}
output {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "logstash-%{+YYYY.MM.dd}"
}
stdout { codec => rubydebug }
}
Kibana Deployment YAML:
Yaml
java
apiVersion: apps/v1
kind: Deployment
metadata:
name: kibana
spec:
replicas: 1
selector:
matchLabels:
app: kibana
template:
metadata:
labels:
app: kibana
spec:
containers:
- name: kibana
image: docker.elastic.co/kibana/kibana:7.10.1
ports:
- containerPort: 5601
env:
- name: ELASTICSEARCH_HOSTS
value: http://elasticsearch:9200
---
apiVersion: v1
kind: Service
metadata:
name: kibana
spec:
ports:
- port: 5601
type: LoadBalancer
selector:
app: kibana
4.4.2 配置 Prometheus and Grafana
在 Kubernetes 中部署 Prometheus 和 Grafana。
Prometheus Deployment YAML:
Yaml
java
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
name: prometheus
spec:
serviceAccountName: prometheus
serviceMonitorSelector:
matchLabels:
team: frontend
ruleSelector:
matchLabels:
role: alert-rules
team: frontend
alertingRulesNamespaceSelector: {}
alertingRulesSelector: {}
enableAdminAPI: false
externalUrl: ""
routePrefix: /
webRoutePrefix: /
---
apiVersion: v1
kind: Service
metadata:
name: prometheus
spec:
ports:
- name: web
port: 9090
targetPort: web
selector:
app: prometheus-server
type: LoadBalancer
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: prometheus-clusterrole-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: prometheus-clusterrole
subjects:
- kind: ServiceAccount
name: prometheus
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: prometheus-clusterrole
rules:
- apiGroups: [""]
resources:
- nodes
- services
- endpoints
- pods
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources:
- configmaps
verbs: ["get"]
- nonResourceURLs: ["/metrics"]
verbs: ["get"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: prometheus
namespace: default
Grafana Deployment YAML:
Yaml
java
apiVersion: apps/v1
kind: Deployment
metadata:
name: grafana
spec:
replicas: 1
selector:
matchLabels:
app: grafana
template:
metadata:
labels:
app: grafana
spec:
containers:
- name: grafana
image: grafana/grafana:latest
ports:
- containerPort: 3000
env:
- name: GF_SECURITY_ADMIN_PASSWORD
value: admin
---
apiVersion: v1
kind: Service
metadata:
name: grafana
spec:
ports:
- port: 3000
type: LoadBalancer
selector:
app: grafana
4.5 消息队列
使用 RabbitMQ 处理订单创建等耗时操作,避免阻塞主线程。这部分已经在前面的章节中详细说明了。
5. 测试
编写单元测试和集成测试来确保代码质量和稳定性。
5.1 单元测试
使用 JUnit 和 Mockito 进行单元测试。以下是一个简单的示例:
java
java
package com.seckillsystem.service;
import com.seckillsystem.model.Product;
import com.seckillsystem.repository.ProductRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.redisson.api.RedissonClient;
import org.redisson.api.RLock;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.*;
class ProductServiceTest {
@Mock
private ProductRepository productRepository;
@Mock
private RedissonClient redissonClient;
@Mock
private RLock rLock;
@InjectMocks
private ProductService productService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
when(redissonClient.getLock(anyString())).thenReturn(rLock);
}
@Test
void testReduceStock_Success() {
Product product = new Product();
product.setId(1L);
product.setStock(10);
when(productRepository.findById(1L)).thenReturn(Optional.of(product));
assertTrue(productService.reduceStock(1L));
verify(productRepository, times(1)).findById(1L);
verify(productRepository, times(1)).save(product);
verify(rLock, times(1)).tryLock();
verify(rLock, times(1)).unlock();
}
@Test
void testReduceStock_OutOfStock() {
Product product = new Product();
product.setId(1L);
product.setStock(0);
when(productRepository.findById(1L)).thenReturn(Optional.of(product));
assertFalse(productService.reduceStock(1L));
verify(productRepository, times(1)).findById(1L);
verify(productRepository, never()).save(product);
verify(rLock, times(1)).tryLock();
verify(rLock, times(1)).unlock();
}
@Test
void testReduceStock_ProductNotFound() {
when(productRepository.findById(1L)).thenReturn(Optional.empty());
assertFalse(productService.reduceStock(1L));
verify(productRepository, times(1)).findById(1L);
verify(productRepository, never()).save(any(Product.class));
verify(rLock, times(1)).tryLock();
verify(rLock, times(1)).unlock();
}
}
5.2 集成测试
使用 Spring Boot Test 进行集成测试。以下是一个简单的示例:
java
java
package com.seckillsystem.service;
import com.seckillsystem.SeckillSystemApplication;
import com.seckillsystem.model.Product;
import com.seckillsystem.repository.ProductRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest(classes = SeckillSystemApplication.class)
@Transactional
class ProductServiceIntegrationTest {
@Autowired
private ProductService productService;
@Autowired
private ProductRepository productRepository;
@BeforeEach
void setUp() {
Product product = new Product();
product.setName("Sample Product");
product.setPrice(100.0);
product.setStock(10);
productRepository.save(product);
}
@Test
void testReduceStock_Success() {
Long productId = productRepository.findByName("Sample Product").getId();
assertTrue(productService.reduceStock(productId));
Product updatedProduct = productRepository.findById(productId).orElseThrow(() -> new RuntimeException("Product not found"));
assertEquals(9, updatedProduct.getStock());
}
@Test
void testReduceStock_OutOfStock() {
Long productId = productRepository.findByName("Sample Product").getId();
for (int i = 0; i < 10; i++) {
assertTrue(productService.reduceStock(productId));
}
assertFalse(productService.reduceStock(productId));
Product updatedProduct = productRepository.findById(productId).orElseThrow(() -> new RuntimeException("Product not found"));
assertEquals(0, updatedProduct.getStock());
}
}
6.1 添加限流
注册拦截器:
java
java
package com.seckillsystem.config;
import com.seckillsystem.interceptor.RateLimitingInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RateLimitingInterceptor rateLimitingInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rateLimitingInterceptor).addPathPatterns("/api/products/seckill/**");
}
}
6.2 使用消息队列处理订单
使用 RabbitMQ 处理订单创建等耗时操作,避免阻塞主线程。
在 pom.xml
中添加 RabbitMQ 依赖:
xml
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置 RabbitMQ 连接:
properties
XML
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
创建消息队列和交换机配置:
java
java
package com.seckillsystem.config;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMqConfig {
public static final String ORDER_QUEUE = "order_queue";
@Bean
public Queue orderQueue() {
return new Queue(ORDER_QUEUE, true);
}
}
修改 OrderService
以支持消息队列:
java
java
package com.seckillsystem.service;
import com.seckillsystem.model.Order;
import com.seckillsystem.repository.OrderRepository;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private RabbitTemplate rabbitTemplate;
public void createOrder(Order order) {
rabbitTemplate.convertAndSend(RabbitMqConfig.ORDER_QUEUE, order);
}
public Order saveOrder(Order order) {
return orderRepository.save(order);
}
}
创建消息监听器来处理订单:
java
java
package com.seckillsystem.listener;
import com.seckillsystem.model.Order;
import com.seckillsystem.service.OrderService;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class OrderListener {
@Autowired
private OrderService orderService;
@RabbitListener(queues = RabbitMqConfig.ORDER_QUEUE)
public void processOrder(Order order) {
orderService.saveOrder(order);
}
}
8. 总结
通过以上步骤,我们成功搭建了一个基于微服务架构的高并发秒杀系统后端,并实现了容器化和部署。以下是整个架构的概览图:
这个架构展示了各个组件之间的关系和交互。你可以根据实际需求进一步扩展和完善这个架构。