راهنمای جامع Ceph
Englishشروع کنید
در این صفحه
لایه‌های پایینی · فصل ۲۶

لایه‌های پایینی (۴): ‏msgr2‏ در سطح بایت، ‏AsyncConnection‏ و ‏EventCenter‏

۳۴ دقیقه مطالعهویرایش این صفحه
حوصله ندارم این فصل در چند خط
  • ‏msgr2‏ پروتکل سیمی ‏Ceph‏ است: یک ‏banner‏ و سپس ‏frame‏هایی با ‏preamble‏، ‏segment‏ها و ‏epilogue‏ در حالت ‏crc‏ یا ‏secure‏ (AES-GCM).
  • هر پیام چهار ‏segment‏ دارد: ‏header‏، ‏front‏، ‏middle‏ و ‏data‏.
  • ‏AsyncConnection‏ ماشین حالت هر اتصال را اجرا می‌کند و ‏EventCenter‏ رویدادهای ‏socket‏ را روی چند ‏thread‏ کارگر پخش می‌کند.
  • فصل ‏frame‏های واقعی را بایت‌به‌بایت رمزگشایی می‌کند، مثل یک ‏KEEPALIVE2‏ در حالت ‏crc‏.

۲۶-۱ مقدمه: سیم بین دو ‏daemon‏#

هر پیامی که در ‏Ceph‏ رد و بدل می‌شود، چه درخواست نوشتن کلاینت به ‏OSD‏، چه ‏heartbeat‏ بین ‏OSD‏ها و چه نقشه‌ای که ‏mon‏ منتشر می‌کند، از لایه‌ی ‏messenger‏ می‌گذرد. از ‏Nautilus‏ به بعد پروتکل پیش‌فرض ‏msgr2‏ (پورت ۳۳۰۰ برای ‏mon‏) است. این فصل آن را در سطح بایت باز می‌کند: ‏banner‏ آغازین، قالب ‏frame‏ با ‏preamble‏ و ‏segment‏ها، دو حالت ‏crc‏ و ‏secure‏ (AES-GCM)، دست‌دهی احراز هویت، جلسه و بازاتصال، و در پایان ماشین وضعیت ‏ProtocolV2‏ و حلقه‌ی رویداد ‏EventCenter‏ که همه‌ی این‌ها را با تعداد کمی ‏thread‏ اجرا می‌کند. منبع ساختارها ‏src/msg/async/frames_v2.h‏، ‏ProtocolV2.cc‏، ‏crypto_onwire.cc‏ و ‏src/include/msgr.h‏ در شاخه‌ی ‏main‏ است.

در این بخش
  • ‏banner‏ برابر ‏ceph v2\n‏ است، به‌همراه طول ۱۶ و دو ویژگی ۶۴ بیتی (‏supported‏ و ‏required‏). ویژگی‌های فعلی ‏REVISION_1‏ (msgr۲.۱) و ‏COMPRESSION‏ هستند.
  • هر ‏frame‏ با یک ‏preamble‏ ۳۲ بایتی آغاز می‌شود: ‏tag‏، تعداد ‏segment‏ (حداکثر ۴)، طول و هم‌ترازی هر ‏segment‏، ‏flags‏ و ‏crc32c‏.
  • حالت ‏crc‏ برای هر ‏segment‏ یک ‏crc32c‏ دارد. حالت ‏secure‏ با ‏AES-128-GCM‏ (کلید ۱۶، ‏IV‏ ۱۲ و ‏tag‏ ۱۶ بایتی) هم رمز و هم احراز اصالت می‌کند.
  • پیش‌فرض‌ها: ‏ms_cluster_mode‏ و ‏ms_client_mode‏ برابر ‏crc secure‏، و ‏ms_mon_*_mode‏ برابر ‏secure crc‏.
  • ‏AsyncMessenger‏ با ‏ms_async_op_threads=3‏ ‏worker‏ کار می‌کند. هر ‏worker‏ یک ‏EventCenter‏ مبتنی بر ‏epoll‏ (edge-triggered) دارد.

۲۶-۲ ‏banner‏ و مذاکره‌ی ویژگی#

نخستین بایت‌هایی که پس از برقراری ‏TCP‏ فرستاده می‌شوند، رمزنگاری نشده‌اند. ‏ProtocolV2::_banner_exchange‏ این ساختار را می‌سازد: رشته‌ی ۸ بایتی ‏CEPH_BANNER_V2_PREFIX = "ceph v2\n"‏، یک ‏u16‏ برای طول ‏payload‏ و سپس ‏payload‏ شامل دو ‏u64‏، یعنی ‏CEPH_MSGR2_SUPPORTED_FEATURES‏ و ‏CEPH_MSGR2_REQUIRED_FEATURES‏.

بایت‌ها (مثال)معنی
‏63 65 70 68 20 76 32 0a‏«‏ceph v2‏\‏n‏»
‏10 00‏طول ‏payload‏ = ۱۶ (‏u16 LE‏)
‏03 00 00 00 00 00 00 00‏‏supported‏ = ‏REVISION_1‏ (بیت ۰) + ‏COMPRESSION‏ (بیت ۱) = ۳
‏00 00 00 00 00 00 00 00‏‏required‏ = ۰
✎ یادداشت

یک کلاینت قدیمی با ‏banner‏ نسخه‌ی ۱ (‏ceph v027‏) به یک پورت ‏msgr2‏ وصل نمی‌شود، چون بایت‌های ‏banner‏ فرق دارند. به همین دلیل ‏mon‏ها دو آدرس ‏v2:...:3300‏ و ‏v1:...:6789‏ را در ‏addrvec‏ منتشر می‌کنند. اگر ‏ms_bind_msgr1=false‏ باشد، کلاینت‌های قدیمی (و نسخه‌های قدیمی ‏krbd‏ پیش از پشتیبانی ‏msgr2‏ در هسته‌ی ۵٫۱۱) نمی‌توانند وصل شوند.

شکل ۲۶-۱: دست‌دهی کامل msgr2 . پس از banner ، هر طرف HELLO (نوع entity و آدرسی که طرف مقابل را با آن می‌بیند) می‌فرستد. سپس احراز هویت cephx با AUTH_REQUEST ، رفت و برگشت‌های MORE و AUTH_DONE (که global_id و حالت اتصال crc یا secure را تعیین می‌کند) انجام می‌شود. از این نقطه frame ها در حالت انتخاب‌شده‌اند. AUTH_SIGNATURE (HMAC-SHA۲۵۶ روی بایت‌های پیش از احراز) از دستکاری مرحله‌ی رمزنشده جلوگیری می‌کند. CLIENT_IDENT و SERVER_IDENT جلسه را با cookie و global_seq برقرار می‌کنند
شکل ۲۶-۱: دست‌دهی کامل ‏msgr2‏. پس از ‏banner‏، هر طرف ‏HELLO‏ (نوع ‏entity‏ و آدرسی که طرف مقابل را با آن می‌بیند) می‌فرستد. سپس احراز هویت ‏cephx‏ با ‏AUTH_REQUEST‏، رفت و برگشت‌های ‏MORE‏ و ‏AUTH_DONE‏ (که ‏global_id‏ و حالت اتصال ‏crc‏ یا ‏secure‏ را تعیین می‌کند) انجام می‌شود. از این نقطه ‏frame‏ها در حالت انتخاب‌شده‌اند. ‏AUTH_SIGNATURE‏ (HMAC-SHA۲۵۶ روی بایت‌های پیش از احراز) از دستکاری مرحله‌ی رمزنشده جلوگیری می‌کند. ‏CLIENT_IDENT‏ و ‏SERVER_IDENT‏ جلسه را با ‏cookie‏ و ‏global_seq‏ برقرار می‌کنند
◆ عمیق‌تر

چرا ‏AUTH_SIGNATURE‏؟ ‏banner‏ و ‏HELLO‏ و بخشی از مذاکره‌ی ‏auth‏ پیش از وجود کلید جلسه ارسال می‌شوند. مهاجمی در میانه می‌تواند مثلاً فهرست ‏preferred_modes‏ را تغییر دهد تا طرفین به‌جای ‏secure‏ روی ‏crc‏ توافق کنند (حمله‌ی ‏downgrade‏). پس از ‏AUTH_DONE‏، هر طرف ‏hmac_sha256‏ تمام بایت‌هایی را که دریافت کرده است (‏pre_auth.rxbuf‏) با کلید جلسه می‌فرستد و طرف دیگر آن را با بایت‌هایی که خودش فرستاده مقایسه می‌کند. هر تغییری در مسیر باعث قطع اتصال می‌شود.

۲۶-۳ ‏frame‏ در سطح بایت#

شکل ۲۶-۲: ساختار frame در msgr2.1. preamble_block_t دقیقاً ۳۲ بایت است و طوری طراحی شده که مضرب بلوک AES (۱۶) باشد. در حالت crc ، segment اول بلافاصله پس از preamble و همراه crc خودش می‌آید و اگر segment دیگری باشد، epilogue شامل late_status و crc بقیه‌ی segment هاست. در حالت secure ، preamble همراه ۴۸ بایت اول segment صفر یکجا رمز می‌شود و یک tag مربوط به GCM می‌گیرد. پیام‌ها چهار segment دارند: header2 ، front ، middle و data (با هم‌ترازی صفحه برای کپی صفر)
شکل ۲۶-۲: ساختار ‏frame‏ در ‏msgr2.1. preamble_block_t‏ دقیقاً ۳۲ بایت است و طوری طراحی شده که مضرب بلوک ‏AES‏ (۱۶) باشد. در حالت ‏crc‏، ‏segment‏ اول بلافاصله پس از ‏preamble‏ و همراه ‏crc‏ خودش می‌آید و اگر ‏segment‏ دیگری باشد، ‏epilogue‏ شامل ‏late_status‏ و ‏crc‏ بقیه‌ی ‏segment‏هاست. در حالت ‏secure‏، ‏preamble‏ همراه ۴۸ بایت اول ‏segment‏ صفر یکجا رمز می‌شود و یک ‏tag‏ مربوط به ‏GCM‏ می‌گیرد. پیام‌ها چهار ‏segment‏ دارند: ‏header2‏، ‏front‏، ‏middle‏ و ‏data‏ (با هم‌ترازی صفحه برای کپی صفر)
‏offset‏اندازهفیلدتوضیح
۰۱‏tag‏نوع ‏frame‏ (جدول بعد)
۱۱‏num_segments‏۱ تا ۴
۲۲۴‏segments[4]‏هر کدام ‏length‏ (‏le32‏) و ‏alignment‏ (‏le16‏)
۲۶۱‏flags‏مثلاً ‏FRAME_EARLY_DATA_COMPRESSED=0x1‏
۲۷۱‏_reserved‏صفر
۲۸۴‏crc‏‏ceph_crc32c(0, preamble, 28)‏
‏Tag‏مقدار‏Tag‏مقدار
‏HELLO‏۱‏SESSION_RETRY‏۱۳
‏AUTH_REQUEST‏۲‏SESSION_RETRY_GLOBAL‏۱۴
‏AUTH_BAD_METHOD‏۳‏SESSION_RECONNECT_OK‏۱۵
‏AUTH_REPLY_MORE‏۴‏WAIT‏۱۶
‏AUTH_REQUEST_MORE‏۵‏MESSAGE‏۱۷
‏AUTH_DONE‏۶‏KEEPALIVE2‏۱۸
‏AUTH_SIGNATURE‏۷‏KEEPALIVE2_ACK‏۱۹
‏CLIENT_IDENT‏۸‏ACK‏۲۰
‏SERVER_IDENT‏۹‏COMPRESSION_REQUEST‏۲۱
‏IDENT_MISSING_FEATURES‏۱۰‏COMPRESSION_DONE‏۲۲
‏SESSION_RECONNECT‏۱۱‏SESSION_RESET‏۱۲

۲۶-۳-۱ مثال کامل: یک ‏KEEPALIVE2‏ در حالت ‏crc‏#

‏KEEPALIVE2‏ یک ‏segment‏ دارد که فقط ‏utime_t‏ است (‏sec‏ و ‏nsec‏، هرکدام ‏u32‏). برای زمان ۱۷۰۰۰۰۰۰۰۰.۵ ثانیه، ‏frame‏ کامل ۴۴ بایت است. این مقادیر را با یک کدگذار مستقل محاسبه کرده‌ایم:

بایت‌هابخش
‏12 01‏‏tag=18‏ (KEEPALIVE۲)، ‏num_segments=1‏
‏08 00 00 00 08 00‏‏segment 0: length=8‏، ‏alignment=8‏ (‏DEFAULT_ALIGNMENT = sizeof(void*)‏)
‏00 × 18‏سه ‏segment‏ خالی
‏00 00‏‏flags‏، ‏reserved‏
‏d8 d5 f5 99‏‏crc preamble‏ = ۰‏x99F5D5D8‏ (seed ۰)
‏00 f1 53 65 00 65 cd 1d‏‏sec=1700000000‏، ‏nsec=500000000‏
‏39 7f 34 4d‏‏crc segment‏ = ۰‏x4D347F39‏ (seed ۰xFFFFFFFF)
شکل ۲۶-۳: همان قاب KEEPALIVE2 بالا، این بار به شکل شبکه‌ی بایت: preamble سی‌ودوبایتی با tag ، تعداد segment و جدول چهار segment (سه‌تای آخر صفر)، سپس crc خود preamble ، سپس segment هشت‌بایتی utime_t و در پایان crc آن segment . جمع ۴۴ بایت است و چون فقط یک segment هست، epilogue ندارد. این کوچک‌ترین قاب پرتکرار روی یک اتصال بی‌کار است و مثلاً MonClient هر mon_client_ping_interval ثانیه یکی از آن را به mon می‌فرستد ( send_keepalive )
شکل ۲۶-۳: همان قاب ‏KEEPALIVE2‏ بالا، این بار به شکل شبکه‌ی بایت: ‏preamble‏ سی‌ودوبایتی با ‏tag‏، تعداد ‏segment‏ و جدول چهار ‏segment‏ (سه‌تای آخر صفر)، سپس ‏crc‏ خود ‏preamble‏، سپس ‏segment‏ هشت‌بایتی ‏utime_t‏ و در پایان ‏crc‏ آن ‏segment‏. جمع ۴۴ بایت است و چون فقط یک ‏segment‏ هست، ‏epilogue‏ ندارد. این کوچک‌ترین قاب پرتکرار روی یک اتصال بی‌کار است و مثلاً ‏MonClient‏ هر ‏mon_client_ping_interval‏ ثانیه یکی از آن را به ‏mon‏ می‌فرستد (‏send_keepalive‏)

چون فقط یک ‏segment‏ هست، ‏epilogue‏ ارسال نمی‌شود (در ‏msgr2.1‏). سرور با ‏KEEPALIVE2_ACK‏ و همان ‏timestamp‏ پاسخ می‌دهد. کلاینت از این پاسخ برای سنجش «آخرین زمان زنده بودن» اتصال استفاده می‌کند (‏mon_client_ping_interval‏ و ‏heartbeat‏ها).

۲۶-۳-۲ پیام: چهار ‏segment‏#

‏MESSAGE‏ همیشه چهار ‏segment‏ دارد (‏SegmentIndex::Msg‏): ‏HEADER‏ (۰) که ‏ceph_msg_header2‏ است (‏seq‏، ‏tid‏، ‏type‏، ‏priority‏، ‏version‏، ‏data_pre_padding_len‏، ‏data_off‏، ‏ack_seq‏، ‏flags‏، ‏compat_version‏)، ‏FRONT‏ (۱) که بدنه‌ی اصلی کدگذاری‌شده‌ی پیام است (مثلاً فیلدهای ‏MOSDOp‏)، ‏MIDDLE‏ (۲) که به‌ندرت استفاده می‌شود، و ‏DATA‏ (۳) که داده‌ی خام نوشتن یا خواندن است. هم‌ترازی ‏segment‏ داده ‏PAGE_SIZE_ALIGNMENT = 4096‏ است تا گیرنده بتواند بافر هم‌تراز با صفحه تخصیص دهد و داده را بدون کپی تا ‏O_DIRECT‏ در ‏BlueStore‏ ببرد (فصل ۲۳). ‏sizeof(ceph_msg_header2)‏ کمتر از ۴۸ است، بنابراین در حالت ‏secure‏ کل ‏header‏ در بخش ‏inline‏ قرار می‌گیرد و گیرنده با یک رمزگشایی می‌تواند طول بقیه را بداند.

۲۶-۴ حالت ‏secure: AES-128-GCM‏ روی سیم#

وقتی ‏con_mode‏ برابر ‏CEPH_CON_MODE_SECURE‏ (۰‏x2‏) باشد، ‏crypto_onwire.cc‏ با ‏OpenSSL‏ (‏EVP_aes_128_gcm‏) کار می‌کند: ‏AESGCM_KEY_LEN=16‏، ‏AESGCM_IV_LEN=12‏ و ‏AESGCM_TAG_LEN=16‏. ‏nonce‏ ساختاری ۱۲ بایتی است: یک بخش ثابت ۳۲ بیتی و یک شمارنده‌ی ۶۴ بیتی که برای هر ‏frame‏ افزایش می‌یابد. کلید و ‏nonce‏ اولیه از کلید جلسه‌ی ‏cephx‏ و ‏connection_secret‏ که در ‏AUTH_DONE‏ مبادله شده است، برای هر جهت جداگانه به دست می‌آیند.

text
pseudocode: ارسال یک frame در حالت secure (msgr2.1، خلاصه‌ی این کتاب)
  plain_head = preamble(32) || segment0[0:48] (با صفر پر می‌شود اگر کوتاه‌تر باشد)
  out  = AES_GCM_encrypt(key_tx, nonce_tx, plain_head) || tag(16); nonce_tx.counter++
  if len(segment0) > 48 or num_segments > 1:
      rest = segment0[48:] || segment1 || segment2 || segment3   # هر کدام pad به 16
      out += AES_GCM_encrypt(key_tx, nonce_tx, rest || epilogue) || tag(16); nonce_tx.counter++
  send(out)

سربار: برای یک پیام کنترلی کوچک، حالت ‏secure‏ حدود ۹۶ بایت (۳۲ + ۴۸ + ۱۶) در برابر ۴۴ بایت حالت ‏crc‏ مصرف می‌کند. برای نوشتن ۴‏M‏، سربار ناچیز است، ولی هزینه‌ی ‏CPU‏ مربوط به ‏AES-GCM‏ (با ‏AES-NI‏ حدود چند گیگابایت در ثانیه در هر هسته) روی ‏OSD‏های پرترافیک ‏NVMe‏ قابل‌اندازه‌گیری است. به همین دلیل پیش‌فرض ترافیک داده ‏crc secure‏ است (‏crc‏ ترجیح داده می‌شود) و برای ‏mon‏ ‏secure crc‏.

گزینهپیش‌فرضکاربرد
‏ms_cluster_mode‏‏crc secure‏‏OSD‏↔‏OSD‏
‏ms_service_mode‏‏crc secure‏حالت‌هایی که سرویس‌دهنده (‏OSD‏، ‏MDS‏) می‌پذیرد
‏ms_client_mode‏‏crc secure‏ترجیح کلاینت
‏ms_mon_cluster_mode‏ / ‏ms_mon_service_mode‏ / ‏ms_mon_client_mode‏‏secure crc‏اتصال به ‏mon‏
‏ms_osd_compress_mode‏‏none‏فشرده‌سازی روی سیم برای ‏OSD‏
‏ms_crc_data‏ / ‏ms_crc_header‏‏true‏ / ‏true‏خاموش کردن ‏crc‏ در حالت ‏crc‏ (توصیه نمی‌شود)
⚠ هشدار

حالت ‏crc‏ هیچ حفاظتی در برابر مهاجم ندارد. ‏crc‏ فقط خطای تصادفی را تشخیص می‌دهد. اگر شبکه‌ی عمومی (‏public network‏) مشترک یا غیرقابل‌اعتماد است، ‏ms_client_mode=secure‏ و ‏ms_service_mode=secure‏ را در نظر بگیرید و تأثیر آن را روی ‏CPU‏ بسنجید. ‏cephx‏ در حالت ‏crc‏ فقط هویت را ثابت می‌کند، نه محرمانگی یا یکپارچگی پیام‌ها را.

