Flask 会话不会与并行请求一致地更新
Flask session doesn't update consistently with parallel requests
我注意到当请求 运行 并行修改 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 脚本演示了该问题。请注意,所有请求都已完成,但最终列表中只有一个密钥,并且在测试运行之间有所不同。
#!/bin/bash
# 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'])
行
行
行
完成3
完成1
完成2
dict_keys(['_permanent', <b>'key2'</b>])
$ sh test.sh
dict_keys(['_permanent'])
行
完成3
行
行
完成2
完成1
dict_keys(['_permanent', <b>'key1'</b>])
为什么在请求完成后不是所有的密钥都存在?
基于 Cookie 的会话不是线程安全的。任何给定的请求只能看到与其一起发送的会话 cookie,并且只有 returns 带有该请求修改的 cookie。这不是 Flask 特有的,它是 HTTP 请求的工作方式。
您同时发出三个请求。他们都读取仅包含 _permanent
键的初始 cookie,发送他们的请求,并获得使用他们的特定键设置 cookie 的响应。每个响应 cookie 将只有 _permanent
键和 key_keyN
键。无论哪个请求最后完成写入文件,覆盖以前的数据,所以你只剩下它的 cookie。
实际上这不是问题。会话并不是真的要存储在请求之间快速变化的数据,这就是数据库的用途。修改会话的事情,例如登录,不会与同一会话并行发生(并且无论如何都是幂等的)。
如果您真的很担心这一点,请使用服务器端会话将数据存储在数据库中。数据库擅长同步写入。
您已经在使用 Flask-Session 和 Redis,但深入研究 Flask-Session 实现会揭示您遇到此问题的原因。 Flask-Session 不单独存储每个会话密钥,它使用所有密钥写入单个序列化值。因此它遇到了与基于 cookie 的会话相同的问题:只有在该请求期间存在的内容才会被放回 Redis,从而覆盖并行发生的内容。
在这种情况下,最好编写自己的 SessionInterface
子类来单独存储每个密钥。您将覆盖 save_session
以设置 session
中的所有键并删除任何不存在的键。
我注意到当请求 运行 并行修改 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 脚本演示了该问题。请注意,所有请求都已完成,但最终列表中只有一个密钥,并且在测试运行之间有所不同。
#!/bin/bash
# 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'])
行
行
行
完成3
完成1
完成2
dict_keys(['_permanent', <b>'key2'</b>])
$ sh test.sh
dict_keys(['_permanent'])
行
完成3
行
行
完成2
完成1
dict_keys(['_permanent', <b>'key1'</b>])
为什么在请求完成后不是所有的密钥都存在?
基于 Cookie 的会话不是线程安全的。任何给定的请求只能看到与其一起发送的会话 cookie,并且只有 returns 带有该请求修改的 cookie。这不是 Flask 特有的,它是 HTTP 请求的工作方式。
您同时发出三个请求。他们都读取仅包含 _permanent
键的初始 cookie,发送他们的请求,并获得使用他们的特定键设置 cookie 的响应。每个响应 cookie 将只有 _permanent
键和 key_keyN
键。无论哪个请求最后完成写入文件,覆盖以前的数据,所以你只剩下它的 cookie。
实际上这不是问题。会话并不是真的要存储在请求之间快速变化的数据,这就是数据库的用途。修改会话的事情,例如登录,不会与同一会话并行发生(并且无论如何都是幂等的)。
如果您真的很担心这一点,请使用服务器端会话将数据存储在数据库中。数据库擅长同步写入。
您已经在使用 Flask-Session 和 Redis,但深入研究 Flask-Session 实现会揭示您遇到此问题的原因。 Flask-Session 不单独存储每个会话密钥,它使用所有密钥写入单个序列化值。因此它遇到了与基于 cookie 的会话相同的问题:只有在该请求期间存在的内容才会被放回 Redis,从而覆盖并行发生的内容。
在这种情况下,最好编写自己的 SessionInterface
子类来单独存储每个密钥。您将覆盖 save_session
以设置 session
中的所有键并删除任何不存在的键。