現在做過幾個web項目,都用到了api,有伺服器發往伺服器的api,也有微信上從前端發往後端的api。然後,我在使用 Pycharm 斷點時發現,如果在斷點暫停時一直發送請求,那麼很多請求都能執行成功。然後在不斷點的正常情況下,我又使用 Charles 重複發送請求,并發設定為10,一共發100個,也有10幾個請求成功了。
前端向後端發送請求
我知道csrf_token可以防止跨站攻擊,但如果現在是一個惡意使用者怎麼辦?我有一個每日簽到API,調用之後,在資料庫中添加對應記錄,并增加積分。我先在的校驗方法是,如果資料庫中存在一個今天簽到的記錄,就傳回錯誤。現在如果使用者進行請求重放,在資料庫寫入記錄之前發了N條記錄,那麼增加積分的操作就會執行N次了。
現在的思路是,看看微信的做法,它的參數中含有 timestamp 和 nonce,并進行了簽名加密。以及 如何防止别人抓包重播攻擊 的一些思路。
一、微信的做法(微信文檔位址)
- 驗證消息的确來自微信伺服器
通過 timestamp, nonce 以及雙方約定好的 token 構成一個簽名,能夠驗證資訊是否來自微信。
在這裡并不适用,因為api就是由使用者調用的。
二、 如何防止别人抓包重播攻擊
希望一個包就實作, 不是反複發包。
一個包實作并不僅僅指第一個包實作,可以是前n個失敗,然後第n+1個成功了,然後就拒絕之後的包了。如果需要達成這樣,我們要使能夠實作的請求唯一,建構一個類似id這樣,不能重複的東西。
三、 加鎖
類似雙十一搶購,當一個使用者下單之後,就鎖定5分鐘等待其付款,期間拒絕其他使用者的下單請求。我們能否做到,這個請求期間,針對一個 openid 的請求隻進行一次,其他就在後面排隊。當一個成功後,後面類似的請求就看做是失敗的。
有一個同僚做過類似的加鎖,明天問問他(沒問,現在在寫新需求以及調優,還沒做這個)。
問了一個同僚,建議使用 redis 的 sadd 指令,如果訂單号存在于 set 中,就傳回失敗。還有 redis 的分布式鎖,他不建議使用,如果沒有弄清楚分布式鎖的原理。
現在回到我們最初的問題:
如何保證使用者每天隻能簽到一次?
擷取簽到請求後,通過唯一值去 redis 中查找,如果存在,則說明簽到過了,傳回 "今日已簽到"
不存在,則說明今天還沒有簽到,進行簽到流程。
将上面的步驟拆開:
1. 根據使用者的簽到請求建構唯一值
2. 去 redis 中查詢唯一值
3.1 已簽到,拒絕
3.2 未簽到,設定 redis,進行簽到流程
問:為什麼要儲存在 redis 而不是 mysql 中?
- 因為儲存到 mysql 中太慢了,
在儲存到硬碟(mysql)的過程中,這個值是不存在的,若這時候使用者再次發送簽到請求,那麼還是可以簽到的。這就違反了"使用者每天隻能簽到一次"
問:如何将資料儲存到 mysql 中?
儲存到 redis 後,使用異步任務将這個資料同步到 mysql。
問:如何确儲存到 redis 就足夠快?
不確定。我本地測試的時候沒發現能夠多次簽到。
儲存到 redis 的速度大于兩次請求的速度就能夠確定。
問:鎖是什麼?這個與鎖有什麼異同?
問:如何查找相關資料?
直接搜尋後,找不到很多資料。那麼我們就把問題誇大或縮小。
誇大:
搜尋`秒殺`
秒殺系統架構分析與實戰
問:能否寫一個函數\裝飾器\上下文管理器實作這個功能?
問:有沒有相關的第三方庫?
伺服器向伺服器發送api
一般的做法是使用AES加密待發送的内容,再使用RSA加密AES秘鑰,然後将以上兩個一起發送出去。令我感到疑惑的是:為什麼需要使用RSA加密AES秘鑰并傳輸,一開始兩邊約定好一個AES秘鑰不就可以了嗎?
是以我現在的做法就是,兩邊約定好AES KEY,然後使用AES加解密内容,并對解密後的内容進行校驗。
在一個抽獎功能中又遇到了庫存的問題
需求:存在 N 種不同的抽獎獎品,抽獎的機率與各獎品的實時數量成正比。希望把所有的獎品發放完畢,又不希望超賣。獎品庫存總量變為 0 後,固定傳回一種獎品。
比如有3中商品,數量為 A:10, B: 20 C: 70。 則開始時抽中 A 的機率為 (10)/(10+20+70) = 10%,
抽中 A 後, 數量變為 A: 9, B: 20 C: 70,則 A 的機率變為 9 / (9 + 20 + 70)
應用場景:現場抽獎。
綜合我的需求與實作難度,決定使用 django transaction + select_for_update
發現的問題:
select_for_update 隻鎖了行,能讀不能寫,會造成超賣;
解決
使用MySQL表鎖
LOCK TABLE business_bankcard READ;
LOCK TABLE business_bankcard WRITE;
# 其他代碼
UNLOCK TABLES;
上面的寫法有問題
LOCK TABLES table_name WRITE;
# 其他代碼
UNLOCK TABLES
因為 WRITE 的含義就是不可讀、不可寫
Option | Description |
---|---|
READ | Read lock, no writes allowed |
READ LOCAL | Read lock, but allow concurrent inserts |
WRITE | Exclusive write lock. No other connections can read or write to this table |
LOW_PRIORITY WRITE | Exclusive write lock, but allow new read locks on the table until we get the write lock. |
WRITE CONCURRENT | Exclusive write lock, but allow READ LOCAL locks to the table. |
出處:LOCK TABLES and UNLOCK TABLES
鎖表的注意事項
注意解鎖
注意異常之後的解鎖
比如捕捉異常後,解鎖,再抛出異常
更好的方法
同僚建議使用分布式鎖
redis 單機鎖
參考:
如何解決秒殺的性能問題和超賣的讨論
select for update 帶來的性能問題
測試
如何測試。設定庫存數量後,使用 timesleep 模拟并發的現象
參考:
(未看,待整合)Web大規模高并發請求和搶購的解決方案
(從上面這篇文章中可以了解到,要吸收強者的經驗,秒殺與搶購還有比12306和淘寶更多的嗎?看看他們是怎麼實作的吧!)
日請求從百萬到八億的技術曆程
(規模性的東西還是大公司牛叉)
程式員對比在大公司和創業公司的工作和報酬
如果你關心你做某件事後産生的實際效果,在大公司絕對是有更大的實際效果,歸因于大公司的規模。如果我是在一家創業公司做我目前的工作,獲得的收益大約是每月 1 萬美元。我沒什麼好蔑視,但這都支付不起我的工資。但是同樣的事情在大公司創造的收益會是1萬美元的1000倍以上。在大公司有更大的實際效果因為它的規模很大。這裡的推論是小公司很小以至于他們很容易對自身造成影響,盡管這個影響值本身很小。我感覺不到我做的事情對大公司會産生促進還是阻礙的作用。但是當我在小公司時,看起來我們所做的事情可以影響整個公司的命運。