۲۶-۵ جلسه، بازاتصال و ‏reset‏#

اتصال‌های ‏lossless‏ (مثلاً ‏OSD‏↔‏OSD‏ یا کلاینت↔‏mon‏ در برخی ‏policy‏ها) باید پس از قطع ‏TCP‏ بدون از دست رفتن یا تکرار پیام ادامه یابند. ‏msgr2‏ برای این کار این حالت‌ها را نگه می‌دارد:

متغیرمعنی
‏client_cookie‏ / ‏server_cookie‏شناسه‌ی تصادفی جلسه که در ‏CLIENT_IDENT‏ و ‏SERVER_IDENT‏ تعیین می‌شود
‏global_seq‏شمارنده‌ی سراسری ‏messenger‏ برای ترتیب‌دهی تلاش‌های اتصال (حل رقابت دو اتصال هم‌زمان)
‏connect_seq‏تعداد بازاتصال‌های موفق این جلسه
‏message seq‏ / ‏ack_seq‏شماره‌ی آخرین پیام دریافتی؛ پیام‌های تأییدنشده در صف ارسال مجدد می‌مانند
text
سناریوی بازاتصال (خلاصه‌ی این کتاب)
  کلاینت: SESSION_RECONNECT(addrs, client_cookie, server_cookie, global_seq, connect_seq, msg_seq)
  سرور:
    cookie ناشناخته (سرور restart شده)       → SESSION_RESET(full=true)  → جلسه‌ی تازه، صف‌ها خالی
    global_seq قدیمی‌تر از آنچه دیده‌ام       → SESSION_RETRY_GLOBAL(gs)  → دوباره با gs بزرگ‌تر
    connect_seq ناسازگار                     → SESSION_RETRY(cs)
    رقابت با اتصالی که خودم در حال برقراری‌ام → WAIT
    همه‌چیز درست                             → SESSION_RECONNECT_OK(msg_seq)
                                               ارسال مجدد پیام‌های بالاتر از msg_seq
◆ عمیق‌تر

رقابت اتصال (‏connection race‏): دو ‏OSD‏ ممکن است هم‌زمان به هم وصل شوند. هر طرف یک اتصال خروجی و یک ورودی دارد. ‏ProtocolV2‏ با مقایسه‌ی آدرس‌ها (و ‏global_seq‏) تصمیم می‌گیرد کدام اتصال بماند. طرف «بازنده» ‏WAIT‏ دریافت می‌کند و اتصال خودش را رها می‌کند، و اتصال برنده (‏replacing‏) جای آن را می‌گیرد. پیام‌های صف‌شده به اتصال برنده منتقل می‌شوند. باگ‌های قدیمی در همین منطق دلیل بسیاری از گزارش‌های «پیام تکراری» یا «اتصال گیرکرده» بودند.

شکل ۲۶-۴: ماشین وضعیت ProtocolV2 . سمت connect از START_CONNECT با banner ، HELLO ، auth و فشرده‌سازی به SESSION_CONNECTING (جلسه‌ی تازه) یا SESSION_RECONNECTING (cookie قبلی) و در نهایت READY می‌رسد. سمت accept مسیر مشابهی دارد. در READY دریافت هر پیام از سه throttler (تعداد پیام، بایت‌های policy و صف dispatch ) می‌گذرد. خطا در اتصال lossless به STANDBY یا بازاتصال با backoff (۰.۲ تا ۱۵ ثانیه)، و در اتصال lossy به CLOSED منجر می‌شود
شکل ۲۶-۴: ماشین وضعیت ‏ProtocolV2‏. سمت ‏connect‏ از ‏START_CONNECT‏ با ‏banner‏، ‏HELLO‏، ‏auth‏ و فشرده‌سازی به ‏SESSION_CONNECTING‏ (جلسه‌ی تازه) یا ‏SESSION_RECONNECTING‏ (cookie قبلی) و در نهایت ‏READY‏ می‌رسد. سمت ‏accept‏ مسیر مشابهی دارد. در ‏READY‏ دریافت هر پیام از سه ‏throttler‏ (تعداد پیام، بایت‌های ‏policy‏ و صف ‏dispatch‏) می‌گذرد. خطا در اتصال ‏lossless‏ به ‏STANDBY‏ یا بازاتصال با ‏backoff‏ (۰.۲ تا ۱۵ ثانیه)، و در اتصال ‏lossy‏ به ‏CLOSED‏ منجر می‌شود
‏throttle‏گزینه یا ‏policy‏اثر وقتی پر است
‏THROTTLE_MESSAGE‏‏policy.throttler_messages‏ (مثلاً ‏osd_client_message_cap=256‏)خواندن از سوکت متوقف می‌شود، پس ‏TCP backpressure‏ به کلاینت می‌رسد
‏THROTTLE_BYTES‏‏policy.throttler_bytes‏ (مثلاً ‏osd_client_message_size_cap=500M‏)همان
‏THROTTLE_DISPATCH_QUEUE‏‏ms_dispatch_throttle_bytes=100M‏همان؛ محافظ حافظه‌ی صف ‏dispatch‏

۲۶-۶ ‏AsyncMessenger‏، ‏Worker‏ و ‏EventCenter‏#

شکل ۲۶-۵: مدل threading در AsyncMessenger. NetworkStack (پیش‌فرض Posix ) تعداد ms_async_op_threads=3 Worker می‌سازد و هر Worker یک EventCenter دارد. هر اتصال هنگام ساخت به یکی از worker ها سپرده می‌شود و تا پایان عمرش همان‌جا می‌ماند. حلقه‌ی هر worker ابتدا زمان‌سنج‌های سررسیده را اجرا می‌کند، سپس در epoll_wait (edge-triggered) منتظر رویدادهای fd می‌ماند و در پایان رویدادهای خارجی‌ای را اجرا می‌کند که thread های دیگر صف کرده‌اند. thread های دیگر worker را با نوشتن در یک eventfd (یا pipe در سیستم‌های بدون آن) بیدار می‌کنند
شکل ۲۶-۵: مدل ‏threading‏ در ‏AsyncMessenger. NetworkStack‏ (پیش‌فرض ‏Posix‏) تعداد ‏ms_async_op_threads=3 Worker‏ می‌سازد و هر ‏Worker‏ یک ‏EventCenter‏ دارد. هر اتصال هنگام ساخت به یکی از ‏worker‏ها سپرده می‌شود و تا پایان عمرش همان‌جا می‌ماند. حلقه‌ی هر ‏worker‏ ابتدا زمان‌سنج‌های سررسیده را اجرا می‌کند، سپس در ‏epoll_wait‏ (edge-triggered) منتظر رویدادهای ‏fd‏ می‌ماند و در پایان رویدادهای خارجی‌ای را اجرا می‌کند که ‏thread‏های دیگر صف کرده‌اند. ‏thread‏های دیگر ‏worker‏ را با نوشتن در یک ‏eventfd‏ (یا ‏pipe‏ در سیستم‌های بدون آن) بیدار می‌کنند

