7 formas de lograr muertes flash de alta concurrencia, la escritura es tan buena que se recomienda coleccionar. !

1. Introducción

Los escenarios de alta concurrencia son muy comunes en el trabajo diario en sitio, especialmente en empresas de Internet, este artículo simula escenarios de alta concurrencia vendiendo productos en segundos. Todos los códigos, scripts y casos de prueba del artículo se adjuntan al final del artículo.

  • Entorno de este artículo: SpringBoot 2.5.7 + MySQL 8.0 X + MybatisPlus + Swagger2.9.2
  • Herramienta de simulación: Jmeter
  • Escenario de simulación: reducir inventario -> crear pedido -> simular pago

2. Pico de materias primas: sobreventa

En desarrollo, es posible que esté familiarizado con el siguiente código: agregue @Transactionalanotaciones de transacciones y Bloquee bloqueos al Servicio.

No se presentarán los conceptos básicos de Spring Boot. Se recomienda ver este tutorial gratuito:

https://github.com/javastacks/spring-boot-best-practice

Capa de control: Controlador

@ApiOperation(value="秒杀实现方式——Lock加锁")
@PostMapping("/start/lock")
public Result startLock(long skgId){
    try {
        log.info("开始秒杀方式一...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        Result result = secondKillService.startSecondKillByLock(skgId, userId);
        if(result != null){
            log.info("用户:{}--{}", userId, result.get("msg"));
        }else{
            log.info("用户:{}--{}", userId, "哎呦喂,人也太多了,请稍后!");
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {

    }
    return Result.ok();
}

Capa empresarial: Servicio

@Override
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByLock(long skgId, long userId) {
    lock.lock();
    try {
        // 校验库存
        SecondKill secondKill = secondKillMapper.selectById(skgId);
        Integer number = secondKill.getNumber();
        if (number > 0) {
            // 扣库存
            secondKill.setNumber(number - 1);
            secondKillMapper.updateById(secondKill);
            // 创建订单
            SuccessKilled killed = new SuccessKilled();
            killed.setSeckillId(skgId);
            killed.setUserId(userId);
            killed.setState((short) 0);
            killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
            successKilledMapper.insert(killed);

            // 模拟支付
            Payment payment = new Payment();
            payment.setSeckillId(skgId);
            payment.setSeckillId(skgId);
            payment.setUserId(userId);
            payment.setMoney(40);
            payment.setState((short) 1);
            payment.setCreateTime(new Timestamp(System.currentTimeMillis()));
            paymentMapper.insert(payment);
        } else {
            return Result.error(SecondKillStateEnum.END);
        }
    } catch (Exception e) {
        throw new ScorpiosException("异常了个乖乖");
    } finally {
        lock.unlock();
    }
    return Result.ok(SecondKillStateEnum.SUCCESS);
}

No debería haber ningún problema con el código anterior, agregar transacciones al método comercial y bloquear al procesar el negocio.

Pero hay un problema con la forma de escribir anterior, y habrá situaciones de sobreventa. Eche un vistazo a los resultados de la prueba: simule 1000 concurrencia, tome 100 productos.

Aquí, el bloqueo se agrega al comienzo del método comercial y el bloqueo se libera después del final del método comercial. Pero el envío de la transacción aquí no es así. Es posible que el bloqueo se haya liberado antes de que se envíe la transacción, lo que conducirá a la sobreventa de productos. ¡Así que el momento del bloqueo es muy importante!

3. Resolver artículos sobrevendidos

Para el fenómeno de sobreventa anterior, el problema principal ocurre cuando se libera el bloqueo en la transacción.Antes de que se confirme la transacción, el bloqueo se ha liberado. (El envío de la transacción se ejecuta después de ejecutar todo el método). La forma de solucionar este problema es adelantar el paso de bloqueo

  • Se puede bloquear en la capa del controlador
  • Aop se puede usar para bloquear antes de que se ejecute el método comercial

3.1 Método 1 (bloqueo de versión mejorado)

@ApiOperation(value="秒杀实现方式——Lock加锁")
@PostMapping("/start/lock")
public Result startLock(long skgId){
    // 在此处加锁
    lock.lock();
    try {
        log.info("开始秒杀方式一...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        Result result = secondKillService.startSecondKillByLock(skgId, userId);
        if(result != null){
            log.info("用户:{}--{}", userId, result.get("msg"));
        }else{
            log.info("用户:{}--{}", userId, "哎呦喂,人也太多了,请稍后!");
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 在此处释放锁
        lock.unlock();
    }
    return Result.ok();
}

El bloqueo anterior puede resolver el problema de la liberación del bloqueo antes de que se confirme la transacción, y las pruebas de estrés se pueden realizar en tres situaciones:

  • Número concurrente 1000, mercancía 100
  • Número concurrente 1000, mercancía 1000
  • Número concurrente 2000, mercancía 1000

En el caso de que el número de concurrencia sea mayor que el número de productos, el producto seleccionado generalmente no parece estar subvendido, pero cuando el número de concurrencia es menor o igual que el número de productos, puede haber menos ventas de productos, que también es muy comprensible.

Si no hay problema, no habrá texturas, porque hay muchas formas, y habrá demasiadas texturas.

3.2 Método 2 (bloqueo de versión AOP)

Para el método anterior de bloqueo en la capa de control, puede parecer poco elegante, entonces hay otra forma de bloquear antes de la transacción, que es AOP.

Recomiende el tutorial más completo de código abierto y gratuito de Spring Boot:

https://github.com/javastacks/spring-boot-best-practice

Anotaciones AOP personalizadas

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public  @interface ServiceLock {
    String description()  default "";
}

Definir la clase de aspecto

@Slf4j
@Component
@Scope
@Aspect
@Order(1) //order越小越是最先执行,但更重要的是最先执行的最后结束
public class LockAspect {
    /**
     * 思考:为什么不用synchronized
     * service 默认是单例的,并发下lock只有一个实例
     */
    private static  Lock lock = new ReentrantLock(true); // 互斥锁 参数默认false,不公平锁

    // Service层切点     用于记录错误日志
    @Pointcut("@annotation(com.scorpios.secondkill.aop.ServiceLock)")
    public void lockAspect() {

    }

    @Around("lockAspect()")
    public  Object around(ProceedingJoinPoint joinPoint) {
        lock.lock();
        Object obj = null;
        try {
            obj = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
   throw new RuntimeException();
        } finally{
            lock.unlock();
        }
        return obj;
    }
}

Agregar anotaciones AOP a los métodos comerciales

@Override
@ServiceLock // 使用Aop进行加锁
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByAop(long skgId, long userId) {

    try {
        // 校验库存
        SecondKill secondKill = secondKillMapper.selectById(skgId);
        Integer number = secondKill.getNumber();
        if (number > 0) {
            //扣库存
            secondKill.setNumber(number - 1);
            secondKillMapper.updateById(secondKill);
            //创建订单
            SuccessKilled killed = new SuccessKilled();
            killed.setSeckillId(skgId);
            killed.setUserId(userId);
            killed.setState((short) 0);
            killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
            successKilledMapper.insert(killed);

            //支付
            Payment payment = new Payment();
            payment.setSeckillId(skgId);
            payment.setSeckillId(skgId);
            payment.setUserId(userId);
            payment.setMoney(40);
            payment.setState((short) 1);
            payment.setCreateTime(new Timestamp(System.currentTimeMillis()));
            paymentMapper.insert(payment);
        } else {
            return Result.error(SecondKillStateEnum.END);
        }
    } catch (Exception e) {
        throw new ScorpiosException("异常了个乖乖");
    }
    return Result.ok(SecondKillStateEnum.SUCCESS);
}

Capa de control:

@ApiOperation(value="秒杀实现方式二——Aop加锁")
@PostMapping("/start/aop")
public Result startAop(long skgId){
    try {
        log.info("开始秒杀方式二...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        Result result = secondKillService.startSecondKillByAop(skgId, userId);
        if(result != null){
            log.info("用户:{}--{}", userId, result.get("msg"));
        }else{
            log.info("用户:{}--{}", userId, "哎呦喂,人也太多了,请稍后!");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return Result.ok();
}

¡Este método es más avanzado y más hermoso en el uso de cerraduras!

3.3 Método 3 (bloqueo pesimista 1)

Además de bloquear en el nivel de código comercial anterior, también puede usar los bloqueos que vienen con la base de datos para el control de concurrencia.

Bloqueo pesimista, ¿qué es el bloqueo pesimista? En términos sencillos, antes de hacer nada, se debe realizar una confirmación de bloqueo. Esta operación de bloqueo a nivel de base de datos es menos eficaz.

Cuando se usa para la actualización, debe agregar una transacción. Después de que se procese la transacción, para la actualización se liberará el bloqueo de nivel de fila.

Si el número de solicitudes es el mismo que el número de productos flash, habrá menos ventas

@ApiOperation(value="秒杀实现方式三——悲观锁")
@PostMapping("/start/pes/lock/one")
public Result startPesLockOne(long skgId){
    try {
        log.info("开始秒杀方式三...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        Result result = secondKillService.startSecondKillByUpdate(skgId, userId);
        if(result != null){
            log.info("用户:{}--{}", userId, result.get("msg"));
        }else{
            log.info("用户:{}--{}", userId, "哎呦喂,人也太多了,请稍后!");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return Result.ok();
}

Lógica de negocios

@Override
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByUpdate(long skgId, long userId) {
    try {
        // 校验库存-悲观锁
        SecondKill secondKill = secondKillMapper.querySecondKillForUpdate(skgId);
        Integer number = secondKill.getNumber();
        if (number > 0) {
            //扣库存
            secondKill.setNumber(number - 1);
            secondKillMapper.updateById(secondKill);
            //创建订单
            SuccessKilled killed = new SuccessKilled();
            killed.setSeckillId(skgId);
            killed.setUserId(userId);
            killed.setState((short) 0);
            killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
            successKilledMapper.insert(killed);

            //支付
            Payment payment = new Payment();
            payment.setSeckillId(skgId);
            payment.setSeckillId(skgId);
            payment.setUserId(userId);
            payment.setMoney(40);
            payment.setState((short) 1);
            payment.setCreateTime(new Timestamp(System.currentTimeMillis()));
            paymentMapper.insert(payment);
        } else {
            return Result.error(SecondKillStateEnum.END);
        }
    } catch (Exception e) {
        throw new ScorpiosException("异常了个乖乖");
    } finally {
    }
    return Result.ok(SecondKillStateEnum.SUCCESS);
}

capa dao

@Repository
public interface SecondKillMapper extends BaseMapper<SecondKill> {

    /**
     * 将此行数据进行加锁,当整个方法将事务提交后,才会解锁
     * @param skgId
     * @return
     */
    @Select(value = "SELECT * FROM seckill WHERE seckill_id=#{skgId} FOR UPDATE")
    SecondKill querySecondKillForUpdate(@Param("skgId") Long skgId);

}

Lo anterior se usa para la actualización para bloquear los datos de la consulta, agregando bloqueos de fila

3.4 Método 4 (bloqueo pesimista 2)

La segunda forma de bloqueo pesimista es usar el comando de actualización de actualización para agregar bloqueos de tabla

/**
 * UPDATE锁表
 * @param skgId  商品id
 * @param userId    用户id
 * @return
 */
@Override
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByUpdateTwo(long skgId, long userId) {
    try {

        // 不校验,直接扣库存更新
        int result = secondKillMapper.updateSecondKillById(skgId);
        if (result > 0) {
            //创建订单
            SuccessKilled killed = new SuccessKilled();
            killed.setSeckillId(skgId);
            killed.setUserId(userId);
            killed.setState((short) 0);
            killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
            successKilledMapper.insert(killed);

            //支付
            Payment payment = new Payment();
            payment.setSeckillId(skgId);
            payment.setSeckillId(skgId);
            payment.setUserId(userId);
            payment.setMoney(40);
            payment.setState((short) 1);
            payment.setCreateTime(new Timestamp(System.currentTimeMillis()));
            paymentMapper.insert(payment);
        } else {
            return Result.error(SecondKillStateEnum.END);
        }
    } catch (Exception e) {
        throw new ScorpiosException("异常了个乖乖");
    } finally {
    }
    return Result.ok(SecondKillStateEnum.SUCCESS);
}

capa dao

@Repository
public interface SecondKillMapper extends BaseMapper<SecondKill> {

    /**
     * 将此行数据进行加锁,当整个方法将事务提交后,才会解锁
     * @param skgId
     * @return
     */
    @Select(value = "SELECT * FROM seckill WHERE seckill_id=#{skgId} FOR UPDATE")
    SecondKill querySecondKillForUpdate(@Param("skgId") Long skgId);

    @Update(value = "UPDATE seckill SET number=number-1 WHERE seckill_id=#{skgId} AND number > 0")
    int updateSecondKillById(@Param("skgId") long skgId);
}

3.5 Método 5 (bloqueo optimista)

El bloqueo optimista, como sugiere el nombre, es muy optimista sobre los resultados de la operación, ya que utiliza el campo de versión para determinar si los datos se han modificado.

Bloqueo optimista, no verifique la cantidad de inventario, haga directamente la deducción de inventario

El bloqueo optimista que se usa aquí provocará una gran cantidad de excepciones de actualización de datos (lanzar una excepción hará que la compra falle).Si la cantidad de complementos configurados es relativamente pequeña, como 120:100 (cantidad de personas: mercancía) , habrá menos compras No se recomienda optimismo Bloqueo.

@ApiOperation(value="秒杀实现方式五——乐观锁")
@PostMapping("/start/opt/lock")
public Result startOptLock(long skgId){
    try {
        log.info("开始秒杀方式五...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        // 参数添加了购买数量
        Result result = secondKillService.startSecondKillByPesLock(skgId, userId,1);
        if(result != null){
            log.info("用户:{}--{}", userId, result.get("msg"));
        }else{
            log.info("用户:{}--{}", userId, "哎呦喂,人也太多了,请稍后!");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return Result.ok();
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByPesLock(long skgId, long userId, int number) {

    // 乐观锁,不进行库存数量的校验,直接
    try {
        SecondKill kill = secondKillMapper.selectById(skgId);
        // 剩余的数量应该要大于等于秒杀的数量
        if(kill.getNumber() >= number) {
            int result = secondKillMapper.updateSecondKillByVersion(number,skgId,kill.getVersion());
            if (result > 0) {
                //创建订单
                SuccessKilled killed = new SuccessKilled();
                killed.setSeckillId(skgId);
                killed.setUserId(userId);
                killed.setState((short) 0);
                killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
                successKilledMapper.insert(killed);

                //支付
                Payment payment = new Payment();
                payment.setSeckillId(skgId);
                payment.setSeckillId(skgId);
                payment.setUserId(userId);
                payment.setMoney(40);
                payment.setState((short) 1);
                payment.setCreateTime(new Timestamp(System.currentTimeMillis()));
                paymentMapper.insert(payment);
            } else {
                return Result.error(SecondKillStateEnum.END);
            }
        }
    } catch (Exception e) {
        throw new ScorpiosException("异常了个乖乖");
    } finally {
    }
    return Result.ok(SecondKillStateEnum.SUCCESS);
}
@Repository
public interface SecondKillMapper extends BaseMapper<SecondKill> {

    /**
     * 将此行数据进行加锁,当整个方法将事务提交后,才会解锁
     * @param skgId
     * @return
     */
    @Select(value = "SELECT * FROM seckill WHERE seckill_id=#{skgId} FOR UPDATE")
    SecondKill querySecondKillForUpdate(@Param("skgId") Long skgId);

    @Update(value = "UPDATE seckill SET number=number-1 WHERE seckill_id=#{skgId} AND number > 0")
    int updateSecondKillById(@Param("skgId") long skgId);

    @Update(value = "UPDATE seckill  SET number=number-#{number},version=version+1 WHERE seckill_id=#{skgId} AND version = #{version}")
    int updateSecondKillByVersion(@Param("number") int number, @Param("skgId") long skgId, @Param("version")int version);
}

Los bloqueos optimistas provocarán una gran cantidad de excepciones de actualización de datos (lanzar una excepción hará que la compra falle) y habrá menos compras. No se recomiendan los bloqueos optimistas.

3.6 Método 6 (cola de bloqueo)

El uso de colas de bloqueo también puede resolver problemas de alta concurrencia. La idea es almacenar las solicitudes recibidas en la cola en orden, y el subproceso del consumidor obtiene los datos de la cola uno por uno para su procesamiento; consulte el código específico.

Cola de bloqueo: aquí, la clase interna estática se usa para implementar el modo singleton y no habrá problemas en condiciones concurrentes.

// 秒杀队列(固定长度为100)
public class SecondKillQueue {

    // 队列大小
    static final int QUEUE_MAX_SIZE = 100;

    // 用于多线程间下单的队列
    static BlockingQueue<SuccessKilled> blockingQueue = new LinkedBlockingQueue<SuccessKilled>(QUEUE_MAX_SIZE);

    // 使用静态内部类,实现单例模式
    private SecondKillQueue(){};

    private static class SingletonHolder{
        // 静态初始化器,由JVM来保证线程安全
        private  static SecondKillQueue queue = new SecondKillQueue();
    }

    /**
     * 单例队列
     * @return
     */
    public static SecondKillQueue getSkillQueue(){
        return SingletonHolder.queue;
    }

    /**
     * 生产入队
     * @param kill
     * @throws InterruptedException
     * add(e) 队列未满时,返回true;队列满则抛出IllegalStateException(“Queue full”)异常——AbstractQueue
     * put(e) 队列未满时,直接插入没有返回值;队列满时会阻塞等待,一直等到队列未满时再插入。
     * offer(e) 队列未满时,返回true;队列满时返回false。非阻塞立即返回。
     * offer(e, time, unit) 设定等待的时间,如果在指定时间内还不能往队列中插入数据则返回false,插入成功返回true。
     */
    public  Boolean  produce(SuccessKilled kill) {
        return blockingQueue.offer(kill);
    }
    /**
     * 消费出队
     * poll() 获取并移除队首元素,在指定的时间内去轮询队列看有没有首元素有则返回,否者超时后返回null
     * take() 与带超时时间的poll类似不同在于take时候如果当前队列空了它会一直等待其他线程调用notEmpty.signal()才会被唤醒
     */
    public  SuccessKilled consume() throws InterruptedException {
        return blockingQueue.take();
    }

    /**
     * 获取队列大小
     * @return
     */
    public int size() {
        return blockingQueue.size();
    }
}

Cola seckill de consumo: implementar la interfaz ApplicationRunner

// 消费秒杀队列
@Slf4j
@Component
public class TaskRunner implements ApplicationRunner{

    @Autowired
    private SecondKillService seckillService;

    @Override
    public void run(ApplicationArguments var){
        new Thread(() -> {
            log.info("队列启动成功");
            while(true){
                try {
                    // 进程内队列
                    SuccessKilled kill = SecondKillQueue.getSkillQueue().consume();
                    if(kill != null){
                        Result result = seckillService.startSecondKillByAop(kill.getSeckillId(), kill.getUserId());
                        if(result != null && result.equals(Result.ok(SecondKillStateEnum.SUCCESS))){
                            log.info("TaskRunner,result:{}",result);
                            log.info("TaskRunner从消息队列取出用户,用户:{}{}",kill.getUserId(),"秒杀成功");
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
@ApiOperation(value="秒杀实现方式六——消息队列")
@PostMapping("/start/queue")
public Result startQueue(long skgId){
    try {
        log.info("开始秒杀方式六...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        SuccessKilled kill = new SuccessKilled();
        kill.setSeckillId(skgId);
        kill.setUserId(userId);
        Boolean flag = SecondKillQueue.getSkillQueue().produce(kill);
        // 虽然进入了队列,但是不一定能秒杀成功 进队出队有时间间隙
        if(flag){
            log.info("用户:{}{}",kill.getUserId(),"秒杀成功");
        }else{
            log.info("用户:{}{}",userId,"秒杀失败");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return Result.ok();
}

Nota: En la capa empresarial y los métodos AOP, no se pueden generar excepciones, y los códigos de generación de excepciones, como throw new RuntimeException(), deben comentarse. ¡Porque una vez que el programa lanza una excepción, se detendrá, lo que resultará en la finalización del proceso de cola de seckill de consumo!

Hay algunos puntos a tener en cuenta al usar colas de bloqueo para implementar picos:

  • También es fácil entender que llamar a los métodos comerciales en la cola seckill de consumo está bloqueado y no bloqueado, es decir seckillService.startSecondKillByAop(), seckillService.startSecondKillByLock()el resultado del método es el mismo.
  • Cuando la longitud de la cola es consistente con la cantidad de productos, habrá menos ventas y el valor se puede aumentar
  • La siguiente es la venta inferior cuando la longitud de la cola es 1000, la cantidad de productos es 1000 y la cantidad de simultaneidad es 2000.

3.7 Método 7 (Cola disruptiva)

Disruptor es una cola de alto rendimiento. La intención original de investigación y desarrollo es resolver el problema de retraso de la cola de memoria. En la prueba de rendimiento, se encontró que está en el mismo orden de magnitud que las operaciones de E/S. El único hilo del sistema desarrollado en base a Disruptor puede soportar 6 millones de pedidos por segundo.

// 事件生成工厂(用来初始化预分配事件对象)
public class SecondKillEventFactory implements EventFactory<SecondKillEvent> {

    @Override
    public SecondKillEvent newInstance() {
        return new SecondKillEvent();
    }
}
// 事件对象(秒杀事件)
public class SecondKillEvent implements Serializable {
    private static final long serialVersionUID = 1L;
    private long seckillId;
    private long userId;

 // set/get方法略

}
// 使用translator方式生产者
public class SecondKillEventProducer {

    private final static EventTranslatorVararg<SecondKillEvent> translator = (seckillEvent, seq, objs) -> {
        seckillEvent.setSeckillId((Long) objs[0]);
        seckillEvent.setUserId((Long) objs[1]);
    };

    private final RingBuffer<SecondKillEvent> ringBuffer;

    public SecondKillEventProducer(RingBuffer<SecondKillEvent> ringBuffer){
        this.ringBuffer = ringBuffer;
    }

    public void secondKill(long seckillId, long userId){
        this.ringBuffer.publishEvent(translator, seckillId, userId);
    }
}
// 消费者(秒杀处理器)
@Slf4j
public class SecondKillEventConsumer implements EventHandler<SecondKillEvent> {

    private SecondKillService secondKillService = (SecondKillService) SpringUtil.getBean("secondKillService");

    @Override
    public void onEvent(SecondKillEvent seckillEvent, long seq, boolean bool) {
        Result result = secondKillService.startSecondKillByAop(seckillEvent.getSeckillId(), seckillEvent.getUserId());
        if(result.equals(Result.ok(SecondKillStateEnum.SUCCESS))){
            log.info("用户:{}{}",seckillEvent.getUserId(),"秒杀成功");
        }
    }
}
public class DisruptorUtil {

    static Disruptor<SecondKillEvent> disruptor;

    static{
        SecondKillEventFactory factory = new SecondKillEventFactory();
        int ringBufferSize = 1024;
        ThreadFactory threadFactory = runnable -> new Thread(runnable);
        disruptor = new Disruptor<>(factory, ringBufferSize, threadFactory);
        disruptor.handleEventsWith(new SecondKillEventConsumer());
        disruptor.start();
    }

    public static void producer(SecondKillEvent kill){
        RingBuffer<SecondKillEvent> ringBuffer = disruptor.getRingBuffer();
        SecondKillEventProducer producer = new SecondKillEventProducer(ringBuffer);
        producer.secondKill(kill.getSeckillId(),kill.getUserId());
    }
}
@ApiOperation(value="秒杀实现方式七——Disruptor队列")
@PostMapping("/start/disruptor")
public Result startDisruptor(long skgId){
    try {
        log.info("开始秒杀方式七...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        SecondKillEvent kill = new SecondKillEvent();
        kill.setSeckillId(skgId);
        kill.setUserId(userId);
        DisruptorUtil.producer(kill);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return Result.ok();
}

Después de la prueba, se encuentra que usar la cola Disruptor tiene el mismo problema que la cola personalizada, y también habrá situaciones de sobreventa, pero se ha mejorado la eficiencia.

4. Resumen

Para las siete formas anteriores de lograr la concurrencia, haga un resumen:

  • El primer y segundo método son usar bloqueos y transacciones en el código para resolver el problema de concurrencia. La solución principal es que el bloqueo debe cargarse antes de la transacción.
  • Los métodos 3, 4 y 5 usan principalmente bloqueos de bases de datos para resolver problemas de concurrencia. El método 3 usa upate para agregar bloqueos de filas a las tablas. El método 4 usa update para bloquear tablas. El método 5 controla la base de datos agregando el campo de versión. Actualizar operación, el método 5 tiene el peor efecto
  • Las formas 6 y 7 usan colas para resolver el problema de concurrencia. Lo que necesita especial atención aquí es que las excepciones no se pueden lanzar mediante el código, de lo contrario, el subproceso de consumo terminará y, debido al intervalo de tiempo entre la entrada y la salida de la cola, menos se venderán productos

Todas las situaciones anteriores han sido probadas por código, y las pruebas se dividen en tres situaciones:

  • El número de concurrencia es 1000 y el número de productos es 100
  • El número de concurrencia es 1000 y el número de productos es 1000
  • El número de concurrencia es 2000 y el número de productos es 1000

Reflexión: ¿Cómo resolver el problema de concurrencia en una situación distribuida? Continúe experimentando la próxima vez.

Declaración de derechos de autor: este artículo es un artículo original del blogger CSDN "Stop Going Forward", siguiendo el acuerdo de derechos de autor CC 4.0 BY-SA, adjunte el enlace de la fuente original y esta declaración para su reimpresión. Enlace original: https://blog.csdn.net/zxd1435513775/article/details/122643285

Recomendación de artículo reciente:

1. Más de 1000 preguntas y respuestas de entrevistas en Java (última versión de 2022)

2. ¡Brillante! Las corrutinas de Java están llegando. . .

3. Tutorial de Spring Boot 2.x, ¡demasiado completo!

4. No llenes la pantalla de explosiones y explosiones, prueba el modo decorador, ¡esta es la forma elegante! !

5. La última versión del "Manual de desarrollo de Java (edición Songshan)", ¡descárguelo rápidamente!

¡Siéntete bien, no olvides darle me gusta + adelante!

Supongo que te gusta

Origin blog.csdn.net/youanyyou/article/details/130626223
Recomendado
Clasificación