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 < <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 < 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 <= <span style="color: rgba(128, 0, 128, 1)">100</span> && valint>=<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><Object> 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 的。我觉得 做的还不完美。不过很有借鉴意义。