国产精品一区二区久久久-99久久久精品免费看国产-久久九九99热这里只有精品-漂亮人妻被中出中文字幕久久-五月情综合网站久久-福利姬3d全彩办公室色欲-av网站在线播放网站-亚洲日本岛国动作片在线观看-男人的天堂啊啊啊啊

日志樣式

天津小程序開發(fā)中高并發(fā)高負載下的3種實戰(zhàn)場景解決方法.

  • 標簽 :

前言:

在實際開發(fā)項目中,產(chǎn)品一旦推廣開來,總能遇到一些小問題。比如某個接口突然就請求崩掉了,某個提交接口明明做了限制為什么就多出了好多重復(fù)的記錄。還有是某個記錄超過限制進行修改了,以下就以這幾個小問題總結(jié)一下平時采取的解決方法。

 

場景:

1. 緩存失效場景,就比如某個接口做了數(shù)據(jù)緩存,緩存過期導(dǎo)致突然某個時刻大量請求直接讀數(shù)據(jù)庫。解決方法設(shè)置redis緩存回調(diào)事件,訂閱失效頻道。所以這個也可以用來處理某些業(yè)務(wù)場景到期處理方式。

2. 接口冪等性場景,就比如注冊接口,通過手機號查詢是否存在記錄。但有時出現(xiàn)網(wǎng)絡(luò)延遲用戶連點等情況,會出現(xiàn)數(shù)據(jù)庫出現(xiàn)幾條一樣的用戶數(shù)據(jù)記錄。

3. 商品庫存超賣場景,比如某個活動商品下單,多個用戶同時下一個商品的訂單,從而導(dǎo)致庫存超賣的現(xiàn)象。解決方法可以使用樂觀鎖或者悲觀鎖解決此問題。

 

場景一,緩存失效回調(diào)。

1. 設(shè)置Redis回調(diào)事件方法。

(1). 打開Redis客戶終端,輸入命令

非持久性的回調(diào)事件設(shè)置

config set notify-keyspace-events Ex

(2). windows平臺打開Redis安裝目錄中找到"
redis.windows-service.conf",然后打開編輯找到notify-keyspace-events那一行,去掉"#",改為notify-keyspace-events “Ex"。

PHP高并發(fā)高負載下的3種實戰(zhàn)場景解決方法

 

(3). 其中Redis還可以設(shè)置訂閱鍵名的回調(diào),比如訂閱某個鍵名的del操作等,可以在conf中設(shè)置不同的,方法網(wǎng)上也有的。

2. 訂閱redis某個庫的鍵失效的頻道名,可以在命令測試,也可以通過PHP代碼訂閱然后cli環(huán)境下運行腳本。

命令:

subscribe __keyevent@0__:expired

3. 重新打開一個新的redis客戶終端輸入一個帶有效期的鍵值對,如下

(鍵名test_key_name, 時間30s, 值ceshi)命令:

setex test_key_name 30 ceshi

4. 查看鍵失效回調(diào)訂閱的命令窗口是否出現(xiàn)失效的鍵名。

PHP高并發(fā)高負載下的3種實戰(zhàn)場景解決方法

 

 

5. 代碼實現(xiàn)鍵名的失效事件訂閱。

<?php
	
//設(shè)置php腳本執(zhí)行時間

set_time_limit(0);

//設(shè)置socket連接超時時間

ini_set('default_socket_timeout', -1);


class redisSubscribe 
{
	protected $config = [
		"host" => "127.0.0.1",
		"password" => "6379"
	];
	
	protected $redis;
	
