Bagaimana untuk menyelesaikan masalah konkurensi tinggi (jualan kilat produk) dalam PHP? Artikel berikut akan berkongsi dengan anda dua penyelesaian (berdasarkan mysql atau berdasarkan Redis), saya harap ia akan membantu anda.
Jualan kilat akan menghasilkan konkurensi tinggi serta-merta Penggunaan pangkalan data akan meningkatkan tekanan akses pangkalan data dan mengurangkan kelajuan akses, jadi kita harus menggunakan caching untuk mengurangkan akses pangkalan data . Tekanan;
Dapat dilihat bahawa operasi di sini berbeza daripada pesanan asal: jualan kilat pra-pesanan yang dihasilkan tidak akan ditulis ke pangkalan data dengan serta-merta, tetapi akan ditulis ke cache terlebih dahulu pengguna membayar dengan jayanya, status akan diubah suai, ditulis ke pangkalan data.
Andaikan num ialah medan yang disimpan dalam pangkalan data, yang menyimpan baki kuantiti produk jualan kilat.
if($num > 0){ //用户抢购成功,记录用户信息 $num--; }
Andaikan bahawa dalam senario dengan konkurensi yang tinggi, apabila nilai num dalam pangkalan data ialah 1, berbilang proses boleh membaca bahawa num ialah 1 pada masa yang sama, dan program menentukan bahawa ia memenuhi syarat Jika snap-up berjaya, jumlah akan dikurangkan satu.
Ini akan menyebabkan penghantaran berlebihan produk Pada asalnya hanya terdapat 10 produk yang boleh diambil, tetapi lebih daripada 10 orang mungkin akan mengambilnya pada masa ini, jumlahnya akan menjadi negatif selepas tergesa-gesa selesai .
Terdapat banyak penyelesaian untuk masalah ini, yang boleh dibahagikan kepada penyelesaian berdasarkan mysql dan redis Prestasi redis adalah disebabkan oleh mysql, jadi ia boleh membawa konkurensi yang lebih tinggi, tetapi penyelesaian yang diperkenalkan di bawah adalah semuanya. berdasarkan Untuk satu mysql dan redis, konkurensi yang lebih tinggi memerlukan penyelesaian yang diedarkan, yang tidak diliputi dalam artikel ini.
Barang jadual produk
CREATE TABLE `goods` ( `id` int(11) NOT NULL, `num` int(11) DEFAULT NULL, `version` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
Log jadual hasil pembelian
CREATE TABLE `log` ( `id` int(11) NOT NULL AUTO_INCREMENT, `good_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
①Pesimis kunci
Skema kunci pesimis menggunakan bacaan eksklusif iaitu hanya satu proses boleh membaca nilai num pada masa yang sama. Selepas transaksi dilakukan atau ditarik balik, kunci akan dilepaskan dan proses lain boleh membacanya.
Penyelesaian ini adalah yang paling mudah dan paling mudah difahami Anda boleh menggunakan penyelesaian ini secara langsung apabila keperluan prestasi tidak tinggi. Perlu diingat bahawa SELECT … FOR UPDATE
gunakan indeks sebanyak mungkin untuk mengunci sebanyak mungkin baris;
kunci eksklusif hanya dikeluarkan selepas pelaksanaan transaksi selesai, bukan selepas bacaan selesai transaksi yang digunakan harus dilakukan atau ditarik balik seawal mungkin untuk melepaskan kunci eksklusif lebih awal.
$this->mysqli->begin_transaction(); $result = $this->mysqli->query("SELECT num FROM goods WHERE id=1 LIMIT 1 FOR UPDATE"); $row = $result->fetch_assoc(); $num = intval($row['num']); if($num > 0){ usleep(100); $this->mysqli->query("UPDATE goods SET num=num-1"); $affected_rows = $this->mysqli->affected_rows; if($affected_rows == 1){ $this->mysqli->query("INSERT INTO log(good_id) VALUES({$num})"); $affected_rows = $this->mysqli->affected_rows; if($affected_rows == 1){ $this->mysqli->commit(); echo "success:".$num; }else{ $this->mysqli->rollback(); echo "fail1:".$num; } }else{ $this->mysqli->rollback(); echo "fail2:".$num; } }else{ $this->mysqli->commit(); echo "fail3:".$num; }
②Kunci optimistik
Skim penguncian optimistik tidak menambah kunci eksklusif semasa membaca data, tetapi melepasi per- Setiap kemas kini secara automatik akan menambah medan versi untuk menyelesaikan masalah berbilang proses membaca nombor yang sama dan kemudian mengemas kini dengan jayanya. Apabila setiap proses membaca num, ia juga membaca nilai versi Apabila mengemas kini num, ia juga mengemas kini versi, dan menambah pertimbangan kesetaraan pada versi semasa mengemas kini.
Andaikan bahawa 10 proses telah membaca bahawa nilai num ialah 1 dan nilai versi ialah 9. Kemudian kenyataan kemas kini yang dilaksanakan oleh 10 proses ini adalah kesemuanya UPDATE goods SET num=num-1,version=version 1 WHERE version=9
,
Walau bagaimanapun , apabila Selepas satu proses dilaksanakan dengan jayanya, nilai versi dalam pangkalan data akan menjadi 10, dan baki 9 proses tidak akan dilaksanakan dengan jayanya Ini memastikan bahawa produk tidak akan terlalu dihantar dan nilai num tidak akan kurang daripada 0, tetapi ini juga membawa kepada Satu masalah ialah pengguna yang membuat permintaan pembelian awal mungkin tidak dapat merebutnya, tetapi akan ditangkap oleh permintaan kemudian.
$result = $this->mysqli->query("SELECT num,version FROM goods WHERE id=1 LIMIT 1"); $row = $result->fetch_assoc(); $num = intval($row['num']); $version = intval($row['version']); if($num > 0){ usleep(100); $this->mysqli->begin_transaction(); $this->mysqli->query("UPDATE goods SET num=num-1,version=version+1 WHERE version={$version}"); $affected_rows = $this->mysqli->affected_rows; if($affected_rows == 1){ $this->mysqli->query("INSERT INTO log(good_id) VALUES({$num})"); $affected_rows = $this->mysqli->affected_rows; if($affected_rows == 1){ $this->mysqli->commit(); echo "success:".$num; }else{ $this->mysqli->rollback(); echo "fail1:".$num; } }else{ $this->mysqli->rollback(); echo "fail2:".$num; } }else{ echo "fail3:".$num; }
③di mana keadaan (operasi atom)
Skim penguncian pesimis memastikan bahawa nilai num dalam pangkalan data hanya boleh digunakan pada masa yang sama Satu proses membaca dan memproses, iaitu, proses bacaan serentak mesti digilir dan dilaksanakan secara berurutan di sini.
Skim penguncian optimistik Walaupun nilai num boleh dibaca oleh berbilang proses pada masa yang sama, pertimbangan kesetaraan versi dalam operasi kemas kini boleh memastikan bahawa hanya satu kemas kini operasi kemas kini serentak boleh berjaya pada masa yang sama masa.
Terdapat penyelesaian yang lebih mudah, iaitu hanya menambah sekatan bersyarat num>0 semasa operasi kemas kini. Walaupun penyelesaian yang dihadkan oleh keadaan di mana kelihatan serupa dengan penyelesaian penguncian yang optimistik dan boleh menghalang berlakunya masalah pengeluaran berlebihan, prestasi masih sangat berbeza apabila bilangannya besar.
Andaikan nombor ialah 10 pada masa ini, dan 5 proses dibaca num=10 pada masa yang sama Untuk skema penguncian optimistik, disebabkan pertimbangan nilai yang sama bagi medan versi, hanya satu daripada 5 proses yang akan dikemas kini dengan jayanya. Ini Selepas pelaksanaan lima proses selesai, nombor ialah 9
Untuk skim penghakiman syarat, selagi num>0 berjaya dikemas kini, nombornya ialah 5 selepas pelaksanaan daripada lima proses selesai.
$result = $this->mysqli->query("SELECT num FROM goods WHERE id=1 LIMIT 1"); $row = $result->fetch_assoc(); $num = intval($row['num']); if($num > 0){ usleep(100); $this->mysqli->begin_transaction(); $this->mysqli->query("UPDATE goods SET num=num-1 WHERE num>0"); $affected_rows = $this->mysqli->affected_rows; if($affected_rows == 1){ $this->mysqli->query("INSERT INTO log(good_id) VALUES({$num})"); $affected_rows = $this->mysqli->affected_rows; if($affected_rows == 1){ $this->mysqli->commit(); echo "success:".$num; }else{ $this->mysqli->rollback(); echo "fail1:".$num; } }else{ $this->mysqli->rollback(); echo "fail2:".$num; } }else{ echo "fail3:".$num; }
①Penyelesaian penguncian optimistik berasaskan jam tangan
Jam tangan digunakan untuk memantau satu (atau lebih) kekunci Jika kekunci ini (atau ini) ditukar oleh arahan lain sebelum urus niaga dilaksanakan, urus niaga akan terganggu.
Penyelesaian ini serupa dengan penyelesaian penguncian optimistik dalam mysql, dan prestasi khusus adalah sama.
$num = $this->redis->get('num'); if($num > 0) { $this->redis->watch('num'); usleep(100); $res = $this->redis->multi()->decr('num')->lPush('result',$num)->exec(); if($res == false){ echo "fail1"; }else{ echo "success:".$num; } }else{ echo "fail2"; }
② Skim baris gilir berasaskan senarai
基于队列的方案利用了redis出队操作的原子性,抢购开始之前首先将商品编号放入响应的队列中,在抢购时依次从队列中弹出操作,这样可以保证每个商品只能被一个进程获取并操作,不存在超发的情况。
该方案的优点是理解和实现起来都比较简单,缺点是当商品数量较多是,需要将大量的数据存入到队列中,并且不同的商品需要存入到不同的消息队列中。
public function init(){ $this->redis->del('goods'); for($i=1;$i<=10;$i++){ $this->redis->lPush('goods',$i); } $this->redis->del('result'); echo 'init done'; } public function run(){ $goods_id = $this->redis->rPop('goods'); usleep(100); if($goods_id == false) { echo "fail1"; }else{ $res = $this->redis->lPush('result',$goods_id); if($res == false){ echo "writelog:".$goods_id; }else{ echo "success".$goods_id; } } }
③基于decr返回值的方案
如果我们将剩余量num设置为一个键值类型,每次先get之后判断,然后再decr是不能解决超发问题的。
但是redis中的decr操作会返回执行后的结果,可以解决超发问题。我们首先get到num的值进行第一步判断,避免每次都去更新num的值,然后再对num执行decr操作,并判断decr的返回值,如果返回值不小于0,这说明decr之前是大于0的,用户抢购成功。
public function run(){ $num = $this->redis->get('num'); if($num > 0) { usleep(100); $retNum = $this->redis->decr('num'); if($retNum >= 0){ $res = $this->redis->lPush('result',$retNum); if($res == false){ echo "writeLog:".$retNum; }else{ echo "success:".$retNum; } }else{ echo "fail1"; } }else{ echo "fail2"; } }
④基于setnx的排它锁方案
redis没有像mysql中的排它锁,但是可以通过一些方式实现排它锁的功能,就类似php使用文件锁实现排它锁一样。
setnx实现了exists和set两个指令的功能,若给定的key已存在,则setnx不做任何动作,返回0;若key不存在,则执行类似set的操作,返回1。
我们设置一个超时时间timeout,每隔一定时间尝试setnx操作,如果设置成功就是获得了相应的锁,执行num的decr操作,操作完成删除相应的key,模拟释放锁的操作。
public function run(){ do { $res = $this->redis->setnx("numKey",1); $this->timeout -= 100; usleep(100); }while($res == 0 && $this->timeout>0); if($res == 0){ echo 'fail1'; }else{ $num = $this->redis->get('num'); if($num > 0) { $this->redis->decr('num'); usleep(100); $res = $this->redis->lPush('result',$num); if($res == false){ echo "fail2"; }else{ echo "success:".$num; } }else{ echo "fail3"; } $this->redis->del("numKey"); } }
推荐学习:《PHP视频教程》
Atas ialah kandungan terperinci Bagaimana untuk menyelesaikan masalah konkurensi tinggi (jualan kilat produk) dalam PHP? Dua penyelesaian dikongsi. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!