微服务实现高并发 秒杀系统,前后端实现

一、前端实现

前端项目初始化

首先,我们需要创建一个新的 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 创建实体类

创建实体类来表示数据库中的表。例如,创建 ProductOrder 实体类:

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 接口来操作数据库。例如,创建 ProductRepositoryOrderRepository

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 层来处理业务逻辑。例如,创建 ProductServiceOrderService

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 请求。例如,创建 ProductControllerOrderController

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 表的 idstock 字段上创建索引。

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. 总结

通过以上步骤,我们成功搭建了一个基于微服务架构的高并发秒杀系统后端,并实现了容器化和部署。以下是整个架构的概览图:

这个架构展示了各个组件之间的关系和交互。你可以根据实际需求进一步扩展和完善这个架构。

相关推荐
小诸葛的博客2 小时前
istio-proxy日志字段解释
云原生·istio
柚几哥哥5 小时前
SpringBoot多数据源架构实现
spring boot·后端·clickhouse·架构·数据库架构
青春~飞鸟5 小时前
ISP架构方案
图像处理·计算机视觉·架构
喵爸的小作坊7 小时前
聊聊我的 dumi-preset-vue2 开发之旅
vue.js·架构·前端框架
weisian1517 小时前
Mysql--架构篇--体系结构(连接层,SQL层,存储引擎层,文件存储层)
mysql·架构
m0_748231317 小时前
深入解析HDFS:定义、架构、原理、应用场景及常用命令
hadoop·hdfs·架构
侬本多情。8 小时前
RIP协议在简单网络架构的使用
服务器·网络·网络协议·架构
移动云开发者联盟8 小时前
移动云自研云原生数据库入围国采!
数据库·云原生
半卷书生9 小时前
新时期下k8s 网络插件calico 安装
云原生·容器·kubernetes