最近开了一些高并发的东西,以及一些秒杀系统,但感觉都没有完整的描述。于是自己就动手实现了一个简单版本的抢购系统。

本系统采用spring boot + mybatis + redis实现。

项目结构图如下:

spring boot + Mybatis + redis 秒杀系统spring boot + Mybatis + redis 秒杀系统

spring boot + Mybatis + redis 秒杀系统

项目工程已放到GitHub上了,https://github.com/feibabm/seckill,需要的请自行下载。

本文主要借鉴网上一些通用的做法,做出一个例子,主要实现了一个抢购接口:
http://localhost:8080/seckill/product/1?userId=1


seckill.sql文件为建表sql
pro_insert.sql文件为success_killed表中数据添加10000条用户预约记录
seckill_insert.sql文件为seckill生成一条产品信息


具体验证逻辑是执行test文件夹下的两个test类:
RemoteInvote.java
RemoteInvote2.java
这两个test类没有什么差别,主要是为了增加并发量

主要的抢票逻辑如下:

spring boot + Mybatis + redis 秒杀系统

 

 
  1. public SecKillResult secKillProduct(String userPhone, long productId) {

  2. String state = (String)redisTemplate.opsForValue().get(userPhone + "_"+ productId);

  3. //用户信息加载

  4. if(null == state){

  5. SuccessKilled successKilled = new SuccessKilled();

  6. successKilled.setSeckillId(productId);

  7. successKilled.setUserPhone(Long.valueOf(userPhone));

  8. successKilled = successKilledMapper.selectOne(successKilled);

  9. if(null == successKilled){

  10. return new SecKillResult(false, "该用户没有预约");

  11. }else{

  12. synchronized (this){

  13. state = (String)redisTemplate.opsForValue().get(userPhone + "_"+ productId);

  14. if(null == state){

  15. redisTemplate.opsForValue().set(userPhone + "_" + productId, successKilled.getState().toString(), 300, TimeUnit.SECONDS);

  16. state = String.valueOf(successKilled.getState());

  17. }

  18. }

  19.  
  20. }

  21. }

  22. if(state.equals("-1")){

  23. //查询产品信息

  24. // ProductInfo productInfo = (ProductInfo)redisTemplate.opsForValue().get(productId + "");

  25. List values = redisTemplate.opsForHash().values(productId + "");

  26. if(values.size() == 0){

  27. Seckill seckill = seckillMapper.selectByPrimaryKey(productId);

  28. if(null == seckill){

  29. return new SecKillResult(false, "没有该秒杀商品信息");

  30. }

  31. synchronized (this){

  32. if(!redisTemplate.opsForHash().hasKey(productId + "", "number")){

  33. // productInfo = new ProductInfo(seckill.getSeckillId(), seckill.getNumber(), seckill.getStartTime(), seckill.getEndTime());

  34. HashMap<String, String> productHash = new HashMap<>();

  35. productHash.put("number", seckill.getNumber() + "");

  36. productHash.put("startTime", seckill.getStartTime().getTime() + "");

  37. productHash.put("endTime", seckill.getEndTime().getTime() + "");

  38. redisTemplate.opsForHash().putAll(productId +"", productHash);

  39. redisTemplate.expire(productId + "", 300, TimeUnit.SECONDS);

  40. values = redisTemplate.opsForHash().values(productId + "");

  41. }

  42. }

  43. }

  44. if( new Date(Long.valueOf((String)values.get(1))).after(new Date(System.currentTimeMillis()))){

  45. return new SecKillResult(false, "抢购还没有开始");

  46. } else if(new Date(Long.valueOf((String)values.get(2))).before(new Date(System.currentTimeMillis()))){

  47. return new SecKillResult(false, "抢购已经结束");

  48. } else {

  49. Long userState = redisTemplate.opsForValue().increment(userPhone + "_" + productId, 1);

  50. if(userState == 0){

  51. // Long increment = redisTemplate.opsForValue().increment(productId, -1);

  52. Long number = redisTemplate.opsForHash().increment(productId + "", "number", -1);

  53. if(number >= 0){

  54. //消息队列异步更新库存,以及用户的预约信息

  55. QueueEntity queueEntity = new QueueEntity(userPhone, productId);

  56. ExecutorPool.queue.offer(queueEntity);

  57. }else {

  58. return new SecKillResult(false, "商品已经抢购完成");

  59. }

  60. }else {

  61. redisTemplate.opsForValue().increment(userPhone + "_" + productId, -1);

  62. return new SecKillResult(false, "您已抢购过该产品");

  63. }

  64. }

  65. } else {

  66. return new SecKillResult(false, "您已抢购过该产品");

  67. }

  68. return null;

  69. }

 

自己用两进程线程,大概两分钟掉了20000次,暂时没有出现啥问题。如果有什么问题,希望大家指出,谢谢。

相关文章: