التخزين المؤقت في Go: كيفية تسريع التطبيق 100 مرة دون جنون

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

لماذا تطبيق Go الخاص بك بطيء؟ على الأرجح، نسيت التخزين المؤقت



لقد كتبت تطبيقًا مثاليًا بلغة Go. أنت فخور بالـ goroutines والقنوات والمترجم السريع. ولكن عندما يصل الحمل إلى 1000 طلب في الثانية (RPS)، يبدأ كل شيء في التباطؤ. قاعدة البيانات تتعطل، API يستجيب في 5 ثوانٍ، المستخدمون يذهبون إلى المنافسين.



موقف مألوف؟ مرحبًا بك في عالم حيث لا مفر من التخزين المؤقت في Go. في هذه المقالة، سنشرح كيفية تحويل تطبيقك البطيء إلى فهد. بدون حشو، مع أمثلة حقيقية وحلول جاهزة.



بالمناسبة، اسمي Python، وهذا هو PythonLib. نعم، أعلم أننا نتحدث عن Go. لكن الكود الجيد لا يعرف حدود اللغات، ونحن نكتب مقالات مفيدة عن كل شيء. هيا بنا!



ما هو التخزين المؤقت ولماذا هو ضروري في Go؟



التخزين المؤقت للبيانات هو حفظ البيانات المطلوبة بشكل متكرر في مخزن سريع (ذاكرة الوصول العشوائي، Redis، ملفات) لتجنب الوصول في كل مرة إلى المصدر البطيء (قاعدة البيانات، API خارجي، نظام الملفات).



Go هي لغة للأنظمة عالية التحميل. ولكن حتى في Go يمكنك كتابة كود بطيء إذا لم تستخدم التخزين المؤقت. تخيل: تطلب قائمة الدول من PostgreSQL 1000 مرة في الثانية. بدلاً من 1000 استعلام إلى قاعدة البيانات، يمكنك إجراء استعلام واحد فقط، وإرجاع البيانات 999 مرة من التخزين المؤقت في الذاكرة Go.



النتيجة: سرعة الاستجابة تنخفض من 50 مللي ثانية إلى 0.5 مللي ثانية. فرق بمقدار 100 مرة. هذا ليس سحرًا، إنها بنية جيدة.



متى يكون التخزين المؤقت إلزاميًا؟

  • الحمل العالي (1000+ طلب في الثانية)
  • واجهات API خارجية بطيئة (الدفع، الطقس، أسعار العملات)
  • الحسابات الثقيلة (التقارير، التحليلات)
  • البيانات المرجعية المطلوبة بشكل متكرر (قوائم المدن، فئات المنتجات)


استراتيجيات التخزين المؤقت: اختر الاستراتيجية الصحيحة



لتجنب تحويل التخزين المؤقت إلى نمط معاكس، تحتاج إلى اختيار استراتيجية التخزين المؤقت الصحيحة. إليك الخيارات الرئيسية التي تعمل في Go:



1. Cache-Aside (التخزين المؤقت الكسول)

النمط الأكثر شيوعًا. يتحقق التطبيق أولاً من التخزين المؤقت. إذا لم تكن البيانات موجودة (cache miss) - يذهب إلى قاعدة البيانات، ويحفظ النتيجة في التخزين المؤقت ويعيدها للعميل.



func GetUser(cache *redis.Client, db *sql.DB, userID int) (*User, error) {    // نحاول الحصول من التخزين المؤقت    val, err := cache.Get(ctx, "user:"+strconv.Itoa(userID)).Result()    if err == nil {        user := &User{}        json.Unmarshal([]byte(val), user)        return user, nil    }

// التخزين المؤقت أخطأ - نذهب إلى قاعدة البيانات user, err := db.Query("SELECT * FROM users WHERE id = ?", userID) if err != nil { return nil, err }

// نحفظ في التخزين المؤقت لمدة ساعة data, _ := json.Marshal(user) cache.Set(ctx, "user:"+strconv.Itoa(userID), data, time.Hour)

return user, nil}


2. Write-Through (الكتابة المباشرة)

تتم كتابة البيانات في نفس الوقت في التخزين المؤقت وقاعدة البيانات. الميزة: التخزين المؤقت دائمًا محدث. العيب: الكتابة أبطأ قليلاً.



3. TTL (وقت الحياة)

أبسط طريقة لإبطال التخزين المؤقت. تعيش البيانات لفترة زمنية محددة، ثم تُحذف تلقائيًا.



// تخزين مؤقت لمدة 5 دقائقcache.Set(ctx, "key", value, 5*time.Minute)


تنفيذ التخزين المؤقت في الذاكرة (in-memory cache) في Go بنفسك



أحيانًا لا ترغب في تشغيل Redis من أجل بضعة مفاتيح. في هذه الحالات، التخزين المؤقت في الذاكرة Go مناسب تمامًا. لنكتب نسخة بسيطة ولكنها عملية:



package cache

import ( "sync" "time")

type Item struct { Value interface{} Expiration int64}

type Cache struct { items map[string]Item mu sync.RWMutex}

func New() *Cache { c := &Cache{ items: make(map[string]Item), } // تشغيل goroutine لتنظيف الإدخالات منتهية الصلاحية go c.cleanup() return c}

func (c *Cache) Set(key string, value interface{}, ttl time.Duration) { c.mu.Lock() defer c.mu.Un

المدونات

توصيات الكتب