鏈接暴露
打開谷歌的開發者模式,查看網頁代碼,請求時就會有接口地址
按鈕置灰
在開放前按鈕一直置灰,URL 不會暴露,定期更換地址
URL 動態化
@RequestMapping(value = "/{goodsId}/getUrl")//獲取動態 md5
@RequestMapping(value = "/{seckillGoodsId}/{md5}/execution")//執行操作,驗證 md5
服務單一職責
單獨建立一個數據庫,訂單服務對應訂單庫,秒殺是秒殺庫。
單一職責的好處就是就算秒殺沒抗住,秒殺庫崩了,服務掛了,也不會影響到其他的服務。(強行高可用)
資源靜態化
秒殺一般都是特定的商品還有頁面模板(前后端分離)與 cdn 加速
庫存預熱
提前把商品的庫存加載到 Redis,流程都在 Redis 里面去做,等秒殺結束再異步的去修改庫存
采用主從,正常情況沒問題,但是高并發的就會同時讀到剩余量
Lua
廣泛作為其它語言的嵌入腳本(互聯網游戲等),尤其是 C/C++,語法簡單、小巧
簡單語法
局部變量效率更高,redis 使用 EVAL 命令來直接執行指定的 Lua 腳本
nil空boolean布爾值number數字string字符串table表:lua 專有數據結構,既是數組又類似 HashMap
原子執行
Lua 腳本在 Redis 中是以原子方式執行的,在 Redis 服務器執行EVAL命令時,在命令執行完畢并向調用者返回結果之前,只會執行當前命令指定的 Lua 腳本包含的所有邏輯,其它客戶端發送的命令將被阻塞,直到EVAL命令執行完畢為止
分布式鎖實現方案(使用 lua 腳本)
Redission 是 Redis 官方推薦的客戶端,提供了一個 RLock 的鎖,RLock 繼承自 juc 的 Lock 接口,提供了中斷,超時,嘗試獲取鎖等操作,支持可重入,互斥等特性
加鎖
為了實現加鎖的原子性,Redisson 使用 Lua 腳本的形式進行加鎖
if (redis.call('exists', KEYS[1]) == 0) then
redis.call('hset', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
return redis.call('pttl', KEYS[1]);
Lease 續約
如果鎖設置了持有鎖的超時時間,在超時后會進行鎖的釋放,如果獲取鎖的時候不指定持有鎖的時間,那么默認獲取鎖 30s 后超時。為了防止任務沒有執行完就釋放鎖,Redisson 使用一個守護線程(看門狗任務)定時刷新這個鎖超時時間進行續約,也就是只要這個鎖被獲取了,則力保這個鎖一直不超時,除非獲取鎖的線程主動釋放。由于獲取到鎖和這個續命任務的守護線程是在同一個線程的,當獲取鎖的線程掛掉了,意味著刷新任務的線程也會停止執行,就不會再刷新鎖的超時時間
解鎖
解鎖同樣使用 Lua 腳本執行
if (redis.call('exists', KEYS[1]) == 0) then
redis.call('publish', KEYS[2], ARGV[1]);
return 1;
end;
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
return nil;
end;
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
if (counter > 0) then
redis.call('pexpire', KEYS[1], ARGV[2]);
return 0;
else
redis.call('del', KEYS[1]);
redis.call('publish', KEYS[2], ARGV[1]);
return 1;
end;
return nil;
首先判斷鎖是否是存在的,如果不存在直接返回 nil。 如果該線程持有鎖,則對當前的重入值 -1,如果計算完后大于 0,重新設置超時持有實踐返回 0; 如果算完不大于0,刪除這個 Hash,并且進行廣播,通知 watch dog 停止進行刷新,并且返回 1