ตัวแปรโกลบอลเธรดปลอดภัยใน Flask หรือไม่ ฉันจะแบ่งปันข้อมูลระหว่างคำขอได้อย่างไร


101

ในแอปพลิเคชันของฉันสถานะของวัตถุทั่วไปจะเปลี่ยนไปโดยการร้องขอและการตอบสนองขึ้นอยู่กับสถานะ

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')

หากฉันเรียกใช้สิ่งนี้บนเซิร์ฟเวอร์การพัฒนาของฉันฉันคาดว่าจะได้รับ 1, 2, 3 และอื่น ๆ หากมีการร้องขอจากลูกค้าที่แตกต่างกัน 100 รายพร้อมกันอาจเกิดข้อผิดพลาดหรือไม่? ผลลัพธ์ที่คาดหวังคือลูกค้าที่แตกต่างกัน 100 รายแต่ละรายจะเห็นหมายเลขที่ไม่ซ้ำกันตั้งแต่ 1 ถึง 100 หรือจะเกิดเหตุการณ์เช่นนี้:

  1. แบบสอบถามลูกค้า 1 self.paramเพิ่มขึ้นทีละ 1
  2. ก่อนที่จะดำเนินการคำสั่ง return เธรดจะสลับไปที่ไคลเอนต์ 2 self.paramจะเพิ่มขึ้นอีกครั้ง
  3. เธรดจะเปลี่ยนกลับไปที่ไคลเอนต์ 1 และไคลเอ็นต์จะส่งคืนหมายเลข 2 เช่น
  4. ตอนนี้เธรดย้ายไปที่ไคลเอนต์ 2 และส่งคืนหมายเลข 3 ให้เขา / เธอ

เนื่องจากมีลูกค้าเพียงสองรายผลลัพธ์ที่คาดหวังคือ 1 และ 2 ไม่ใช่ 2 และ 3 จึงถูกข้ามไป

สิ่งนี้จะเกิดขึ้นจริงหรือไม่เมื่อฉันขยายแอปพลิเคชันของฉัน ฉันควรพิจารณาทางเลือกใดบ้างสำหรับตัวแปรส่วนกลาง

คำตอบ:


98

คุณไม่สามารถใช้ตัวแปรส่วนกลางเพื่อเก็บข้อมูลประเภทนี้ได้ ไม่เพียง แต่เธรดจะไม่ปลอดภัยเท่านั้น แต่ยังไม่ปลอดภัยในกระบวนการและเซิร์ฟเวอร์ WSGI ในการผลิตจะวางไข่หลายกระบวนการ ไม่เพียง แต่การนับของคุณจะผิดหากคุณใช้เธรดเพื่อจัดการกับคำขอเท่านั้น แต่ยังแตกต่างกันไปขึ้นอยู่กับกระบวนการที่จัดการคำขอ

ใช้แหล่งข้อมูลภายนอก Flask เพื่อเก็บข้อมูลส่วนกลาง ฐานข้อมูล memcached หรือ redis ล้วนเป็นพื้นที่จัดเก็บแยกต่างหากที่เหมาะสมขึ้นอยู่กับความต้องการของคุณ multiprocessing.Managerหากคุณจำเป็นต้องโหลดและการเข้าถึงข้อมูลหลามพิจารณา คุณยังสามารถใช้เซสชันสำหรับข้อมูลธรรมดาที่เป็นข้อมูลต่อผู้ใช้


เซิร์ฟเวอร์การพัฒนาอาจทำงานในเธรดเดียวและกระบวนการ คุณจะไม่เห็นพฤติกรรมที่คุณอธิบายเนื่องจากแต่ละคำขอจะได้รับการจัดการพร้อมกัน เปิดใช้งานเธรดหรือกระบวนการแล้วคุณจะเห็น app.run(threaded=True)หรือapp.run(processes=10). (ใน 1.0 เซิร์ฟเวอร์จะถูกเธรดโดยค่าเริ่มต้น)


เซิร์ฟเวอร์ WSGI บางตัวอาจรองรับ gevent หรือ async worker อื่น ตัวแปรส่วนกลางยังไม่ปลอดภัยต่อเธรดเนื่องจากยังไม่มีการป้องกันสภาพการแข่งขันส่วนใหญ่ คุณยังสามารถมีสถานการณ์สมมติที่ผู้ปฏิบัติงานคนหนึ่งได้รับค่าผลตอบแทนอีกคนหนึ่งแก้ไขให้ผลตอบแทนจากนั้นผู้ปฏิบัติงานคนแรกก็แก้ไขได้เช่นกัน


หากคุณต้องการในการจัดเก็บข้อมูลทั่วโลกบางส่วนในระหว่างการร้องขอคุณอาจใช้ขวดของวัตถุg อีกกรณีหนึ่งที่พบบ่อยคืออ็อบเจ็กต์ระดับบนสุดที่จัดการการเชื่อมต่อฐานข้อมูล ความแตกต่างสำหรับ "ทั่วโลก" ประเภทนี้คือคำขอแต่ละรายการไม่ซ้ำกันไม่ได้ใช้ระหว่างคำขอและมีบางอย่างที่จัดการการตั้งค่าและการแยกทรัพยากร


30

นี่ไม่ใช่คำตอบสำหรับความปลอดภัยของเธรดอย่างแท้จริง

แต่ฉันคิดว่าการพูดถึงเซสชันที่นี่เป็นสิ่งสำคัญ คุณกำลังมองหาวิธีจัดเก็บข้อมูลเฉพาะลูกค้า การเชื่อมต่อทุกครั้งควรเข้าถึงกลุ่มข้อมูลของตัวเองด้วยวิธีที่ปลอดภัย

สิ่งนี้เป็นไปได้กับเซสชันฝั่งเซิร์ฟเวอร์และมีอยู่ในปลั๊กอินขวดที่เรียบร้อยมาก: https://pythonhosted.org/Flask-Session/

หากคุณตั้งค่าเซสชันsessionตัวแปรจะพร้อมใช้งานในทุกเส้นทางของคุณและจะทำงานเหมือนพจนานุกรม ข้อมูลที่จัดเก็บในพจนานุกรมนี้เป็นข้อมูลส่วนบุคคลสำหรับไคลเอ็นต์ที่เชื่อมต่อแต่ละเครื่อง

นี่คือการสาธิตสั้น ๆ :

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()

หลังจากนั้นpip install Flask-Sessionคุณควรจะเรียกใช้สิ่งนี้ได้ ลองเข้าถึงจากเบราว์เซอร์ต่างๆคุณจะเห็นว่าตัวนับไม่ได้ใช้ร่วมกันระหว่างพวกเขา


3

ในขณะที่ยอมรับคำตอบที่ได้รับการโหวตก่อนหน้านี้โดยสิ้นเชิงและไม่สนับสนุนการใช้ตัวแปรทั่วโลกสำหรับการผลิตและการจัดเก็บ Flask ที่ปรับขนาดได้เพื่อจุดประสงค์ในการสร้างต้นแบบหรือเซิร์ฟเวอร์ที่เรียบง่ายจริงๆซึ่งทำงานภายใต้ 'เซิร์ฟเวอร์การพัฒนา' ของขวด ...

...

ธ ในตัวชนิดข้อมูลและฉันใช้ส่วนตัวและผ่านการทดสอบทั่วโลกdict, ตามเอกสารหลามเป็นด้ายปลอดภัย ไม่ใช่กระบวนการปลอดภัยใน

การแทรกการค้นหาและการอ่านจากคำสั่ง (เซิร์ฟเวอร์ทั่วโลก) ดังกล่าวจะตกลงจากเซสชัน Flask แต่ละเซสชัน (อาจพร้อมกัน) ที่ทำงานภายใต้เซิร์ฟเวอร์การพัฒนา

เมื่อป้อนคำสั่งทั่วโลกด้วยคีย์เซสชัน Flask ที่ไม่ซ้ำกันมันจะมีประโยชน์มากสำหรับการจัดเก็บข้อมูลเฉพาะเซสชันทางฝั่งเซิร์ฟเวอร์มิฉะนั้นจะไม่พอดีกับคุกกี้ (ขนาดสูงสุด 4 kB)

แน่นอนเซิร์ฟเวอร์ทั่วโลกควรได้รับการปกป้องอย่างรอบคอบเนื่องจากมีขนาดใหญ่เกินไปซึ่งอยู่ในหน่วยความจำ คุณสามารถเข้ารหัสคู่คีย์ / ค่า 'เก่า' บางประเภทได้ในระหว่างการประมวลผลคำขอ

อีกครั้งไม่แนะนำสำหรับการใช้งานจริงหรือการปรับใช้ที่ปรับขนาดได้ แต่อาจเป็นไปได้สำหรับเซิร์ฟเวอร์ที่มุ่งเน้นงานในพื้นที่ซึ่งฐานข้อมูลแยกต่างหากมีมากเกินไปสำหรับงานที่กำหนด

...

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.