我注意到,当并行运行的请求修改Flask的session
时,只会记录一些键。 Flask的默认Cookie会话和使用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"
以下shell脚本演示了该问题。请注意,所有请求均已完成,但最终列表中仅存在一个密钥,并且在测试运行之间有所不同。
# set session
curl -c 'cookie' http://localhost:5007/keys
# run parallel
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
# get result
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'])
为什么请求完成后不是所有键都存在?
答案 0 :(得分:17)
基于Cookie的会话不是线程安全的。任何给定的请求都只会看到与它一起发送的会话cookie,并且仅返回具有该请求修改的cookie。这不是Flask特有的,它是HTTP请求的工作方式。
您并行发出三个请求。他们都读取仅包含_permanent
键的初始cookie,发送其请求,并获得一个响应,该响应使用其特定键设置cookie。每个响应cookie仅具有_permanent
键和key_keyN
键。无论哪个请求完成,最后一次写入文件,都会覆盖先前的数据,因此只剩下它的cookie。
实际上这不是问题。会话并不是要存储在请求之间快速变化的数据,这正是数据库的目的。修改会话的操作(例如登录)不会在同一会话中并行发生(并且始终是幂等的)。
如果您真的对此感到担心,请使用服务器端会话将数据存储在数据库中。数据库擅长同步写入。
您已经在使用Flask-Session和Redis,但是深入研究Flask-Session实现可以揭示您为什么会遇到此问题。 Flask-Session不会单独存储每个会话密钥,它会与所有密钥一起写入一个序列化值。因此,它遇到了与基于cookie的会话相同的问题:仅将请求中存在的内容放回Redis中,从而覆盖并行发生的事情。
在这种情况下,最好编写自己的SessionInterface
子类来分别存储每个密钥。您将覆盖save_session
来设置session
中的所有键,并删除所有不存在的键。