کشف حقیقت پشت سازگاری داده های لوا و ردیس

توسط

در


تیم ما در مجله پزشکی و درمان استین از Redis به عنوان یکی از صف های پیام ما استفاده می کند. سرور Redis به صورت یک تنه/تکثیر استقرار می شود. اخیراً اختلاف قابل توجهی در استفاده از CPU رپلیک های Redis را هنگام بالا آوردن سرویس مشاهده کرده ایم، حتی زمانی که رپلیک ها در حال استفاده نباشند و هیچ ترافیک خواندن به آن وجود نداشته باشد. با این حال، این مشکل پس از راه‌اندازی مجدد رپلیک حل می شود.

زیرا راه اندازی مجدد رپلیک هر بار مشکل را حل می کند، ما فکر کردیم که ممکن است به دلیل برخی از مشکلات تکرار الاستیکیش باشد و به آن توجه نکردیم. با این حال، یک جابجایی failover Redis اخیر به ما یادآوری کرد که در حقیقت رپلیک دارای مشکلاتی است و پس از جابجایی failover، رپلیک مشکل دار به عنوان یکی از rحرفهروش های خواندن، نیمه درست و با استفاده از مصارف بالا که بیانگر این است که کلاستر بعد از جابجایی failover کارایی ندارد. و این بار ما با شور و هیجان به بررسی مشکل پرداختیم. آنچه در تحقیقات ما پیدا کردیم به ما منجر به غوطه ور شدن عمیق در جزئیات Replication Redis و پیاده سازی آن شد.

آیا می دانستید که مستر/رپلیک Redis در برخی از سناریوها می تواند ناسازگار شود؟

آیا می دانستید که رمزگذاری اشیاء هش در مستر و رپلیک متفاوت است حتی اگر عملیات نوشتن دقیقاً همان عملیات باشد و به همان ترتیب باشد؟ برای متوجه شدن به خواندن ادامه دهید.

مشکل

نمودار زیر نشان دهنده استفاده از CPU مستر در مقابل رپلیک است بلافاصله پس از استقرار سرویس ما.

استفاده از CPU

از نمودار می توانید روند استفاده از CPU رپلیک را مشاهده کنید. استفاده از CPU رپلیک:

    بلافاصله پس از استقرار سرویس ما بالا می رود.بالاتر از مستر بعد از مدتی بالا می رود.بعد از راه اندازی مجدد به طور طبیعی برمی گردد.

بررسی سطحی

زیرا عالمت فقط زمانی بوجود می آید که سرویس ما راه اندازی می شود، ما همه اسکریپت هایی که فوراً پس از راه اندازی فعال می شوند را با دقت بررسی کردیم. اسکریپت نظارت لوا به عنوان مظنون مشکوک شناخته شد. این اسکریپت پیام های ناکارآمد نمونه های سرویس در صف را به نمونه های فعال سرویس مجدد توزیع می کند تا پیام ها توسط نمونه های سالم دیگری پردازش شوند.

ما چند آزمایش مرتبط با اسکریپت نظارت لوا با استفاده از دستور Redismonitor برای مقایسه رفتار اسکریپت در مستر و رپلیک انجام دادیم. یک نکته جانبی، زیرا این دستور تخریب عملکرد را ایجاد می کند، با صرف نظر از آن با احتیاط استفاده کنید. به بازگشت به اسکریپت، ما متعجب شدیم که اسکریپت نظارت بین مستر و رپلیک به طور متفاوت رفتار می کند:

    Redis اسکریپت را به طور جداگانه در مستر و رپلیک اجرا می کند. ما انتظار داشتیم که اسکریپت فقط در مستر اجرا شود و تغییرات نتیجه ای بر روی دومی تکرار شود. دستور RedisHGETALL که در اسکریپت استفاده می شود، کلید های هش را به ترتیب متفاوتی در مستر نسبت به رپلیک برمی گرداند.

به دلیل دلایل فوق، اسکریپت عدم اتساق داده ها بین مستر و رپلیک را به وجود می آورد. از آن لحظه به بعد، داده ها بین مستر و رپلیک با هم اختلاف دارند تا زمانی که کاملاً متفاوت شوند. به علت ناسازگاری، داده ها در دومی به درستی حذف نمی شوند و به طور نتیجه به یک مجموعه داده بسیار بزرگ می روند. هر عملیات بعدی روی مجموعه داده بزرگ نیاز به استفاده از CPU بیشتری دارد که توجیه می کند چرا استفاده از CPU رپلیک بیشتر از مستر است.

در راه اندازی مجدد رپلیک، داده ها همگام می شوند و قابل اعتماد می شوند که به همین دلیل استفاده از CPU به مقادیر طبیعی برمی گردد پس از راه‌اندازی مجدد.

غوطه ور شدن عمیق در HGETALL

ما می دانستیم که کلیدهای هش مرتب نیستند و نباید بر روی ترتیب تکیه کنیم. اما باز هم برای ما عجیب بود که حتی زمانی که ترتیب نوشتن یکسان بین مستر و رپلیک است، ترتیب ها متفاوت است. به علاوه اینکه حقیقت این است که ترتیب ها در محیط محلی ما با یک تنظیم مشابه ثابت است ترکیبی از کنجکاوی ما را بیشتر می کند.

