高并发下的防并发实战:C端/B端项目并发控制完全指南

67 阅读17分钟

高并发下的防并发实战:C端/B端项目并发控制完全指南

摘要:本文系统梳理C端/B端项目中的防并发方案,涵盖前端防重复提交(按钮禁用、请求去重)和后端并发控制(分布式锁、乐观锁、悲观锁、幂等性、限流、队列削峰)。通过秒杀、支付、批量导入等实战场景,提供完整的代码示例和方案选择指南,适配SpringBoot 2.x和Vue3技术栈。

一、引言

在互联网应用中,并发控制是保障系统稳定性和数据一致性的核心问题。无论是C端用户的秒杀抢购、下单支付,还是B端系统的批量操作、数据同步,都需要有效的防并发机制。本文将从前后端两个维度,系统梳理C端和B端项目中常见的防并发思路和实战方案。

1.1 并发问题的典型场景

C端场景
  • 秒杀抢购:库存扣减、订单创建
  • 支付下单:重复支付、订单重复创建
  • 优惠券领取:重复领取、超发
  • 签到打卡:重复签到、数据重复
B端场景
  • 批量导入:重复导入、数据覆盖
  • 审核流程:重复审核、状态冲突
  • 数据同步:重复同步、数据不一致
  • 报表生成:重复生成、资源浪费

1.2 并发问题的危害

  1. 数据不一致:库存超卖、余额错误
  2. 重复操作:重复下单、重复扣款
  3. 资源浪费:重复计算、重复调用
  4. 业务异常:状态错乱、流程中断

二、防并发方案全景图

2.1 防并发方案-思维导图

image.png

2.2 方案选择决策树

image.png

三、前端防并发方案

3.1 UI层防重复提交

3.1.1 按钮禁用方案

Vue3 实现(Composition API):

<template>
  <el-button 
    :loading="submitting" 
    :disabled="submitting"
    @click="handleSubmit"
  >
    {{ submitting ? '提交中...' : '提交订单' }}
  </el-button>
</template>

<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'

const submitting = ref(false)

const handleSubmit = async () => {
  // 防止重复点击
  if (submitting.value) {
    return
  }
  
  submitting.value = true
  try {
    await submitOrder()
    ElMessage.success('提交成功')
  } catch (error) {
    ElMessage.error('提交失败:' + error.message)
  } finally {
    submitting.value = false
  }
}

const submitOrder = async () => {
  // 调用后端接口
  const response = await axios.post('/api/order/submit', orderData)
  return response.data
}
</script>

Vue3 实现(Options API):

<template>
  <el-button 
    :loading="submitting" 
    :disabled="submitting"
    @click="handleSubmit"
  >
    提交订单
  </el-button>
</template>

<script>
export default {
  data() {
    return {
      submitting: false
    }
  },
  methods: {
    async handleSubmit() {
      if (this.submitting) {
        return
      }
      
      this.submitting = true
      try {
        await this.submitOrder()
        this.$message.success('提交成功')
      } catch (error) {
        this.$message.error('提交失败:' + error.message)
      } finally {
        this.submitting = false
      }
    },
    
    async submitOrder() {
      const response = await this.$http.post('/api/order/submit', this.orderData)
      return response.data
    }
  }
}
</script>
3.1.2 防抖(Debounce)方案

适用场景: 搜索框输入、按钮快速点击

<template>
  <el-input 
    v-model="keyword" 
    @input="handleSearch"
    placeholder="请输入关键词"
  />
  <el-button @click="handleSubmit">提交</el-button>
</template>

<script setup>
import { ref } from 'vue'
import { debounce } from 'lodash-es'

const keyword = ref('')

// 防抖处理:500ms内只执行最后一次
const handleSearch = debounce((value) => {
  console.log('搜索:', value)
  // 执行搜索逻辑
  searchData(value)
}, 500)

// 按钮防抖
let submitTimer = null
const handleSubmit = () => {
  if (submitTimer) {
    clearTimeout(submitTimer)
  }
  
  submitTimer = setTimeout(() => {
    submitOrder()
    submitTimer = null
  }, 1000) // 1秒内只允许提交一次
}
</script>

自定义防抖指令(Vue3):

// directives/debounce.js
export default {
  mounted(el, binding) {
    let timer = null
    el.addEventListener('click', () => {
      if (timer) {
        clearTimeout(timer)
      }
      timer = setTimeout(() => {
        binding.value()
      }, binding.arg || 1000)
    })
  }
}

// 使用
<el-button v-debounce:1000="handleSubmit">提交</el-button>
3.1.3 节流(Throttle)方案

适用场景: 滚动加载、窗口resize

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { throttle } from 'lodash-es'

const handleScroll = throttle(() => {
  // 滚动处理逻辑
  const scrollTop = document.documentElement.scrollTop
  if (scrollTop > 1000) {
    loadMore()
  }
}, 200) // 200ms内最多执行一次

onMounted(() => {
  window.addEventListener('scroll', handleScroll)
})

onUnmounted(() => {
  window.removeEventListener('scroll', handleScroll)
})
</script>

3.2 请求拦截与去重

3.2.1 Axios 请求拦截器

实现请求去重:

// utils/request.js
import axios from 'axios'
import { ElMessage } from 'element-plus'

// 存储正在进行的请求
const pendingRequests = new Map()

// 生成请求唯一标识
const generateRequestKey = (config) => {
  const { method, url, params, data } = config
  return `${method}_${url}_${JSON.stringify(params)}_${JSON.stringify(data)}`
}

// 请求拦截器
axios.interceptors.request.use(
  (config) => {
    const requestKey = generateRequestKey(config)
    
    // 检查是否有相同的请求正在进行
    if (pendingRequests.has(requestKey)) {
      // 取消当前请求
      config.cancelToken = new axios.CancelToken((cancel) => {
        cancel('重复请求已取消')
      })
      return config
    }
    
    // 将请求添加到pending列表
    pendingRequests.set(requestKey, config)
    
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

// 响应拦截器
axios.interceptors.response.use(
  (response) => {
    const requestKey = generateRequestKey(response.config)
    pendingRequests.delete(requestKey)
    return response
  },
  (error) => {
    if (error.config) {
      const requestKey = generateRequestKey(error.config)
      pendingRequests.delete(requestKey)
    }
    
    // 处理取消的请求
    if (axios.isCancel(error)) {
      console.log('请求已取消:', error.message)
      return Promise.reject(error)
    }
    
    return Promise.reject(error)
  }
)

export default axios
3.2.2 基于请求ID的幂等性

前端生成请求ID:

// utils/requestId.js
import { v4 as uuidv4 } from 'uuid'

// 为每个请求添加唯一ID
axios.interceptors.request.use((config) => {
  if (!config.headers['X-Request-Id']) {
    config.headers['X-Request-Id'] = uuidv4()
  }
  return config
})

后端验证请求ID(SpringBoot 2.x):

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.data.redis.core.StringRedisTemplate;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

/**
 * 请求幂等性拦截器
 * 【规范校验】:使用Redis存储请求ID,防止重复请求
 * 【版本兼容】:SpringBoot 2.x
 */
@Component
public class IdempotentInterceptor extends HandlerInterceptorAdapter {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String REQUEST_ID_PREFIX = "request:id:";
    private static final int EXPIRE_SECONDS = 300; // 5分钟过期
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        // 只拦截POST、PUT、DELETE请求
        String method = request.getMethod();
        if (!"POST".equals(method) && !"PUT".equals(method) && !"DELETE".equals(method)) {
            return true;
        }
        
        String requestId = request.getHeader("X-Request-Id");
        if (StringUtils.isBlank(requestId)) {
            // 没有请求ID,允许通过(兼容旧接口)
            return true;
        }
        
        String key = REQUEST_ID_PREFIX + requestId;
        
        // 检查请求ID是否已存在
        Boolean exists = redisTemplate.hasKey(key);
        if (Boolean.TRUE.equals(exists)) {
            // 请求重复,返回错误
            response.setStatus(HttpStatus.CONFLICT.value());
            response.getWriter().write("{\"code\":409,\"message\":\"请求重复\"}");
            return false;
        }
        
        // 存储请求ID
        redisTemplate.opsForValue().set(key, "1", EXPIRE_SECONDS, TimeUnit.SECONDS);
        
        return true;
    }
}

3.3 状态管理控制

3.3.1 Vuex/Pinia 状态管理

Vue3 + Pinia 实现:

// stores/order.js
import { defineStore } from 'pinia'

export const useOrderStore = defineStore('order', {
  state: () => ({
    submitting: false,
    submitTimestamp: 0
  }),
  
  actions: {
    async submitOrder(orderData) {
      // 防止重复提交:5秒内不允许重复提交
      const now = Date.now()
      if (this.submitting || (now - this.submitTimestamp < 5000)) {
        throw new Error('请勿重复提交')
      }
      
      this.submitting = true
      this.submitTimestamp = now
      
      try {
        const response = await axios.post('/api/order/submit', orderData)
        return response.data
      } finally {
        this.submitting = false
      }
    }
  }
})

// 组件中使用
<script setup>
import { useOrderStore } from '@/stores/order'

const orderStore = useOrderStore()

const handleSubmit = async () => {
  try {
    await orderStore.submitOrder(orderData)
    ElMessage.success('提交成功')
  } catch (error) {
    ElMessage.error(error.message)
  }
}
</script>

四、后端防并发方案

4.1 应用层防并发

4.1.1 分布式锁(Redis实现)

完整实现(SpringBoot 2.x):

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Redis 分布式锁工具类
 * 【规范校验】:使用Lua脚本保证原子性,防止误删其他实例的锁
 * 【版本兼容】:SpringBoot 2.x + Spring Data Redis 1.7.2
 */
@Component
public class DistributedLockUtil {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String UNLOCK_SCRIPT = 
        "if redis.call('get', KEYS[1]) == ARGV[1] then " +
        "return redis.call('del', KEYS[1]) " +
        "else return 0 end";
    
    /**
     * 尝试获取锁
     * @param lockKey 锁的key
     * @param lockValue 锁的value(建议使用UUID)
     * @param expireSeconds 过期时间(秒)
     * @return 是否获取成功
     */
    public boolean tryLock(String lockKey, String lockValue, int expireSeconds) {
        Boolean result = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, lockValue, expireSeconds, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(result);
    }
    
    /**
     * 释放锁(使用Lua脚本保证原子性)
     * @param lockKey 锁的key
     * @param lockValue 锁的value
     * @return 是否释放成功
     */
    public boolean releaseLock(String lockKey, String lockValue) {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(UNLOCK_SCRIPT);
        script.setResultType(Long.class);
        Long result = redisTemplate.execute(
            script, 
            Collections.singletonList(lockKey), 
            lockValue
        );
        return result != null && result == 1;
    }
    
    /**
     * 获取锁(带重试机制)
     * @param lockKey 锁的key
     * @param expireSeconds 过期时间(秒)
     * @param maxWaitSeconds 最大等待时间(秒)
     * @return 锁的value,获取失败返回null
     */
    public String lockWithRetry(String lockKey, int expireSeconds, int maxWaitSeconds) {
        String lockValue = UUID.randomUUID().toString();
        long endTime = System.currentTimeMillis() + maxWaitSeconds * 1000;
        
        while (System.currentTimeMillis() < endTime) {
            if (tryLock(lockKey, lockValue, expireSeconds)) {
                return lockValue;
            }
            
            try {
                Thread.sleep(100); // 等待100ms后重试
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return null;
            }
        }
        
        return null;
    }
}

使用示例:

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 订单服务示例
 * 【规范校验】:使用分布式锁防止并发创建订单
 * 【版本兼容】:SpringBoot 2.x
 */
@Slf4j
@Service
public class OrderService {
    
    @Autowired
    private DistributedLockUtil distributedLockUtil;
    
    @Autowired
    private OrderMapper orderMapper;
    
    /**
     * 创建订单(防并发)
     */
    @Transactional(rollbackFor = Exception.class)
    public OrderDTO createOrder(CreateOrderParam param) {
        // 构建锁的key:用户ID + 商品ID
        String lockKey = String.format("order:create:%s:%s", 
            param.getUserId(), param.getProductId());
        String lockValue = UUID.randomUUID().toString();
        int expireSeconds = 30; // 锁30秒过期
        
        // 尝试获取锁
        if (!distributedLockUtil.tryLock(lockKey, lockValue, expireSeconds)) {
            throw new ServiceException(CommonCode.SERVICE_ERROR.code(), 
                "订单创建中,请勿重复提交");
        }
        
        try {
            // 检查是否已存在订单
            OrderPO existingOrder = orderMapper.selectByUserIdAndProductId(
                param.getUserId(), param.getProductId());
            if (existingOrder != null) {
                throw new ServiceException(CommonCode.RECORD_EXISTED.code(), 
                    "订单已存在");
            }
            
            // 创建订单
            OrderPO order = new OrderPO();
            order.setUserId(param.getUserId());
            order.setProductId(param.getProductId());
            order.setAmount(param.getAmount());
            order.setStatus(OrderStatusEnum.PENDING.getCode());
            orderMapper.insert(order);
            
            log.info("订单创建成功:orderId={}", order.getId());
            return convertToDTO(order);
            
        } finally {
            // 释放锁
            distributedLockUtil.releaseLock(lockKey, lockValue);
        }
    }
}
4.1.2 本地锁(单机场景)

synchronized 实现:

/**
 * 本地锁示例(单机部署)
 * 【规范校验】:使用synchronized保证线程安全
 * 【版本兼容】:JDK 1.8+
 */
@Service
public class LocalLockService {
    
    /**
     * 使用synchronized方法锁
     */
    public synchronized void processWithMethodLock(String key) {
        // 业务逻辑
        doBusiness(key);
    }
    
    /**
     * 使用synchronized代码块锁
     */
    private final Object lock = new Object();
    
    public void processWithBlockLock(String key) {
        synchronized (lock) {
            // 业务逻辑
            doBusiness(key);
        }
    }
    
    /**
     * 使用ReentrantLock(更灵活)
     */
    private final ReentrantLock reentrantLock = new ReentrantLock();
    
    public void processWithReentrantLock(String key) {
        reentrantLock.lock();
        try {
            // 业务逻辑
            doBusiness(key);
        } finally {
            reentrantLock.unlock();
        }
    }
}
4.1.3 幂等性设计

基于唯一业务标识的幂等性:

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;

/**
 * 幂等性服务示例
 * 【规范校验】:使用Redis + 数据库唯一索引保证幂等性
 * 【版本兼容】:SpringBoot 2.x
 */
@Slf4j
@Service
public class IdempotentService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private OrderMapper orderMapper;
    
    private static final String IDEMPOTENT_PREFIX = "idempotent:";
    private static final int EXPIRE_SECONDS = 300; // 5分钟
    
    /**
     * 幂等性创建订单
     * @param param 订单参数
     * @param idempotentKey 幂等性key(如:订单号、请求ID)
     * @return 订单ID
     */
    @Transactional(rollbackFor = Exception.class)
    public Long createOrderIdempotent(CreateOrderParam param, String idempotentKey) {
        // 1. 检查Redis中是否存在幂等性key
        String redisKey = IDEMPOTENT_PREFIX + idempotentKey;
        String existingOrderId = redisTemplate.opsForValue().get(redisKey);
        
        if (StringUtils.isNotBlank(existingOrderId)) {
            log.info("幂等性检查:订单已存在,orderId={}", existingOrderId);
            return Long.parseLong(existingOrderId);
        }
        
        // 2. 检查数据库中是否存在(双重检查)
        OrderPO existingOrder = orderMapper.selectByOrderNo(idempotentKey);
        if (existingOrder != null) {
            // 回写Redis
            redisTemplate.opsForValue().set(
                redisKey, 
                existingOrder.getId().toString(), 
                EXPIRE_SECONDS, 
                TimeUnit.SECONDS
            );
            return existingOrder.getId();
        }
        
        // 3. 创建订单
        OrderPO order = new OrderPO();
        order.setOrderNo(idempotentKey); // 使用幂等性key作为订单号
        order.setUserId(param.getUserId());
        order.setAmount(param.getAmount());
        orderMapper.insert(order);
        
        // 4. 写入Redis
        redisTemplate.opsForValue().set(
            redisKey, 
            order.getId().toString(), 
            EXPIRE_SECONDS, 
            TimeUnit.SECONDS
        );
        
        log.info("订单创建成功:orderId={}, orderNo={}", order.getId(), idempotentKey);
        return order.getId();
    }
}

幂等性注解实现:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 幂等性注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    /**
     * 幂等性key的生成策略
     */
    String keyGenerator() default "default";
    
    /**
     * 过期时间(秒)
     */
    int expireSeconds() default 300;
}

/**
 * 幂等性切面
 */
@Aspect
@Component
public class IdempotentAspect {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
        // 生成幂等性key
        String idempotentKey = generateKey(joinPoint, idempotent);
        String redisKey = "idempotent:" + idempotentKey;
        
        // 检查是否已执行
        String result = redisTemplate.opsForValue().get(redisKey);
        if (StringUtils.isNotBlank(result)) {
            // 已执行,返回缓存结果
            return JSON.parseObject(result, getReturnType(joinPoint));
        }
        
        // 执行方法
        Object returnValue = joinPoint.proceed();
        
        // 缓存结果
        redisTemplate.opsForValue().set(
            redisKey, 
            JSON.toJSONString(returnValue), 
            idempotent.expireSeconds(), 
            TimeUnit.SECONDS
        );
        
        return returnValue;
    }
}

// 使用示例
@Idempotent(keyGenerator = "orderNo", expireSeconds = 600)
public OrderDTO createOrder(CreateOrderParam param) {
    // 创建订单逻辑
}

4.2 数据库层防并发

4.2.1 悲观锁(SELECT FOR UPDATE)

MyBatis实现:

<!-- OrderMapper.xml -->
<select id="selectByIdForUpdate" resultType="OrderPO">
    SELECT * FROM t_order 
    WHERE id = #{id} 
    FOR UPDATE
</select>

<update id="updateStock">
    UPDATE t_product 
    SET stock = stock - #{quantity}
    WHERE id = #{productId} 
    AND stock >= #{quantity}
</update>

Service层使用:

/**
 * 使用悲观锁扣减库存
 * 【规范校验】:使用SELECT FOR UPDATE保证数据一致性
 * 【版本兼容】:SpringBoot 2.x + MyBatis
 */
@Service
public class ProductService {
    
    @Autowired
    private ProductMapper productMapper;
    
    @Transactional(rollbackFor = Exception.class)
    public boolean deductStock(Long productId, Integer quantity) {
        // 1. 使用悲观锁查询商品
        ProductPO product = productMapper.selectByIdForUpdate(productId);
        if (product == null) {
            throw new ServiceException(CommonCode.DATA_NOT_FOUND.code(), "商品不存在");
        }
        
        // 2. 检查库存
        if (product.getStock() < quantity) {
            throw new ServiceException(CommonCode.SERVICE_ERROR.code(), "库存不足");
        }
        
        // 3. 扣减库存
        int updateCount = productMapper.updateStock(productId, quantity);
        if (updateCount == 0) {
            throw new ServiceException(CommonCode.SERVICE_ERROR.code(), "库存扣减失败");
        }
        
        return true;
    }
}
4.2.2 乐观锁(版本号控制)

数据库表设计:

CREATE TABLE t_order (
    id BIGINT PRIMARY KEY,
    order_no VARCHAR(64) UNIQUE,
    user_id BIGINT,
    amount DECIMAL(10,2),
    status INT,
    version INT DEFAULT 0,  -- 版本号字段
    create_time DATETIME,
    update_time DATETIME
);

MyBatis实现:

<!-- OrderMapper.xml -->
<update id="updateWithVersion">
    UPDATE t_order 
    SET 
        status = #{status},
        version = version + 1,
        update_time = NOW()
    WHERE id = #{id} 
    AND version = #{version}
</update>

Service层使用:

/**
 * 使用乐观锁更新订单状态
 * 【规范校验】:使用版本号控制,失败时重试
 * 【版本兼容】:SpringBoot 2.x
 */
@Service
public class OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    /**
     * 更新订单状态(乐观锁)
     */
    public boolean updateOrderStatus(Long orderId, Integer newStatus) {
        int maxRetries = 3;
        int retryCount = 0;
        
        while (retryCount < maxRetries) {
            // 1. 查询订单(带版本号)
            OrderPO order = orderMapper.selectById(orderId);
            if (order == null) {
                throw new ServiceException(CommonCode.DATA_NOT_FOUND.code(), "订单不存在");
            }
            
            // 2. 使用乐观锁更新
            int updateCount = orderMapper.updateWithVersion(
                orderId, 
                newStatus, 
                order.getVersion()
            );
            
            if (updateCount > 0) {
                log.info("订单状态更新成功:orderId={}, newStatus={}", orderId, newStatus);
                return true;
            }
            
            // 3. 更新失败,版本冲突,重试
            retryCount++;
            log.warn("订单状态更新失败(版本冲突),重试:orderId={}, retryCount={}", 
                orderId, retryCount);
            
            try {
                Thread.sleep(100); // 等待100ms后重试
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
        
        throw new ServiceException(CommonCode.SERVICE_ERROR.code(), 
            "订单状态更新失败,请重试");
    }
}
4.2.3 唯一索引防重复

数据库设计:

-- 防止重复订单
CREATE UNIQUE INDEX uk_order_user_product ON t_order(user_id, product_id, status);

-- 防止重复支付
CREATE UNIQUE INDEX uk_payment_order ON t_payment(order_id, status);

Service层处理:

/**
 * 使用唯一索引防止重复数据
 * 【规范校验】:数据库唯一索引 + 异常处理
 * 【版本兼容】:SpringBoot 2.x
 */
@Service
public class OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Transactional(rollbackFor = Exception.class)
    public Long createOrder(CreateOrderParam param) {
        try {
            OrderPO order = new OrderPO();
            order.setUserId(param.getUserId());
            order.setProductId(param.getProductId());
            order.setStatus(OrderStatusEnum.PENDING.getCode());
            orderMapper.insert(order);
            return order.getId();
            
        } catch (DuplicateKeyException e) {
            // 唯一索引冲突,说明订单已存在
            log.warn("订单已存在:userId={}, productId={}", 
                param.getUserId(), param.getProductId());
            
            // 查询已存在的订单
            OrderPO existingOrder = orderMapper.selectByUserIdAndProductId(
                param.getUserId(), 
                param.getProductId()
            );
            
            if (existingOrder != null) {
                return existingOrder.getId();
            }
            
            throw new ServiceException(CommonCode.RECORD_EXISTED.code(), 
                "订单已存在");
        }
    }
}

4.3 限流方案

4.3.1 基于Redis的限流

令牌桶算法实现:

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;

/**
 * Redis限流工具类(令牌桶算法)
 * 【规范校验】:使用Lua脚本保证原子性
 * 【版本兼容】:SpringBoot 2.x
 */
@Component
public class RateLimiterUtil {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String TOKEN_BUCKET_SCRIPT = 
        "local key = KEYS[1] " +
        "local capacity = tonumber(ARGV[1]) " +
        "local tokens = tonumber(ARGV[2]) " +
        "local interval = tonumber(ARGV[3]) " +
        "local now = tonumber(ARGV[4]) " +
        " " +
        "local bucket = redis.call('HMGET', key, 'tokens', 'lastRefill') " +
        "local currentTokens = tonumber(bucket[1]) or capacity " +
        "local lastRefill = tonumber(bucket[2]) or now " +
        " " +
        "local elapsed = now - lastRefill " +
        "local tokensToAdd = math.floor(elapsed / interval) " +
        "currentTokens = math.min(capacity, currentTokens + tokensToAdd) " +
        " " +
        "if currentTokens >= tokens then " +
        "    currentTokens = currentTokens - tokens " +
        "    redis.call('HMSET', key, 'tokens', currentTokens, 'lastRefill', now) " +
        "    redis.call('EXPIRE', key, 3600) " +
        "    return 1 " +
        "else " +
        "    redis.call('HMSET', key, 'tokens', currentTokens, 'lastRefill', now) " +
        "    redis.call('EXPIRE', key, 3600) " +
        "    return 0 " +
        "end";
    
    /**
     * 限流检查(令牌桶算法)
     * @param key 限流key
     * @param capacity 桶容量
     * @param tokens 需要的令牌数
     * @param interval 令牌生成间隔(毫秒)
     * @return 是否允许通过
     */
    public boolean tryAcquire(String key, int capacity, int tokens, long interval) {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(TOKEN_BUCKET_SCRIPT);
        script.setResultType(Long.class);
        
        long now = System.currentTimeMillis();
        Long result = redisTemplate.execute(
            script,
            Collections.singletonList(key),
            String.valueOf(capacity),
            String.valueOf(tokens),
            String.valueOf(interval),
            String.valueOf(now)
        );
        
        return result != null && result == 1;
    }
}

使用示例:

/**
 * 限流使用示例
 */
@Service
public class OrderService {
    
    @Autowired
    private RateLimiterUtil rateLimiterUtil;
    
    public OrderDTO createOrder(CreateOrderParam param) {
        // 限流:每个用户每秒最多创建1个订单
        String limitKey = "rate:limit:order:create:" + param.getUserId();
        if (!rateLimiterUtil.tryAcquire(limitKey, 10, 1, 1000)) {
            throw new ServiceException(CommonCode.SERVICE_ERROR.code(), 
                "请求过于频繁,请稍后再试");
        }
        
        // 创建订单逻辑
        return doCreateOrder(param);
    }
}
4.3.2 滑动窗口限流
/**
 * 滑动窗口限流
 */
@Component
public class SlidingWindowRateLimiter {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 滑动窗口限流
     * @param key 限流key
     * @param windowSize 窗口大小(秒)
     * @param maxRequests 最大请求数
     * @return 是否允许通过
     */
    public boolean tryAcquire(String key, int windowSize, int maxRequests) {
        long now = System.currentTimeMillis();
        long windowStart = now - windowSize * 1000;
        
        // 使用ZSet存储请求时间戳
        String zsetKey = "rate:limit:sliding:" + key;
        
        // 移除窗口外的数据
        redisTemplate.opsForZSet().removeRangeByScore(zsetKey, 0, windowStart);
        
        // 获取当前窗口内的请求数
        Long count = redisTemplate.opsForZSet().count(zsetKey, windowStart, now);
        
        if (count != null && count >= maxRequests) {
            return false;
        }
        
        // 添加当前请求
        redisTemplate.opsForZSet().add(zsetKey, String.valueOf(now), now);
        redisTemplate.expire(zsetKey, windowSize + 1, TimeUnit.SECONDS);
        
        return true;
    }
}

4.4 队列削峰方案

4.4.1 异步队列处理

使用线程池 + 队列:

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 订单队列处理
 * 【规范校验】:使用队列削峰,防止系统过载
 * 【版本兼容】:SpringBoot 2.x
 */
@Component
public class OrderQueueProcessor {
    
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private ThreadPoolTaskExecutor orderExecutor;
    
    // 订单队列
    private final BlockingQueue<CreateOrderParam> orderQueue = 
        new LinkedBlockingQueue<>(10000);
    
    /**
     * 提交订单到队列
     */
    public void submitOrder(CreateOrderParam param) {
        if (!orderQueue.offer(param)) {
            throw new ServiceException(CommonCode.SERVICE_ERROR.code(), 
                "系统繁忙,请稍后再试");
        }
        
        // 异步处理
        orderExecutor.execute(() -> processOrderQueue());
    }
    
    /**
     * 处理订单队列
     */
    private void processOrderQueue() {
        while (!orderQueue.isEmpty()) {
            try {
                CreateOrderParam param = orderQueue.poll();
                if (param != null) {
                    orderService.createOrder(param);
                }
            } catch (Exception e) {
                log.error("订单处理失败", e);
            }
        }
    }
}

五、C端/B端典型场景实战

5.1 C端场景:秒杀抢购

完整方案:

/**
 * 秒杀服务
 * 【场景识别】:高并发秒杀场景
 * 【防并发策略】:限流 + 分布式锁 + 数据库乐观锁 + 队列削峰
 */
@Service
public class SeckillService {
    
    @Autowired
    private RateLimiterUtil rateLimiterUtil;
    
    @Autowired
    private DistributedLockUtil distributedLockUtil;
    
    @Autowired
    private ProductMapper productMapper;
    
    @Autowired
    private OrderService orderService;
    
    /**
     * 秒杀下单
     */
    @Transactional(rollbackFor = Exception.class)
    public SeckillResult seckill(Long userId, Long productId) {
        // 1. 限流:每个用户每秒最多1次请求
        String limitKey = "seckill:limit:" + userId;
        if (!rateLimiterUtil.tryAcquire(limitKey, 5, 1, 1000)) {
            return SeckillResult.fail("请求过于频繁,请稍后再试");
        }
        
        // 2. 分布式锁:防止同一用户重复下单
        String lockKey = "seckill:lock:" + userId + ":" + productId;
        String lockValue = UUID.randomUUID().toString();
        if (!distributedLockUtil.tryLock(lockKey, lockValue, 10)) {
            return SeckillResult.fail("正在处理中,请勿重复提交");
        }
        
        try {
            // 3. 检查商品库存(使用乐观锁)
            ProductPO product = productMapper.selectById(productId);
            if (product == null || product.getStock() <= 0) {
                return SeckillResult.fail("商品已售罄");
            }
            
            // 4. 扣减库存(乐观锁)
            int updateCount = productMapper.updateStockWithVersion(
                productId, 1, product.getVersion());
            if (updateCount == 0) {
                return SeckillResult.fail("库存不足,请重试");
            }
            
            // 5. 创建订单(幂等性保证)
            String orderNo = generateOrderNo(userId, productId);
            Long orderId = orderService.createOrderIdempotent(
                buildOrderParam(userId, productId), 
                orderNo
            );
            
            return SeckillResult.success(orderId);
            
        } finally {
            distributedLockUtil.releaseLock(lockKey, lockValue);
        }
    }
}

5.2 B端场景:批量导入

完整方案:

/**
 * 批量导入服务
 * 【场景识别】:B端批量数据导入
 * 【防并发策略】:分布式锁 + 唯一索引 + 幂等性
 */
@Service
public class BatchImportService {
    
    @Autowired
    private DistributedLockUtil distributedLockUtil;
    
    @Autowired
    private ImportRecordMapper importRecordMapper;
    
    /**
     * 批量导入数据
     */
    @Transactional(rollbackFor = Exception.class)
    public ImportResult batchImport(Long userId, List<ImportData> dataList, String importBatchNo) {
        // 1. 分布式锁:防止重复导入
        String lockKey = "import:lock:" + importBatchNo;
        String lockValue = UUID.randomUUID().toString();
        if (!distributedLockUtil.tryLock(lockKey, lockValue, 300)) {
            throw new ServiceException(CommonCode.SERVICE_ERROR.code(), 
                "导入任务正在处理中,请勿重复提交");
        }
        
        try {
            // 2. 检查导入记录是否已存在(幂等性)
            ImportRecordPO existingRecord = importRecordMapper.selectByBatchNo(importBatchNo);
            if (existingRecord != null) {
                return ImportResult.existing(existingRecord.getId());
            }
            
            // 3. 创建导入记录
            ImportRecordPO record = new ImportRecordPO();
            record.setBatchNo(importBatchNo);
            record.setUserId(userId);
            record.setStatus(ImportStatusEnum.PROCESSING.getCode());
            importRecordMapper.insert(record);
            
            // 4. 批量导入数据(使用唯一索引防止重复)
            int successCount = 0;
            int failCount = 0;
            
            for (ImportData data : dataList) {
                try {
                    importData(data, importBatchNo);
                    successCount++;
                } catch (DuplicateKeyException e) {
                    // 唯一索引冲突,数据已存在,跳过
                    log.warn("数据已存在,跳过:{}", data);
                    successCount++;
                } catch (Exception e) {
                    log.error("数据导入失败", e);
                    failCount++;
                }
            }
            
            // 5. 更新导入记录
            record.setStatus(ImportStatusEnum.COMPLETED.getCode());
            record.setSuccessCount(successCount);
            record.setFailCount(failCount);
            importRecordMapper.updateById(record);
            
            return ImportResult.success(record.getId(), successCount, failCount);
            
        } finally {
            distributedLockUtil.releaseLock(lockKey, lockValue);
        }
    }
    
    private void importData(ImportData data, String importBatchNo) {
        // 导入逻辑(数据库唯一索引保证不重复)
        DataPO dataPO = convertToPO(data);
        dataPO.setImportBatchNo(importBatchNo);
        dataMapper.insert(dataPO);
    }
}

5.3 支付场景:防止重复支付

完整方案:

/**
 * 支付服务
 * 【场景识别】:支付防重复
 * 【防并发策略】:幂等性 + 分布式锁 + 状态机
 */
@Service
public class PaymentService {
    
    @Autowired
    private DistributedLockUtil distributedLockUtil;
    
    @Autowired
    private PaymentMapper paymentMapper;
    
    /**
     * 创建支付订单(防重复)
     */
    @Transactional(rollbackFor = Exception.class)
    public PaymentDTO createPayment(CreatePaymentParam param) {
        // 1. 幂等性检查:使用订单ID作为幂等性key
        String idempotentKey = "payment:" + param.getOrderId();
        PaymentPO existingPayment = paymentMapper.selectByOrderId(param.getOrderId());
        if (existingPayment != null) {
            // 支付订单已存在,返回已有订单
            return convertToDTO(existingPayment);
        }
        
        // 2. 分布式锁:防止并发创建
        String lockKey = "payment:lock:" + param.getOrderId();
        String lockValue = UUID.randomUUID().toString();
        if (!distributedLockUtil.tryLock(lockKey, lockValue, 30)) {
            throw new ServiceException(CommonCode.SERVICE_ERROR.code(), 
                "支付订单创建中,请勿重复提交");
        }
        
        try {
            // 3. 双重检查
            existingPayment = paymentMapper.selectByOrderId(param.getOrderId());
            if (existingPayment != null) {
                return convertToDTO(existingPayment);
            }
            
            // 4. 创建支付订单
            PaymentPO payment = new PaymentPO();
            payment.setOrderId(param.getOrderId());
            payment.setAmount(param.getAmount());
            payment.setStatus(PaymentStatusEnum.PENDING.getCode());
            payment.setPaymentNo(generatePaymentNo());
            paymentMapper.insert(payment);
            
            return convertToDTO(payment);
            
        } finally {
            distributedLockUtil.releaseLock(lockKey, lockValue);
        }
    }
    