	public function __construct() 
	{
		
		try {
			
			$this->redis = new \Redis();
			$this->redis->pconnect($this->config['host'],$this->config['password']);
			
		} catch(\Exception $e) {
			echo "redis錯誤:".$e->getMessage().PHP_EOL;
		}
	}
	
	
	// 普通消息訂閱
	public function normal()
	{
		
		//聲明頻道名稱
		$channelName = "test";

		try {

			$this->redis->subscribe([$channelName], function ($redis, $channel, $msg)
			{

				echo 'channel:' . $channel . ',message:' . $msg . PHP_EOL;
				
				file_put_contents('subscribe.log',"\n-".$msg."-\n",FILE_APPEND);

			});

		} catch (\Exception $e) {
			echo $e->getMessage();
		}
	}
	
	
	// 訂閱Key失效事件的頻道
	public function keyNotify()
	{
		echo "wathc keyNotify start~~".PHP_EOL;
		
		// Key事件回調(diào)
		//$channel = "__keyevent@0__:expired";		// 0號庫的Key過期事件頻道名
		$channel = "__keyevent@*__:expired";		// 所有庫的Key過期事件頻道名
		
		try {

			$this->redis->subscribe([$channel], function ($redis, $channel, $msg)
			{
				echo 'channel:' . $channel . '===========' . ',message:' . $msg . PHP_EOL;
				
				file_put_contents('subscribe.log',"\n-".$msg."-\n",FILE_APPEND);

			});

		} catch (\Exception $e) {
			echo $e->getMessage();
		}
	}
}

(new redisSubscribe())->keyNotify();


?>

6. 通過PHP-cli運行該腳本,然后也可以setex一個短時間的鍵,然后查看命令是否輸出該失效的鍵名。

PHP高并發(fā)高負載下的3種實戰(zhàn)場景解決方法

 

7. 實際項目中的緩存失效的應(yīng)用就可以展開了。

(1). 代碼中設(shè)置的所有鍵名都配置到項目的全局配置文件中。

(2). 服務(wù)器中開一個守護進程(持續(xù)運行訂閱某個庫或者所有庫的鍵失效回調(diào)事件腳本)。

(3). 當該腳本有回調(diào)時,取出鍵名去全局緩存鍵名數(shù)組中匹配。

(4). 規(guī)則業(yè)務(wù)可以自行設(shè)計。

(5). 比如取出一個"cate5"的鍵名,則可以取資訊表中查詢分類ID為5的所有數(shù)據(jù)然后再進行緩存。

(6). 緩存失效事件還一個高端玩法,就是取代某些定時任務(wù)。比如可以將某個訂單作為鍵名緩存,當該鍵名失效就可以取出鍵名拿到ID去數(shù)據(jù)庫中將訂單狀態(tài)修改為失效。

 

場景二,接口冪等性。

接口重復(fù)數(shù)據(jù)也就是在高并發(fā)下的數(shù)據(jù)添加場景。最典型的是注冊接口,用戶在網(wǎng)絡(luò)延遲大或者信號不穩(wěn)定的情況下。并且同時大量用戶在進行注冊操作,用戶點擊了一次沒反應(yīng)然后再次點擊多個。

在沒有做冪等性處理只是拿到手機號查詢數(shù)據(jù)庫是否存在,用戶表又沒分庫分表,查詢緩慢,查詢出來后,多條并發(fā)的請求都繞過了手機號已經(jīng)存在的條件判斷,所以就出現(xiàn)了ID不同,但是其他字段一樣的記錄。

1. 對于高并發(fā)數(shù)據(jù)添加,可以使用Redis的setnx。

2. setnx是設(shè)置鍵并且在有效期內(nèi)有值時,再次對該鍵名進行重復(fù)賦值無法進行,會返回0。

PHP高并發(fā)高負載下的3種實戰(zhàn)場景解決方法

 

3. 可以代碼在對某些條件查詢是否存在時,可以將條件組成鍵名賦值。添加記錄時再次對鍵名重新賦值,返回null則表示已經(jīng)存在。

4. 以下代碼是項目中的一個測試方法,使用的redis是封裝的,借鑒需要修改。

