
سنحاول معرفة كيفية تقليل الحمل على أجهزة الخادم ، مع ضمان أقصى أداء لتطبيق الويب.
في تطوير المشاريع الكبيرة ذات الأحمال العالية ذات الحجم الضخم عبر الإنترنت ، غالبًا ما يتعين عليك التفكير في كيفية تقليل الحمل على الخادم ، خاصة عند العمل في webSockets وتغيير الواجهات ديناميكيًا. يأتي إلينا 100500 مستخدم ولدينا 100500 وصلة مقبس مفتوحة. وإذا فتح كل منهما علامتي تبويب - فهذا * 201000 اتصال. وإذا خمسة؟
فكر في مثال تافه. لدينا ، على سبيل المثال ، Twitch.tv ، والتي ترفع لكل مستخدم اتصال WS. مثل هذا المشروع ضخم على الإنترنت ، لذلك كل التفاصيل مهمة. لا يمكننا فتح اتصال WS جديد في كل علامة تبويب ، يدعم علامة التبويب القديمة ، لأن الغدة تحتاج إلى قياسها لهذا الغرض.
ولدت الفكرة - ماذا لو تم رفع اتصالات WS في علامة تبويب واحدة فقط وابقائها مفتوحة دائمًا ، وفي العلامات الجديدة لا تهيئ الاتصال ، ولكن فقط استمع من علامة التبويب المجاورة؟ حول تنفيذ هذه الفكرة التي أريد أن أقولها.
سلوك منطق المستعرض
- افتح علامة التبويب الأولى ، وقم بوضع علامة "أساسي" عليها
- قم بإجراء الاختبار - إذا كانت علامة التبويب is_primary ، فقم برفع اتصال WS
- نحن نعمل ...
- افتح علامة التبويب الثانية (كرر النافذة ، أدخل العنوان يدويًا ، افتح في علامة تبويب جديدة ، لا يهم)
- من علامة التبويب الجديدة ، تحقق من وجود علامة تبويب أساسية في مكان ما. إذا كانت الإجابة بنعم ، ضع علامة على المرحلة الثانوية الحالية وانتظر ما سيحدث.
- افتح 10 علامات تبويب أخرى. والجميع ينتظر.
- عند نقطة ما ، يتم إغلاق علامة التبويب الأساسية. قبل وفاتها ، تصرخ على الجميع حول وفاتها. كل شيء في حالة صدمة.
- ثم تحاول جميع علامات التبويب أن تصبح أساسية على الفور. رد فعل كل شخص مختلف (عشوائي) ومن لديه الوقت ، ذلك والنعال. بمجرد أن تصبح إحدى علامات التبويب أمرًا أساسيًا ، تصرخ للجميع أن يتم أخذ المكان. بعد ذلك ، يدخل اتصال WS من جديد. نحن نعمل. والباقون ينتظرون.
- إلخ. ينتظر الزبالون وفاة علامة التبويب الأساسية لتسقط في مكانها.
الجانب الفني للقضية
للتواصل بين علامات التبويب ، سنستخدم ما يربطها داخل نفس المجال - localStorage. المكالمات إليها ليست مكلفة لموارد الحديد للمستخدم والاستجابة منها سريعة للغاية. حولها ، يتم بناء الفكرة بأكملها.
هناك مكتبة لم يدعمها منشئ المحتوى لفترة طويلة ، ولكن يمكنك جعلها شوكة محلية ، كما فعلت. منه نحصل على الملف:
/intercom.js
جوهر المكتبة هو أنها تسمح للانبعاث / على الأحداث بالتواصل بين علامات التبويب باستخدام localStorage لهذا الغرض.
بعد ذلك ، نحتاج إلى أداة تتيح لنا قفل ( حظر التغييرات ) مفتاحًا معينًا في localStorage ، دون السماح لأي شخص بتغييره دون الحقوق اللازمة. لهذا ، تم كتابة مكتبة صغيرة تسمى " locableStorage " ، جوهرها موجود في دالة trySyncLock ()
رمز مكتبة LocableStorage (function () { function now() { return new Date().getTime(); } function someNumber() { return Math.random() * 1000000000 | 0; } let myId = now() + ":" + someNumber(); function getter(lskey) { return function () { let value = localStorage[lskey]; if (!value) return null; let splitted = value.split(/\|/); if (parseInt(splitted[1]) < now()) { return null; } return splitted[0]; } } function _mutexTransaction(key, callback, synchronous) { let xKey = key + "__MUTEX_x", yKey = key + "__MUTEX_y", getY = getter(yKey); function criticalSection() { try { callback(); } finally { localStorage.removeItem(yKey); } } localStorage[xKey] = myId; if (getY()) { if (!synchronous) setTimeout(function () { _mutexTransaction(key, callback); }, 0); return false; } localStorage[yKey] = myId + "|" + (now() + 40); if (localStorage[xKey] !== myId) { if (!synchronous) { setTimeout(function () { if (getY() !== myId) { setTimeout(function () { _mutexTransaction(key, callback); }, 0); } else { criticalSection(); } }, 50) } return false; } else { criticalSection(); return true; } } function lockImpl(key, callback, maxDuration, synchronous) { maxDuration = maxDuration || 5000; let mutexKey = key + "__MUTEX", getMutex = getter(mutexKey), mutexValue = myId + ":" + someNumber() + "|" + (now() + maxDuration); function restart() { setTimeout(function () { lockImpl(key, callback, maxDuration); }, 10); } if (getMutex()) { if (!synchronous) restart(); return false; } let aquiredSynchronously = _mutexTransaction(key, function () { if (getMutex()) { if (!synchronous) restart(); return false; } localStorage[mutexKey] = mutexValue; if (!synchronous) setTimeout(mutexAquired, 0) }, synchronous); if (synchronous && aquiredSynchronously) { mutexAquired(); return true; } return false; function mutexAquired() { try { callback(); } finally { _mutexTransaction(key, function () { if (localStorage[mutexKey] !== mutexValue) throw key + " was locked by a different process while I held the lock" localStorage.removeItem(mutexKey); }); } } } window.LockableStorage = { lock: function (key, callback, maxDuration) { lockImpl(key, callback, maxDuration, false) }, trySyncLock: function (key, callback, maxDuration) { return lockImpl(key, callback, maxDuration, true) } }; })();
الآن من الضروري دمج كل شيء في آلية واحدة ، مما سيسمح لنا بتنفيذ خططنا.
كود التنفيذ if (Intercom.supported) { let intercom = Intercom.getInstance(),
الآن على الأصابع سأشرح ما يحدث هنا.
مشروع جيثب التجريبي
الخطوة 1. فتح علامة التبويب الأولى
يطبق هذا المثال جهاز توقيت يعمل في عدة مساهمات ، ولكن يتم حسابه في مساهمة واحدة فقط. يمكن استبدال رمز المؤقت بأي شيء ، على سبيل المثال ، عن طريق تهيئة اتصال WS. عند البدء ، يتم تنفيذ webSocketInit () على الفور ، وهو ما سيقودنا في علامة التبويب الأولى إلى بدء العداد ( فتح المقبس ) ، وكذلك لبدء جهاز ضبط الوقت startHeartBitInterval () لتحديث القيمة الرئيسية " wsLU " في localStorage. هذا المفتاح مسؤول عن إنشاء وصيانة علامة التبويب الأساسية. هذا عنصر رئيسي في الهيكل بأكمله. في نفس الوقت ، يتم إنشاء مفتاح wsOpen ، وهو المسؤول عن حالة العداد (أو فتح اتصال WS) ويصبح الوضع الأساسي المتغير ، الذي يجعل علامة التبويب الحالية رئيسية ، صحيحًا. سيتم إصدار إيصال أي حدث من العداد (اتصال WS) في Intercom ، مع التصميم:
intercom.emit('incoming', {data: count});
الخطوة 2. فتح علامة التبويب الثانية
سيؤدي فتح علامة التبويب الثانية والثالثة وأي علامة تبويب أخرى إلى حدوث webSocketInit () ، وبعد ذلك يدخل المفتاح " wsLU " و " forceOpen " في المعركة. إذا كان الرمز:
if (wsLU) { let diff = Date.now() - parseInt(wsLU); forceOpen = diff > period_heart_bit * 5 * 1000; }
... سيجعل ForceOpen حقيقة ، ثم سيتوقف العداد ويبدأ مرة أخرى ، لكن هذا لن يحدث ، لأن لن يكون الفرق أكبر من القيمة المحددة ، لأن مفتاح wsLU مدعوم من علامة التبويب الأساسية الحالية. ستستمع جميع علامات التبويب الثانوية إلى الأحداث التي تمنحها علامة التبويب الأساسية لها من خلال الاتصال الداخلي ، مع التصميم:
intercom.on('incoming', data => { document.getElementById('counter').innerHTML = data.data; document.getElementById('socketStatus').innerHTML = primaryStatus.toString(); return false; });
الخطوة 3. إغلاق علامة التبويب
تؤدي علامات التبويب الختامية إلى تشغيل حدث onbeforeunload في المتصفحات الحديثة. نعالجها على النحو التالي:
window.onbeforeunload = function () { if (primaryStatus) { localStorage.setItem('wsOpen', false); clearInterval(refreshIntervalId); intercom.emit('TAB_CLOSED', {count: count}); } };
وتجدر الإشارة إلى أنه سيتم استدعاء جميع الطرق فقط في علامة التبويب الأساسية. عندما تغلق أي علامة تبويب ثانوية ، لن يحدث شيء للعداد. تحتاج فقط إلى إزالة التنصت على الأحداث لتحرير الذاكرة. ولكن إذا أغلقنا علامة التبويب الأساسية ، فعندئذٍ نقوم بتعيين wsOpen على false ونطلق الحدث TAB_CLOSED. سترد جميع علامات التبويب المفتوحة على الفور على ذلك:
intercom.on('TAB_CLOSED', function (data) { if (localStorage.wsId !== wsId) { count = data.count; setTimeout(() => { webSocketInit() }, parseInt(getRandomArbitary(1, 1000), 10));
هنا يبدأ السحر. الوظيفة ...
getRandomArbitary (1 ، 1000) function getRandomArbitary(min, max) { return Math.random() * (max - min) + min; }
... يسمح لك باستدعاء تهيئة المقبس (في حالتنا ، العداد) على فترات مختلفة ، مما يجعل من الممكن أن تصبح إحدى علامات التبويب الثانوية أساسية وكتابة المعلومات عنها إلى localStorage. بعد أن تم تجسيده بأرقام (1 ، 1000) ، يمكنك تحقيق أسرع استجابة لعلامات التبويب. تبقى علامات التبويب الثانوية المتبقية للاستماع إلى الأحداث والرد عليها ، في انتظار وفاة الابتدائية.
الملخص
لقد حصلنا على تصميم يسمح لك بالحفاظ على اتصال webSocket واحد فقط للتطبيق بأكمله ، بغض النظر عن عدد علامات التبويب التي تحتوي عليها ، مما سيقلل بشكل كبير من الحمل على أجهزة خوادمنا ، ونتيجة لذلك ، سيسمح لك بالحفاظ على المزيد عبر الإنترنت.