لمراقبة الخوادم والخدمات ، استخدمنا منذ فترة طويلة وما زلنا ناجحين حلاً مدمجًا يستند إلى Nagios و Munin. ومع ذلك ، فإن هذه المجموعة لديها عدد من العيوب ، لذلك نحن ، مثل الكثيرين ، نستغل بنشاط
Zabbix . في هذه المقالة ، سنتحدث عن كيفية حل مشكلة الأداء بأقل جهد ممكن عند زيادة عدد المقاييس التي تم إزالتها وزيادة حجم قاعدة بيانات MySQL
مشاكل في استخدام قاعدة بيانات MySQL مع Zabbix
بينما كانت قاعدة البيانات صغيرة وكان عدد المقاييس المخزنة فيها صغيرًا ، فقد كان كل شيء رائعًا. نجحت عملية مدبرة المنزل العادية التي تقوم بتشغيل Zabbix Server نفسها في حذف السجلات القديمة من قاعدة البيانات ، مما حال دون نموها. ومع ذلك ، بمجرد زيادة عدد المقاييس التي تم التقاطها ووصل حجم قاعدة البيانات إلى حجم معين ، أصبح كل شيء أسوأ. توقف مدبرة المنزل عن إدارة حذف البيانات عن الفاصل الزمني المخصص ، بدأت البيانات القديمة في البقاء في قاعدة البيانات. أثناء تشغيل مدبرة المنزل ، كان هناك حمل متزايد على خادم Zabbix ، والذي قد يستمر لفترة طويلة. أصبح من الواضح أنه كان من الضروري حل الوضع الحالي بطريقة أو بأخرى.
هذه مشكلة معروفة ، واجه كل شخص تقريبًا يعمل بكميات كبيرة من المراقبة على Zabbix نفس الشيء. كان هناك العديد من الحلول أيضًا: على سبيل المثال ، استبدال MySQL بـ PostgreSQL أو حتى Elasticsearch ، ولكن الحل الأبسط والأكثر إثباتًا كان التبديل إلى تقسيم الجداول التي تخزن البيانات المترية في قاعدة بيانات MySQL. قررنا أن نذهب بهذه الطريقة.
الترحيل من جداول MySQL العادية إلى تلك المقسمة
تم توثيق Zabbix جيدًا وتعرف الجداول التي تخزن فيها المقاييس. هذه الجداول:
history
، حيث يتم تخزين قيم الطفو ،
history_str
، حيث يتم تخزين قيم السلسلة القصيرة ،
history_text
، حيث يتم تخزين قيم النص الطويلة ، و
history_uint
، حيث يتم تخزين قيم الأعداد الصحيحة. يوجد أيضًا جدول
trends
يخزن ديناميات التغييرات ، لكننا قررنا عدم لمسها ، لأن حجمها صغير وبعد ذلك بقليل سنعود إليه.
بشكل عام ، ما الجداول اللازمة للمعالجة كانت واضحة. قررنا إنشاء أقسام لكل أسبوع ، باستثناء الأسبوع الماضي ، بناءً على أرقام الشهر ، أي أربعة أقسام شهريًا: من الأول إلى السابع ، ومن الثامن إلى الرابع عشر ، ومن الخامس عشر إلى الحادي والعشرين ومن الثاني والعشرين إلى الأول (الشهر المقبل). كانت الصعوبة هي أننا كنا بحاجة إلى تحويل الجداول التي نحتاجها إلى قسم "سريعًا" دون مقاطعة Zabbix Server وجمع المقاييس.
ومن الغريب أن هيكل هذه الجداول جاء لمساعدتنا في هذا. على سبيل المثال ، يحتوي جدول
history
على البنية التالية:
`itemid` bigint(20) unsigned NOT NULL, `clock` int(11) NOT NULL DEFAULT '0', `value` double(16,4) NOT NULL DEFAULT '0.0000', `ns` int(11) NOT NULL DEFAULT '0',
في حين
KEY `history_1` (`itemid`,`clock`)
كما ترون ، يتم إدخال كل مقياس في نهاية المطاف في جدول به حقلان مهمان للغاية
ومريحان بالنسبة لنا في حقل
itemid و
clock . وبالتالي ، يمكننا إنشاء جدول مؤقت ، على سبيل المثال ، باستخدام اسم
history_tmp
، قم بإعداد
history_tmp
له ثم نقل جميع البيانات من جدول
history
هناك ، ثم إعادة تسمية جدول
history
إلى
history_old
، وجدول
history_tmp
إلى
history
، ثم إضافة البيانات التي
history_old
من
history_old
إلى
history
وحذف
history_old
. يمكنك القيام بذلك بأمان تام ، فلن نفقد شيئًا ، لأن حقول
itemid و
clock الموضحة أعلاه توفر مقياس ارتباط لوقت معين ، وليس لنوع من الرقم التسلسلي.
عملية الانتقال نفسها
تحذير! من المرغوب فيه للغاية ، قبل البدء في أي إجراء ، عمل نسخة احتياطية كاملة من قاعدة البيانات. نحن جميعنا أشخاص أحياء ويمكننا ارتكاب خطأ في مجموعة الأوامر ، مما قد يؤدي إلى فقدان البيانات. نعم. لن توفر نسخة احتياطية أقصى درجة من الأهمية ، ولكن من الأفضل الحصول على نسخة واحدة.
لذلك ، لا تقم بإيقاف تشغيل أي شيء أو إيقافه. الشيء الرئيسي هو أنه على خادم MySQL نفسه يجب أن يكون هناك قدر كاف من مساحة القرص الحرة ، أي بحيث يكون لكل من سجل الجداول أعلاه ،
history_text
،
history_str
،
history_uint
، على الأقل ، مساحة كافية لإنشاء جدول باللاحقة "_tmp" ، بالنظر إلى أنه سيكون بنفس مقدار الجدول الأصلي.
لن نصف كل شيء عدة مرات لكل من الجداول المذكورة أعلاه وننظر في كل شيء مع مثال واحد منهم فقط - جدول
history
.
لذلك ، قم بإنشاء جدول
history_tmp
فارغًا استنادًا إلى هيكل جدول
history
.
CREATE TABLE `history_tmp` LIKE `history`;
نخلق الأقسام التي نحتاجها. على سبيل المثال ، دعونا نفعل ذلك لمدة شهر. يتم إنشاء كل قسم بناءً على قاعدة القسم ، استنادًا إلى قيمة حقل
الساعة ، والتي نقارنها مع الطابع الزمني:
ALTER TABLE `history_tmp` PARTITION BY RANGE( clock ) ( PARTITION p20190201 VALUES LESS THAN (UNIX_TIMESTAMP("2019-02-01 00:00:00")), PARTITION p20190207 VALUES LESS THAN (UNIX_TIMESTAMP("2019-02-07 00:00:00")), PARTITION p20190214 VALUES LESS THAN (UNIX_TIMESTAMP("2019-02-14 00:00:00")), PARTITION p20190221 VALUES LESS THAN (UNIX_TIMESTAMP("2019-02-21 00:00:00")), PARTITION p20190301 VALUES LESS THAN (UNIX_TIMESTAMP("2019-03-01 00:00:00")) );
يضيف عامل التشغيل هذا التقسيم إلى جدول
history_tmp
الذي
history_tmp
. دعنا نوضح أن البيانات التي تكون قيمة حقل
الساعة الخاصة بها أقل من "2019-02-01 00:00:00" ستندرج في القسم
p20190201 ، ثم البيانات التي تكون قيمة حقل
الساعة أكبر من "2019-02-01 00:00:00" ولكن أقل "2019-02-07 00:00:00" سوف نقع في حزب
p20190207 وهلم جرا.
ملاحظة مهمة: وماذا يحدث إذا كانت لدينا بيانات في الجدول المقسم حيث تكون قيمة حقل الساعة أكبر من أو تساوي "2019-03-01 00:00:00"؟ نظرًا لعدم وجود قسم مناسب لهذه البيانات ، فلن يتم وضعها في الجدول وسيتم فقدها. لذلك ، يجب ألا تنس إنشاء أقسام إضافية في الوقت المناسب ، من أجل تجنب فقدان البيانات (حول أي أدناه).
لذلك ، يتم إعداد الجدول المؤقت. ملء البيانات. قد تستغرق العملية وقتًا طويلاً ، لكن لحسن الحظ لا تمنع أي طلبات أخرى ، لذلك عليك فقط التحلي بالصبر:
INSERT IGNORE INTO `history_tmp` SELECT * FROM history;
الكلمة الأساسية IGNORE غير مطلوبة أثناء التعبئة الأولية ، لأنه لا توجد بيانات في الجدول ، ومع ذلك ، ستحتاج إليها عند إضافة البيانات. بالإضافة إلى ذلك ، قد يكون من المفيد أن تضطر إلى مقاطعة هذه العملية والبدء من جديد عند ملء البيانات.
لذلك ، بعد مرور بعض الوقت (ربما حتى بضع ساعات) ، فقد مرت عملية تحميل البيانات الأولى. كما فهمت ، لا يحتوي جدول
history_tmp
الآن على جميع البيانات من جدول
history
، ولكن فقط البيانات التي كانت موجودة فيه في وقت بدء الاستعلام. هنا ، في الواقع ، لديك خيار: إما أن نجتاز تمريرة واحدة أخرى (إذا استمرت عملية التعبئة لفترة طويلة) ، أو شرعنا فورًا في إعادة تسمية الجداول المذكورة أعلاه. أولاً ، لنأخذ التمريرة الثانية. أولاً ، نحتاج إلى فهم وقت آخر سجل مدرج في
history_tmp
:
SELECT max(clock) FROM history_tmp;
لنفترض أنك تلقيت:
1551045645 . الآن نستخدم القيمة التي تم الحصول عليها في المقطع الثاني من تعبئة البيانات:
INSERT IGNORE INTO `history_tmp` SELECT * FROM history WHERE clock>=1551045645;
يجب أن ينتهي هذا المقطع بشكل أسرع. ولكن إذا تم تنفيذ التمريرة الأولى ساعات ، وتم تنفيذ التمريرة الثانية أيضًا لفترة طويلة ، فقد يكون من الصحيح إجراء التمريرة الثالثة ، التي يتم تنفيذها بشكل مشابه تمامًا للثاني.
في النهاية ، ننفذ مرة أخرى عملية الحصول على وقت آخر إدخال للسجل في
history_tmp
عن طريق القيام بما يلي:
SELECT max(clock) FROM history_tmp;
لنفترض أنك حصلت على
1551085645 . حافظ على هذه القيمة - سنحتاجها لإعادة تعبئتها.
والآن ، في الواقع ، عندما
history_tmp
تعبئة البيانات الأساسية
history_tmp
، ننتقل إلى إعادة تسمية الجداول:
BEGIN; RENAME TABLE history TO history_old; RENAME TABLE history_tmp TO history; COMMIT;
لقد صممنا هذه الكتلة كمعاملة واحدة لتجنب لحظة إدراج البيانات في جدول غير موجود ، لأنه بعد أول RENAME حتى يتم تنفيذ RENAME الثاني ، لن يكون جدول
history
موجودًا. ولكن حتى إذا وصلت بعض البيانات بين عمليات RENAME في جدول
history
ولم يكن الجدول نفسه موجودًا (بسبب إعادة التسمية) ، فسنحصل على عدد صغير من أخطاء الإدراج التي يمكن إهمالها (لدينا مراقبة ، وليس البنك).
الآن لدينا جدول
history
جديد مع التقسيم ، لكنه لا يحتوي على بيانات كافية تم تلقيها أثناء آخر مرور لإدراج البيانات في جدول
history_tmp
. لكن لدينا هذه البيانات في الجدول
history_old
الآن من هناك. لهذا ، سنحتاج إلى القيمة المحفوظة مسبقًا 1551085645. لماذا قمنا بحفظ هذه القيمة ولم نستخدم الحد الأقصى لوقت التعبئة بالفعل من جدول
history
الحالي؟ لأن البيانات الجديدة تأتي بالفعل إليها وسنحصل على الوقت الخطأ. لذلك ، نقيس البيانات:
INSERT IGNORE INTO `history` SELECT * FROM history_old WHERE clock>=1551045645;
بعد نهاية هذه العملية ، لدينا في جدول
history
جديد مقسم جميع البيانات التي كانت في القديم ، بالإضافة إلى البيانات التي جاءت بعد إعادة تسمية الجدول. لم يعد هناك حاجة إلى الجدول
history_old
. يمكنك حذفها على الفور ، أو يمكنك عمل نسخة احتياطية منها (إذا كان لديك جنون العظمة) قبل حذفها.
يجب تكرار العملية بالكامل الموضحة أعلاه
history_str
history_text
و
history_uint
و
history_uint
.
ما يحتاج إلى إصلاح في إعدادات خادم Zabbix
الآن صيانة قاعدة البيانات بشأن تاريخ البيانات تقع على عاتقنا. هذا يعني أن Zabbix يجب ألا تحذف البيانات القديمة بعد الآن - سنفعل ذلك بأنفسنا. حتى لا يحاول Zabbix Server تنظيف البيانات نفسها ، تحتاج إلى الانتقال إلى واجهة الويب Zabbix ، وحدد "الإدارة" في القائمة ، ثم القائمة الفرعية "عام" ، ثم حدد "محو السجل" في القائمة المنسدلة على اليمين. في الصفحة التي تظهر ، قم بإلغاء تحديد جميع خانات الاختيار لمجموعة "السجل" وانقر على زر "تحديث". سيمنع هذا الجداول من التنظيف من خلال مدبرة المنزل.
انتبه في نفس الصفحة إلى مجموعة "ديناميات التغييرات". هذا هو مجرد جدول
trends
، الذي وعدنا بالعودة إليه. إذا أصبحت أيضًا كبيرة جدًا بالنسبة لك وتحتاج إلى التقسيم ، فقم بإلغاء تحديد هذه المجموعة أيضًا ، ثم قم بمعالجة هذا الجدول تمامًا كما فعلت بالنسبة لجداول
history*
.
مزيد من صيانة قاعدة البيانات
كما هو مكتوب مسبقًا ، للتشغيل العادي على الجداول المقسمة ، من الضروري إنشاء أقسام في الوقت المناسب. يمكنك القيام بهذا مثل هذا:
ALTER TABLE `history` ADD PARTITION (PARTITION p20190307 VALUES LESS THAN (UNIX_TIMESTAMP("2019-03-07 00:00:00")));
بالإضافة إلى ذلك ، نظرًا لأننا أنشأنا جداول مقسمة ونهىنا عن Zabbix Server لتنظيفها ، فإن حذف البيانات القديمة هو الآن شاغلنا. لحسن الحظ ، لا توجد مشاكل على الإطلاق. يتم ذلك ببساطة عن طريق حذف القسم الذي لم نعد نحتاج إلى بياناته.
على سبيل المثال:
ALTER TABLE history DROP PARTITION p20190201;
على عكس عبارات DELETE FROM بنطاق زمني ، يتم تنفيذ DROP PARTITION في بضع ثوانٍ ، ولا يتم تحميل الخادم على الإطلاق ويعمل بنفس السلاسة عند استخدام النسخ المتماثل في MySQL.
استنتاج
الحل الموصوف هو اختبار الزمن. حجم البيانات ينمو ، ولكن لا يوجد تباطؤ ملحوظ في الأداء.