Memcached هو نظام تخزين مؤقت لكائنات الذاكرة الموزعة تم تطويره بواسطة danga.com (الفريق الفني الذي يقوم بتشغيل LiveJournal) لتقليل تحميل قاعدة البيانات وتحسين الأداء في الأنظمة الديناميكية. فيما يتعلق بهذا الأمر، أعتقد أن العديد من الأشخاص قد استخدموه. الغرض من هذه المقالة هو الحصول على فهم أعمق لهذا البرنامج الممتاز مفتوح المصدر من خلال تنفيذ memcached وتحليل التعليمات البرمجية له، وتحسينه بشكل أكبر وفقًا لاحتياجاتنا. أخيرًا، من خلال تحليل ملحق BSM_Memcache، سنعمل على تعميق فهمنا لاستخدام memcached.
قد تتطلب بعض محتويات هذه المقالة أساسًا رياضيًا أفضل كمساعدة.
◎ ما هو Memcached؟
قبل أن نتناول هذه المسألة، يجب علينا أولاً أن نفهم ما هو "ليس كذلك". يستخدمه العديد من الأشخاص كحامل تخزين مثل SharedMemory. على الرغم من أن memcached يستخدم نفس طريقة "Key=>Value" لتنظيم البيانات، إلا أنه يختلف تمامًا عن ذاكرات التخزين المؤقت المحلية مثل الذاكرة المشتركة وAPC. يتم توزيع Memcached، مما يعني أنها ليست محلية. يكمل الخدمة بناءً على الاتصال بالشبكة (بالطبع يمكنه أيضًا استخدام المضيف المحلي وهو برنامج مستقل عن التطبيق أو عملية خفية (وضع البرنامج الخفي).
تستخدم Memcached مكتبة libevent لتنفيذ خدمات اتصال الشبكة ويمكنها نظريًا التعامل مع عدد غير محدود من الاتصالات، ومع ذلك، على عكس Apache، فهي غالبًا ما تكون موجهة نحو اتصالات مستمرة مستقرة، لذا فإن قدراتها المتزامنة الفعلية محدودة. في ظل ظروف متحفظة، يبلغ الحد الأقصى لعدد الاتصالات المتزامنة لـ memcached 200، وهو ما يرتبط بقدرة سلسلة عمليات Linux. للحصول على معلومات حول libevent، يرجى الرجوع إلى الوثائق ذات الصلة. يختلف استخدام الذاكرة Memcached أيضًا عن APC. يعتمد APC على الذاكرة المشتركة وMMAP له خوارزمية تخصيص الذاكرة وطريقة الإدارة الخاصة به، ولا علاقة له بالذاكرة المشتركة وليس له أي قيود على الذاكرة المشتركة. في العادة، يمكن لكل عملية مخزنة مؤقتًا إدارة مساحة ذاكرة تبلغ 2 جيجابايت هناك حاجة إلى مساحة أكبر، ويمكن زيادة عدد العمليات.
◎ما هي المناسبات التي يناسبها Memcached؟
في كثير من الحالات، تم إساءة استخدام memcached، الأمر الذي يؤدي حتماً إلى الشكاوى بشأنه. كثيرًا ما أرى أشخاصًا ينشرون في المنتديات، مثل "كيفية تحسين الكفاءة"، والرد هو "استخدام memcached". أما بالنسبة لكيفية استخدامه، ومكان استخدامه، وفيم يتم استخدامه، فلا توجد جملة. Memcached ليس علاجًا سحريًا، كما أنه ليس مناسبًا لجميع الحالات.
Memcached هو نظام تخزين مؤقت لكائنات الذاكرة "موزعة"، وهذا يعني أنه بالنسبة لتلك التطبيقات التي لا تحتاج إلى "التوزيع"، أو لا تحتاج إلى المشاركة، أو أنها ببساطة صغيرة بما يكفي لتحتوي على خادم واحد فقط، فلن يتم استخدام memcached. على العكس من ذلك، فهو يؤدي أيضًا إلى إبطاء كفاءة النظام لأن اتصالات الشبكة تتطلب أيضًا موارد، حتى اتصالات UNIX المحلية. أظهرت بيانات الاختبار السابقة أن سرعات القراءة والكتابة المحلية المخزنة مؤقتًا أبطأ بعشرات المرات من مصفوفات ذاكرة PHP المباشرة، في حين أن أساليب APC والذاكرة المشتركة تشبه المصفوفات المباشرة. يمكن ملاحظة أنه إذا كانت ذاكرة التخزين المؤقت على المستوى المحلي فقط، فإن استخدام memcached غير اقتصادي للغاية.
غالبًا ما يتم استخدام Memcached كذاكرة تخزين مؤقت للواجهة الأمامية لقاعدة البيانات. نظرًا لأن تحليل SQL وعمليات القرص والحمل الزائد الآخر أقل بكثير من قاعدة البيانات، ويستخدم الذاكرة لإدارة البيانات، فيمكنه توفير أداء أفضل من قراءة قاعدة البيانات مباشرة، في الأنظمة الكبيرة، يكون من الصعب جدًا الوصول إلى نفس البيانات في كثير من الأحيان، يمكن لـ memcached تقليل ضغط قاعدة البيانات بشكل كبير وتحسين كفاءة تنفيذ النظام. بالإضافة إلى ذلك، غالبًا ما يتم استخدام memcached كوسيط تخزين لمشاركة البيانات بين الخوادم. على سبيل المثال، يمكن حفظ البيانات التي تحفظ حالة تسجيل الدخول الموحد للنظام في نظام SSO في memcached ومشاركتها بواسطة تطبيقات متعددة.
وتجدر الإشارة إلى أن memcached يستخدم الذاكرة لإدارة البيانات، لذا فهي متطايرة. عند إعادة تشغيل الخادم أو إنهاء عملية memcached، سيتم فقدان البيانات، لذلك لا يمكن استخدام memcached لاستمرار البيانات. كثير من الناس يسيئون فهم أن أداء memcached جيد جدًا، مثل المقارنة بين الذاكرة والقرص الصلب. في الواقع، لن يحصل memcached على مئات أو آلاف تحسينات في سرعة القراءة والكتابة باستخدام الذاكرة. إن عنق الزجاجة الفعلي يكمن في الشبكة الاتصال المرتبط باستخدام الذاكرة، بالمقارنة مع نظام قاعدة بيانات القرص، فإن الميزة هي أنه "خفيف" للغاية لأنه لا يوجد حمل زائد وطرق قراءة وكتابة مباشرة، ويمكنه بسهولة التعامل مع كمية كبيرة جدًا من تبادل البيانات، لذلك غالبًا ما يكون هناك نطاقان تردديان لشبكة جيجابت، وكلها محملة بالكامل، ولا تشغل عملية memcached نفسها الكثير من موارد وحدة المعالجة المركزية.
◎كيف يعمل Memcached
في الأقسام التالية، من الأفضل للقراء إعداد نسخة من الكود المصدري لـ memcached.
Memcached هو برنامج خدمة شبكة تقليدي، إذا تم استخدام المعلمة -d عند البدء، فسيتم تنفيذه كعملية خفية. يتم إكمال عملية إنشاء البرنامج الخفي بواسطة daemon.c. يحتوي هذا البرنامج على وظيفة خفية واحدة فقط، وهي بسيطة جدًا (إذا لم يتم تقديم تعليمات خاصة، فيجب أن يخضع الكود إلى 1.2.1):
الكود:[نسخ إلى الحافظة]#تتضمن <fcntl.h>
#تشمل <stdlib.h>
#تشمل <unistd.h>
int
الشيطان (nochdir، no Close)
إنت nochdir، no Close؛
{
كثافة العمليات
فد التبديل (شوكة ()) {
الحالة-1:
العودة (-1)؛
الحالة 0:
استراحة؛
تقصير:
_exit(0);
}
إذا (setsid() == -1)
العودة (-1)؛
إذا (! nochdir)
(void)chdir("/");
if (!no Close && (fd = open("/dev/null", O_RDWR, 0)) != -1) {
(باطل)dup2(fd, STDIN_FILENO);
(باطل)dup2(fd, STDOUT_FILENO);
(باطل)dup2(fd, STDERR_FILENO);
إذا (fd > STDERR_FILENO)
(باطل)إغلاق(fd);
}
العودة (0)؛
}
بعد أن تقوم هذه الوظيفة بتفرع العملية بأكملها، تخرج العملية الأصلية، ثم تنقل STDIN وSTDOUT وSTDERR إلى الأجهزة الفارغة، ويتم إنشاء البرنامج الخفي بنجاح.
عملية بدء تشغيل Memcached نفسها هي كما يلي في الوظيفة الرئيسية لـ memcached.c:
1. اتصل بـ settings_init() لتعيين معلمات التهيئة.
2. اقرأ المعلمات من أمر بدء التشغيل لتعيين قيمة الإعداد
3. قم بتعيين معلمة LIMIT
4. ابدأ مراقبة مأخذ توصيل الشبكة (في حالة وجود مسار غير مأخذ توصيل) (يتم دعم وضع UDP بعد الإصدار 1.2)
5. التحقق من هوية المستخدم (Memcached لا يسمح ببدء هوية الجذر)
6. في حالة وجود مسار المقبس، افتح اتصال UNIX المحلي (أنبوب Sock)
7. إذا بدأت في الوضع -d، فقم بإنشاء عملية خفية (استدعاء وظيفة البرنامج الخفي كما هو مذكور أعلاه)
8. تهيئة العنصر، الحدث، معلومات الحالة، التجزئة، الاتصال، اللوحة
9. إذا دخلت الإدارة حيز التنفيذ في الإعدادات، فقم بإنشاء مصفوفة دلو
10. تحقق مما إذا كانت صفحة الذاكرة بحاجة إلى القفل
11. تهيئة الإشارة والاتصال وحذف قائمة الانتظار
12. إذا كنت في الوضع الخفي، قم بمعرف العملية
13. يبدأ الحدث، وتنتهي عملية بدء التشغيل، وتدخل الوظيفة الرئيسية في الحلقة.
في الوضع الخفي، نظرًا لأنه تم توجيه stderr إلى الثقب الأسود، فلن تتم تغذية أي رسائل خطأ مرئية أثناء التنفيذ.
وظيفة الحلقة الرئيسية لـ memcached.c هي drive_machine. المعلمة الواردة هي مؤشر بنية يشير إلى الاتصال الحالي، ويتم تحديد الإجراء بناءً على حالة عضو الحالة.
يستخدم Memcached مجموعة من البروتوكولات المخصصة لإكمال تبادل البيانات، ويمكن الرجوع إلى مستند البروتوكول الخاص به: http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
في واجهة برمجة التطبيقات (API)، رموز السطر الجديد. موحدة كـ rn
◎طريقة إدارة الذاكرة الخاصة بـ Memcached
تمتلك Memcached طريقة فريدة جدًا لإدارة الذاكرة، ومن أجل تحسين الكفاءة، فإنها تستخدم أساليب التطبيق المسبق والتجميع لإدارة مساحة الذاكرة، بدلاً من استخدام malloc في كل مرة تحتاج فيها إلى كتابة البيانات. . حرر المؤشر عند حذف البيانات . يستخدم Memcached أسلوب التنظيم slab->chunk لإدارة الذاكرة.
توجد بعض الاختلافات في خوارزميات تقسيم مساحة اللوحة في slabs.c في الإصدارين 1.1 و1.2، والتي سيتم تقديمها بشكل منفصل لاحقًا.
يمكن فهم Slab على أنها كتلة ذاكرة، وهي أصغر وحدة يمكن لـ memcached تطبيقها على الذاكرة في وقت واحد. في memcached، الحجم الافتراضي للوحة هو 1048576 بايت (1 ميجابايت)، لذا فإن memcached يستخدم كامل ميغابايت من الذاكرة. يتم تقسيم كل شريحة إلى عدة أجزاء، وكل قطعة تخزن عنصرًا. يحتوي كل عنصر أيضًا على بنية العنصر ومفتاحه وقيمته (لاحظ أن القيمة في الذاكرة المؤقتة هي مجرد سلسلة). تشكل الألواح قوائم مرتبطة وفقًا لمعرفاتها الخاصة، ويتم تعليق هذه القوائم المرتبطة على مصفوفة slabclass وفقًا لمعرفاتها. تبدو البنية بأكملها مثل مصفوفة ثنائية الأبعاد. طول slabclass هو 21 في 1.1 و 200 في 1.2.
slab لها حجم قطعة أولي، وهو 1 بايت في 1.1 و80 بايت في 1.2. توجد قيمة عامل في 1.2، والتي تكون افتراضيًا 1.25.
في 1.1، يتم التعبير عن حجم القطعة بالحجم الأولي * 2^n, n is classid، أي: اللوحة ذات المعرف 0 لها حجم قطعة 1 بايت، اللوحة ذات المعرف 1 لها حجم قطعة 2 بايت، اللوحة ذات المعرف 2 لها حجم قطعة 4 بايت... اللوحة ذات المعرف 20 لديه قطعة بحجم 4 بايت، الحجم هو 1 ميجابايت، مما يعني أن هناك قطعة واحدة فقط في اللوحة ذات المعرف 20:
الكود: [نسخ إلى الحافظة] void slabs_init(size_t Limit) {
كثافة العمليات أنا؛
int size=1;
mem_limit = Limit;
ل(i=0; i<=POWER_LARGEST; i++, size*=2) {
slabclass[i].size = size;
slabclass[i].perslab = POWER_BLOCK / size;
slabclass[i].slots = 0;
slabclass[i].sl_curr = slabclass[i].sl_total = slabclass[i].slabs = 0;
slabclass[i].end_page_ptr = 0;
slabclass[i].end_page_free = 0;
slabclass[i].slab_list = 0;
slabclass[i].list_size = 0;
slabclass[i].killing = 0;
}
/* لمجموعة الاختبار: تزييف المبلغ الذي قمنا به بالفعل */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
إذا (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
}
}
/* التخصيص المسبق للألواح بشكل افتراضي، ما لم يكن متغير البيئة
للاختبار تم ضبطه على شيء غير صفر */
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
إذا (!pre_alloc || atoi(pre_alloc)) {
slabs_preallocate(limit / POWER_BLOCK);
}
}
}
في 1.2، يتم التعبير عن حجم القطعة بالحجم الأولي * f^n، وf هو العامل الذي تم تعريفه في memcached.c، وn مصنف، وفي الوقت نفسه، لا يلزم تهيئة جميع الرؤوس الـ 201 لأن العامل متغير وتتكرر حلقات التهيئة فقط حتى يصل الحجم المحسوب إلى نصف حجم اللوحة، ويبدأ من id1، أي: لوح بمعرف 1، حجم كل قطعة هو 80 بايت، لوح بمعرف 2، حجم كل قطعة هو 80* f، المعرف هو 3 ألواح، حجم كل قطعة هو 80*f^2، وحجم التهيئة له قيمة تصحيح CHUNK_ALIGN_BYTES لضمان محاذاة n-byte (مما يضمن أن النتيجة هي مضاعف متكامل لـ CHUNK_ALIGN_BYTES). بهذه الطريقة، في ظل الظروف القياسية، ستتم تهيئة memcached1.2 إلى id40. حجم كل قطعة في هذه اللوحة هو 504692، وهناك قطعتان في كل لوحة. أخيرًا، ستضيف الدالة slab_init id41 في النهاية، وهو عبارة عن كتلة كاملة، أي أن هناك قطعة واحدة فقط بحجم 1 ميجابايت في هذه اللوحة:
الكود: [نسخ إلى الحافظة] void slabs_init(size_t Limit, double Factor) {
int i = POWER_SMALLEST - 1;
unsigned int size = sizeof(item) + settings.chunk_size
/* العامل 2.0 يعني استخدام سلوك الذاكرة المخزنة مؤقتًا */
إذا (العامل == 2.0 && الحجم < 128)
الحجم = 128
؛
memset(slabclass, 0, sizeof(slabclass));
while (++i < POWER_LARGEST && size <= POWER_BLOCK / 2) {
/* تأكد من محاذاة العناصر دائمًا مع n بايت */
إذا (الحجم٪ CHUNK_ALIGN_BYTES)
size += CHUNK_ALIGN_BYTES - (الحجم % CHUNK_ALIGN_BYTES)؛
slabclass[i].size = size;
slabclass[i].perslab = POWER_BLOCK / slabclass[i].size;
الحجم *= العامل؛
إذا (الإعدادات. مطول > 1) {
fprintf(stderr, "فئة اللوحة %3d: حجم القطعة %6d perslab %5dn",
i، slabclass[i].size، slabclass[i].perslab);
}
}
power_largest = i;
slabclass[power_largest].size = POWER_BLOCK;
slabclass[power_largest].perslab = 1;
/* لمجموعة الاختبار: تزوير المبلغ الذي قمنا به بالفعل */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
إذا (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
}
}
#ifndef DONT_PREALLOC_SLABS
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
إذا (!pre_alloc || atoi(pre_alloc)) {
slabs_preallocate(limit / POWER_BLOCK);
}
}
#endif
}
كما يتبين مما سبق، فإن تخصيص ذاكرة memcached يكون زائدًا عن الحاجة. عندما لا تكون اللوحة قابلة للقسمة على حجم القطعة التي تمتلكها، يتم تجاهل المساحة المتبقية في نهاية اللوحة. على سبيل المثال، في id40، تشغل قطعتان 1009384 بايت، يبلغ إجمالي حجم هذه اللوحة 1 ميجابايت، لذا يتم إهدار 39192 بايت.
يستخدم Memcached هذه الطريقة لتخصيص الذاكرة من أجل تحديد موقع فئة اللوحة بسرعة من خلال طول العنصر، وهي تشبه إلى حد ما التجزئة، لأنه يمكن حساب طول العنصر على سبيل المثال، 300 بايت يمكنك الحصول على أنه يجب تخزينه في شريحة id7، لأنه وفقًا لطريقة الحساب المذكورة أعلاه، يبلغ حجم قطعة id6 252 بايت، وحجم قطعة id7 هو 316 بايت، وحجم قطعة id8 هو 396 بايت. مما يعني أنه يجب تخزين جميع العناصر ذات الـ 252 إلى جميع العناصر ذات الـ 316 بايت في id7. وبالمثل، في الإصدار 1.1، يمكن أيضًا حساب أنه يقع بين 256 و512، ويجب وضعه في id9 بحجم قطعة يبلغ 512 (نظام 32 بت).
عند تهيئة Memcached، ستتم تهيئة الألواح (كما ترون سابقًا، يتم استدعاء slabs_init() في الوظيفة الرئيسية). سوف يتحقق من DONT_PREALLOC_SLABS الثابت في slabs_init(). إذا لم يتم تعريف ذلك، فهذا يعني أنه تمت تهيئة اللوحة باستخدام الذاكرة المخصصة مسبقًا، بحيث يتم إنشاء لوحة لكل معرف بين جميع فئات الألواح التي تم تعريفها. هذا يعني أن الإصدار 1.2 سيخصص 41 ميجابايت من مساحة اللوحة بعد بدء العملية في البيئة الافتراضية، أثناء هذه العملية، يحدث تكرار ثانٍ للذاكرة في memcached، لأنه من الممكن عدم استخدام المعرف على الإطلاق، ولكنه أيضًا A. يتم تطبيق اللوحة بشكل افتراضي، وسوف تستخدم كل لوحة 1 ميجابايت من الذاكرة.
عند استخدام اللوحة، ويلزم إدخال عنصر جديد بهذا المعرف، سيتم إعادة تقديم الطلب للحصول على لوحة جديدة slab، المعرف المقابل سوف تنمو القائمة المرتبطة باللوح بشكل كبير في وظيفة Grow_slab_list، يتغير طول هذه السلسلة من 1 إلى 2، من 2 إلى 4، من 4 إلى 8...:
الكود: [نسخ إلى الحافظة] ثابت int Grow_slab_list (معرف int غير موقع) {
slabclass_t *p = &slabclass[id];
إذا (ص->ألواح == ص->list_size) {
size_t new_size = p->list_size p->list_size * 2 : 16;
void *new_list = realloc(p->slab_list, new_size*sizeof(void*));
إذا (new_list == 0) يُرجع 0؛
p->list_size = new_size;
p->slab_list = new_list;
}
العودة 1؛
}
عند تحديد موقع العنصر، يتم استخدام وظيفة slabs_clsid. المعلمة الواردة هي حجم العنصر وقيمة الإرجاع هي classid. من هذه العملية، يمكن ملاحظة أن التكرار الثالث للذاكرة memcached يحدث أثناء عملية حفظ العنصر. يكون العنصر دائمًا أصغر من أو يساوي حجم القطعة. عندما يكون العنصر أصغر من حجم القطعة، يتم إهدار المساحة مرة أخرى.
◎ خوارزمية NewHash الخاصة بـ Memcached
يعتمد تخزين العناصر في Memcached على جدول تجزئة كبير. عنوانه الفعلي هو إزاحة القطعة في اللوحة، لكن موضعه يعتمد على نتيجة تجزئة المفتاح، الموجود في Primary_hashtable. يتم تعريف جميع عمليات التجزئة والعناصر في assoc.c و items.c.
يستخدم Memcached خوارزمية تسمى NewHash، وهي فعالة للغاية. هناك بعض الاختلافات بين NewHash في الإصدار 1.1 و1.2. لا تزال طريقة التنفيذ الرئيسية هي نفسها، وقد تم تنظيم وتحسين وظيفة التجزئة في الإصدار 1.2، كما أن قدرتها على التكيف أفضل.
مرجع النموذج الأولي لـ NewHash: http://burtleburtle.net/bob/hash/evahash.html . علماء الرياضيات دائمًا غريبون بعض الشيء، هاها ~
من أجل تسهيل التحويل، تم تحديد نوعين من البيانات، u4 وu1، وهو عدد صحيح طويل غير موقّع، وu1 هو حرف غير موقّع (0-255).
للحصول على رموز محددة، يرجى الرجوع إلى حزم التعليمات البرمجية المصدرية 1.1 و1.2.
انتبه إلى طول جدول التجزئة. هناك أيضًا فرق بين 1.1 و1.2. في 1.1، تم تعريف ثابت HASHPOWER على أنه 20، وطول جدول التجزئة هو حجم التجزئة (HASHPOWER)، وهو 4 ميجابايت (حجم التجزئة هو ماكرو). أن 1 يتم إزاحته إلى اليمين بمقدار n بت). في 1.2 يكون المتغير 16، أي أن طول الجدول القابل للتجزئة هو 65536:
الكود: [نسخ إلى الحافظة] typedef unsigned long int ub4; /* كميات 4 بايت غير موقعة */
typedef unsigned char ub1; /* كميات 1 بايت غير موقعة */
#define hashsize(n) ((ub4)1<<(n))
#تعريف hashmask(n) (hashsize(n)-1)
في assoc_init()، ستتم تهيئة Primary_hashtable وتشمل عمليات التجزئة المقابلة: assoc_find()، assoc_expand()، assoc_move_next_bucket()، assoc_insert()، assoc_delete()، المتوافقة مع عمليات القراءة والكتابة للعنصر. من بينها، assoc_find() هي دالة تبحث عن عنوان العنصر المقابل استنادًا إلى المفتاح وطول المفتاح (لاحظ أنه في لغة C، يتم تمرير السلسلة وطول السلسلة في كثير من الأحيان مباشرة في نفس الوقت بدلاً من القيام بـ strlen داخل الوظيفة )، وما يتم إرجاعه هو مؤشر بنية العنصر، وعنوان بياناته موجود على قطعة في اللوحة.
items.c هو برنامج تشغيل عناصر البيانات، ويتضمن كل عنصر كامل عدة أجزاء، والتي تم تعريفها في item_make_header () على النحو التالي:
المفتاح: المفتاح
nkey: طول المفتاح
الأعلام: علامة محددة من قبل المستخدم (في الواقع، هذه العلامة غير ممكّنة في الذاكرة المؤقتة)
nbytes: طول القيمة (بما في ذلك رمز السطر الجديد rn)
لاحقة: لاحقة المخزن المؤقت
nsuffix: طول اللاحقة
للعنصر الكامل هو طول المفتاح + طول القيمة + طول اللاحقة + حجم بنية العنصر (32 بايت).
يتم تعليق كل مجموعة في جدول التجزئة بقائمة مرتبطة مزدوجة. أثناء item_init()، تمت تهيئة المصفوفات الثلاثة من الرؤوس والذيول والأحجام إلى 0. أحجام هذه المصفوفات الثلاثة هي LARGEST_ID الثابت (القيمة الافتراضية هي 255، تتطلب هذه القيمة تعديلها باستخدام العامل)، في كل مرة يتم فيها استدعاء item_assoc()، سيحاول أولاً الحصول على قطعة مجانية من اللوحة. إذا لم يكن هناك قطعة متاحة، فسوف يقوم بمسح القائمة المرتبطة 50 مرة للحصول على القطعة التي كانت تم تشغيله بواسطة عنصر LRU، وقم بإلغاء ربطه، ثم قم بإدراج العنصر المراد إدراجه في القائمة المرتبطة.
انتبه إلى عضو إعادة حساب العنصر. بعد إلغاء ربط العنصر، تتم إزالته فقط من القائمة المرتبطة، ولا يتم تحريره على الفور، بل يتم وضعه في قائمة انتظار الحذف (وظيفة item_unlink_q()).
يتوافق العنصر مع بعض عمليات القراءة والكتابة، بما في ذلك الإزالة والتحديث والاستبدال، بالطبع، أهمها هي عملية التخصيص.
ميزة أخرى للعنصر هي أن لديه وقت انتهاء الصلاحية، وهي ميزة مفيدة جدًا لـ memcached. تعتمد العديد من التطبيقات على انتهاء صلاحية عنصر memcached، مثل تخزين الجلسة، وأقفال التشغيل، وما إلى ذلك. تقوم الدالة item_flush_expired() بفحص العناصر الموجودة في الجدول وتنفيذ عملية إلغاء الارتباط للعناصر منتهية الصلاحية. بالطبع، هذا مجرد إجراء إعادة تدوير، وفي الواقع، يلزم أيضًا تقدير الوقت عند الحصول على:
الكود: [نسخ إلى الحافظة]/* تنتهي صلاحية العناصر الأحدث من الإعداد الأقدم */.
باطل item_flush_expired() {
كثافة العمليات أنا؛
البند *iter، *next؛
إذا (! settings.oldest_live)
يعود؛
لـ (i = 0; i < LARGEST_ID; i++) {
/* يتم فرز LRU بترتيب زمني تنازلي، والطابع الزمني للعنصر
* ليس أحدث من آخر وقت وصول له، لذلك نحتاج فقط إلى المشي
* نعود حتى نصل إلى عنصر أقدم من أقدم وقت.
* سيؤدي التحقق الأقدم من الحياة إلى انتهاء صلاحية العناصر المتبقية تلقائيًا.
*/
for (iter = heads[i]; iter != NULL; iter = next) {
إذا (iter->time >= settings.oldest_live) {
next = iter->next;
إذا ((iter->it_flags & ITEM_SLABBED) == 0) {
item_unlink(iter);
}
} آخر {
/* لقد وصلنا إلى العنصر القديم الأول. تابع إلى قائمة الانتظار التالية.
استراحة؛
}
}
}
}
الكود: [نسخ إلى الحافظة]/* غلاف حول assoc_find الذي يقوم بمنطق انتهاء الصلاحية/الحذف البطيء */
العنصر *get_item_notedeleted(char *key, size_t nkey, int *delete_locked) {
item *it = assoc_find(key, nkey);
إذا (delete_locked) *delete_locked = 0;
إذا (it && (it->it_flags & ITEM_DELETED)) {
/* تم وضع علامة عليه على أنه مقفل للحذف، فلنرى ما إذا كان هذا الشرط
لقد فات موعد استحقاقه، ولم يحن موعد الحذف لمدة 5 ثوانٍ
لم أصل إليه بعد... */
إذا (!item_delete_lock_over(it)) {
إذا (delete_locked) *delete_locked = 1;
هو = 0؛
}
}
إذا (it && settings.oldest_live && settings.oldest_live <=current_time &&
it->الوقت <= settings.oldest_live) {
item_unlink(it);
هو = 0؛
}
إذا (it && it->exptime && it->exptime <=current_time) {
item_unlink(it);
هو = 0؛
}
إعادته؛
}
تعد طريقة إدارة الذاكرة الخاصة بـ Memcached متطورة وفعالة للغاية، فهي تقلل بشكل كبير من عدد التخصيصات المباشرة لذاكرة النظام، وتقلل من الحمل الوظيفي واحتمال تجزئة الذاكرة. على الرغم من أن هذه الطريقة ستتسبب في بعض الهدر الزائد، إلا أن هذا الهدر تافه في النظام الكبير التطبيقات.
◎ تحتوي طريقة حساب المعلمات النظرية لـ Memcached
على العديد من المعلمات التي تؤثر على عمل memcached:
الثابت REALTIME_MAXDELTA 60*60*24*30
الحد الأقصى لوقت انتهاء الصلاحية هو 30 يومًا
مجانًا (= 200) في conn_init ()
الحد الأقصى لعدد الاتصالات المتزامنة
ثابت KEY_MAX_LENGTH 250
الحد الأقصى لإعدادات طول المفتاح
. العامل (=1.25)
سيؤثر العامل على حجم خطوة
إعدادات القطعة.maxconns (= 1024)
الحد الأقصى لإعدادات الاتصال الناعم
.chunk_size (=48)
طول مفتاح + قيمة مقدر بشكل متحفظ، يستخدم لإنشاء طول القطعة (1.2) في id1. طول القطعة للمعرف 1 يساوي هذه القيمة بالإضافة إلى طول بنية العنصر (32)، وهو 80 بايت الافتراضية.
ثابت POWER_SMALLEST 1
الحد الأدنى للفئة (1.2)
POWER_LARGEST 200
الحد الأقصى للتصنيف (1.2)
POWER_BLOCK 1048576
حجم اللوح الافتراضي
CHUNK_ALIGN_BYTES (sizeof(void *))
تأكد من أن حجم القطعة هو عدد صحيح مضاعف لهذه القيمة لمنع تجاوز الحدود (يختلف طول الفراغ * باختلاف الأنظمة، فهو 4 على أنظمة 32 بت القياسية)
ثابت ITEM_UPDATE_INTERVAL 60
الفاصل الزمني لتحديث قائمة الانتظار
ثابت LARGEST_ID 255
الحد الأقصى لعدد العناصر في القائمة المرتبطة (لا يمكن أن تكون هذه القيمة أصغر من أكبر فئة)
قوة التجزئة المتغيرة (HASHPOWER ثابت في 1.1)
تحديد حجم جدول التجزئة
بناءً على إعدادات المحتوى والمعلمات المقدمة أعلاه، يمكن حساب بعض النتائج:
1. لا يوجد حد أقصى للبرنامج لعدد العناصر التي يمكن حفظها في memcached، وكان بياني السابق 1 مليون خطأ.
2. بافتراض أن خوارزمية NewHash بها تصادمات موحدة، فإن عدد الدورات للعثور على عنصر ما هو إجمالي عدد العناصر مقسومًا على الحجم القابل للتجزئة (المحدد بواسطة قوة التجزئة)، وهو خطي.
3. يحدد Memcached الحد الأقصى المسموح به للعنصر بـ 1 ميجابايت، وسيتم تجاهل البيانات الأكبر من 1 ميجابايت.
4. استخدام مساحة Memcached له علاقة كبيرة بخصائص البيانات، ويرتبط أيضًا بثابت DONT_PREALLOC_SLABS. في أسوأ الحالات، سيتم إهدار 198 لوحًا (تتركز جميع العناصر في لوح واحد، ويتم تخصيص جميع المعرفات البالغ عددها 199 معرفًا بالكامل).
◎ تحسين الطول الثابت لـ Memcached
استنادًا إلى الأوصاف الواردة في الأقسام أعلاه، لدي فهم أكثر تعمقًا لـ memcached. لا يمكن تحسينه إلا بناءً على الفهم المتعمق.
تم تصميم Memcached نفسها للبيانات ذات الطول المتغير، وفقًا لخصائص البيانات، يمكن القول إنها تصميم "موجه للعامة". ومع ذلك، في كثير من الأحيان، لا تكون بياناتنا "عالمية". التوزيع غير الموحد، أي أن طول البيانات يتركز في عدة مجالات (مثل حفظ جلسات المستخدم)؛ والحالة الأخرى الأكثر تطرفًا هي البيانات ذات الطول المتساوي (مثل القيم الأساسية ذات الطول الثابت، والبيانات ذات الطول الثابت في الغالب). تظهر في الوصول أو الإحصائيات عبر الإنترنت أو أقفال التنفيذ).
ندرس هنا بشكل أساسي الحل الأمثل للبيانات ذات الطول الثابت (1.2). البيانات المركزية الموزعة ذات الطول المتغير هي للمرجعية فقط وسهلة التنفيذ.
لحل البيانات ذات الطول الثابت، أول شيء يجب حله هو مشكلة تخصيص الألواح. أول شيء يجب تأكيده هو أننا لا نحتاج إلى الكثير من الألواح بأطوال قطع مختلفة من أجل تعظيم الاستخدام من الأفضل أن تكون القطع والعناصر متساوية الطول، لذا قم أولاً بحساب طول العنصر.
كانت هناك خوارزمية لحساب طول العنصر من قبل. تجدر الإشارة إلى أنه بالإضافة إلى طول السلسلة، يجب إضافة طول بنية العنصر البالغ 32 بايت.
لنفترض أننا حسبنا أننا بحاجة إلى حفظ 200 بايت من البيانات ذات الطول المتساوي.
والخطوة التالية هي تعديل العلاقة بين طول الطبقة وطول القطعة. في الإصدار الأصلي، هناك علاقة مقابلة بين طول القطعة والفئة. الآن إذا تم تعيين جميع القطع على 200 بايت، فإن هذه العلاقة غير موجودة. تتمثل إحدى الطرق في استخدام معرف ثابت فقط لبنية التخزين بأكملها، أي استخدام فتحة واحدة فقط من أصل 199 فتحة. وفي ظل هذا الشرط، يجب تحديد DONT_PREALOC_SLABS لتجنب نفايات التخصيص المسبق الإضافية. هناك طريقة أخرى تتمثل في إنشاء علاقة تجزئة لتحديد الفئة من العنصر. لا يمكنك استخدام الطول كمفتاح. يمكنك استخدام بيانات متغيرة مثل نتيجة NewHash للمفتاح، أو إجراء التجزئة مباشرةً بناءً على المفتاح ( يجب أن تكون مفاتيح البيانات ذات الطول الثابت بنفس الطول). من أجل التبسيط هنا، نختار الطريقة الأولى وعيب هذه الطريقة هو أنه يتم استخدام معرف واحد فقط عندما تكون كمية البيانات كبيرة جدًا، ستكون سلسلة الألواح طويلة جدًا (لأن جميع البيانات مزدحمة). سلسلة واحدة). تكلفة العبور مرتفعة نسبيا.
تم تقديم الأنواع الثلاثة لتكرار المساحة مسبقًا. إن تحديد طول القطعة مساويًا لطول العنصر يحل مشكلة هدر المساحة الأولى، ويحل مشكلة هدر المساحة الثانية )؟ لحل هذه المشكلة، تحتاج إلى تعديل ثابت POWER_BLOCK بحيث يكون حجم كل لوح مساويًا تمامًا لعدد صحيح مضاعف لطول القطعة، بحيث يمكن تقسيم اللوحة إلى قطع n. يجب أن تكون هذه القيمة أقرب إلى 1 ميجابايت. إذا كانت كبيرة جدًا، فستتسبب أيضًا في التكرار. إذا كانت صغيرة جدًا، فسوف تتسبب في تخصيص عدد كبير جدًا من العناصر. وفقًا لطول القطعة 200، اختر 1000000 كقيمة POWER_BLOCK بهذه الطريقة، سيكون حجم اللوحة مليون بايت، وليس 1048576. تم حل جميع مشكلات التكرار الثلاثة، وسيتم تحسين استخدام المساحة بشكل كبير.
قم بتعديل الدالة slabs_clsid بحيث تُرجع مباشرة قيمة ثابتة (مثل 1):
الكود: [نسخ إلى الحافظة] غير موقعة int slabs_clsid(size_t size) {
العودة 1؛
}
قم بتعديل الدالة slabs_init، وقم بإزالة الجزء الذي يتكرر لإنشاء جميع سمات الفئة، وأضف slabclass[1] مباشرة:
الكود:[نسخ إلى الحافظة]slabclass[1].size = 200; //200 بايت لكل قطعة
slabclass[1].perslab = 5000; //1000000/200
◎ عميل Memcached
Memcached هو برنامج خدمة، عند استخدامه، يمكنك الاتصال بخادم memcached وفقًا لبروتوكوله، وإرسال الأوامر إلى عملية الخدمة، ثم تشغيل البيانات المذكورة أعلاه. لسهولة الاستخدام، لدى memcached العديد من برامج العملاء المتاحة، والتي تتوافق مع العديد من اللغات، ويوجد عملاء بلغات مختلفة. تتضمن تلك المستندة إلى لغة C libmemcache وAPR_Memcache؛ وتتضمن تلك المستندة إلى Perl Cache::Memcached؛ وهناك أيضًا دعم لـ Python وRuby وJava وC# ولغات أخرى. PHP لديها أكبر عدد من العملاء، ليس فقط الامتدادين mcache وPECL memcache، ولكن أيضًا عددًا كبيرًا من فئات التغليف المكتوبة بواسطة PHP، فيما يلي مقدمة لكيفية استخدام memcached في PHP:
تتم إعادة تغليف ملحق mcache استنادًا إلى libmemcache. . لم يصدر libmemcache نسخة مستقرة. الإصدار الحالي هو 1.4.0-rc2، والذي يمكن العثور عليه هنا. الميزة السيئة للغاية في libmemcache هي أنه يكتب الكثير من رسائل الخطأ إلى stderr. بشكل عام، عند استخدامه كـ lib، يتم توجيه stderr عادةً إلى أماكن أخرى، مثل سجل أخطاء Apache، وسوف ينتحر libmemcache، مما قد يتسبب في حدوث أمر غير طبيعي. ، ولكن أدائها لا يزال جيدا جدا.
تم تحديث ملحق mcache آخر مرة إلى 1.2.0-beta10. ربما استقال المؤلف، ولم يتوقف عن التحديث فحسب، بل لم يتمكن أيضًا من فتح موقع الويب (~_~) وكان عليه الذهاب إلى مكان آخر للحصول على هذا الامتداد غير المسؤول . بعد فك الضغط، تكون طريقة التثبيت كالمعتاد: phpize & Configure & make & make install تأكد من تثبيت libmemcache أولاً. استخدام هذا الامتداد بسيط:
الكود:[نسخ إلى الحافظة]<?php
$mc = memcache(); // إنشاء كائن اتصال memcache. لاحظ أن الجديد غير مستخدم هنا!
$mc->add_server('localhost', 11211); // إضافة عملية خدمة
$mc->add_server('localhost', 11212); // أضف عملية خدمة ثانية
$mc->set('key1', 'Hello'); // اكتب key1 => مرحبًا
$mc->set('key2', 'World', 10); // اكتب key2 => العالم، تنتهي صلاحيته خلال 10 ثوانٍ
$mc->set('arr1', array('Hello', 'World')); // اكتب مصفوفة
$key1 = $mc->get('key1'); // احصل على قيمة 'key1' وقم بتعيينها إلى $key1
$key2 = $mc->get('key2'); // احصل على قيمة 'key2' وقم بتعيينها إلى $key2. إذا تجاوزت 10 ثوانٍ، فلن تكون متاحة.
$arr1 = $mc->get('arr1'); // احصل على المصفوفة 'arr1'
$mc->delete('arr1'); // حذف 'arr1';
$mc->flush_all(); // حذف كافة البيانات
$stats = $mc->stats();
var_dump($stats); // معلومات الخادم عبارة عن مصفوفة
?>
تتمثل ميزة هذا الامتداد في أنه يمكنه بسهولة تنفيذ التخزين الموزع وموازنة التحميل، لأنه يمكن إضافة عناوين خدمة متعددة عند حفظ البيانات، وسيتم وضعها على خادم معين بناءً على نتيجة التجزئة، وهذه أيضًا إحدى ميزات libmemcache . يدعم libmemcache طرق التجزئة المركزية، بما في ذلك CRC32 وELF وPerl hash.
PECL memcache هو امتداد تم إصداره بواسطة PECL، الإصدار الأحدث هو 2.1.0، والذي يمكن الحصول عليه من موقع pecl. يمكن العثور على استخدام ملحق memcache في بعض أدلة PHP الأحدث، وهو مشابه جدًا لـ mcache، وهو مشابه جدًا:
الكود:[نسخ إلى الحافظة]<?php
$memcache = new Memcache;
$memcache->connect('localhost', 11211) أو يموت ("تعذر الاتصال");
$version = $memcache->getVersion();
echo "إصدار الخادم: ".$version."n";
$tmp_object = new stdClass;
$tmp_object->str_attr = 'اختبار';
$tmp_object->int_attr = 123;
$memcache->set('key', $tmp_object, false, 10) أو يموت ("فشل حفظ البيانات على الخادم");
echo "تخزين البيانات في ذاكرة التخزين المؤقت (ستنتهي صلاحية البيانات خلال 10 ثوانٍ)n"
$get_result = $memcache->get('key');
صدى "بيانات من ذاكرة التخزين المؤقت: n"
var_dump($get_result ?
>
يستخدم هذا الامتداد دفق PHP للاتصال مباشرة بخادم memcached وإرسال الأوامر من خلال المقبس. إنه ليس كاملاً مثل libmemcache، ولا يدعم العمليات الموزعة مثل add_server، ولكن لأنه لا يعتمد على برامج خارجية أخرى، فهو يتمتع بتوافق أفضل ومستقر نسبيًا. أما بالنسبة للكفاءة فالفرق ليس كبيرا.
بالإضافة إلى ذلك، هناك العديد من فئات PHP المتاحة، مثل MemcacheClient.inc.php، ويمكن العثور على العديد منها على phpclasses.org وهي بشكل عام عبارة عن إعادة تغليف لواجهة برمجة تطبيقات عميل Perl وتستخدم بطرق مماثلة.
◎BSM_Memcache
من منظور عميل C، يعد APR_Memcache برنامج عميل ناضجًا ومستقرًا للغاية ويدعم أقفال الخيوط والعمليات على المستوى الذري لضمان الاستقرار التشغيلي. ومع ذلك ، فإنه يعتمد على APR (سيتم تقديم APR في القسم الأخير) ولا يحتوي على مجموعة واسعة من التطبيقات مثل Libmemcache. لأنه لا يمكن تشغيله خارج بيئة APR. ومع ذلك ، يمكن تثبيت APR بشكل منفصل عن Apache.
BSM_MEMCACHE هو امتداد PHP يعتمد على APR_MEMCACHE الذي طورته في مشروع BS.MAGIC. هذا البرنامج بسيط للغاية ولا يفعل الكثير من الوظائف.
تختلف عن امتداد McAche الذي يدعم التخزين الموزعة متعددة الخدمات ، ويدعم BSM_MEMCACHE مجموعات متعددة من الخوادم. ، يتم تنفيذ النسخ الاحتياطي الساخن. بطبيعة الحال ، فإن تكلفة تنفيذ هذه الوظيفة هي التضحية بالأداء. عادة يمكنك الحصول عليها في المرة القادمة.
يدعم BSM_Memcache هذه الوظائف فقط:
الرمز: [نسخ إلى الحافظة] zend_function_entry bsm_memcache_functions [] =
{
php_fe (mc_get ، null)
php_fe (mc_set ، null)
php_fe (mc_del ، null)
php_fe (mc_add_group ، null)
php_fe (mc_add_server ، null)
php_fe (mc_shutdown ، null)
{null ، null ، null}
};
تُرجع دالة MC_ADD_Group عددًا صحيحًا (في الواقع يجب أن تكون كائنًا ، كنت كسولًا ~ _ ~) كمعرف المجموعة. addrort).
الرمز: [نسخ إلى الحافظة]/**
*إضافة مجموعة خادم
*/
php_function (mc_add_group)
{
APR_INT32_T GROUP_ID ؛
APR_STATUS_T RV
؛
{
error_param_count ؛
return_null () ؛
}
group_id = free_group_id () ؛
إذا (-1 == group_id)
{
return_false ؛
}
APR_MEMCACHE_T *MC ؛
RV = APR_MEMCACHE_CREATE (P ، MAX_G_SERVER ، 0 ، & MC
)
؛
}
الرمز: [نسخ إلى الحافظة]/**
* إضافة خادم إلى مجموعة
*/
php_function (mc_add_server)
{
APR_STATUS_T RV ؛
APR_INT32_T GROUP_ID ؛
مزدوج ز.
char *srv_str ؛
int srv_str_l
؛
{
error_param_count ؛
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc ، "ds" ، & g ، & srv_str ، & srv_str_l) == failure)
{
return_false ؛
}
group_id = (apr_int32_t) g
؛
{
return_false ؛
}
char *host ، *scope ؛
APR_PORT_T PORT
؛
إذا (APR_SUCCESS == RV)
{
// إنشاء كائن الخادم هذا
apr_memcache_server_t *st ؛
rv = apr_memcache_server_create (p ، مضيف ، منفذ ، 0 ، 64 ، 1024 ، 600 ، & st) ؛
إذا (APR_SUCCESS == RV)
{
if (null == mc_groups [group_id])
{
return_false ؛
}
// إضافة خادم
rv = apr_memcache_add_server (mc_groups [group_id] ، st)
؛
{
return_true ؛
}
}
}
return_false ؛
}
عند إعداد البيانات وحذفها ، حلقة من خلال جميع المجموعات:
الرمز: [نسخ إلى الحافظة]/**
* تخزين عنصر في جميع المجموعات
*/
php_function (mc_set)
{
char *مفتاح ، *القيمة ؛
int key_l ، value_l ؛
مزدوج TTL = 0 ؛
double set_ct = 0
؛
{
error_param_count ؛
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc ، "ss | d" ، & key ، & key_l ، & value ، & value_l ، ttl) == failure)
{
return_false ؛
}
// كتابة البيانات في كل كائن
APR_INT32_T I = 0 ؛
إذا (TTL <0)
{
TTL = 0 ؛
}
APR_STATUS_T RV
؛
{
إذا (0 == is_validate_group (i))
{
// اكتبها!
rv = apr_memcache_add (mc_groups [i] ، key ، value ، value_l ، (apr_uint32_t) ttl ، 0) ؛
إذا (APR_SUCCESS == RV)
{
set_ct ++ ؛
}
}
}
return_double (set_ct) ؛
}
في MC_GET ، يمكنك أولاً تحديد مجموعة بشكل عشوائي ثم البدء في الاقتراع من هذه المجموعة:
الرمز: [نسخ إلى الحافظة]/**
* جلب عنصر من مجموعة عشوائية
*/
php_function (mc_get)
{
char *key ، *value = null ؛
int key_l ؛
Apr_size_t value_l
؛
{
error_param_count ؛
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc ، "s" ، key ، & key_l) == failure)
{
return_mull () ؛
}
// سأحاول...
// قراءة عشوائية
apr_int32_t curr_group_id = random_group () ؛
APR_INT32_T I = 0 ؛
APR_INT32_T TRAW = 0 ؛
Apr_uint32_t flag ؛
Apr_memcache_t *oper ؛
APR_STATUS_T RV
؛
{
حاول = i + curr_group_id ؛
حاول = جرب ٪ max_group ؛
إذا (0 == is_validate_group (جرب))
{
// احصل على قيمة
oper = mc_groups [try] ؛
rv = apr_memcache_getp (mc_groups [try] ، p ، (const char *) key ، & value ، & value_l ، 0) ؛
إذا (APR_SUCCESS == RV)
{
return_string (القيمة ، 1) ؛
}
}
}
return_false ؛
}
الرمز: [نسخ إلى الحافظة]/**
* معرف مجموعة عشوائي
* لـ MC_GET ()
*/
APR_INT32_T Random_Group ()
{
بنية Timeval TV ؛
بنية Timezone TZ ؛
int
usec
؛
}
يشبه استخدام BSM_Memcache العملاء الآخرين:
الرمز: [نسخ إلى الحافظة] <؟ php
$ g1 = mc_add_group ()
$ g2 = mc_add_group ()
mc_add_server ($ g1 ، 'LocalHost: 11211') ؛
mc_add_server ($ g1 ، 'LocalHost: 11212') ؛
MC_ADD_SERVER ($ G2 ، '10 .0.0.16: 11211 ') ؛
mc_add_server ($ g2 ، '10.0.0.17: 11211 ')
؛
$ KEY = MC_GET ('KEY') ؛
MC_DEL ('KEY') ؛
mc_shutdown () ؛
?>
يمكن العثور على المعلومات ذات الصلة حول Apr_memcache هنا ، ويمكن تنزيل bsm_memcache من هذا الموقع.
◎ بيئة APR مقدمة
الاسم الكامل لـ APR: Apache Portable Runtime. إنها مجموعة من مكتبات اللغات عبر المنصات C التي تم إنشاؤها وصيانتها بواسطة مؤسسة Apache Software. يتم استخلاصه من Apache httpd1.x وهو مستقل عن httpd. يوفر APR العديد من واجهات API مريحة للاستخدام ، بما في ذلك الوظائف العملية مثل تجمعات الذاكرة ، وعمليات السلسلة ، والشبكات ، والصفائف ، وجداول التجزئة ، إلخ. يتطلب تطوير وحدة APACH2 التعرض للعديد من وظائف APR.
◎ PostScript
هذا هو مقالتي الأخيرة في سنة Bingxu من التقويم القمري (سنة ميلادها). بفضل sina.com لتوفير فرص البحث والزملاء في القسم لمساعدتهم.
Dr. NP02-13-2007