    /**
     * 支付回调处理(防重复)
     */
    @Transactional(rollbackFor = Exception.class)
    public void handlePaymentCallback(String paymentNo, String thirdPartyPaymentNo) {
        // 1. 查询支付订单
        PaymentPO payment = paymentMapper.selectByPaymentNo(paymentNo);
        if (payment == null) {
            throw new ServiceException(CommonCode.DATA_NOT_FOUND.code(), "支付订单不存在");
        }
        
        // 2. 状态检查:只有待支付状态才能处理
        if (payment.getStatus() != PaymentStatusEnum.PENDING.getCode()) {
            log.warn("支付订单状态异常,跳过处理:paymentNo={}, status={}", 
                paymentNo, payment.getStatus());
            return; // 已处理,直接返回(幂等性)
        }
        
        // 3. 分布式锁:防止并发处理
        String lockKey = "payment:callback:lock:" + paymentNo;
        String lockValue = UUID.randomUUID().toString();
        if (!distributedLockUtil.tryLock(lockKey, lockValue, 30)) {
            throw new ServiceException(CommonCode.SERVICE_ERROR.code(), 
                "支付回调处理中,请勿重复提交");
        }
        
        try {
            // 4. 双重检查状态
            payment = paymentMapper.selectByIdForUpdate(payment.getId());
            if (payment.getStatus() != PaymentStatusEnum.PENDING.getCode()) {
                return; // 已处理
            }
            
            // 5. 更新支付状态(乐观锁)
            int updateCount = paymentMapper.updateStatusWithVersion(
                payment.getId(),
                PaymentStatusEnum.SUCCESS.getCode(),
                payment.getVersion()
            );
            
            if (updateCount > 0) {
                // 6. 更新订单状态
                orderService.updateOrderStatus(payment.getOrderId(), OrderStatusEnum.PAID.getCode());
                log.info("支付回调处理成功:paymentNo={}", paymentNo);
            }
            
        } finally {
            distributedLockUtil.releaseLock(lockKey, lockValue);
        }
    }
}

六、最佳实践总结

6.1 方案选择指南

场景推荐方案理由
前端防重复提交按钮禁用 + 请求去重用户体验好,实现简单
单机应用本地锁(synchronized/ReentrantLock)性能好,无网络开销
分布式应用分布式锁(Redis)支持集群部署
高并发读多写少乐观锁 + 版本号性能好,无锁竞争
高并发写多悲观锁 + 分布式锁保证强一致性
幂等性要求唯一索引 + Redis幂等性key双重保障
限流需求Redis令牌桶/滑动窗口防止系统过载
削峰需求队列 + 异步处理平滑处理请求

6.2 组合使用策略

高并发秒杀场景:

前端:按钮禁用 + 请求去重
  ↓
后端:限流 → 分布式锁 → 乐观锁 → 幂等性

批量导入场景:

前端:文件上传进度 + 防重复上传
  ↓
后端:分布式锁 → 唯一索引 → 幂等性

支付场景:

前端:支付按钮禁用 + 请求ID
  ↓
后端:幂等性 → 分布式锁 → 状态机 → 乐观锁

6.3 注意事项

6.3.1 分布式锁注意事项
  1. 锁的过期时间:必须大于业务执行时间,避免锁提前释放
  2. 锁的释放:必须使用Lua脚本保证原子性,防止误删其他实例的锁
  3. 锁的重入:如需支持重入,使用Redisson等框架
  4. 死锁预防:设置合理的超时时间,避免死锁
6.3.2 幂等性注意事项
  1. 幂等性key的选择:使用业务唯一标识(订单号、用户ID+商品ID等)
  2. 过期时间设置:根据业务特点设置合理的过期时间
  3. 双重检查:Redis检查 + 数据库检查,保证可靠性
  4. 异常处理:幂等性失败时的降级策略
6.3.3 数据库锁注意事项
  1. 悲观锁:必须在事务中使用,避免长时间持有锁
  2. 乐观锁:失败时需要重试机制
  3. 唯一索引:合理设计索引,避免性能问题
  4. 死锁预防:按相同顺序获取锁,避免死锁

6.4 性能优化建议

  1. 锁的粒度:尽量缩小锁的范围,提高并发性能
  2. 锁的超时:设置合理的超时时间,避免长时间等待
  3. 异步处理:非关键路径使用异步处理,提高响应速度
  4. 缓存优化:合理使用缓存,减少数据库压力

七、总结

7.1 核心要点

  1. 前端防并发:UI层控制 + 请求拦截,提升用户体验
  2. 后端防并发:分布式锁 + 数据库锁 + 幂等性,保证数据一致性
  3. 限流削峰:防止系统过载,保证系统稳定性
  4. 组合使用:根据场景组合多种方案,达到最佳效果

7.2 方案对比

方案优点缺点适用场景
前端防重复实现简单、用户体验好可被绕过所有场景(第一道防线)
分布式锁支持集群、功能强大有网络开销、需考虑死锁分布式应用
悲观锁强一致性性能较差、可能死锁写多读少
乐观锁性能好、无锁竞争需要重试机制读多写少
幂等性保证不重复执行需要存储空间所有写操作
限流防止系统过载可能拒绝正常请求高并发场景

7.3 SpringBoot 2.x 版本兼容性

方案兼容性注意事项
分布式锁(Redis)✅ 完全兼容使用Spring Data Redis
数据库锁✅ 完全兼容MyBatis支持SELECT FOR UPDATE
乐观锁✅ 完全兼容需要手动实现版本号控制
限流✅ 完全兼容使用Redis Lua脚本
队列削峰✅ 完全兼容使用ThreadPoolTaskExecutor

文档版本: v1.0
适用版本: SpringBoot 2.x、Vue3 3.3+、JDK 1.8+