併發情況下Flask Session無法正確儲存資料
問題
在多個併發請求修改session資料的情況下, Flask Session無法成功儲存所有的session資料,不管是預設的Flask session模組還是使用redis作為儲存的Flask-Session庫。舉個例子: 伺服器程式碼如下:
import time from flask import Flask, session from flask_session import Session app = Flask(__name__) app.secret_key = "example" app.config["SESSION_TYPE"] = "redis" Session(app) @app.route("/set/<value>") def set_value(value): """Simulate long running task.""" time.sleep(1) session[value] = "done" return "ok\n" @app.route("/keys") def keys(): return str(session.keys()) + "\n"
測試程式碼如下:
# 建立cookie檔案 curl -c 'cookie' http://localhost:5007/keys # 使用上面建立的cookie檔案,發起三個併發請求。 curl -b 'cookie' http://localhost:5007/set/key1 && echo "done1" & curl -b 'cookie' http://localhost:5007/set/key2 && echo "done2" & curl -b 'cookie' http://localhost:5007/set/key3 && echo "done3" & wait # 獲取伺服器端Session資料 curl -b 'cookie' http://localhost:5007/keys
執行的結果如下:
$ sh test.sh dict_keys(['_permanent']) ok ok ok done3 done1 done2 dict_keys(['_permanent', 'key2']) $ sh test.sh dict_keys(['_permanent']) ok done3 ok ok done2 done1 dict_keys(['_permanent', 'key1'])
每次都是發起三個併發請求,但是都只儲存了最後一個請求的資料,理論上不應該是三個都儲存成功嗎?
分析
Cookie-based session不是執行緒安全的,所有的請求都是根據Cookie來操作Session裡的資料的。這不是Flask的問題,而是HTTP標準就這麼設計的。 這地方的主要問題是,三個併發請求使用的同樣的Cookie向伺服器發起請求,這個cookie只包含了_permanent這一個資料。伺服器端根據三個併發請求的cookie資料,三個請求的session值都是隻包含_permanent這一個資料,所以後來的請求就覆蓋了前面請求的session資料。 在實際的情況下,比如登入,是不會出現在同一個時間,發起多個併發請求的,所以一般情況下,這種設計沒有問題的。 在上面的問題中,也使用的Flask-Session這個庫,並且使用Redis作為資料儲存,但是當我們仔細查詢Flask-Session的程式碼會發現,Flask-Session這個庫沒有單獨存放每個session資料,而是把所有的session資料序列化成一個字串,然後存放在Redis裡面,而Redis的Key-Value結構是新資料覆蓋舊資料的,所以也有同樣的問題。如果採用其他的資料庫,比如Mysql,則不會有這樣的問題。但是具體的Session中介軟體邏輯,就得靠自己實現了。