‏EventCenter::process_events‏ در هر دور این کارها را انجام می‌کند:

  1. ۱. زمان رسیدن نزدیک‌ترین رویداد زمانی را حساب می‌کند تا مدت انتظار تعیین شود (حداقل ‏ms_time_events_min_wait_interval‏).
  2. ۲. اگر ‏poller‏ فعال یا رویداد خارجی در صف نباشد، ‏event_wait‏ (یعنی ‏epoll_wait‏ در ‏EventEpoll.cc‏) را تا آن زمان بلوک می‌کند.
  3. ۳. برای هر ‏fd‏ آماده، ‏read_cb‏ یا ‏write_cb‏ ثبت‌شده را صدا می‌زند. برای اتصال‌ها، این‌ها ‏AsyncConnection::process‏ و ‏handle_write‏ هستند.
  4. ۴. زمان‌سنج‌های سررسیده را اجرا می‌کند (مثلاً ‏keepalive‏، ‏timeout‏ اتصال و ‏backoff‏ بازاتصال).
  5. ۵. صف ‏external_events‏ را خالی و اجرا می‌کند. ‏AsyncConnection::send_message‏ از ‏thread‏ دیگری (مثلاً ‏thread‏ مربوط به ‏PG‏ در ‏OSD‏) پیام را در ‏out_q‏ می‌گذارد و با ‏dispatch_event_external‏ نوشتن را در ‏worker‏ درست زمان‌بندی می‌کند.
✎ یادداشت

چون ‏epoll‏ در حالت ‏EPOLLET‏ (‏edge-triggered‏) ثبت می‌شود، ‏handler‏ باید تا ‏EAGAIN‏ بخواند یا بنویسد. در غیر این صورت، داده‌ی باقی‌مانده هیچ رویداد جدیدی ایجاد نمی‌کند. ‏ProtocolV2‏ برای همین وضعیت خواندن را در ‏continuation‏ها (‏CONTINUATION(...)‏) نگه می‌دارد و با بازگشت ‏nullptr‏ منتظر داده‌ی بیشتر می‌ماند.

چرا فقط سه ‏thread‏؟ پردازش پروتکل (رمزنگاری یا ‏crc‏، تجزیه‌ی ‏frame‏ و ‏throttle‏) سبک است و کار اصلی در ‏thread pool‏‌های ‏OSD‏ (‏osd_op_num_threads_per_shard‏) انجام می‌شود. سه ‏worker‏ معمولاً برای ده‌ها هزار اتصال کافی است، ولی با حالت ‏secure‏ روی ‏OSD‏های ‏NVMe‏ پرترافیک، این ‏worker‏ها ممکن است گلوگاه ‏CPU‏ شوند. در این صورت ‏top -H -p <pid>‏ ‏thread‏هایی با نام ‏msgr-worker-N‏ را نشان می‌دهد که نزدیک ۱۰۰٪ هستند.

bashReef+
top -H -p $(pidof -s ceph-osd)            # msgr-worker-0..2
ceph daemon osd.4 perf dump AsyncMessenger::Worker-0 | jq '.[] | {msgr_recv_messages, msgr_send_messages, msgr_running_total_time}'
ceph daemon osd.4 messenger dump        # بدون آرگومان فهرست messengerها؛ با نام یکی، اتصال‌هایش
ss -tinp | grep ceph-osd | head            # cwnd، rtt و retrans برای هر اتصال
tcpdump -i any -s 128 -w /tmp/msgr.pcap port 3300   # banner و preamble در crc mode قابل دیدن است
✚ عیب‌یابی

‏Wireshark‏ تجزیه‌گر ‏Ceph‏ دارد، ولی پشتیبانی آن از ‏msgr2‏ محدود است. در حالت ‏crc‏ می‌توانید با جدول ‏tag‏ این فصل، ‏preamble‏ را دستی بخوانید: بایت ۰ برابر ‏tag‏ و بایت‌های ۲ تا ۵ برابر طول ‏segment‏ اول (‏LE‏) است. در حالت ‏secure‏، بعد از ‏banner‏ و ‏HELLO‏ همه‌چیز رمز است.

۲۶-۷ حساب دقیق اندازه‌ی قاب روی سیم#

‏FrameAssembler‏ در ‏frames_v2.h‏ برای هر بخش قاب تابع جداگانه‌ای دارد: ‏get_preamble_onwire_len‏، ‏get_segment_onwire_len‏ و ‏get_epilogue_onwire_len‏. با این سه تابع می‌توان سربار دقیق هر پیام را پیش از ارسال حساب کرد.

∑ الگوریتم: طول قاب ‏msgr2.1‏ روی سیم

منبع در کد: src/msg/async/frames_v2.h :: FrameAssembler::get_preamble_onwire_len، get_segment_onwire_len، get_epilogue_onwire_len، get_frame_onwire_len

پیچیدگی: ‏O‏(تعداد ‏segment‏ها) = ‏O(1)‏ (حداکثر ۴)

text
pseudocode
preamble = secure ? 32 + 48 (inline) + 16 (tag) : 32
seg(i):
    if secure:
        p = round_up(len_i, 16)
        if i == 0: return p > 48 ? p + 16 − 48 : 0       # تا 48 بایت درون preamble جا می‌شود
        return p
    return (i == 0 and len_0 > 0) ? len_0 + 4 : len_i        # crc جدا برای segment 0
epilogue = (تنها یک segment) ? 0 : secure ? 16 + 16 : 1 + 3×4
total = preamble + Σ seg(i) + epilogue

مثال عددی. یک پیام ‏MESSAGE‏ را در نظر بگیرید با ‏segment 0‏ = ‏ceph_msg_header2‏ (packed، ۴۱ بایت)، ‏front‏ = ۲۰۰ بایت، ‏middle‏ = ۰ و ‏data‏ = ۴۰۹۶ بایت. داده‌ی منطقی ۴۳۳۷ بایت است.

  • حالت ‏crc‏: ۳۲ + (۴۱ + ۴) + ۲۰۰ + ۰ + ۴۰۹۶ + ۱۳ = ۴۳۸۶ بایت، یعنی ۴۹ بایت سربار (۱.۱٪).
  • حالت ‏secure‏: ۹۶ + ۰ (هدر ۴۱ بایتی به ۴۸ گرد می‌شود و کامل در ‏preamble‏ جا می‌گیرد) + ۲۰۸ + ۰ + ۴۰۹۶ + ۳۲ = ۴۴۳۲ بایت، یعنی ۹۵ بایت سربار (۲.۲٪).

برای ‏KEEPALIVE2‏ در حالت ‏crc‏ عدد همان ۴۴ بایت جدول بالاست، چون فقط یک ‏segment‏ دارد و ‏epilogue‏ ندارد. در ‏secure‏، ‏segment‏ هشت‌بایتی به ۱۶ گرد می‌شود و درون ‏preamble‏ جا می‌گیرد، پس کل قاب ۹۶ بایت است.

شکل ۲۶-۶: مقایسه‌ی بایت‌های روی سیم برای یک پیام با داده‌ی ۴ KiB در دو حالت crc و secure . در crc ، segment 0 crc چهاربایتی خودش را دارد و epilogue سیزده‌بایتی crc سه segment دیگر را. در secure ، preamble جای ۴۸ بایت inline و تگ GCM شانزده‌بایتی را دارد، پس هدر پیام بدون قاب جداگانه رمز می‌شود. front به مضرب ۱۶ گرد می‌شود و epilogue یک بلوک ۱۶ بایتی به‌اضافه‌ی تگ است. نوار داده برای خوانایی در ۱۰۰۰ بایت بریده شده است
شکل ۲۶-۶: مقایسه‌ی بایت‌های روی سیم برای یک پیام با داده‌ی ۴ ‏KiB‏ در دو حالت ‏crc‏ و ‏secure‏. در ‏crc‏، ‏segment 0 crc‏ چهاربایتی خودش را دارد و ‏epilogue‏ سیزده‌بایتی ‏crc‏ سه ‏segment‏ دیگر را. در ‏secure‏، ‏preamble‏ جای ۴۸ بایت ‏inline‏ و تگ ‏GCM‏ شانزده‌بایتی را دارد، پس هدر پیام بدون قاب جداگانه رمز می‌شود. ‏front‏ به مضرب ۱۶ گرد می‌شود و ‏epilogue‏ یک بلوک ۱۶ بایتی به‌اضافه‌ی تگ است. نوار داده برای خوانایی در ۱۰۰۰ بایت بریده شده است

۲۶-۸ ‏nonce‏ در ‏AES-GCM‏ و جلوگیری از بازاستفاده#

شکل ۲۶-۷: چیدمان ۴۰ بایت ابتدایی connection_secret که cephx در پایان احراز هویت به هر دو طرف می‌دهد: ۱۶ بایت کلید AES-128 مشترک، سپس nonce دوازده‌بایتی دریافت و nonce دوازده‌بایتی ارسال. هر nonce یک بخش ثابت چهاربایتی و یک شمارنده‌ی هشت‌بایتی دارد. سمت سرور با پرچم crossed نقش rx و tx را جابه‌جا می‌کند تا هر جهت nonce مستقل داشته باشد
شکل ۲۶-۷: چیدمان ۴۰ بایت ابتدایی ‏connection_secret‏ که ‏cephx‏ در پایان احراز هویت به هر دو طرف می‌دهد: ۱۶ بایت کلید ‏AES-128‏ مشترک، سپس ‏nonce‏ دوازده‌بایتی دریافت و ‏nonce‏ دوازده‌بایتی ارسال. هر ‏nonce‏ یک بخش ثابت چهاربایتی و یک شمارنده‌ی هشت‌بایتی دارد. سمت سرور با پرچم ‏crossed‏ نقش ‏rx‏ و ‏tx‏ را جابه‌جا می‌کند تا هر جهت ‏nonce‏ مستقل داشته باشد
∑ الگوریتم: مدیریت ‏nonce‏ در ‏AES-128-GCM‏

منبع در کد: src/msg/async/crypto_onwire.cc :: rxtx_t::create_handler_pair، AES128GCM_OnWireTxHandler::reset_tx_handler، AES128GCM_OnWireRxHandler::reset_rx_handler

پیچیدگی: ‏O‏(۱) برای هر قاب؛ رمزگذاری ‏O‏(L) با ‏AES-NI‏

text
pseudocode
create_handler_pair(secret, crossed):
    key = secret[0:16] ; rx = secret[16:28] ; tx = secret[28:40]
    Tx = AESGCM(key, crossed ? rx : tx) ; Rx = AESGCM(key, crossed ? tx : rx)
reset_tx_handler(segments):
    if nonce == initial_nonce:
        if used_initial_nonce: throw "nonce reuse"     # چرخه‌ی کامل شمارنده
        used_initial_nonce = true
    EVP_EncryptInit_ex(..., iv = nonce)
    if msgr2.1: nonce.counter += 1  else: nonce.fixed += 1   # msgr2.0: سرریز پس از 2^32

مثال عددی. یک ‏OSD‏ با ۱۰۰۰۰ قاب در ثانیه روی یک اتصال، در ‏msgr2.0‏ پس از ‏232/104≈4294972^{32} / 10^4 \approx 429497‏ ثانیه (حدود ۵ روز) به ‏nonce‏ اولیه برمی‌گشت و اتصال باید ‏reset‏ می‌شد. در ‏msgr2.1‏ شمارنده‌ی ۶۴ بیتی ‏264/104≈5.8×1072^{64} / 10^4 \approx 5.8 \times 10^7‏ سال دوام می‌آورد. ‏GCM‏ هم برای هر ‏nonce‏ حداکثر حدود ۶۴ ‏GiB‏ داده را مجاز می‌داند (‏2322^{32}‏ بلوک ۱۶ بایتی)، و قاب‌های ‏msgr‏ با سقف ‏segment‏ از این بسیار کوچک‌ترند.

۲۶-۹ حلقه‌ی ‏EventCenter‏ و بیدار شدن بین ‏thread‏ها#

∑ الگوریتم: حلقه‌ی رویداد ‏Worker‏

منبع در کد: src/msg/async/Event.cc :: EventCenter::process_events، dispatch_event_external، wakeup؛ src/msg/async/Stack.cc :: NetworkStack::add_thread

پیچیدگی: ‏O‏(k) برای ‏k fd‏ آماده + ‏O‏(t) رویداد زمان‌دار سررسیده + ‏O‏(e) رویداد خارجی در هر دور

text
pseudocode
loop (هر Worker، msgr-worker-N): process_events(30 s)
process_events(timeout):
    if نخستین time event < now + timeout: timeout = max(آن فاصله، 1000 µs) ; trigger_time = true
    if pollers یا external_num_events > 0: timeout = 0
    n = epoll_wait(timeout)
    for each fd آماده: read_cb و/یا write_cb
    if trigger_time: process_time_events()
    if external_num_events: lock ; swap(external_events) ; unlock ; do_request روی همه
dispatch_event_external(e) (از thread دیگر):
    lock ; external_events.push(e) ; external_num_events++ ; unlock
    if thread جاری همان Worker نیست: wakeup() ⇒ write(eventfd, 1)

