当前位置:网站首页>Seckill system - single order solution (from 0 to 1)

Seckill system - single order solution (from 0 to 1)

2020-12-07 08:06:57 osc_ c1wjdynh

Seckill system - Order solutions ( from 0 To 1)

Stand-alone version ( Don't think about inventory ):

  • General order —— Regardless of inventory 、 Don't think about oversold 、 Don't think about concurrency , Just think about performance .

Stand-alone version ( Consider the inventory problem ):

  • Program lock .
  • aop lock .
  • queue (blockingQueue)

Distributed :

  • The database lock ( Pessimistic locking 、 Optimism lock ).
  • Distributed lock .
  • queue (mq)

inventory control :

When you place an order , No inventory control , The same product has been sold many times . This is what we usually call oversold .( Correct concept : Oversold doesn't mean selling inventory to a negative number , It's that the same product is sold many times


Stand-alone version ( Don't think about inventory )

General order

Regardless of inventory 、 Don't think about oversold 、 Don't think about concurrency , Just think about performance .
There is no lock operation , Directly reduce inventory and increase sales , Order directly ( Insert order data directly into the order Library —— The above operations are direct operation of the database )

private void startSubmitOrder() {
   
    
        // Query product information 
        seckillGoods.findOneById( goods Id);
        // Judge whether the goods are on the shelves 、 Whether the event started 、 Do you have any in stock .....
        // The stock of goods is reduced 1、 Sales plus 1, Update product list 
        seckillGoods.update();
        // Save order 
        order.save();
}
Optimize : Multithreading orders
private void startSubmitOrderMultiThread() {
   
    
        // Query product information 
        seckillGoods.findOneById( goods Id);
        // Judge whether the goods are on the shelves 、 Whether the event started 、 Do you have any in stock .....
        // The stock of goods is reduced 1、 Sales plus 1, Update product list 
        seckillGoods.update();
		// Save order ( Multi thread mode order )
		new Thread(() -> {
   
    
			order.save();
		}).start();
}
Optimize : cache

Put the product information into the cache before the activity starts , Query products are retrieved directly from the cache .

private void startSubmitOrderCache() {
   
    
		// Query product information from cache 
		redisTemplate.get( goods Id);
        // Judge whether the goods are on the shelves 、 Whether the event started 、 Do you have any in stock .....
        // The stock of goods is reduced 1、 Sales plus 1, Update product list 
        seckillGoods.update();
		// Save order ( Multi thread mode order )
		new Thread(() -> {
   
    
			order.save();
		}).start();
}

10000 Users place orders at the same time , In theory, there is still... In stock 0, The stock is still in stock 113 individual , The program lock is oversold .

problem :10000 One user placed an order at the same time ( They all think they're buying things , In fact, there are serious problems ), Only bought 887 A commodity .

Stand-alone version ( Consider the inventory problem )

Program lock
//  Define global program locks (ReentrantLock)
private ReentrantLock reentrantLock = new ReentrantLock();

@Transactional(rollbackFor = Exception.class)
private void startSubmitOrderReentrantLock() {
   
    
		try {
   
    
            // Lock 
            reentrantLock.lock();
            // Query product information from cache 
            redisTemplate.get( goods Id);
            // Judge whether the goods are on the shelves 、 Whether the event started 、 Do you have any in stock .....
            // The stock of goods is reduced 1、 Sales plus 1, Update product list 
            seckillGoods.update();
            // Save order 
            order.save();
        } catch (Exception e) {
   
    
            e.printStackTrace();
        } finally {
   
    
            // Unlock 
            reentrantLock.unlock();
        }
}

10000 Users place orders at the same time , In theory, there is still... In stock 0, The stock is still in stock 281 individual , The program lock is oversold .

The reason why inventory can't be controlled , and Spring Business matters :

  • Transaction rollback principle : Method throws an exception , Instead, submit .
  • Program lock : The issue of transaction commit time , In other words, the lock is released , The transaction has not yet been committed , Causes other threads to read dirty data .

Solution :

  • Lock up .
  • Commit transactions manually .

AOP lock

utilize Spring Faceted programming mode , Realization AOP lock .

/**
 * @Author: LailaiMonkey
 * @Description: Customize aop Cut notes 
 * @Date:Created in 2020-12-06 14:19
 * @Modified By:
 */
@Target({
   
     ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ServiceLock {
   
     
    String description() default "";
}

aop Interceptor :

/**
 * @Author: LailaiMonkey
 * @Description: Annotation blocking 
 * @Date:Created in 2020-12-06 14:21
 * @Modified By:
 */
@Component
@Scope
@Aspect
//order The smaller, the better 
@Order(1)
public class LockAspect {
   
     
    private ReentrantLock reentrantLock = new ReentrantLock();

    @Around("@annotation(com.monkey.test.sdfsdaf.ServiceLock)")
    public Object lockAspect(ProceedingJoinPoint joinPoint) {
   
     
        // Lock 
        Object object = null;
        try {
   
     
            reentrantLock.lock();
            object = joinPoint.proceed();
        } catch (Throwable e) {
   
     
            e.printStackTrace();
        } finally {
   
     
            // Unlock 
            reentrantLock.unlock();
        }
        return object;
    }
}

Same as ordinary order business logic , You need to add blocking comments

@Transactional(rollbackFor = Exception.class)
@ServiceLock
private void startSubmitOrder() {
   
     
        // Query product information 
        seckillGoods.findOneById( goods Id);
        // Judge whether the goods are on the shelves 、 Whether the event started 、 Do you have any in stock .....
        // The stock of goods is reduced 1、 Sales plus 1, Update product list 
        seckillGoods.update();
        // Save order 
        order.save();
}

The inventory is under perfect control !!!

queue

Put the order information in the queue , The background thread processes the order business slowly ( Orders are processed asynchronously )——blockingQueue
Place an order —— Put the data in the queue —— Queue consumption data .

Define global queues :

/**
 * @Author: LailaiMonkey
 * @Description: Define global queues 
 * @Date:Created in 2020-12-06 15:02
 * @Modified By:
 */
public class SeckillQueue {
   
     

    private final static BlockingQueue<OrderModel> BLOCKING_QUEUE = new LinkedBlockingDeque<>();

    // You can't instantiate 
    private SeckillQueue() {
   
     
    }

    private static class SingletonHolder {
   
     
        private static SeckillQueue queue = new SeckillQueue();
    }

    public static SeckillQueue getQueue() {
   
     
        return SingletonHolder.queue;
    }

    /**
     *  Put in queue 
     *
     * @param model
     * @return
     */
    public Boolean product(OrderModel model) {
   
     
        return BLOCKING_QUEUE.offer(model);
    }

    /**
     *  Outgoing queue 
     *
     * @param model
     * @return
     */
    public OrderModel consumer() throws InterruptedException {
   
     
        return BLOCKING_QUEUE.take();
    }

    /**
     *  Get the queue length 
     *
     * @param model
     * @return
     */
    public int size() {
   
     
        return BLOCKING_QUEUE.size();
    }
}

Queue consumption :

/**
 * @Author: LailaiMonkey
 * @Description: Queue consumption 
 * @Date:Created in 2020-12-06 15:09
 * @Modified By:
 */
@Component
public class TaskRunner implements ApplicationRunner {
   
     

    @Override
    public void run(ApplicationArguments args) throws Exception {
   
     
        new Thread(() -> {
   
     
            while (true) {
   
     
                try {
   
     
                    OrderModel model = SeckillQueue.getQueue().consumer();
                    if (model != null) {
   
     
                        // The stock of goods is reduced 1、 Sales plus 1, Update product list 
                        seckillGoods.update();
                        // Save order 
                        order.save();
                    }
                } catch (Exception ex) {
   
     
                    ex.printStackTrace();
                }
            }
        }).start();
    }
}

Place an order :

@Transactional(rollbackFor = Exception.class)
@ServiceLock
private void startSubmitOrder() {
   
     
		// Build an order model 
		OrderModel model = new OrderModel();
		model.set.......
        // Put in queue  
		Boolean flag = SeckillQueue.getQueue().product(model);
		if (flag) {
   
     
			// Notify the user that the order was placed successfully 
		} else {
   
     
			// Seckill failure 
		}
}

The queue size is 100, At this point, if the queue is full , The action to put in the queue will fail , That means the order failed , At this time, the size of the queue is set according to the back-end business processing capacity .
Using queues as cache objects , Reduce service pressure , Improve service throughput .

Optimize :

  • You can change the process of putting into the queue into multithreading .

shortcoming :

  • BlockingQueue Queues are memory queues , Occupy jvm Memory size , If the queue is too big and jvm Process preempts resources , Performance degradation , If the queue is too small , The order queue is full , There will be a failure to place an order .
  • Throughput problem
  • There is no way to control inventory in a distributed environment

Solution :

  • Distributed inventory 、 Distributed deployment 、 Distributed queues

Distributed

Database pessimistic lock

Query the inventory of goods plus for update Lock the product .

@Transactional(rollbackFor = Exception.class)
private void startSubmitOrderBySqlLock() {
   
     
        // Query product information (select .... for update)
        seckillGoods.findOneByIdSqlLock( goods Id);
        // Judge whether the goods are on the shelves 、 Whether the event started 、 Do you have any in stock .....
        // The stock of goods is reduced 1、 Sales plus 1, Update product list 
        seckillGoods.update();
        // Save order 
        order.save();
}

There is no problem with inventory control , Poor performance

Database optimistic lock

Add a... To the list of goods version Field , When multithreading ( Concurrent ) When querying and modifying, the version comparison data can be modified , Prevent dirty reading of data .

@Transactional(rollbackFor = Exception.class)
private void startSubmitOrderBySqlLock() {
   
     
        // Query product information 
        seckillGoods.findOneById( goods Id);
        // Judge whether the goods are on the shelves 、 Whether the event started 、 Do you have any in stock .....
        // The stock of goods is reduced 1、 Sales plus 1, Update product list , Judgment database version And query version Whether or not the same 
        //update ..... where version =  Check the product version
        int flag = seckillGoods.update();
        // The operation can be continued after the optimistic lock is updated successfully 
        if (flag > 0) {
   
     
			// Save order 
        	order.save();
		} else {
   
     
			// The hint is too hot 
		}
}

Because the database lock is operating on disk data , High performance consumption , Therefore, memory locks can be used to improve the performance of an order .

Distributed lock
@Transactional(rollbackFor = Exception.class)
private void startSubmitOrderReentrantLock() {
   
     
		boolean result = false;
		try {
   
     
            // Use redisson Lock , Try to wait 3s, After locking 20s Automatic release 
            result = redisson.tryLock("seckill_goods_lock" +  goods Id, TimeUnit.SECONDS, 3, 10);
            if (result) {
   
     
				// Query product information from cache 
				redisTemplate.get( goods Id);
	            // Judge whether the goods are on the shelves 、 Whether the event started 、 Do you have any in stock .....
	            // The stock of goods is reduced 1、 Sales plus 1, Update product list 
	            seckillGoods.update();
	            // Save order 
	            order.save();
			} else {
   
     
				// The tip is too hot 
			}
        } catch (Exception e) {
   
     
            e.printStackTrace();
        } finally {
   
     
            // Unlock 
            if (result) {
   
     
				redisson.unlock("seckill_goods_lock" +  goods Id);
			}
        }
}

redis Locks still conflict with local transactions , But because of the operation reids It's remote operation , Through the network io Conduct , There are network delays , Therefore, there will be no inventory control failure .redis Operations on data are asynchronous , The delay , So when the data transaction is committed redis To release , So that's OK .

Make sure 100% No problem redis Upgrade to aop lock , Put the above aop lock ReentrantLock Switch to redis Of lock that will do .

mq

ditto , hold blockingQueue Switch to rabbitMq that will do .


reflection

The project uses distributed deployment , Data consistency processing is very difficult :

  • Network pull
  • Network delay
  • The service outage
  • Machine down
  • Program crash
  • abnormal

The above problems must happen , So there must be trade-offs in dealing with data consistency , Strong consistency ( database-based )——CAP( Distributed )、Base theory ( Final consistency )

版权声明
本文为[osc_ c1wjdynh]所创,转载请带上原文链接,感谢
https://chowdera.com/2020/12/20201207080044148c.html