1. 程式人生 > >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類沒有什麼差別,主要是為了增加併發量

主要的搶票邏輯如下:

  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. if(state.equals("-1")){

  22. //查詢產品資訊

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

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

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

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

  27. if(null == seckill){

  28. return new SecKillResult(false, "沒有該秒殺商品資訊");

  29. }

  30. synchronized (this){

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

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

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

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

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

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

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

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

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

  40. }

  41. }

  42. }

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

  44. return new SecKillResult(false, "搶購還沒有開始");

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

  46. return new SecKillResult(false, "搶購已經結束");

  47. } else {

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

  49. if(userState == 0){

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

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

  52. if(number >= 0){

  53. //訊息佇列非同步更新庫存,以及使用者的預約資訊

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

  55. ExecutorPool.queue.offer(queueEntity);

  56. }else {

  57. return new SecKillResult(false, "商品已經搶購完成");

  58. }

  59. }else {

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

  61. return new SecKillResult(false, "您已搶購過該產品");

  62. }

  63. }

  64. } else {

  65. return new SecKillResult(false, "您已搶購過該產品");

  66. }

  67. return null;

  68. }

自己用兩程序執行緒,大概兩分鐘掉了20000次,暫時沒有出現啥問題。如果有什麼問題,希望大家指出,謝謝。