
أهلا وسهلا بك! اسمي مكسيم Matyukhin ، أنا مبرمج PHP على
Badoo . في عملنا ، نستخدم بنشاط الخلية. لكن في بعض الأحيان نفتقر إلى أدائها ، لذلك نحن نبحث باستمرار عن طرق لتسريع عملها.
في عام 2010 ، قدم Yoshinori Matsunobu البرنامج المساعد NoSQL MySQL المسمى HandlerSocket. تم ادعاء أن هذا البرنامج المساعد يسمح لك بتنفيذ أكثر من 750،000 طلب في الثانية. أصبحنا فضوليين ، وبدأنا على الفور في استخدام هذا الحل. لقد أحببنا النتيجة لدرجة أننا بدأنا في تقديم
العروض التقديمية وكتابة
المقالات التي تروّج لـ HandlerSocket.
على ما يبدو ، كنا واحدة من عدد قليل من المستخدمين لهذا البرنامج المساعد - منذ MySQL 5.7 توقف عن العمل. ولكن في هذا الإصدار ظهر مكون إضافي من Oracle - InnoDB memcached plugin ، والذي وعد بوظيفة مماثلة.
على الرغم من حقيقة أن المكوّن الإضافي memcached ظهر مرة أخرى في MySQL 5.6 في عام 2013 ، إلا أنه لا يوجد الكثير من المقالات حول هذا الموضوع ، وفي الغالب يكررون الوثائق: يتم إنشاء تصنيف بسيط وتقديم الطلبات إليه من خلال العميل memcached.
لدينا خبرة واسعة مع Memcached وتستخدم لسهولة التفاعل معها. من InnoDB memcached المساعد كنا نتوقع نفس البساطة. ولكن في الواقع ، اتضح أنه إذا كانت أنماط استخدام المكون الإضافي مختلفة قليلاً على الأقل عن تلك الموضحة في الوثائق والمقالات ، فثمة الكثير من الفروق الدقيقة والقيود المنبثقة ، والتي تستحق بالتأكيد النظر فيما إذا كنت ستستخدم المكون الإضافي.
MySQL HandlerSocket
في هذه المقالة ، سنقوم بطريقة أو بأخرى بمقارنة المكون الإضافي memcached الجديد مع HandlerSocket القديم. لذلك ، أذكر أنه كان الأخير.
بعد تثبيت المكون الإضافي HandlerSocket ، بدأ MySQL في الاستماع إلى منفذين إضافيين:
- تلقى المنفذ الأول طلبات العميل لقراءة البيانات.
- تلقى المنفذ الثاني طلبات العميل لتسجيل البيانات.
كان على العميل إنشاء اتصال TCP منتظم على أحد هذه المنافذ (لم يتم دعم أي مصادقة) ، وبعد ذلك كان من الضروري إرسال أمر "الفهرس المفتوح" (أمر خاص أبلغ العميل من خلاله جدول أي الحقول التي سنذهب إليها) قراءة (أو الكتابة)).
إذا نجح الأمر "index index" بنجاح ، فيمكنك إرسال أوامر GETs أو INSERT / UPDATE / DELETE وفقًا للمنفذ الذي تم تأسيس الاتصال به.
يسمح HandlerSocket بأداء GETs ليس فقط على المفتاح الأساسي ، ولكن أيضًا عينات بسيطة من فهرس غير فريد وعينات نطاق وشبكات متعددة مدعومة و LIMIT. في الوقت نفسه ، كان من الممكن العمل مع الجدول من SQL العادي ومن خلال البرنامج المساعد. هذا ، على سبيل المثال ، سمح لك بإجراء بعض التغييرات في المعاملات من خلال SQL ، ثم قراءة هذه البيانات من خلال HandlerSocket.
من المهم أن HandlerSocket تعاملت مع جميع الاتصالات مع مجموعة محدودة من المواضيع من خلال epoll ، لذلك كان من السهل دعم عشرات الآلاف من الاتصالات ، بينما في MySQL نفسها تم إنشاء خيط لكل اتصال وكان عددهم محدود للغاية.
في الوقت نفسه ، لا يزال خادم MySQL عاديًا - تقنية مألوفة بالنسبة لنا. نحن نعرف كيفية تكرارها ومراقبتها. مراقبة HandlerSocket صعبة لأنها لا توفر أي مقاييس محددة ؛ ومع ذلك ، فإن بعض مقاييس MySQL و InnoDB القياسية مفيدة.
بالطبع ، كانت هناك إزعاجات ، على وجه الخصوص ، لم يدعم هذا البرنامج المساعد العمل مع نوع الطابع الزمني. حسنًا ، يصعب قراءة بروتوكول HandlerSocket وبالتالي يصعب تصحيحه.
قراءة المزيد عن HandlerSocket
هنا . يمكنك أيضًا مشاهدة
أحد عروضنا التقديمية .
InnoDB memcached المساعد
ماذا يقدم البرنامج المساعد الجديد memcached لنا؟
كما يوحي الاسم ، فكرته هي استخدام العميل memcached للعمل مع MySQL وتلقي وحفظ البيانات من خلال أوامر memcached.
يمكنك أن
تقرأ عن المزايا الرئيسية للبرنامج المساعد
هنا .
نحن مهتمون أكثر بما يلي:
- انخفاض استهلاك وحدة المعالجة المركزية.
- يتم تخزين البيانات في InnoDB ، والذي يعطي بعض الضمانات.
- يمكنك العمل مع البيانات من خلال Memcached ومن خلال SQL؛ يمكن نسخها باستخدام أدوات MySQL المدمجة.
يمكنك إضافة هذه الإيجابيات إلى هذه القائمة على النحو التالي:
- اتصال سريع ورخيص. تتم معالجة اتصال MySQL العادي بواسطة مؤشر ترابط واحد ، وعدد مؤشرات الترابط محدود ، وفي البرنامج المساعد memcached ، يعالج مؤشر ترابط واحد جميع الاتصالات في حلقة الحدث.
- القدرة على طلب مفاتيح متعددة مع طلب GET واحد.
- إذا ما قورنت بـ MySQL HandlerSocket ، فعندئذٍ في البرنامج المساعد memcached ، لن تحتاج إلى استخدام أمر "Open Table" وتحدث جميع عمليات القراءة والكتابة على نفس المنفذ.
يمكن الاطلاع على مزيد من التفاصيل حول البرنامج المساعد في
الوثائق الرسمية. بالنسبة لنا ، كانت أكثر الصفحات فائدة هي:
- InnoDB memcached الهندسة المعمارية .
- InnoDB memcached البرنامج المساعد Internals .
بعد تثبيت المكوّن الإضافي ، يبدأ MySQL بقبول الاتصالات على المنفذ 11211 (منفذ memcached القياسي). تظهر أيضًا قاعدة بيانات خاصة (مخطط) innodb_memcache ، حيث سيتم تكوين الوصول إلى الجداول الخاصة بك.
مثال بسيط
افترض أن لديك بالفعل جدولاً تريد العمل معه من خلال بروتوكول memcached:
CREATE TABLE `auth` ( `email` varchar(96) NOT NULL, `password` varchar(64) NOT NULL, `type` varchar(32) NOT NULL DEFAULT '', PRIMARY KEY (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
وتريد تلقي وتعديل البيانات على المفتاح الأساسي.
تحتاج أولاً إلى وصف المراسلات بين المفتاح memcached وجدول SQL في جدول innodb_memcache.containers. يشبه هذا الجدول شيئًا من هذا القبيل (أزلت وصف الترميز لتسهيل قراءته):
CREATE TABLE `containers` ( `name` varchar(50) NOT NULL, `db_schema` varchar(250) NOT NULL, `db_table` varchar(250) NOT NULL, `key_columns` varchar(250) NOT NULL, `value_columns` varchar(250) DEFAULT NULL, `flags` varchar(250) NOT NULL DEFAULT '0', `cas_column` varchar(250) DEFAULT NULL, `expire_time_column` varchar(250) DEFAULT NULL, `unique_idx_name_on_key` varchar(250) NOT NULL, PRIMARY KEY (`name`) ) ENGINE=InnoDB DEFAULT
أهم المجالات:
- اسم - بادئة مفتاح Memcached الخاص بك ؛
- db_schema - اسم القاعدة (الدائرة) ؛
- db_table هو الجدول الخاص بك.
- key_columns - اسم الحقل في الجدول الذي سنبحث به (عادة ما يكون هذا هو مفتاحك الأساسي) ؛
- value_columns - قائمة الحقول من الجدول التي ستكون متاحة للمكون الإضافي memcached ؛
- unique_idx_name_on_key هو الفهرس الذي تبحث عنه (على الرغم من أنك حددت أعمدة key_ بالفعل ، إلا أنها يمكن أن تكون في فهارس مختلفة وتحتاج إلى تحديد الفهرس بشكل صريح).
الحقول المتبقية ليست مهمة للغاية لبداية.
إضافة وصف الجدول الخاص بنا إلى innodb_memcache.containers:
INSERT INTO innodb_memcache.containers SET name='auth', db_schema='test', db_table='auth', key_columns='email', value_columns='password|type', flags='0', cas_column='0', expire_time_column='0', unique_idx_name_on_key='PRIMARY';
في هذا المثال ، name = 'auth' هو بادئة المفتاح memcached الخاص بنا. غالبًا ما يطلق عليه في table_id table_id ، وفي وقت لاحق في المقالة سأستخدم هذا المصطلح.
الآن اتصل TELNET بالملحق الإضافي memcached وحاول حفظ البيانات والحصول عليها:
[21:26:22] maxm@localhost: ~> telnet memchached-mysql.dev 11211 Trying 127.0.0.1... Connected to memchached-mysql.dev. Escape character is '^]'. get @@auth.max@example.com END set @@auth.max@example.com 0 0 10 1234567|89 STORED get @@auth.max@example.com VALUE @@auth.max@example.com 0 10 1234567|89 END
أولاً ، أرسلنا طلب GET ، ولم يعيد أي شيء إلينا. ثم قمنا بحفظ البيانات مع طلب SET ، وبعد ذلك استعادناها باستخدام GET.
إرجاع GET السطر التالي: 1234567 | 89. هذه هي قيم حقول "كلمة المرور" و "الكتابة" ، مفصولة برمز "|". يتم إرجاع الحقول بالترتيب الذي تم وصفها به في innodb_memcache.containers.value_columns.
ربما تتساءل الآن: "ماذا سيحدث إذا تمت مصادفة الرمز" | "في" كلمة المرور "؟" سأتحدث عن هذا أدناه.
من خلال SQL ، تتوفر هذه البيانات أيضًا:
MySQL [(none)]> select * from auth where email='max@example.com'; +-----------------+----------+------+ | email | password | type | +-----------------+----------+------+ | max@example.com | 1234567 | 89 | +-----------------+----------+------+ 1 row in set (0.00 sec)
table_id الافتراضي
هناك أيضا مثل هذا النمط من العملية:
get @@auth VALUE @@auth 0 21 test/auth END get max@example.com VALUE max@example.com 0 10 1234567|99 END set ivan@example.com 0 0 10 qwerty|xxx STORED get ivan@example.com VALUE ivan@example.com 0 10 qwerty|xxx END
في هذا المثال ، مع getauth ، نجعل table_id auth البادئة الافتراضية لهذا الاتصال. بعد ذلك ، يمكن إجراء جميع الاستعلامات اللاحقة دون تحديد table_id.
حتى الآن ، كل شيء بسيط ومنطقي. ولكن إذا بدأت في الفهم ، فهناك العديد من الفروق الدقيقة. سأخبرك بما وجدناه.
الفروق الدقيقة
التخزين المؤقت innodb_memcache.containers الجدول
يقرأ البرنامج المساعد memcached جدول innodb_memcache.containers مرة واحدة عند بدء التشغيل. علاوة على ذلك ، إذا وصل table_id غير معروف عبر بروتوكول Memcached ، يبحث المكون الإضافي عنه في الجدول. لذلك ، يمكنك بسهولة إضافة مفاتيح جديدة (table_id) ، ولكن إذا كنت ترغب في تغيير إعدادات table_id الحالية ، فسيتعين عليك إعادة تشغيل المكون الإضافي memcached:
mysql> UNINSTALL PLUGIN daemon_memcached; mysql> INSTALL PLUGIN daemon_memcached soname "libmemcached.so";
بين هذين الطلبين ، لن تعمل واجهة Memcached. لهذا السبب ، يكون إنشاء table_id جديد أسهل من تغيير الجدول الحالي وإعادة تشغيل المكون الإضافي.
لقد كانت مفاجأة بالنسبة لنا أن مثل هذا الفروق الدقيقة في عملية المكونات الإضافية موصوف في صفحة
تكييف تطبيق memcached لصفحة
InnoDB memcached Plugin ، وهو ليس مكانًا منطقيًا جدًا لمثل هذه المعلومات.
علامات ، cas_column ، expire_time_column
هذه الحقول مطلوبة لمحاكاة بعض ميزات Memcached. وثائق لهم غير متناسقة. توضح معظم الأمثلة الموجودة فيه العمل مع الجداول التي توجد فيها هذه الحقول. قد تكون هناك مخاوف من أنك ستحتاج إلى إضافتها إلى الجداول الخاصة بك (وهذه على الأقل ثلاثة حقول INT). لكن لا. إذا لم يكن لديك مثل هذه الحقول في الجداول ولن تستخدم وظيفة Memcached مثل CAS أو انتهاء الصلاحية أو العلامات ، فلن تحتاج إلى إضافة هذه الحقول إلى الجداول.
عند تكوين الجدول في innodb_memcache.containers ، ستحتاج إلى إدخال "0" في هذه الحقول ، وجعل السطر مع الصفر تمامًا:
INSERT INTO innodb_memcache.containers SET name='auth', db_schema='test', db_table='auth', key_columns='email', value_columns='password|type', flags='0', cas_column='0', expire_time_column='0', unique_idx_name_on_key='PRIMARY';
من المزعج أن يكون للقيمة cas_column و expire_time_column قيمة افتراضية لـ NULL ، وإذا قمت بتنفيذ INSERT INTO innodb_memcache.containers دون تحديد قيمة "0" لهذه الحقول ، فلن يتم تخزين NULL فيها ولن تعمل بادئة memcache هذه ببساطة.
أنواع البيانات
من الوثائق ، ليس من الواضح ما هي أنواع البيانات التي يمكن استخدامها عند العمل مع البرنامج المساعد. في العديد من الأماكن ، يقال إن المكون الإضافي يمكن أن يعمل فقط مع حقول النص (CHAR ، VARCHAR ، BLOB). هنا:
تكييف مخطط MySQL حالي لـ InnoDB memcached Plugin يقدم لتخزين الأرقام في حقول السلسلة ، وإذا كنت بحاجة إلى العمل مع حقول الأرقام هذه من SQL ، فقم بإنشاء VIEW حيث سيتم تحويل حقول VARCHAR مع الأرقام إلى حقول INTEGER :
CREATE VIEW numbers AS SELECT c1 KEY, CAST(c2 AS UNSIGNED INTEGER) val FROM demo_test WHERE c2 BETWEEN '0' and '9999999999';
ومع ذلك ، في بعض الأماكن في الوثائق لا يزال مكتوبًا أنه يمكنك التعامل مع الأرقام. حتى الآن ، لدينا فقط تجربة إنتاج حقيقية مع حقول النص ، ولكن النتائج التجريبية تظهر أن البرنامج المساعد يعمل أيضًا مع الأرقام:
CREATE TABLE `numbers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `counter` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB INSERT INTO innodb_memcache.containers SET name='numbers', db_schema='test', db_table='numbers', key_columns='id', value_columns='counter', flags='0', cas_column='0',expire_time_column='0',unique_idx_name_on_key='PRIMARY';
بعد ذلك ، من خلال بروتوكول Memcached:
get @@numbers.1 END set @@numbers.1 0 0 2 12 STORED get @@numbers.1 VALUE @@numbers.1 0 2 12 END
نرى أن المكوّن الإضافي memcached يمكنه إرجاع أي أنواع بيانات. لكنها ترجعها بالشكل الذي توجد به في InnoDB ، لذلك ، على سبيل المثال ، في حالة الطابع الزمني / وقت / تعويم / عشري / JSON ، يتم إرجاع سلسلة ثنائية. ولكن يتم إرجاع الأعداد الصحيحة كما نراها من خلال SQL.
Multiget
يسمح لك البروتوكول memcached بطلب مفاتيح متعددة مع طلب واحد:
get @@numbers.2 @@numbers.1 VALUE @@numbers.2 0 2 12 VALUE @@numbers.1 0 2 13 END
حقيقة أن multiget يعمل جيدة بالفعل. ولكنه يعمل في إطار table_id واحد:
get @@auth.ivan@example.com @@numbers.2 VALUE @@auth.ivan@example.com 0 10 qwerty|xxx END
تم توضيح هذه النقطة في الوثائق هنا:
https://dev.mysql.com/doc/refman/8.0/en/innodb-memcached-multiple-get-range-query.html . اتضح أنه في multiget يمكنك تحديد table_id فقط للمفتاح الأول ، إذا كانت جميع المفاتيح الأخرى مأخوذة من table_id الافتراضي (مثال من الوثائق):
get @@aaa.AA BB VALUE @@aaa.AA 8 12 HELLO, HELLO VALUE BB 10 16 GOODBYE, GOODBYE END
في هذا المثال ، يتم أخذ المفتاح الثاني من table_id الافتراضي. يمكننا تحديد مفاتيح أكثر بكثير من table_id الافتراضي ، وبالنسبة للمفتاح الأول حددنا table_id منفصلًا ، وهذا ممكن فقط في حالة المفتاح الأول.
يمكننا أن نقول إن multiget يعمل في إطار جدول واحد ، لأنك لا تشعر بالاعتماد على مثل هذا المنطق في رمز الإنتاج: ليس من الواضح ، فمن السهل أن ننسى ذلك ، نخطئ.
إذا ما قورنت مع HandlerSocket ، فهناك أيضًا ، عمل شبكات متعددة في نفس الجدول. لكن هذا التقييد بدا طبيعيا: يفتح العميل الفهرس في الجدول ويطلب منه قيمة واحدة أو أكثر منه. ولكن عند العمل مع البرنامج المساعد memcached multiget على عدة مفاتيح مع بادئات مختلفة ، هذه ممارسة عادية. وتتوقع نفس الشيء من البرنامج المساعد memcached MySQL. لكن لا :(
INCR ، DEL
لقد قدمت بالفعل أمثلة على طلبات GET / SET. تحتوي استعلامات INCR و DEL على ميزة. يكمن في حقيقة أنها تعمل فقط عند استخدام table_id الافتراضي:
DELETE @@numbers.1 ERROR get @@numbers VALUE @@numbers 0 24 test/numbers END delete 1 DELETED
قيود بروتوكول Memcached
يحتوي Memcached على بروتوكول نصي يفرض بعض القيود. على سبيل المثال ، يجب ألا تحتوي مفاتيح memcached على أحرف مسافة بيضاء (مسافة ، سطر تغذية). إذا نظرت مرة أخرى إلى وصف الجدول من مثالنا:
CREATE TABLE `auth` ( `email` varchar(96) NOT NULL, `password` varchar(64) NOT NULL, `type` varchar(32) NOT NULL DEFAULT '', PRIMARY KEY (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
هذا يعني أنه في حقل "البريد الإلكتروني" يجب ألا يكون هناك مثل هذه الشخصيات.
أيضًا ، يجب أن تكون مفاتيح memcached أقل من 250 بايت (بايت ، وليس أحرف). إذا قمت بإرسال المزيد ، فسوف تحصل على خطأ:
"CLIENT_ERROR bad command line format"
بالإضافة إلى ذلك ، يجب على المرء أن يأخذ في الاعتبار حقيقة أن البرنامج المساعد memcached يضيف بناء الجملة الخاص به إلى بروتوكول memcached. على سبيل المثال ، يستخدم الحرف "|" كما فاصل الحقل في الاستجابة. تحتاج إلى التأكد من عدم استخدام هذا الرمز في الجدول الخاص بك. يمكن تكوين الفاصل ، ولكن سيتم تطبيق الإعدادات على جميع الجداول على خادم MySQL بأكمله.
محدد الحقل value_columns
إذا كنت بحاجة إلى إرجاع عدة أعمدة من خلال بروتوكول memcached ، كما في المثال الأول لدينا:
get @@auth.max@example.com VALUE @@auth.max@example.com 0 10 1234567|89 END
ثم يتم فصل قيم العمود بواسطة الفاصل القياسي "|". السؤال الذي يطرح نفسه: "ماذا سيحدث إذا ، على سبيل المثال ، الحرف" | "في الحقل الأول في السطر؟" سيعود البرنامج المساعد memcached في هذه الحالة السلسلة كما هي ، مثل هذا: 1234 | 567 | 89. في الحالة العامة ، من المستحيل فهم مكان وجود الحقل.
لذلك ، من المهم اختيار الفاصل الصحيح على الفور. ونظرًا لأنه سيتم استخدامه لجميع مفاتيح جميع الجداول ، فيجب أن يكون طابعًا عامًا لن يتم العثور عليه في أي مجال ستعمل عليه من خلال بروتوكول memcached.
ملخص
هذا لا يعني أن البرنامج المساعد memcached سيئة. ولكن يبدو أنه تمت كتابته لنظام عمل محدد: خادم MySQL مع جدول واحد يمكن الوصول إليه باستخدام بروتوكول memcached ، ويتم جعل table_id هذا افتراضيًا. يقوم العملاء بتأسيس اتصال مستمر مع المكون الإضافي Memcached وتقديم طلبات إلى table_id الافتراضي. ربما ، في مثل هذا المخطط ، كل شيء سوف يعمل بلا عيب. إذا ابتعدت عنك ، واجهت العديد من المضايقات.
ربما كنت تتوقع رؤية بعض تقارير أداء المكون الإضافي. لكننا لم نقرر بعد استخدامه في الأماكن المحملة بدرجة عالية. لقد استخدمناها فقط في عدد قليل من الأنظمة غير المحملة للغاية وهناك يعمل بنفس سرعة HandlerSocket ، لكننا لم نضع معايير صادقة. لكن مع ذلك ، يوفر البرنامج المساعد مثل هذه الواجهة التي يمكن للمبرمج أن يخطئ بها بسهولة - تحتاج إلى وضع الكثير من الفروق الدقيقة في الاعتبار. لذلك ، لسنا مستعدين بعد لاستخدام هذا البرنامج المساعد بكميات كبيرة.
لقد قدمنا بعض طلبات الميزات في متتبع أخطاء MySQL:
https://bugs.mysql.com/bug.php؟id=95091https://bugs.mysql.com/bug.php؟id=95092https://bugs.mysql.com/bug.php؟id=95093https://bugs.mysql.com/bug.php؟id=95094دعونا نأمل أن يقوم فريق تطوير البرنامج المساعد memcached بتحسين منتجه.