במאמר זה, אני אספק הולך דרך פיתוח של אפליקציית אינטרנט מבוססת Flask שמתקשרת עם מסד נתונים של שרת SQL כדי לנתח נתוני אוכלוסייה. האפליקציה מאפשרת למשתמשים לשאול על טווחי אוכלוסייה, לאחזר מחוזות לפי מדינה, ולאחזר מדינות בטווחי אוכלוסייה ספציפיים. אני גם אדבר על איך לשלב את Redis עבור מטמון תוצאות שאילתות לשפר ביצועים.
למה Flask, SQL Server, ו-Redis?
Flask הוא סביר וגמיש של Python המתאים מאוד לבניית אפליקציות אינטרנט קטנות ובינוניות. זה מספק את הכלים הנחוצים ליצירת RESTful APIs, להציג תבניות HTML דינמיות, ולהתקשר עם מסדי נתונים. מצד שני, SQL Server הוא מערכת ניהול מסד נתונים רצינית ויציבה (RDBMS) הנפוצה המשמשת באפליקציות עסקיות. לשלב את Flask עם SQL Server מאפשר לנו לבנות אפליקציה עצמאית לניתוח נתונים ותצוגה.
כדי לשפר עוד יותר את הביצועים, נשלב את Redis, חנות נתונים בזיכרון, למטמון תוצאות שאילתות שנגישות בתדירות. זה מפחית את העומס על מסד הנתונים ומאיץ את זמני התגובה לשאילתות חוזרות.
סקירת האפליקציה
אפליקציית Flask שלנו מבצעת את המשימות הבאות:
- טווחי אוכלוסייה שאילתה. משתמשים יכולים לציין שנה וטווח אוכלוסייה כדי לקבל ספירות של מדינות הנפלות בטווחים אלה.
- משיכת מחוזות לפי מדינה. משתמשים יכולים להזין קוד מדינה כדי לקבל רשימת מחוזות.
- אחזור מדינות לפי טווח אוכלוסייה. משתמשים יכולים לציין טווח אוכלוסייה ושנה כדי לקבל רשימת מדינות בטווח זה.
- הערה. לבדיקה, ניתן ליצור סכימה משלך בבסיס הנתונים ולהכניס נתוני דוגמה כפי שנדרש על סמך ה- APIים הבאים ששיתפו בשימוש בשאילתות SQL. כמו כן, העמודים ב-HTML שמשמשים כאן יכולים להיות עיצוב טבלה בסיסי שמשך את הנתונים המוחזרים מקוד ה- Flask של האפליקציה ומציגים את התוצאות.
בואו נצפה בפרטי היישום.
הגדרת אפליקציית Flask
1. דרישות מוקדמות
לפני התחלת העבודה, ודא שיש לך את התוכנות הבאות מותקנות דרך פקודות משורש הטרמינל שלך (פקודות שגילוי עם MacOS):
- Python 3.x
- Flask (
pip install flask
) - SQLAlchemy (
pip install sqlalchemy
) - PyODBC (
pip install pyodbc
) - Redis (
pip install redis
)
2. חיבור לבסיס הנתונים
אנו משתמשים ב-SQLAlchemy כדי להתחבר לבסיס הנתונים של שרת SQL. כך ניתן להגדיר את החיבור:
from sqlalchemy import create_engine
import urllib
# SQL Server connection string
params = urllib.parse.quote_plus(
"Driver={ODBC Driver 17 for SQL Server};"
"Server=tcp:username.database.windows.net,1433;"
"Database=population;"
"Uid=user@username;"
"Pwd={azure@123};"
"Encrypt=yes;"
"TrustServerCertificate=no;"
"Connection Timeout=30;"
)
engine = create_engine("mssql+pyodbc:///?odbc_connect=%s" % params)
מחרוזת החיבור הזו משתמשת בנהג ODBC עבור SQL Server וכוללת פרמטרים עבור הצפנה וזמן קצבה.
3. הגדרות Redis
Redis משמש למטמון תוצאות שאילתות. הנה איך להגדיר את חיבור ה-Redis:
import redis
# Redis connection
redis_client = redis.StrictRedis(
host='username.redis.cache.windows.net',
port=6380,
db=0,
password='encryptedpasswordstring',
ssl=True
)
4. מימוש המסלולים של היישום
מסלול דף הבית
מסלול דף הבית מציג את הדף הראשי של היישום:
route('/') .
def index():
return render_template('index.html')
שאילתת טווח אוכלוסייה עם מטמון Redis
מסלול זה מטפל בשאילתות עבור טווחי אוכלוסייה. תחילה הוא בודק האם התוצאה מאוחסנת ב-Redis. אם לא, הוא מבצע שאילתה לבסיס הנתונים ומאחסן את התוצאה לשימוש עתידי:
route('/population-range', methods=['GET', 'POST']) .
def population_range():
if request.method == 'POST': # input params defined for this api
year = request.form['yr1']
range1_start = request.form['r1']
range1_end = request.form['r2']
range2_start = request.form['r3']
range2_end = request.form['r4']
range3_start = request.form['r5']
range3_end = request.form['r6']
# Map year to column name
year_map = {
'2010': 'ten',
'2011': 'eleven',
'2012': 'twelve',
'2013': 'thirteen',
'2014': 'fourteen',
'2015': 'fifteen',
'2016': 'sixteen',
'2017': 'seventeen',
'2018': 'eighteen'
}
year_column = year_map.get(year, 'ten') # Default to 'ten' if year not found
# Build cache key
cache_key = f"population_range_{year_column}_{range1_start}_{range1_end}_{range2_start}_{range2_end}_{range3_start}_{range3_end}"
# Check if result is cached
cached_result = redis_client.get(cache_key)
if cached_result:
result = eval(cached_result) # Deserialize cached result
time_taken = 0 # No database query, so time taken is negligible
cache_status = "Cache Hit"
else:
# Build SQL query
query = f"""
SELECT
SUM(CASE WHEN {year_column} BETWEEN '{range1_start}' AND '{range1_end}' THEN 1 ELSE 0 END) AS range1_count,
SUM(CASE WHEN {year_column} BETWEEN '{range2_start}' AND '{range2_end}' THEN 1 ELSE 0 END) AS range2_count,
SUM(CASE WHEN {year_column} BETWEEN '{range3_start}' AND '{range3_end}' THEN 1 ELSE 0 END) AS range3_count
FROM popul
"""
print(query) # For debugging
# Execute query and measure time
start_time = time()
result = engine.execute(query).fetchall()
end_time = time()
time_taken = end_time - start_time
cache_status = "Cache Miss"
# Cache the result
redis_client.set(cache_key, str(result), ex=3600) # Cache for 1 hour
return render_template('display.html', data1=result, t1=time_taken, cache_status=cache_status)
return render_template('index.html')
אחזור מחוזות לפי קוד מדינה עם מטמון Redis
מסלול זה מחזיר מחוזות עבור קוד מדינה נתון. גם הוא משתמש ב-Redis כדי לאחסן את התוצאות:
route('/counties-by-state', methods=['GET', 'POST']) .
def counties_by_state():
if request.method == 'POST':
state_code = request.form['state_code']
# Build cache key
cache_key = f"counties_by_state_{state_code}"
# Check if result is cached
cached_result = redis_client.get(cache_key)
if cached_result:
result = eval(cached_result) # Deserialize cached result
time_taken = 0 # No database query, so time taken is negligible
cache_status = "Cache Hit"
else:
# Build SQL query
query = f"""
SELECT county
FROM dbo.county
WHERE state = (SELECT state FROM codes WHERE code = '{state_code}')
"""
print(query) # For debugging
# Execute query and measure time
start_time = time()
result = engine.execute(query).fetchall()
end_time = time()
time_taken = end_time - start_time
cache_status = "Cache Miss"
# Cache the result
redis_client.set(cache_key, str(result), ex=3600) # Cache for 1 hour
return render_template('counties.html', data=result, time_taken=time_taken, cache_status=cache_status)
return render_template('index.html')
אחזור מדינות לפי טווח אוכלוסייה עם מטמון Redis
מסלול זה משיג מדינות בתוך טווח אוכלוסייה מסוים ומאחסן את התוצאות:
route('/states-by-population', methods=['GET', 'POST']) .
def states_by_population():
if request.method == 'POST':
year = request.form['year']
population_start = request.form['population_start']
population_end = request.form['population_end']
# Map year to column name
year_map = {
'2010': 'ten',
'2011': 'eleven',
'2012': 'twelve',
'2013': 'thirteen',
'2014': 'fourteen',
'2015': 'fifteen',
'2016': 'sixteen',
'2017': 'seventeen',
'2018': 'eighteen'
}
year_column = year_map.get(year, 'ten') # Default to 'ten' if year not found
# Build cache key
cache_key = f"states_by_population_{year_column}_{population_start}_{population_end}"
# Check if result is cached
cached_result = redis_client.get(cache_key)
if cached_result:
result = eval(cached_result) # Deserialize cached result
time_taken = 0 # No database query, so time taken is negligible
cache_status = "Cache Hit"
else:
# Build SQL query
query = f"""
SELECT state
FROM popul
WHERE {year_column} BETWEEN '{population_start}' AND '{population_end}'
"""
print(query) # For debugging
# Execute query and measure time
start_time = time()
result = engine.execute(query).fetchall()
end_time = time()
time_taken = end_time - start_time
cache_status = "Cache Miss"
# Cache the result
redis_client.set(cache_key, str(result), ex=3600) # Cache for 1 hour
return render_template('states.html', data=result, time_taken=time_taken, cache_status=cache_status)
return render_template('index.html')
השוואת ביצועים: SQL Server נגד Redis
Query Type | Redis Fetch Time | SQL Execution Time |
---|---|---|
שאילתת טווח אוכלוסייה (מאוחסנת) | 0.002 שניות | 0.000 שניות |
שאילתת טווח אוכלוסייה (חדשה) | 0.002 שניות | 1.342 שניות |
מסקנה מרכזית: Redis מפחית את זמן הביצוע מ-~1.3 שניות ל-~0.002 שניות, מה שהופך את השאילתות ל-650 פעמים מהירות יותר!
כיצד Redis משפר ביצועים
Redis הוא חנות נתונים בזיכרון המהווה שכבת מטמון בין האפליקציה ומסד הנתונים. הנה כיצד זה עובד באפליקציה שלנו:
- מפתח המטמון. מפתח ייחודי נוצר עבור כל שאילתה בהתבסס על הפרמטרים שלה.
- בדיקת המטמון. לפני ביצוע שאילתת מסד נתונים, האפליקציה בודקת האם התוצאה כבר מטמנת ב-Redis.
- פגיעה במטמון. אם התוצאה נמצאת ב-Redis, היא מוחזרת מיד, ממנעת שאילתת מסד נתונים.
- פספוס מטמון. אם התוצאה לא נמצאת, השאילתה נבצעת, והתוצאה מוטמנת ב-Redis לשימוש עתידי.
- פסק זמן מטמון. תוצאות המטמון מוגדרות לפגות לאחר פרק זמן מסוים (לדוגמה, שעה אחת) כדי להבטיח רעננות של הנתונים.
על ידי מטימון של תוצאות שאילתות שגיאה, Redis מפחית באופן משמעותי את העומס על מסד הנתונים ומשפר את זמני התגובה עבור שאילתות חוזרות.
מסקנה
במאמר זה, בנינו אפליקציית Flask שמתקשרת עם מסד נתונים של SQL Server כדי לנתח נתוני אוכלוסייה. שלבנו את Redis למטמן את תוצאות השאילתות, משפרים ביצועים ומפחיתים את העומס על מסד הנתונים. על ידי עקיפת שיטות הפעולה הטובות, ניתן להרחיב את האפליקציה זו לטיפול בשאילות מורכבות יותר ולקנות אותה לשימוש בייצור.
קישור: קוד המקור של האפליקציה המלאה ניתן למצוא ב-GitHub.
Source:
https://dzone.com/articles/build-data-analytics-platform-flask-sql-redis