現在做過幾個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 單機鎖
參考:
測試
如何測試。設置庫存數量后,使用 timesleep 模擬並發的現象
參考:
(未看,待整合)Web大規模高並發請求和搶購的解決方案
(從上面這篇文章中可以了解到,要吸收強者的經驗,秒殺與搶購還有比12306和淘寶更多的嗎?看看他們是怎么實現的吧!)
(規模性的東西還是大公司牛叉)
如果你關心你做某件事后產生的實際效果,在大公司絕對是有更大的實際效果,歸因於大公司的規模。如果我是在一家創業公司做我當前的工作,獲得的收益大約是每月 1 萬美元。我沒什么好蔑視,但這都支付不起我的工資。但是同樣的事情在大公司創造的收益會是1萬美元的1000倍以上。在大公司有更大的實際效果因為它的規模很大。這里的推論是小公司很小以至於他們很容易對自身造成影響,盡管這個影響值本身很小。我感覺不到我做的事情對大公司會產生促進還是阻礙的作用。但是當我在小公司時,看起來我們所做的事情可以影響整個公司的命運。