Các biến toàn cục có chuỗi an toàn trong flask không? Làm cách nào để chia sẻ dữ liệu giữa các yêu cầu?


96

Trong ứng dụng của tôi, trạng thái của một đối tượng chung được thay đổi bằng cách đưa ra yêu cầu và phản hồi phụ thuộc vào trạng thái.

class SomeObj():
    def __init__(self, param):
        self.param = param
    def query(self):
        self.param += 1
        return self.param

global_obj = SomeObj(0)

@app.route('/')
def home():
    flash(global_obj.query())
    render_template('index.html')

Nếu tôi chạy điều này trên máy chủ phát triển của mình, tôi mong đợi nhận được 1, 2, 3, v.v. Nếu các yêu cầu được thực hiện đồng thời từ 100 khách hàng khác nhau, có thể xảy ra sự cố không? Kết quả mong đợi sẽ là 100 khách hàng khác nhau, mỗi khách hàng nhìn thấy một số duy nhất từ ​​1 đến 100. Hoặc điều tương tự sẽ xảy ra:

  1. Khách hàng 1 truy vấn. self.paramđược tăng thêm 1.
  2. Trước khi câu lệnh return có thể được thực thi, luồng chuyển sang máy khách 2. self.paramđược tăng trở lại.
  3. Chuỗi chuyển trở lại máy khách 1 và máy khách được trả lại là số 2, chẳng hạn.
  4. Bây giờ luồng di chuyển đến khách hàng 2 và trả về khách hàng số 3.

Vì chỉ có hai khách hàng, kết quả mong đợi là 1 và 2, không phải 2 và 3. Một số đã bị bỏ qua.

Điều này có thực sự xảy ra khi tôi mở rộng ứng dụng của mình không? Tôi nên xem những lựa chọn thay thế nào cho một biến toàn cục?

Câu trả lời:


91

Bạn không thể sử dụng các biến toàn cục để chứa loại dữ liệu này. Nó không chỉ không an toàn cho luồng mà còn không an toàn cho quy trình và các máy chủ WSGI trong sản xuất tạo ra nhiều quy trình. Số lượng của bạn không chỉ sai nếu bạn đang sử dụng các chuỗi để xử lý yêu cầu, chúng cũng sẽ khác nhau tùy thuộc vào quá trình xử lý yêu cầu.

Sử dụng nguồn dữ liệu bên ngoài Flask để lưu giữ dữ liệu chung. Cơ sở dữ liệu, memcached hoặc redis đều là các vùng lưu trữ riêng biệt thích hợp, tùy thuộc vào nhu cầu của bạn. Nếu bạn cần tải và truy cập dữ liệu Python, hãy cân nhắc multiprocessing.Manager. Bạn cũng có thể sử dụng phiên cho dữ liệu đơn giản cho mỗi người dùng.


Máy chủ phát triển có thể chạy trong một luồng và quy trình. Bạn sẽ không thấy hành vi mình mô tả vì mỗi yêu cầu sẽ được xử lý đồng bộ. Kích hoạt các chuỗi hoặc quy trình và bạn sẽ thấy nó. app.run(threaded=True)hoặc app.run(processes=10). (Trong 1.0, máy chủ được phân luồng theo mặc định.)


Một số máy chủ WSGI có thể hỗ trợ gevent hoặc một công nhân không đồng bộ khác. Các biến toàn cục vẫn không an toàn cho chuỗi vì vẫn không có biện pháp bảo vệ chống lại hầu hết các điều kiện chủng tộc. Bạn vẫn có thể có một tình huống trong đó một công nhân nhận được một giá trị, tạo ra, một công nhân khác sửa đổi nó, cho ra, sau đó công nhân đầu tiên cũng sửa đổi nó.


Nếu bạn cần lưu trữ một số dữ liệu chung trong một yêu cầu, bạn có thể sử dụng gđối tượng của Flask . Một trường hợp phổ biến khác là một số đối tượng cấp cao nhất quản lý các kết nối cơ sở dữ liệu. Sự khác biệt đối với loại "toàn cầu" này là nó là duy nhất cho mỗi yêu cầu, không được sử dụng giữa các yêu cầu và có thứ gì đó quản lý việc thiết lập và chia nhỏ tài nguyên.


25

Đây không thực sự là một câu trả lời cho sự an toàn của chuỗi hình cầu.

Nhưng tôi nghĩ điều quan trọng là phải đề cập đến các phiên ở đây. Bạn đang tìm cách lưu trữ dữ liệu dành riêng cho khách hàng. Mọi kết nối phải có quyền truy cập vào nhóm dữ liệu của riêng nó, theo cách an toàn luồng.

Điều này có thể thực hiện được với các phiên phía máy chủ và chúng có sẵn trong một plugin bình rất gọn gàng: https://pythonhosted.org/Flask-Session/

Nếu bạn thiết lập phiên, một sessionbiến có sẵn trong tất cả các tuyến của bạn và nó hoạt động như một từ điển. Dữ liệu được lưu trữ trong từ điển này là riêng lẻ cho từng máy khách kết nối.

Đây là một bản demo ngắn:

from flask import Flask, session
from flask_session import Session

app = Flask(__name__)
# Check Configuration section for more details
SESSION_TYPE = 'filesystem'
app.config.from_object(__name__)
Session(app)

@app.route('/')
def reset():
    session["counter"]=0

    return "counter was reset"

@app.route('/inc')
def routeA():
    if not "counter" in session:
        session["counter"]=0

    session["counter"]+=1

    return "counter is {}".format(session["counter"])

@app.route('/dec')
def routeB():
    if not "counter" in session:
        session["counter"] = 0

    session["counter"] -= 1

    return "counter is {}".format(session["counter"])


if __name__ == '__main__':
    app.run()

Sau đó pip install Flask-Session, bạn sẽ có thể chạy nó. Hãy thử truy cập nó từ các trình duyệt khác nhau, bạn sẽ thấy rằng bộ đếm không được chia sẻ giữa chúng.


2

Mặc dù hoàn toàn chấp nhận các câu trả lời được ủng hộ ở trên và không khuyến khích sử dụng toàn cầu để sản xuất và lưu trữ bình có thể mở rộng, cho mục đích tạo mẫu hoặc máy chủ thực sự đơn giản, chạy dưới 'máy chủ phát triển' của bình ...

... các kiểu dữ liệu tích hợp trong python và cá nhân tôi đã sử dụng và thử nghiệm toàn cầu dict, theo tài liệu python ( https://docs.python.org/3/glossary.html#term-global-interpreter-lock ) là chủ đề an toàn. Không quá trình an toàn.

Việc chèn, tra cứu và đọc từ dict (máy chủ toàn cầu) như vậy sẽ ổn từ mỗi phiên hoạt động (có thể đồng thời) chạy dưới máy chủ phát triển.

Khi lệnh chung như vậy được khóa bằng khóa phiên bình duy nhất, nó có thể khá hữu ích cho việc lưu trữ phía máy chủ dữ liệu cụ thể của phiên nếu không sẽ không phù hợp với cookie (kích thước tối đa 4k).

Tất nhiên, máy chủ toàn cầu dict như vậy nên được bảo vệ cẩn thận vì phát triển quá lớn, nằm trong bộ nhớ. Một số loại cặp khóa / giá trị 'cũ' sắp hết hạn có thể được mã hóa trong quá trình xử lý yêu cầu.

Một lần nữa, không được khuyến nghị cho các triển khai sản xuất hoặc có thể mở rộng, nhưng có thể tốt cho các máy chủ hướng tác vụ cục bộ nơi db riêng biệt quá nhiều cho tác vụ đã cho

...

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.