/**
     * @Notes: 高并發(fā)防止重復(fù)提交(插入數(shù)據(jù)) 【保證接口的冪等性】
     * @Interface preventRepeatSubmit
     * @return mixed
     * @author: bqs
     * @Time: 2020/6/19 14:56
     */
    public function preventRepeatSubmit()
    {
        /* 比如查詢某條(什么條件)記錄是否存在,分布式鎖機制[redis的原子性setnx]
         * 1. 通過條件拼接為唯一的鍵名,將鍵名setnx設(shè)置一個30s有效期的值
         * 2. setnx設(shè)置鍵名不成功(返回0)表示已經(jīng)存在,接口則直接返回記錄已經(jīng)存在
         * 3. 根據(jù)該條件查詢數(shù)據(jù)庫記錄,如果存在,接口再返回記錄已經(jīng)存在
         * 【只要添加記錄前需要查詢什么是否存在則都需要考慮高并發(fā)情況,則通過此方案】
        */

        $redis = Redis::db(0);

        $no = date('YmdHis',time()).mt_rand(1000,9999);
        //$no = 202006191537447811;

        // 是否添加鎖表
        $addLock = false;

        if ($redis->setnx($no,1)) {
            $redis->expire($no,30);     //設(shè)置30s過期時間
        } else {
            $addLock = true;        // 訂單已經(jīng)存在則鎖住
        }

        // 數(shù)據(jù)庫查詢是否存在
        $isExist = Db::name('ztest')->where(['no'=>$no])->find();

        if ($isExist) {
            $addLock = true;
        }

        if (!$addLock) {
            $data = [
                "no" => $no,
                "tab_num" => 2,
                "stock" => 20,
                "create_time" => time()
            ];

            $res = Db::name('ztest')->insertGetId($data);
        }

        return "添加數(shù)據(jù)成功";
    }

場景三,庫存超賣。

庫存超賣是一個很常見的秒殺或者其他高并發(fā)場景下的數(shù)據(jù)更新問題。網(wǎng)絡(luò)上的解決方法也是多種多樣,對該問題延伸的數(shù)據(jù)庫樂觀鎖,悲觀鎖的知識點也是數(shù)不勝數(shù)。

所以,這里我也不再介紹數(shù)據(jù)庫的存儲引擎機制,事務(wù),表鎖等概念。直接以代碼展現(xiàn),以下是以樂觀鎖實現(xiàn)的數(shù)據(jù)庫更新問題。

1. 高并發(fā)下,對單條記錄的修改。一般修改前會對某字段進行判斷,但是并發(fā)情況下,拿查詢的結(jié)果進行攔截是極其的不靠譜。不過也可以對查詢進行加鎖,但是需要在同一事務(wù)中。

2. 庫存字段添加無符號的字段約束,所以再大的并發(fā)在修改為0之后也不會出現(xiàn)負數(shù)了,在修改的操作時捕捉修改為負數(shù)時的數(shù)據(jù)庫異常。

PHP高并發(fā)高負載下的3種實戰(zhàn)場景解決方法

 

3. 表中添加version字段,這個也是網(wǎng)上盛傳的樂觀鎖經(jīng)典實例了,后面的原理和流程我就不介紹了,代碼也是這樣寫的,所以直接貼代碼了。轉(zhuǎn)載至頭條號北橋蘇

天津市犀思科技有限公司是專業(yè)從事web應(yīng)用定制開發(fā)的一家公司,主營業(yè)務(wù)包括定制功能型網(wǎng)站建設(shè)開發(fā)、微信小程序開發(fā)、微信公眾號開發(fā)、APP定制開發(fā)、天津企業(yè)微信開發(fā)、ERP、CRM、OA等企業(yè)應(yīng)用場景信息化解決方案等服務(wù),致力于成為中國領(lǐng)先的IT服務(wù)及行業(yè)解決方案的提供商。


發(fā)表評論

電子郵件地址不會被公開。 必填項已用*標注

看不清?點擊更換