في هذه المقالة، سأقوم بشرح تطوير تطبيق ويب قائم على Flask الذي يتفاعل مع قاعدة بيانات SQL Server لتحليل بيانات السكان. يسمح التطبيق للمستخدمين باستعلام نطاقات السكان، واسترجاع المقاطعات حسب الولاية، واسترداد الولايات ضمن نطاقات السكان المحددة. كما سأناقش كيفية دمج Redis لتخزين نتائج الاستعلام بغرض تحسين الأداء.
لماذا Flask، SQL Server، وRedis؟
Flask هو إطار عمل ويب بسيط ومرن مكتوب بلغة Python مثالي لبناء تطبيقات ويب صغيرة إلى متوسطة الحجم. يوفر الأدوات اللازمة لإنشاء واجهات برمجة تطبيقات RESTful، وتقديم قوالب HTML ديناميكية، والتفاعل مع قواعد البيانات. من ناحية أخرى، SQL Server هو نظام إدارة قواعد بيانات علاقية قوي يُستخدم على نطاق واسع في تطبيقات الشركات. يسمح دمج Flask مع SQL Server لنا ببناء تطبيق قوي لتحليل البيانات والتصوير.
لتعزيز الأداء بشكل أكبر، سندمج Redis، مخزن بيانات في الذاكرة، لتخزين نتائج الاستعلام المستخدمة بشكل متكرر. يقلل هذا من العبء على قاعدة البيانات ويسرع زمن الاستجابة للاستعلامات المتكررة.
نظرة عامة على التطبيق
يقوم تطبيق Flask الخاص بنا بأداء المهام التالية:
- نطاقات تعداد السكان. يمكن للمستخدمين تحديد عام ونطاق سكاني للحصول على عدد الولايات التي تقع ضمن تلك النطاقات.
- احصل على المقاطعات حسب الولاية. يمكن للمستخدمين إدخال رمز ولاية لاسترداد قائمة بالمقاطعات.
- استرداد الولايات حسب نطاق السكان. يمكن للمستخدمين تحديد نطاق سكاني وعام للحصول على قائمة بالولايات ضمن تلك النطاق.
- ملاحظة. للقيام بالاختبار، لا تتردد في إنشاء بنية البيانات الخاصة بك في قاعدة البيانات وإدراج بيانات عينية حسب الحاجة استنادًا إلى واجهات برمجة التطبيقات المشتركة التالية باستخدام استعلامات 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 وتتضمن معلمات للتشفير والمهلة.
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