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

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

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

一、引言

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

1.1 并发问题的典型场景

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

1.2 并发问题的危害

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

二、防并发方案全景图

2.1 防并发方案-思维导图

2.2 方案选择决策树

三、前端防并发方案

3.1 UI层防重复提交

3.1.1 按钮禁用方案

Vue3 实现(Composition API):

vue 复制代码
<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):

vue 复制代码
<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)方案

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

vue 复制代码
<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):

javascript 复制代码
// 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

vue 复制代码
<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 请求拦截器

实现请求去重:

javascript 复制代码
// 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:

javascript 复制代码
// 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):

java 复制代码
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 实现:

javascript 复制代码
// 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):

java 复制代码
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;
    }
}

使用示例:

java 复制代码
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 实现:

java 复制代码
/**
 * 本地锁示例(单机部署)
 * 【规范校验】:使用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 幂等性设计

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

java 复制代码
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();
    }
}

幂等性注解实现:

java 复制代码
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实现:

xml 复制代码
<!-- 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层使用:

java 复制代码
/**
 * 使用悲观锁扣减库存
 * 【规范校验】:使用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 乐观锁(版本号控制)

数据库表设计:

sql 复制代码
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实现:

xml 复制代码
<!-- 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层使用:

java 复制代码
/**
 * 使用乐观锁更新订单状态
 * 【规范校验】:使用版本号控制,失败时重试
 * 【版本兼容】: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 唯一索引防重复

数据库设计:

sql 复制代码
-- 防止重复订单
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层处理:

java 复制代码
/**
 * 使用唯一索引防止重复数据
 * 【规范校验】:数据库唯一索引 + 异常处理
 * 【版本兼容】: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的限流

令牌桶算法实现:

java 复制代码
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;
    }
}

使用示例:

java 复制代码
/**
 * 限流使用示例
 */
@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 滑动窗口限流
java 复制代码
/**
 * 滑动窗口限流
 */
@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 异步队列处理

使用线程池 + 队列:

java 复制代码
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端场景:秒杀抢购

完整方案:

java 复制代码
/**
 * 秒杀服务
 * 【场景识别】:高并发秒杀场景
 * 【防并发策略】:限流 + 分布式锁 + 数据库乐观锁 + 队列削峰
 */
@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端场景:批量导入

完整方案:

java 复制代码
/**
 * 批量导入服务
 * 【场景识别】: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 支付场景:防止重复支付

完整方案:

java 复制代码
/**
 * 支付服务
 * 【场景识别】:支付防重复
 * 【防并发策略】:幂等性 + 分布式锁 + 状态机
 */
@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+

相关推荐
VX:Fegn08953 小时前
计算机毕业设计|基于ssm + vue超市管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·课程设计
Java天梯之路7 小时前
Spring Boot 钩子全集实战(七):BeanFactoryPostProcessor详解
java·spring boot·后端
wr2005148 小时前
第二次作业,渗透
java·后端·spring
短剑重铸之日8 小时前
《SpringCloud实用版》生产部署:Docker + Kubernetes + GraalVM 原生镜像 完整方案
后端·spring cloud·docker·kubernetes·graalvm
爬山算法8 小时前
Hibernate(67)如何在云环境中使用Hibernate?
java·后端·hibernate
女王大人万岁9 小时前
Go标准库 io与os库详解
服务器·开发语言·后端·golang
露天赏雪9 小时前
Java 高并发编程实战:从线程池到分布式锁,解决生产环境并发问题
java·开发语言·spring boot·分布式·后端·mysql
短剑重铸之日10 小时前
《SpringCloud实用版》 Seata 分布式事务实战:AT / TCC / Saga /XA
后端·spring·spring cloud·seata·分布式事务
FAFU_kyp10 小时前
RISC0_ZERO项目在macOs上生成链上证明避坑
开发语言·后端·学习·macos·rust
qq_124987075310 小时前
基于springboot的会议室预订系统设计与实现(源码+论文+部署+安装)
java·vue.js·spring boot·后端·信息可视化·毕业设计·计算机毕业设计