بنابراین برای بهتر درک جادوی پایه Redis و جلوگیری از حشره های مشابه در آینده، تصمیم گرفتیم که بیشتر به Redis وب کد منبع خواندن و جزئیات بیشتری دریافت کنیم.

کد رفتار دستور HGETALL

دستور HGETALL به وسیله تابعgenericHgetallCommandمدیریت می شود و این تابع دستیابیhashTypeNextرا برای تکرار Hash object فراخوانی می کند. یک قطعه کد نمونه به شرح زیر نشان داده شده است:

/* Move to the next entry in the hash. Return C_OK when the next entry
 * could be found and C_ERR when the iterator reaches the end. */
int hashTypeNext(hashTypeIterator *hi) {
    if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
        // call zipListNext
    } else if (hi->encoding == OBJ_ENCODING_HT) {
        // call dictNext
    } else {
        serverPanic("Unknown hash encoding");
    }
    return C_OK;
}

از قطعه کد می توانید ببینید که شئ Hash Redis در واقع دو نمایش زیری دارد:

    ZIPLISTHASHTABLE (dict)

با مطالعه آنلاین کمکی ما متوجه شدیم که برای صرفه جویی در حافظه، Redis بین دو نمایش هش بر اساس محدودیت های زیر انتخاب می کند:

لحظه پرتاب

بر اساس این درک، ما رمزگشایی نوشته ها را در استاژینگ را بررسی کردیم.

stg-bookings-qu-002.pcxebj.0001.apse1.cache.amazonaws.com:6379> object encoding queue_stats
"hashtable"

stg-bookings-qu-001.pcxebj.0001.apse1.cache.amazonaws.com:6379> object encoding queue_stats
"ziplist"

با تعجب متوجه شدیم که رمزگذاری شئ Hash در مستر و رپلیک متفاوت بود. این بدان معنا است که اگر ما عناصری را در هش اضافه یا حذف کنیم، ترتیب کلیدها به دلیل عملیات hashtable در مقابل عملیات لیست متفاوت خواهد بود!

اکنون که ریشه علت را شناسایی کرده ایم، هنوز هم کنجکاو در مورد تفاوت در رمزگذاری بین مستر و رپلیک هستیم.

نحوه امکان اینکه نمایش زیرین متفاوت باشد؟

استدلال کردیم، “اگر عملیات نوشتن مستر و رپلیک همان عملیات بوده و به همان ترتیب انجام می شود، چرا نمایش زیرین همچنان متفاوت است؟”

برای پاسخ به این سوال، ما به طور دقیقتر در منبع Redis جستجو کردیم تا تمام جاهای ممکنی که نمایش اشیاء Hash را می توان به تغییر کردن پیدا کرد و به زودی قطعه کد زیر را پیدا کردیم:

/* Load a Redis object of the specified type from the specified file.
 * On success a newly allocated object is returned, otherwise NULL. */
robj *rdbLoadObject(int rdbtype, rio *rdb) {
  //...
  if (rdbtype == RDB_TYPE_HASH) {
    //...
    o = createHashObject();  // ziplist

    /* Too many entries? Use a hash table. */
    if (len > server.hash_max_ziplist_entries)
        hashTypeConvert(o, OBJ_ENCODING_HT);

    //...
  }
}

با مطالعه کدی که به آن پی بردیم، متوجه رفتار زیری شدیم:

    هنگام بازیابی از یک پرونده RDB، Redis ابتدا یک ziplist برای اشیاء Hash ایجاد می کند. تنها زمانیکه اندازه شئ Hash بزرگتر از hash_max_ziplist_entries است، ziplist به hashtable تبدیل می شود.

پس، اگر یک شئ Redis Hash باکدگذاری hashtable و طول کمتر از hash_max_ziplist_entries(۵۱۲)در مستر داشته باشید، زمانی که یک رپلیک راه اندازی می کنید، به صورت یک ziplist رمزگذاری می شود.

ما توانستیم این رفتار را در تنظیمات محلی خود تأیید کنیم.

چگونه آن را تصحیح کردیم؟

ما می توانیم از دو رویکرد زیر برای رفع این مشکل استفاده کنیم:

    اطمینان حاصل کنید که رفتار اسکریپت نظارت لوا قطعی است. در مورد ما، ما می توانیم با مرتب کردن خروجی HKEYS/HGETALL این کار را انجام دهیم.

ما به رویکرد دوم رای دادیم زیرا:

    شئ Hash به طور کلی کوچک است (کمتر از ۳۰ عضو) بنابراین هزینه مرتب سازی کم است، کمتر از ۱ میلی ثانیه برای ۱۰۰ عضو بر اساس آزمایشات ما.اثر نسخه برازش ما به هزاران دستور نوشتن Redis روی دومی منجر به هزینه بالاتری نسبت به تکرار فقط اسکریپت خواهد شد.

بعد از تصحیح، استفاده از CPU رپلیک پس از هر استقرار در محدوده باقی ماند. این





    دیدگاه‌ها

    دیدگاهتان را بنویسید

    نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *