1. 程式人生 > >基於redis的高併發秒殺的JAVA-DEMO實現!

基於redis的高併發秒殺的JAVA-DEMO實現!

轉自:http://www.cnblogs.com/longtaosoft/p/6627568.html

 

基於redis的高併發秒殺的JAVA-DEMO實現!

      在Redis的事務中,WATCH命令可用於提供CAS(check-and-set)功能。假設我們通過WATCH命令在事務執行之前監控了多個Keys,倘若在WATCH之後有任何Key的值發生了變化,EXEC命令執行的事務都將被放棄,同時返回Null multi-bulk應答以通知呼叫者事務執行失敗。例如,我們再次假設Redis中並未提供incr命令來完成鍵值的原子性遞增,如果要實現該功能,我們只能自行編寫相應的程式碼。其偽碼如下:
      val = GET mykey
      val = val + 1
      SET mykey $val
      以上程式碼只有在單連線的情況下才可以保證執行結果是正確的,因為如果在同一時刻有多個客戶端在同時執行該段程式碼,那麼就會出現多執行緒程式中經常出現的一種錯誤場景--競態爭用(race condition)。比如,客戶端A和B都在同一時刻讀取了mykey的原有值,假設該值為10,此後兩個客戶端又均將該值加一後set回Redis伺服器,這樣就會導致mykey的結果為11,而不是我們認為的12。為了解決類似的問題,我們需要藉助WATCH命令的幫助,見如下程式碼:
      WATCH mykey
      val = GET mykey
      val = val + 1
      MULTI
      SET mykey $val
      EXEC
      和此前程式碼不同的是,新程式碼在獲取mykey的值之前先通過WATCH命令監控了該鍵,此後又將set命令包圍在事務中,這樣就可以有效的保證每個連線在執行EXEC之前,如果當前連接獲取的mykey的值被其它連線的客戶端修改,那麼當前連線的EXEC命令將執行失敗。這樣呼叫者在判斷返回值後就可以獲悉val是否被重新設定成功。

      根據這樣的思路,我們在JAVA下進行實現:

  新建一個專案,首先引入JAVA的redis操作庫:Jedis,這裡用的是jedis-2.9.0.jar

     新建一個類:MyRedistest.class做執行緒操作

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

package com.myredistest;

import java.util.Random;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

 

import redis.clients.jedis.Jedis;

 

/**

 * redis

 *

 * @author 10255_000

 *

 */

 

public class MyRedistest {

     

public static void main(String[] args) {

            final String watchkeys = "watchkeys";

            ExecutorService executor = Executors.newFixedThreadPool(20);  //20個執行緒池併發數

 

            final Jedis jedis = new Jedis("192.168.56.101"6379);

            jedis.set(watchkeys, "100");//設定起始的搶購數

           // jedis.del("setsucc", "setfail");

            jedis.close();

             

            for (int i = 0; i < 1000; i++) {//設定1000個人來發起搶購

                executor.execute(new MyRunnable("user"+getRandomString(6)));

            }

            executor.shutdown();

        }

 

      

     public static String getRandomString(int length) { //length是隨機字串長度

            String base = "abcdefghijklmnopqrstuvwxyz0123456789";  

            Random random = new Random();  

            StringBuffer sb = new StringBuffer();  

            for (int i = 0; i < length; i++) {  

                int number = random.nextInt(base.length());  

                sb.append(base.charAt(number));  

            }  

            return sb.toString();  

         

}

   建一個類:MyRunnable.class 實現Runnable做執行緒操作:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

package com.myredistest;

 

import java.util.List;

 

import redis.clients.jedis.Jedis;

import redis.clients.jedis.Transaction;

 

public class MyRunnable implements Runnable {

 

    String watchkeys = "watchkeys";// 監視keys

    Jedis jedis = new Jedis("192.168.56.101"6379);

    String userinfo;

    public MyRunnable() {

    }

    public MyRunnable(String uinfo) {

        this.userinfo=uinfo;

    }

    @Override

    public void run() {

        try {

            jedis.watch(watchkeys);// watchkeys

 

            String val = jedis.get(watchkeys);

            int valint = Integer.valueOf(val);

            

            if (valint <= 100 && valint>=1) {

            

                 Transaction tx = jedis.multi();// 開啟事務

               // tx.incr("watchkeys");

                tx.incrBy("watchkeys", -1);

 

                List<Object> list = tx.exec();// 提交事務,如果此時watchkeys被改動了,則返回null

                 

                if (list == null ||list.size()==0) {

 

                    String failuserifo = "fail"+userinfo;

                    String failinfo="使用者:" + failuserifo + "商品爭搶失敗,搶購失敗";

                    System.out.println(failinfo);

                    /* 搶購失敗業務邏輯 */

                    jedis.setnx(failuserifo, failinfo);

                else {

                    for(Object succ : list){

                         String succuserifo ="succ"+succ.toString() +userinfo ;

                         String succinfo="使用者:" + succuserifo + "搶購成功,當前搶購成功人數:"

                                 + (1-(valint-100));

                         System.out.println(succinfo);

                         /* 搶購成功業務邏輯 */

                         jedis.setnx(succuserifo, succinfo);

                    }

                     

                }

 

            else {

                String failuserifo ="kcfail" +  userinfo;

                String failinfo1="使用者:" + failuserifo + "商品被搶購完畢,搶購失敗";

                System.out.println(failinfo1);

                jedis.setnx(failuserifo, failinfo1);

                // Thread.sleep(500);

                return;

            }

 

        catch (Exception e) {

            e.printStackTrace();

        finally {

            jedis.close();

        }

 

    }

     

   

}

  執行MyRedistest ,檢視redis中插入的key值