(Turn) How to solve the problem of oversold inventory - chaos

  Generally, e-commerce websites will encounter activities such as group purchases, seckills, and special offers, and such activities have a common feature that the traffic surges, and thousands or even tens of thousands of people snap up a commodity. However, as an active commodity, the inventory is definitely very limited. How to control the inventory to prevent overbought to prevent unnecessary losses is a headache for many e-commerce website programmers, which is also the most basic problem. Source of this article: http://blog.csdn.net/caomiao2006/article/details/38568825, and made a few modifications to the author's relevant views. [Content] Attention! It is mentioned in the article that memcache is used for caching, and it is used for inventory operation control! The author believes that redis should be used instead of memcache, because redis supports transactions, and the atomicity and isolation are better than memcache. So pay attention to this.   From a technical perspective, many people will definitely think of transactions, but transactions are a necessary condition for controlling inventory oversold, but not a sufficient and necessary condition. Example: total inventory: 4 items, requester: a, 1 item b, 2 items c, 3 items Examples are as follows:
  


  



begin ...
try {
    $result = $dbca -> query ( 'select amount from s_store where postID = 12345' ); if ( result -> amount > 0 ){ //quantity is the requested amount of inventory         $dbca - > query ( 'update s_store set amount = amount - quantity where postID = 12345' ); } } catch ( $e Exception ){     rollBack (rollback) } commit ...
   
       

   



  The above code is the code we usually write to control the inventory. Most people will write it like this. It seems that the problem is not big, but it actually hides a huge loophole.

  • Database access is actually the access to disk files. The tables in the database are actually files saved on the disk, and even a file contains multiple tables. For example, due to high concurrency, there are currently three users a, b, and c who have entered this transaction. At this time, a shared lock will be generated. Therefore, when selecting, the number of inventories found by these three users is 4. .
  • At the same time, it should also be noted that the results found by mysql innodb are version controlled. Before other users update without commit (that is, before a new version is generated), the results found by the current user are still the old version;
  • Then execute the update. If the three users arrive at the update at the same time, the update update statement will serialize the concurrency at this time, that is, the three users who arrive here at the same time are sorted, executed one by one, and an exclusive lock is generated. Before the current update statement is committed, other users wait for execution. After the commit, a new version is generated; after this is executed, the inventory must be negative.

  Let's modify the code below:

begin....
try{
    //quantity为请求减掉的库存数量
    $dbca->query('update s_store set amount = amount - quantity where postID = 12345');
    $result = $dbca->query('select amount from s_store where postID = 12345');
    if(result->amount <0){
       thrownewException('库存不足');
    }
}catch($e Exception){
    rollBack(回滚)
}
commit....

  再优化一下,如:

begin...
try{
    //quantity为请求减掉的库存数量
    $dbca->query('update s_store set amount = amount - quantity where amount>=quantity and postID = 12345');
}catch($e Exception){
    rollBack(回滚)
}
commit...

  但是,使用事务也会带来性能问题,下面我们来分析一下:

  1. 在秒杀的情况下,肯定不能如此高频率的去读写数据库,会严重造成性能问题的必须使用缓存,将需要秒杀的商品放入缓存中,并使用锁来处理其并发情况。当接到用户秒杀提交订单的情况下,先将商品数量递减(加锁/解锁)后再进行其他方面的处理,处理失败在将数据递增1(加锁/解锁),否则表示交易成功。当商品数量递减到0时,表示商品秒杀完毕,拒绝其他用户的请求。
  2. 这个肯定不能直接操作数据库的,会挂的。直接读库写库对数据库压力太大,要用缓存。把你要卖出的商品比如10个商品放到缓存中;然后在memcache里设置一个计数器来记录请求数,这个请求书你可以以你要秒杀卖出的商品数为基数,比如你想卖出10个商品,只允许100个请求进来。那当计数器达到100的时候,后面进来的就显示秒杀结束,这样可以减轻你的服务器的压力。然后根据这100个请求,先付款的先得后付款的提示商品以秒杀完。
  3. 首先,多用户并发修改同一条记录时,肯定是后提交的用户将覆盖掉前者提交的结果了。

  
  如果不采用事务,我们采用数据库锁来实现,锁分为乐观锁或者悲观锁。

  • 乐观锁:就是在数据库设计一个版本号的字段,每次修改都使其+1,这样在提交时比对提交前的版本号就知道是不是并发提交了,但是有个缺点就是只能是应用中控制,如果有跨应用修改同一条数据乐观锁就没办法了,这个时候可以考虑悲观锁。
  • 悲观锁,就是直接在数据库层面将数据锁死,类似于oralce中使用select xxxxx from xxxx where xx=xx for update,这样其他线程将无法提交数据。


  其它思路我们也可以来发挥一下:
  除了加锁的方式也可以使用接收锁定的方式,思路是在数据库中设计一个状态标识位,用户在对数据进行修改前,将状态标识位标识为正在编辑的状态,这样其他用户要编辑此条记录时系统将发现有其他用户正在编辑,则拒绝其编辑的请求,类似于你在操作系统中某文件正在执行,然后你要修改该文件时,系统会提醒你该文件不可编辑或删除。

[结论]
1,不建议在数据库层面加锁,建议通过服务端的内存锁(锁主键)。当某个用户要修改某个id的数据时,把要修改的id存入memcache,若其他用户触发修改此id的数据时,读到memcache有这个id的值时,就阻止那个用户修改。

2,实际应用中,并不是让mysql去直面大并发读写,会借助“外力”,比如缓存、利用主从库实现读写分离、分表、使用队列写入等方法来降低并发读写。

3,注意防止缓存穿透问题。即:如果缓存中所存商品的id范围可知,如100-1000,那么当我查询id为<100及1000的商品时,这里缓存中肯定是不存在的,按逻辑肯定会去查DB,查到后把数据放到缓存。这时问题来了,如果有人故意刷你这个范围外的东西,这样每次去查DB,并发量上来时,DB一定会挂。因此,为了避免这个问题,应在缓存放置当前数据存在的记录ID,通过ID确定是否需要查询DB。这样大大减轻了DB的压力。

 

 

REFS:http://blog.163.com/sujoe_2006/blog/static/335315120141139728398/

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326671804&siteId=291194637