19

我注意到当并行运行的请求修改 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'])
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'])

为什么请求完成后所有键都不存在?

4

1 回答 1

20

基于 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并删除任何不存在的键。

于 2018-10-18T15:02:18.997 回答