مثال عددی و تأخیر. یک ‏thread‏ از ‏op shard‏ در ‏OSD‏ پاسخ یک ‏op‏ را آماده می‌کند و ‏send_message‏ را صدا می‌زند. اتصال به ‏msgr-worker-1‏ تعلق دارد، پس ارسال به شکل رویداد خارجی صف می‌شود و ‏eventfd‏ بیدار می‌شود. هزینه‌ی این مسیر یک ‏write‏ روی ‏eventfd‏، یک بیدار شدن ‏epoll_wait‏ (معمولاً چند میکروثانیه تا چند ده میکروثانیه، بسته به بار و تنظیمات ‏C-state‏) و سپس ‏writev‏ روی سوکت است. اگر ‏Worker‏ در آن لحظه مشغول پردازش ‏fd‏های دیگر باشد، رویداد تا پایان همان دور منتظر می‌ماند. به همین دلیل یک ‏Worker‏ که هم‌زمان صدها اتصال پرترافیک دارد، تأخیر دنباله‌دار (‏tail latency‏) را بالا می‌برد. کف ۱۰۰۰ میکروثانیه‌ای ‏ms_time_events_min_wait_interval‏ مانع می‌شود که تایمرهای خیلی نزدیک حلقه را به ‏busy-loop‏ تبدیل کنند.

شکل ۲۶-۸: ساختار process_events در EventCenter . مهلت انتظار از نزدیک‌ترین رویداد زمان‌دار به دست می‌آید و کف ۱ میلی‌ثانیه دارد. اگر رویداد خارجی منتظر باشد، صفر می‌شود. پس از epoll_wait ، اول callback های fd ، سپس تایمرها و در پایان رویدادهای خارجی اجرا می‌شوند. ردیف پایین مسیر بیدار کردن از thread دیگر را نشان می‌دهد: صف زیر قفل، شمارنده‌ی اتمی و یک write هشت‌بایتی روی eventfd
شکل ۲۶-۸: ساختار ‏process_events‏ در ‏EventCenter‏. مهلت انتظار از نزدیک‌ترین رویداد زمان‌دار به دست می‌آید و کف ۱ میلی‌ثانیه دارد. اگر رویداد خارجی منتظر باشد، صفر می‌شود. پس از ‏epoll_wait‏، اول ‏callback‏های ‏fd‏، سپس تایمرها و در پایان رویدادهای خارجی اجرا می‌شوند. ردیف پایین مسیر بیدار کردن از ‏thread‏ دیگر را نشان می‌دهد: صف زیر قفل، شمارنده‌ی اتمی و یک ‏write‏ هشت‌بایتی روی ‏eventfd‏

۲۶-۱۰ انتخاب ‏Worker‏ برای هر اتصال#

∑ الگوریتم: توزیع اتصال‌ها بین ‏Worker‏ها

منبع در کد: src/msg/async/Stack.cc :: NetworkStack::get_worker؛ Stack.h :: Worker::release_worker؛ AsyncConnection.cc :: AsyncConnection::_stop

پیچیدگی: ‏O‏(W) با ‏W‏ = ‏ms_async_op_threads‏ (پیش‌فرض ۳) زیر یک ‏spinlock‏

text
pseudocode
get_worker():
    lock(pool_spin)
    best = argmin_w w.references       # تساوی ⇒ نخستین در بردار
    unlock ; best.references++ ; return best
release_worker(): references.fetch_sub(1)    # در AsyncConnection::_stop هنگام بستن اتصال

مثال عددی. با سه ‏Worker‏ و ده اتصال پیاپی، توزیع به‌صورت چرخشی ۰، ۱، ۲، ۰، ۱، ۲ است. اگر اتصال پنجم (روی ‏Worker 1‏) بسته شود، شمارنده‌ها [۲، ۱، ۲] می‌شوند و اتصال هفتم به ‏Worker 1‏ می‌رود. در پایان، شمارنده‌ها [۳، ۳، ۳] هستند. این تعادل فقط بر اساس تعداد اتصال است، نه ترافیک آن‌ها. یک ‏OSD‏ که ۲ اتصال پرترافیک به دو ‏OSD‏ همسایه و ۲۰۰ اتصال بی‌کار به کلاینت‌ها دارد، ممکن است هر دو اتصال پرترافیک را روی یک ‏Worker‏ داشته باشد. افزایش ‏ms_async_op_threads‏ (نیاز به راه‌اندازی دوباره دارد) احتمال این تصادف را کم می‌کند. شمارنده‌های ‏msgr_recv_bytes‏ و ‏msgr_send_bytes‏ هر ‏Worker‏ در ‏ceph daemon osd.N perf dump AsyncMessenger::Worker-N‏ نشان می‌دهند که تعادل واقعی برقرار است یا نه.

شکل ۲۶-۹: ردیابی get_worker برای ده اتصال و یک بستن. ستون‌های سمت راست شمارنده‌ی references هر Worker را پس از هر رویداد نشان می‌دهند. انتخاب همیشه کمترین شمارنده است و در تساوی، نخستین Worker برنده می‌شود. بستن اتصال پنجم یک جای خالی در Worker 1 باز می‌کند که بلافاصله اتصال بعدی آن را پر می‌کند
شکل ۲۶-۹: ردیابی ‏get_worker‏ برای ده اتصال و یک بستن. ستون‌های سمت راست شمارنده‌ی ‏references‏ هر ‏Worker‏ را پس از هر رویداد نشان می‌دهند. انتخاب همیشه کمترین شمارنده است و در تساوی، نخستین ‏Worker‏ برنده می‌شود. بستن اتصال پنجم یک جای خالی در ‏Worker 1‏ باز می‌کند که بلافاصله اتصال بعدی آن را پر می‌کند

۲۶-۱۱ بازاتصال: کدام پیام‌ها دوباره فرستاده می‌شوند؟#

∑ الگوریتم: بازفرستادن و دور ریختن پیام‌ها پس از بازاتصال

منبع در کد: src/msg/async/ProtocolV2.cc :: ProtocolV2::requeue_sent، discard_requeued_up_to، handle_reconnect_ok

پیچیدگی: ‏O‏(s) برای ‏s‏ پیام ارسال‌شده‌ی بی‌‏ack‏

text
pseudocode
on fault (اتصال lossless): requeue_sent():
    out_seq −= |sent|
    برای پیام‌های sent از آخر به اول: clear_payload ; push_front در out_queue[HIGHEST]
handle_reconnect_ok(msg_seq از طرف مقابل):
    out_seq = discard_requeued_up_to(out_seq, msg_seq)
discard_requeued_up_to(out_seq, seq):
    count = out_seq
    while صف HIGHEST خالی نیست و front.seq ≠ 0 و front.seq ≤ seq: pop_front ; count++
    return count

