redis技術的使用:
redis真的是一個很好的技術,它可以很好的在一定程度上解決網站一瞬間的并發量,例如商品搶購秒殺等活動。。。
redis之所以能解決高并發的原因是它可以直接訪問內存,而以往我們用的是數據庫(硬盤),提高了訪問效率,解決了數據庫服務器壓力。
為什么redis的地位越來越高,我們為何不選擇memcache,這是因為memcache只能存儲字符串,而redis存儲類型很豐富(例如有字符串、LIST、SET等),memcache每個值最大只能存儲1M,存儲資源非常有限,十分消耗內存資源,而redis可以存儲1G,最重要的是memcache它不如redis安全,當服務器發生故障或者意外關機等情況時,redsi會把內存中的數據備份到硬盤中,而memcache所存儲的東西全部丟失;這也說明了memcache不適合做數據庫來用,可以用來做緩存。
引言
這里我們主要利用Redis的setnx的命令來處理高并發。
setnx 有兩個參數。第一個參數表示鍵。第二個參數表示值。如果當前鍵不存在,那么會插入當前鍵,將第二個參數做為值。返回 1。如果當前鍵存在,那么會返回0。
創建庫存表
1
2
3
4
5
|
CREATE TABLE `storage` ( `id` int (11) unsigned NOT NULL AUTO_INCREMENT, `number` int (11) DEFAULT NULL , PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1 |
設置初始庫存為10
創建訂單表
1
2
3
4
5
|
CREATE TABLE ` order ` ( `id` int (11) unsigned NOT NULL AUTO_INCREMENT, `number` int (11) DEFAULT NULL , PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1 |
測試不用鎖的時候
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
$pdo = new PDO( 'mysql:host=127.0.0.1;dbname=test' , 'root' , 'root' ); $sql= "select `number` from storage where id=1 limit 1" ; $res = $pdo->query($sql)-> fetch (); $number = $res[ 'number' ]; if($number>0) { $sql = "insert into `order` VALUES (null,$number)" ; $order_id = $pdo->query($sql); if($order_id) { $sql= "update storage set `number`=`number`-1 WHERE id=1" ; $pdo->query($sql); } } |
ab測試模擬并發,發現庫存是正確的。
1
2
3
4
5
6
7
|
mysql> select * from storage; + ----+--------+ | id | number | + ----+--------+ | 1 | 0 | + ----+--------+ 1 row in set (0.00 sec) |
在來看訂單表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
mysql> select * from ` order `; + ----+--------+ | id | number | + ----+--------+ | 1 | 10 | | 2 | 10 | | 3 | 9 | | 4 | 7 | | 5 | 6 | | 6 | 5 | | 7 | 5 | | 8 | 5 | | 9 | 4 | | 10 | 1 | + ----+--------+ 10 rows in set (0.00 sec) |
發現存在幾個訂單都是操作的同一個庫存數據,這樣就可能引起超賣的情況。
修改代碼加入redis鎖進行數據控制
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
<?php /** * Created by PhpStorm. * User: daisc * Date: 2018/7/23 * Time: 14:45 */ class Lock { private static $_instance ; private $_redis ; private function __construct() { $this ->_redis = new Redis(); $this ->_redis ->connect( '127.0.0.1' ); } public static function getInstance() { if (self:: $_instance instanceof self) { return self:: $_instance ; } return self:: $_instance = new self(); } /** * @function 加鎖 * @param $key 鎖名稱 * @param $expTime 過期時間 */ public function set( $key , $expTime ) { //初步加鎖 $isLock = $this ->_redis->setnx( $key ,time()+ $expTime ); if ( $isLock ) { return true; } else { //加鎖失敗的情況下。判斷鎖是否已經存在,如果鎖存在切已經過期,那么刪除鎖。進行重新加鎖 $val = $this ->_redis->get( $key ); if ( $val && $val <time()) { $this ->del( $key ); } return $this ->_redis->setnx( $key ,time()+ $expTime ); } } /** * @param $key 解鎖 */ public function del( $key ) { $this ->_redis->del( $key ); } } $pdo = new PDO( 'mysql:host=127.0.0.1;dbname=test' , 'root' , 'root' ); $lockObj = Lock::getInstance(); //判斷是能加鎖成功 if ( $lock = $lockObj ->set( 'storage' ,10)) { $sql = "select `number` from storage where id=1 limit 1" ; $res = $pdo ->query( $sql )->fetch(); $number = $res [ 'number' ]; if ( $number >0) { $sql = "insert into `order` VALUES (null,$number)" ; $order_id = $pdo ->query( $sql ); if ( $order_id ) { $sql = "update storage set `number`=`number`-1 WHERE id=1" ; $pdo ->query( $sql ); } } //解鎖 $lockObj ->del( 'storage' ); } else { //加鎖不成功執行其他操作。 } |
再次進行ab測試,查看測試結果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
mysql> select * from ` order `; + ----+--------+ | id | number | + ----+--------+ | 1 | 10 | | 2 | 9 | | 3 | 8 | | 4 | 7 | | 5 | 6 | | 6 | 5 | | 7 | 4 | | 8 | 3 | | 9 | 2 | | 10 | 1 | + ----+--------+ 10 rows in set (0.00 sec) |
發現訂單表沒有操作同一個庫存數據的情況。所以利用redis鎖是可以有效的處理高并發的。
這里在加鎖的時候其實是可以不需要判斷過期時間的,這里我們為了避免造成死鎖,所以加一個過期時間的判斷。當過期的時候主動刪除該鎖。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://segmentfault.com/a/1190000016251978