اختبار A/B في Go: تقسيم حركة المرور كالمحترفين

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

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

ابدأ الدورة

مقدمة: لماذا تحتاج خدمة Go الخاصة بك إلى التجارب

\n\n

تخيل: لقد أعدت كتابة صفحة الدفع باستخدام مكدس جديد. كل شيء يعمل، الكود بلغة Go يطير، الاختبارات خضراء. لكن معدل التحويل انخفض بنسبة 15%. بغض النظر عن مدى محاولتك، في الظروف الحقيقية، من المستحيل التنبؤ بسلوك المستخدمين. هنا يأتي دور اختبار A/B في Go - الطريقة الوحيدة لاتخاذ القرارات بناءً على البيانات، وليس الحدس.

\n\n

في هذه المقالة، سنشرح كيفية دمج اختبار الانقسام Golang في تطبيقك. ستتعلم كيفية تنفيذ نظام الحاويات، وتجنب لعنة المقارنات المتعددة، وعدم تعطيل الإنتاج. هيا بنا!

\n\n

1. البنية الأساسية لاختبار A/B في Go

\n\n

أي اختبار A/B Golang يتكون من ثلاثة أركان: مقسّم حركة المرور، نظام التسجيل، ومحرك الإحصائيات. لنبدأ بالأول.

\n\n

1.1. التوزيع الحتمي: خوارزمية الحاويات

\n\n

القاعدة الأساسية: يجب أن يقع نفس المستخدم دائمًا في نفس المجموعة. نستخدم التجزئة المتسقة بناءً على معرف المستخدم:

\n\n
package abtest\n\nimport (\n    "crypto/sha256"\n    "encoding/binary"\n    "fmt"\n)\n\n// Experiment يحدد هيكل التجربة\ntype Experiment struct {\n    Name   string\n    Buckets []Bucket\n}\n\ntype Bucket struct {\n    Name  string\n    Ratio float64 // 0.0 - 1.0\n}\n\n// AssignGroup يُرجع اسم المجموعة لـ userID\nfunc (e *Experiment) AssignGroup(userID string) string {\n    hash := sha256.Sum256([]byte(fmt.Sprintf("%s:%s", e.Name, userID)))\n    value := binary.BigEndian.Uint64(hash[:8]) % 1000\n    threshold := uint64(0)\n    \n    for _, b := range e.Buckets {\n        threshold += uint64(b.Ratio * 1000)\n        if value < threshold {\n            return b.Name\n        }\n    }\n    return "control" // fallback\n}\n
\n\n

لماذا sha256؟ لأنه يوفر توزيعًا متساويًا ومقاومًا للتصادمات. اختبار الحاويات Go بهذا النهج يضمن أن المستخدم لن ينتقل بين المجموعات عند إعادة تشغيل الخادم.

\n\n

1.2. الوسيط لخادم HTTP

\n\n

ندمج اختبار A/B في Go في الموجه الخاص بك. مثال مع net/http:

\n\n
func ABMiddleware(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        userID := r.Header.Get("X-User-ID") // أو من الكوكيز\n        if userID == "" {\n            next.ServeHTTP(w, r)\n            return\n        }\n        \n        exp := &Experiment{\n            Name: "checkout_v2",\n            Buckets: []Bucket{\n                {Name: "control", Ratio: 0.5},\n                {Name: "treatment", Ratio: 0.5},\n            },\n        }\n        \n        group := exp.AssignGroup(userID)\n        ctx := context.WithValue(r.Context(), "ab_group", group)\n        next.ServeHTTP(w, r.WithContext(ctx))\n    })\n}\n
\n\n

الآن في المعالجات، يمكنك قراءة المجموعة عبر r.Context().Value("ab_group"). بسيط وفعال.

\n\n

2. تقنيات متقدمة: أعلام الميزات و"لصوص متعددو الأذرع"

\n\n

اختبار A/B البسيط جيد، لكن في التجارب الحقيقية في Golang غالبًا ما تكون هناك حاجة للمرونة.

\n\n

2.1. أعلام الميزات مع التفعيل الديناميكي

\n\n

أحيانًا تحتاج إلى تفعيل ميزة لـ 10% من المستخدمين، ثم زيادتها تدريجيًا إلى 100%. ننفذ ذلك عبر Redis:

\n\n
type DynamicExperiment struct {\n    Client *redis.Client\n    Name   string\n}\n\nfunc (d *DynamicExperiment) IsEnabled(userID string) bool {\n    // نأخذ النسبة المئوية الحالية من Redis (يمكن تغييرها دون نشر)\n    percent, err := d.Client.Get(context.Background(), d.Name).Float64()\n    if err != nil {\n        return false // معطل افتراضيًا\n    }\n    \n    hash := sha256.Sum256([]byte(userID))\n    value := binary.BigEndian.Uint64(hash[:8]) % 1                

المدونات

توصيات الكتب