مثال عددی. کلاینت تا ‏seq 120‏ فرستاده، ولی ‏ack‏ فقط تا ۱۱۷ رسیده است. پس ‏sent‏ = {۱۱۸، ۱۱۹، ۱۲۰}. با قطع ‏TCP‏، ‏requeue_sent‏ مقدار ‏out_seq‏ را به ۱۱۷ برمی‌گرداند و سه پیام را به سر صف می‌برد. ‏OSD‏ پیش از قطع ۱۱۸ را گرفته بود (‏in_seq‏ = ۱۱۸)، پس در ‏SESSION_RECONNECT_OK‏ مقدار ‏msg_seq‏ = ۱۱۸ برمی‌گرداند. ‏discard_requeued_up_to‏(۱۱۷، ۱۱۸) پیام ۱۱۸ را دور می‌ریزد و ‏out_seq‏ = ۱۱۸ می‌شود. فقط ۱۱۹ و ۱۲۰ دوباره فرستاده می‌شوند و هیچ پیامی دو بار به ‏dispatch‏ نمی‌رسد. ‏payload‏ هر پیام پاک می‌شود (‏clear_payload‏)، چون ممکن است ویژگی‌های اتصال تازه فرق کند و ‏encode‏ دوباره لازم باشد.

شکل ۲۶-۱۰: توالی بازاتصال یک اتصال lossless . کلاینت پس از قطع، پیام‌های بی‌ ack را به سر صف برمی‌گرداند و پس از احراز هویت دوباره SESSION_RECONNECT می‌فرستد. OSD آخرین seq دریافتی را در RECONNECT_OK اعلام می‌کند و کلاینت هر چیزی تا آن شماره را دور می‌ریزد. اتصال‌های lossy (مثل کلاینت به OSD در Objecter ) این مسیر را ندارند و به جای آن reset می‌شوند، و Objecter خودش op ها را دوباره می‌فرستد
شکل ۲۶-۱۰: توالی بازاتصال یک اتصال ‏lossless‏. کلاینت پس از قطع، پیام‌های بی‌‏ack‏ را به سر صف برمی‌گرداند و پس از احراز هویت دوباره ‏SESSION_RECONNECT‏ می‌فرستد. ‏OSD‏ آخرین ‏seq‏ دریافتی را در ‏RECONNECT_OK‏ اعلام می‌کند و کلاینت هر چیزی تا آن شماره را دور می‌ریزد. اتصال‌های ‏lossy‏ (مثل کلاینت به ‏OSD‏ در ‏Objecter‏) این مسیر را ندارند و به جای آن ‏reset‏ می‌شوند، و ‏Objecter‏ خودش ‏op‏ها را دوباره می‌فرستد

۲۶-۱۲ تکمیلی و به‌روز#

  • ‏msgr2.1‏ (بیت ‏REVISION_1‏) در نسخه‌های ‏Nautilus‏ به بعد وجود دارد. قالب ‏epilogue‏ و ‏inline‏ شدن ‏segment‏ اول در حالت ‏secure‏ را تغییر داد و در ‏frame‏های تک‌‏segment‏، ‏epilogue‏ را حذف کرد. ‏msgr2.0‏ فقط برای سازگاری با نسخه‌های اولیه‌ی ‏Nautilus‏ نگه داشته شده است.
  • فشرده‌سازی روی سیم (‏COMPRESSION_REQUEST‏ و ‏COMPRESSION_DONE‏، گزینه‌ی ‏ms_osd_compress_mode‏) پس از ‏auth‏ مذاکره می‌شود. پیش‌فرض آن ‏none‏ است.
  • هسته‌ی لینوکس از نسخه‌ی ۵٫۱۱ ‏msgr2.1‏ را با گزینه‌ی ‏ms_mode‏ (‏legacy‏، ‏crc‏، ‏secure‏، ‏prefer-crc‏، ‏prefer-secure‏) پشتیبانی می‌کند (فصل ۲۹).
  • ‏crimson-osd‏ به‌جای ‏AsyncMessenger‏ از ‏messenger‏ مبتنی بر ‏Seastar‏ استفاده می‌کند ولی همان قالب ‏msgr2‏ روی سیم را دارد.

۲۶-۱۳ تمرین عملی#

  1. ۱. با ‏tcpdump‏ اولین ۲۶ بایت یک اتصال به پورت ۳۳۰۰ را بگیرید و ‏banner‏ و ویژگی‌ها را با جدول این فصل مقایسه کنید.
  2. ۲. یک کدگذار پایتونی برای ‏KEEPALIVE2‏ بنویسید و ‏crc preamble‏ را با محاسبه‌ی این فصل (۰‏x99F5D5D8‏) تطبیق دهید.
  3. ۳. روی یک خوشه‌ی آزمایشی ‏ms_client_mode=secure‏ و ‏ms_cluster_mode=secure‏ را تنظیم کنید و با ‏rados bench‏ و ‏top -H‏، ‏CPU‏ مصرفی ‏msgr-worker‏ها را مقایسه کنید.
  4. ۴. در حین ‏rados bench‏، کارت شبکه‌ی یک ‏OSD‏ را برای ۵ ثانیه ‏down‏ کنید و در لاگ (‏debug_ms=1‏) پیام‌های ‏SESSION_RECONNECT‏ و ‏RECONNECT_OK‏ را پیدا کنید.

۲۶-۱۴ پرسش‌های مرور#

  1. ۱. ساختار ‏banner‏ در ‏msgr2‏ چیست و ویژگی‌های فعلی کدام‌اند؟
  2. ۲. چرا ‏preamble‏ دقیقاً ۳۲ بایت است؟
  3. ۳. ‏crc‏ مربوط به ‏preamble‏ و ‏crc‏ مربوط به ‏segment‏ با چه ‏seed‏هایی محاسبه می‌شوند؟
  4. ۴. چرا ‏segment‏ داده هم‌ترازی ۴۰۹۶ دارد؟
  5. ۵. ‏inline‏ کردن ۴۸ بایت در حالت ‏secure‏ چه سودی دارد؟
  6. ۶. ‏AUTH_SIGNATURE‏ از چه حمله‌ای جلوگیری می‌کند؟
  7. ۷. ‏SESSION_RESET‏ و ‏SESSION_RETRY_GLOBAL‏ چه زمانی ارسال می‌شوند؟
  8. ۸. سه ‏throttler‏ در دریافت پیام کدام‌اند و چگونه ‏backpressure‏ ایجاد می‌کنند؟
  9. ۹. چرا ‏handler‏ها در ‏EventCenter‏ باید تا ‏EAGAIN‏ بخوانند؟
  10. ۱۰. ‏thread‏ دیگری چگونه یک ‏worker‏ را برای ارسال پیام بیدار می‌کند؟

پاسخ این پرسش‌ها در پیوست ه: پاسخ پرسش‌ها آمده است. پاسخ پرسش‌های این فصل →

۲۶-۱۵ منابع این فصل#

راهنمای جامع Ceph · ۱٫۶ · آبان ۱۴۰۵
© 2026 Ramin Najarbashi · CC BY-NC-SA 4.0

حریم خصوصی: این سایت کوکی نمی‌گذارد. برای شمارش بازدیدها از Cloudflare Web Analytics استفاده می‌شود که کوکی ندارد و افراد را دنبال نمی‌کند.