java redis 实现抢购秒杀

2018.10.24 今天研究了下抢购秒杀的功能实现

网上查了一大堆 用 redis 的最多。

主要是通过 redis 的 watch multi 事务来控制秒杀数量 不超卖。

这里说下自己的感受:

不超卖的话 那就要一个个的来减库存 这样的话 效率上会有点问题 这里上下代码 基本上是再网上抄的 。

我用的是 springboot jedis

我就直接上代码了 

Controller 层 1

package com.bicon.basedemo.controller;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.annotation.Resource;

import org.omg.CORBA.PRIVATE_MEMBER;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@RestController
@RequestMapping("/test")
public class test {

// @Resource
// RedisOperation redisOps;

@Resource
private JedisPool jedisPool;

@RequestMapping(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/redis</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> redisTest() {
    Jedis jedis </span>=<span style="color: rgba(0, 0, 0, 1)"> jedisPool.getResource();
    final String watchkeys </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">watchkeys</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    ExecutorService executor </span>= Executors.newFixedThreadPool(<span style="color: rgba(128, 0, 128, 1)">20</span>);  <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">20个线程池并发数</span>
jedis.set(watchkeys, "10");//设置起始的抢购数 // jedis.del("setsucc", "setfail"); jedis.close();
    </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i &lt; <span style="color: rgba(128, 0, 128, 1)">101</span>; i++) {<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">设置101个人来发起抢购 模拟101个人抢购</span>
        executor.execute(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MyRunnable(jedisPool));
    }
    executor.shutdown();
}

 </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> String getRandomString(<span style="color: rgba(0, 0, 255, 1)">int</span> length) { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">length是随机字符串长度</span>
     String <span style="color: rgba(0, 0, 255, 1)">base</span> = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">abcdefghijklmnopqrstuvwxyz0123456789</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;  
     Random random </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Random();  
     StringBuffer sb </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StringBuffer();  
     </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i &lt; length; i++<span style="color: rgba(0, 0, 0, 1)">) {  
         </span><span style="color: rgba(0, 0, 255, 1)">int</span> number = random.nextInt(<span style="color: rgba(0, 0, 255, 1)">base</span><span style="color: rgba(0, 0, 0, 1)">.length());  
         sb.append(</span><span style="color: rgba(0, 0, 255, 1)">base</span><span style="color: rgba(0, 0, 0, 1)">.charAt(number));  
     }  
     </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> sb.toString();  
  } 

}


MyRunnable 代码
package com.bicon.basedemo.controller;

import java.util.List;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;

public class MyRunnable implements Runnable{

</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> JedisPool jedisPool;
String userinfo;
String watchkeys </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">watchkeys</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> MyRunnable(JedisPool jedisPoo){
    jedisPool </span>=<span style="color: rgba(0, 0, 0, 1)"> jedisPoo;
};

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> run() {
    Jedis jedis </span>=<span style="color: rgba(0, 0, 0, 1)"> jedisPool.getResource();
    </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
        jedis.watch(watchkeys);</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> watchkeys</span>
String val = jedis.get(watchkeys); int valint = Integer.valueOf(val);
        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (valint &lt;= <span style="color: rgba(128, 0, 128, 1)">100</span> &amp;&amp; valint&gt;=<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">) {
        
             Transaction tx </span>= jedis.multi();<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 开启事务
           </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> tx.incr("watchkeys");</span>
            tx.incrBy(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">watchkeys</span><span style="color: rgba(128, 0, 0, 1)">"</span>, -<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">);

            List</span>&lt;Object&gt; list = tx.exec();<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 提交事务,如果此时watchkeys被改动了,则返回null</span>
             
            <span style="color: rgba(0, 0, 255, 1)">if</span> (list == <span style="color: rgba(0, 0, 255, 1)">null</span> ||list.size()==<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">) {
                System.</span><span style="color: rgba(0, 0, 255, 1)">out</span>.println(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">重新抢购</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
                </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.run();
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)">(Object succ : list){
                     String succuserifo </span>=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">succ</span><span style="color: rgba(128, 0, 0, 1)">"</span>+succ.toString() +<span style="color: rgba(0, 0, 0, 1)">userinfo ;
                     String succinfo</span>=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">用户:</span><span style="color: rgba(128, 0, 0, 1)">"</span> + succuserifo + <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">抢购成功,当前抢购成功人数:</span><span style="color: rgba(128, 0, 0, 1)">"</span>
                             + (<span style="color: rgba(128, 0, 128, 1)">1</span>-(valint-<span style="color: rgba(128, 0, 128, 1)">10</span><span style="color: rgba(0, 0, 0, 1)">));
                     System.</span><span style="color: rgba(0, 0, 255, 1)">out</span><span style="color: rgba(0, 0, 0, 1)">.println(succinfo);
                     </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 抢购成功业务逻辑 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
                     jedis.setnx(succuserifo, succinfo);
                }
            }
        } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                String failuserifo </span>=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">kcfail</span><span style="color: rgba(128, 0, 0, 1)">"</span> +<span style="color: rgba(0, 0, 0, 1)">  userinfo;
                String failinfo1</span>=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">用户:</span><span style="color: rgba(128, 0, 0, 1)">"</span> + failuserifo + <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">商品被抢购完毕,抢购失败</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
                System.</span><span style="color: rgba(0, 0, 255, 1)">out</span><span style="color: rgba(0, 0, 0, 1)">.println(failinfo1);
                jedis.setnx(failuserifo, failinfo1);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
        }

    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
        e.printStackTrace();
    } </span><span style="color: rgba(0, 0, 255, 1)">finally</span><span style="color: rgba(0, 0, 0, 1)"> {
        jedis.close();
    }
}

}

 

最后是效果

 

这段代码问题其实还是有的:就是没有按照顺来来抢购 

其实我觉得有种方法。就是将请求 存入 kafka 中 

然后取 kafka 中前面的数据 一直取到抢购的数量(用户不重复)

这样不就可以了吗,不需要考虑超卖问题啥的。纯属自己的感想。

 

后来看了一个用 rabbitMQ 做的 抢购

把请求插入 rabbitMQ 队列。然后 消费端订阅数据 来实现抢购。

2018-11-21 今天在 github 上看到一个秒杀的项目 还不错 分享给大家

https://github.com/hfbin/Seckill

这个里面有两个 分支,第二个分支是支持 rabbitmq 的。我觉得 做的还不完美。不过很有借鉴意义。