NewSQL = NoSQL+ACID

NewSQL = NoSQL+ACID
تا همین اواخر، Odnoklassniki حدود 50 ترابایت داده پردازش شده را در زمان واقعی در SQL Server ذخیره می کرد. برای چنین حجمی، تقریباً غیرممکن است که با استفاده از یک DBMS SQL، دسترسی سریع و قابل اعتماد و حتی با تحمل خرابی مرکز داده را فراهم کنید. به طور معمول، در چنین مواردی، یکی از حافظه های NoSQL استفاده می شود، اما همه چیز را نمی توان به NoSQL منتقل کرد: برخی از نهادها به ضمانت تراکنش های ACID نیاز دارند.

این ما را به استفاده از ذخیره‌سازی NewSQL سوق داد، یعنی یک DBMS که تحمل خطا، مقیاس‌پذیری و عملکرد سیستم‌های NoSQL را فراهم می‌کند، اما در عین حال تضمین‌های ACID آشنا برای سیستم‌های کلاسیک را حفظ می‌کند. تعداد کمی از سیستم‌های صنعتی فعال از این کلاس جدید وجود دارد، بنابراین ما خودمان چنین سیستمی را پیاده‌سازی کردیم و آن را به بهره‌برداری تجاری رساندیم.

چگونه کار می کند و چه اتفاقی افتاد - زیر برش بخوانید.

امروزه مخاطبان ماهانه Odnoklassniki بیش از 70 میلیون بازدید کننده منحصر به فرد است. ما ما جزو پنج نفر اول هستیم بزرگترین شبکه های اجتماعی جهان و در بین بیست سایتی که کاربران بیشترین زمان را در آنها می گذرانند. زیرساخت OK بارهای بسیار بالایی را مدیریت می کند: بیش از یک میلیون درخواست HTTP در هر ثانیه. بخش‌هایی از ناوگان سرور با بیش از 8000 قطعه در نزدیکی یکدیگر قرار دارند - در چهار مرکز داده مسکو، که امکان تأخیر شبکه کمتر از 1 میلی‌ثانیه را بین آنها فراهم می‌کند.

ما از سال 2010 از کاساندرا استفاده می کنیم و از نسخه 0.6 شروع می کنیم. امروزه ده ها خوشه در حال فعالیت هستند. سریعترین خوشه بیش از 4 میلیون عملیات در ثانیه را پردازش می کند و بزرگترین آنها 260 ترابایت را ذخیره می کند.

با این حال، اینها همه کلاسترهای NoSQL معمولی هستند که برای ذخیره سازی استفاده می شوند با هماهنگی ضعیف داده ها. ما می‌خواستیم فضای ذخیره‌سازی ثابت اصلی، Microsoft SQL Server را جایگزین کنیم، که از زمان تأسیس Odnoklassniki استفاده می‌شده است. فضای ذخیره سازی شامل بیش از 300 دستگاه SQL Server Standard Edition بود که حاوی 50 ترابایت داده - نهادهای تجاری بود. این داده ها به عنوان بخشی از تراکنش های ACID اصلاح می شوند و نیاز دارند قوام بالا.

برای توزیع داده ها در گره های SQL Server، از هر دو عمودی و افقی استفاده کردیم پارتیشن بندی (شریک کردن). از لحاظ تاریخی، ما از یک طرح ساده به اشتراک گذاری داده استفاده می کردیم: هر موجودیت با یک نشانه مرتبط بود - تابعی از شناسه موجودیت. موجودیت هایی با توکن یکسان در سرور SQL یکسان قرار گرفتند. رابطه master-detail به گونه‌ای پیاده‌سازی شد که نشانه‌های رکوردهای اصلی و فرزند همیشه مطابقت داشته باشند و در یک سرور قرار داشته باشند. در یک شبکه اجتماعی، تقریباً تمام رکوردها از طرف کاربر تولید می شوند - به این معنی که تمام داده های کاربر در یک زیر سیستم عملکردی در یک سرور ذخیره می شود. یعنی، یک تراکنش تجاری تقریباً همیشه شامل جداول از یک سرور SQL می‌شود، که اطمینان از سازگاری داده‌ها با استفاده از تراکنش‌های محلی ACID، بدون نیاز به استفاده را ممکن می‌سازد. کند و غیر قابل اعتماد تراکنش های ACID توزیع شده

با تشکر از اشتراک گذاری و افزایش سرعت SQL:

  • ما از محدودیت‌های کلید خارجی استفاده نمی‌کنیم، زیرا هنگام اشتراک‌گذاری، شناسه موجودیت ممکن است در سرور دیگری قرار داشته باشد.
  • به دلیل بار اضافی روی CPU DBMS، از رویه‌ها و تریگرهای ذخیره شده استفاده نمی‌کنیم.
  • ما به دلیل همه موارد بالا و تعداد زیادی خواندن تصادفی از دیسک از JOIN استفاده نمی کنیم.
  • خارج از تراکنش، ما از سطح جداسازی Read Uncommitted برای کاهش بن بست ها استفاده می کنیم.
  • ما فقط تراکنش های کوتاه (به طور متوسط ​​کمتر از 100 میلی ثانیه) انجام می دهیم.
  • ما از UPDATE و DELETE چند ردیفی به دلیل تعداد زیاد بن بست ها استفاده نمی کنیم - هر بار فقط یک رکورد را به روز می کنیم.
  • ما همیشه پرس و جوها را فقط روی فهرست ها انجام می دهیم - یک پرس و جو با طرح اسکن جدول کامل برای ما به معنای بارگذاری بیش از حد پایگاه داده و باعث شکست آن است.

این مراحل به ما این امکان را داد که تقریباً حداکثر عملکرد را از سرورهای SQL خارج کنیم. با این حال، مشکلات بیشتر و بیشتر شد. بیایید به آنها نگاه کنیم.

مشکلات با SQL

  • از آنجایی که ما از اشتراک گذاری خودنوشته استفاده کردیم، افزودن خرده های جدید به صورت دستی توسط مدیران انجام شد. در تمام این مدت، کپی های داده های مقیاس پذیر به درخواست ها سرویس نمی دادند.
  • با افزایش تعداد رکوردها در جدول، سرعت درج و اصلاح کاهش می‌یابد؛ هنگام افزودن شاخص‌ها به جدول موجود، سرعت یک عامل کاهش می‌یابد؛ ایجاد و ایجاد مجدد نمایه‌ها با خرابی رخ می‌دهد.
  • وجود مقدار کمی ویندوز برای SQL Server در تولید، مدیریت زیرساخت را دشوار می کند

اما مشکل اصلی این است

تحمل خطا

سرور کلاسیک SQL تحمل خطا ضعیفی دارد. فرض کنید شما فقط یک سرور پایگاه داده دارید و هر سه سال یک بار از کار می افتد. در این مدت سایت به مدت 20 دقیقه خاموش است که قابل قبول است. اگر 64 سرور دارید، سایت هر سه هفته یک بار قطع می شود. و اگر 200 سرور دارید، سایت هر هفته کار نمی کند. این مشکل است.

برای بهبود تحمل خطای سرور SQL چه کاری می توان انجام داد؟ ویکی پدیا ما را به ساختن دعوت می کند خوشه بسیار در دسترس: که در صورت خرابی هر یک از اجزای یک نسخه پشتیبان وجود دارد.

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

اما نقطه ضعف اصلی چنین خوشه ای بسیار در دسترس، در دسترس بودن صفر است اگر مرکز داده ای که در آن قرار دارد از کار بیفتد. Odnoklassniki دارای چهار مرکز داده است و ما باید در صورت خرابی کامل یکی از آنها از عملکرد آن اطمینان حاصل کنیم.

برای این ما می توانیم استفاده کنیم چند استاد Replication ساخته شده در SQL Server. این راه حل به دلیل هزینه نرم افزار بسیار گران تر است و از مشکلات شناخته شده برای تکرار رنج می برد - تاخیرهای غیرقابل پیش بینی تراکنش با تکرار همزمان و تاخیر در اعمال تکرار (و در نتیجه تغییرات از دست رفته) با تکرار ناهمزمان. ضمنی حل تعارض دستی این گزینه را برای ما کاملا غیر قابل اجرا می کند.

همه این مشکلات نیاز به یک راه حل ریشه ای داشتند و ما شروع به تجزیه و تحلیل دقیق آنها کردیم. در اینجا باید با آنچه که SQL Server عمدتاً انجام می دهد - تراکنش ها - آشنا شویم.

معامله ساده

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

  1. آلبوم را با کلید قفل می کنیم.
  2. یک ورودی در جدول عکس ایجاد کنید.
  3. اگر عکس دارای وضعیت عمومی است، یک شمارنده عکس عمومی به آلبوم اضافه کنید، رکورد را به روز کنید و معامله را انجام دهید.

یا در شبه کد:

TX.start("Albums", id);
Album album = albums.lock(id);
Photo photo = photos.create(…);

if (photo.status == PUBLIC ) {
    album.incPublicPhotosCount();
}
album.update();

TX.commit();

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

هنگام اجرای یک تراکنش، ممکن است تغییر همزمان داده های مشابه از یک سیستم دیگر رخ دهد. به عنوان مثال، Antispam ممکن است تصمیم بگیرد که کاربر به نوعی مشکوک است و بنابراین همه عکس‌های کاربر دیگر نباید عمومی باشند، آنها باید برای تعدیل ارسال شوند، که به معنای تغییر وضعیت photo.status به مقدار دیگری و خاموش کردن شمارنده‌های مربوطه است. بدیهی است، اگر این عملیات بدون تضمین اتمی بودن کاربرد و جداسازی تغییرات رقیب اتفاق بیفتد، همانطور که در ACID، سپس نتیجه مورد نیاز نخواهد بود - یا شمارنده عکس مقدار اشتباهی را نشان می دهد یا همه عکس ها برای تعدیل ارسال نمی شوند.

بسیاری از کدهای مشابه، دستکاری نهادهای تجاری مختلف در یک تراکنش، در سراسر وجود Odnoklassniki نوشته شده است. بر اساس تجربه مهاجرت به NoSQL از سازگاری نهایی ما می دانیم که بزرگترین چالش (و سرمایه گذاری در زمان) از توسعه کد برای حفظ ثبات داده ها ناشی می شود. بنابراین، ما نیاز اصلی برای ذخیره سازی جدید را فراهم کردن تراکنش های واقعی ACID برای منطق برنامه در نظر گرفتیم.

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

  • اگر مرکز داده از کار بیفتد، خواندن و نوشتن در حافظه جدید باید در دسترس باشد.
  • حفظ سرعت توسعه فعلی یعنی هنگام کار با یک مخزن جدید، مقدار کد باید تقریباً یکسان باشد؛ نیازی به افزودن چیزی به مخزن، توسعه الگوریتم‌هایی برای حل تضادها، حفظ شاخص‌های ثانویه و غیره نباشد.
  • سرعت ذخیره‌سازی جدید باید بسیار بالا باشد، هم هنگام خواندن داده‌ها و هم هنگام پردازش تراکنش‌ها، که عملاً به این معنی بود که راه‌حل‌های آکادمیک دقیق، جهانی، اما کند، مانند، برای مثال، قابل اجرا نیستند. تعهدات دو فازی.
  • مقیاس بندی خودکار در حین پرواز.
  • استفاده از سرورهای ارزان قیمت معمولی، بدون نیاز به خرید سخت افزارهای عجیب و غریب.
  • امکان توسعه ذخیره سازی توسط توسعه دهندگان شرکت. به عبارت دیگر اولویت به راه حل های اختصاصی یا منبع باز ترجیحاً در جاوا داده شد.

تصمیم گیری تصمیم گیری

با تجزیه و تحلیل راه حل های ممکن، به دو گزینه ممکن برای معماری رسیدیم:

اولین مورد این است که هر سرور SQL را بگیرید و تحمل خطا، مکانیسم مقیاس بندی، خوشه شکست، حل تضاد و تراکنش های ACID قابل اعتماد و سریع را پیاده سازی کنید. ما این گزینه را بسیار بی اهمیت و کار فشرده ارزیابی کردیم.

گزینه دوم این است که یک ذخیره‌سازی NoSQL آماده با مقیاس‌بندی پیاده‌سازی‌شده، یک خوشه خطا، حل تضاد و پیاده‌سازی تراکنش‌ها و SQL را خودتان تهیه کنید. در نگاه اول، حتی کار پیاده سازی SQL، بدون ذکر تراکنش های ACID، کاری به نظر می رسد که سال ها طول می کشد. اما بعد متوجه شدیم که مجموعه ویژگی های SQL که ما در عمل استفاده می کنیم به همان اندازه با ANSI SQL فاصله دارد کاساندرا CQL دور از ANSI SQL. با نگاهی دقیق تر به CQL، متوجه شدیم که کاملاً به آنچه نیاز داشتیم نزدیک است.

کاساندرا و CQL

بنابراین، چه چیزی در مورد کاساندرا جالب است، چه قابلیت هایی دارد؟

ابتدا، در اینجا می‌توانید جداولی ایجاد کنید که از انواع داده‌های مختلف پشتیبانی می‌کنند؛ می‌توانید SELECT یا UPDATE را روی کلید اصلی انجام دهید.

CREATE TABLE photos (id bigint KEY, owner bigint,…);
SELECT * FROM photos WHERE id=?;
UPDATE photos SET … WHERE id=?;

برای اطمینان از سازگاری داده های مشابه، کاساندرا استفاده می کند رویکرد حد نصاب. در ساده‌ترین حالت، این بدان معناست که وقتی سه کپی از یک ردیف روی گره‌های مختلف خوشه قرار می‌گیرند، در صورتی که اکثر گره‌ها (یعنی دو از سه) موفقیت این عملیات نوشتن را تأیید کنند، نوشتن موفق در نظر گرفته می‌شود. . اگر هنگام خواندن، اکثر گره‌ها نظرسنجی شده و آنها را تأیید کنند، داده‌های ردیف سازگار در نظر گرفته می‌شوند. بنابراین، با سه تکرار، در صورت خرابی یک گره، سازگاری کامل و فوری داده تضمین می شود. این رویکرد به ما اجازه می‌دهد تا طرحی حتی مطمئن‌تر را پیاده‌سازی کنیم: همیشه درخواست‌ها را به هر سه نسخه ارسال کنید، منتظر پاسخ از دو سریع‌ترین نسخه باشید. پاسخ دیرهنگام نسخه سوم در این مورد کنار گذاشته می شود. گره ای که دیر پاسخ می دهد ممکن است مشکلات جدی داشته باشد - ترمز، جمع آوری زباله در JVM، بازیابی مستقیم حافظه در هسته لینوکس، خرابی سخت افزار، قطع شدن از شبکه. با این حال، این به هیچ وجه بر عملیات یا داده های مشتری تأثیر نمی گذارد.

رویکرد زمانی که با سه گره تماس می گیریم و از دو گره پاسخ دریافت می کنیم، فراخوانی می شود حدس و گمان: یک درخواست برای کپی اضافی حتی قبل از "افتادن" ارسال می شود.

یکی دیگر از مزایای Cassandra Batchlog است، مکانیزمی که تضمین می کند دسته ای از تغییراتی که ایجاد می کنید یا به طور کامل اعمال می شوند یا اصلا اعمال نمی شوند. این به ما اجازه می دهد تا A را در ACID - اتمی خارج از جعبه حل کنیم.

نزدیکترین چیز به معاملات در کاساندرا به اصطلاح "معاملات سبک". اما آنها از معاملات "واقعی" ACID دور هستند: در واقع، این فرصتی برای انجام است CAS بر روی داده‌های تنها یک رکورد، با استفاده از اجماع با استفاده از پروتکل سنگین وزن Paxos. بنابراین سرعت این گونه تراکنش ها پایین است.

چیزی که ما در کاساندرا از دست می دادیم

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

ج * یک

بنابراین یک DBMS جدید متولد شد ج * یک، متشکل از سه نوع گره سرور:

  • ذخیره سازی - (تقریبا) سرورهای استاندارد Cassandra که مسئول ذخیره داده ها در دیسک های محلی هستند. با افزایش بار و حجم داده ها، مقدار آنها را می توان به راحتی به ده ها و صدها مقیاس کرد.
  • هماهنگ کننده های تراکنش - از اجرای تراکنش ها اطمینان حاصل کنند.
  • کلاینت ها سرورهای کاربردی هستند که عملیات تجاری را پیاده سازی کرده و تراکنش ها را آغاز می کنند. هزاران مشتری از این دست می توانند وجود داشته باشند.

NewSQL = NoSQL+ACID

سرورهای همه نوع بخشی از یک خوشه مشترک هستند، از پروتکل پیام کاساندرا داخلی برای ارتباط با یکدیگر استفاده می کنند و شایعات بی اساس برای تبادل اطلاعات خوشه ای با Heartbeat، سرورها در مورد خرابی های متقابل یاد می گیرند، یک طرح داده واحد را حفظ می کنند - جداول، ساختار و تکرار آنها. طرح پارتیشن بندی، توپولوژی خوشه ای و غیره

مشتریان

NewSQL = NoSQL+ACID

به جای درایورهای استاندارد، از حالت Fat Client استفاده می شود. چنین گره‌ای داده‌ها را ذخیره نمی‌کند، اما می‌تواند به عنوان هماهنگ‌کننده برای اجرای درخواست عمل کند، یعنی کلاینت خودش به عنوان هماهنگ‌کننده درخواست‌هایش عمل می‌کند: کپی‌های ذخیره‌سازی را جستجو می‌کند و تضادها را حل می‌کند. این نه تنها قابل اعتمادتر و سریعتر از درایور استاندارد است، که به ارتباط با هماهنگ کننده از راه دور نیاز دارد، بلکه به شما امکان می دهد تا انتقال درخواست ها را کنترل کنید. خارج از تراکنش باز روی مشتری، درخواست ها به مخازن ارسال می شوند. اگر مشتری یک تراکنش را باز کرده باشد، تمام درخواست های داخل تراکنش به هماهنگ کننده تراکنش ارسال می شود.
NewSQL = NoSQL+ACID

C*One هماهنگ کننده تراکنش

هماهنگ کننده چیزی است که ما برای C*One از ابتدا پیاده سازی کردیم. مسئولیت مدیریت تراکنش ها، قفل ها و ترتیب اعمال تراکنش ها را بر عهده دارد.

برای هر تراکنش انجام شده، هماهنگ کننده یک مهر زمانی ایجاد می کند: هر تراکنش بعدی بزرگتر از تراکنش قبلی است. از آنجایی که سیستم حل تعارض کاساندرا مبتنی بر مهرهای زمانی است (از دو رکورد متضاد، یکی با آخرین مهر زمانی جاری در نظر گرفته می شود)، تضاد همیشه به نفع تراکنش بعدی حل می شود. بنابراین ما اجرا کردیم ساعت لامپورت - یک راه ارزان برای حل تعارضات در یک سیستم توزیع شده.

قفل

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

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

از آنجایی که در مورد ما داده ها قبلاً بین گروه های تراکنش های محلی در SQL توزیع شده است، تصمیم گرفته شد که گروه های تراکنش محلی را به هماهنگ کننده ها اختصاص دهیم: یک هماهنگ کننده همه تراکنش ها را با توکن ها از 0 تا 9 انجام می دهد، دومی - با توکن های 10 تا 19، و غیره در نتیجه، هر یک از نمونه های هماهنگ کننده، استاد گروه تراکنش می شود.

سپس قفل ها را می توان در قالب یک HashMap پیش پا افتاده در حافظه هماهنگ کننده پیاده سازی کرد.

شکست هماهنگ کننده

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

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

NewSQL = NoSQL+ACID

با دریافت اطلاعات مشابه از دیگران به عنوان بخشی از پیام‌های ضربان قلب خود، هر هماهنگ کننده برای خود تصمیم می‌گیرد که کدام گره‌های خوشه کار می‌کنند و کدام‌ها نیستند، با هدایت اصل حد نصاب: اگر گره X اطلاعاتی را از اکثر گره‌های خوشه در مورد حالت عادی دریافت کرده باشد. دریافت پیام از گره Y، سپس، Y کار می کند. و بالعکس، به محض اینکه اکثریت پیام‌های گم شده را از گره Y گزارش کنند، Y رد کرده است. جالب است که اگر حد نصاب به گره X اطلاع دهد که دیگر پیامی از آن دریافت نمی کند، آنگاه خود گره X خود را شکست خورده می داند.

پیام های ضربان قلب با فرکانس بالا، حدود 20 بار در ثانیه، با دوره زمانی 50 میلی ثانیه ارسال می شوند. در جاوا، تضمین پاسخ برنامه در عرض 50 میلی ثانیه به دلیل طول قابل مقایسه مکث های ایجاد شده توسط زباله جمع کننده دشوار است. ما توانستیم با استفاده از جمع‌آوری زباله G1 به این زمان پاسخ‌دهی برسیم، که به ما امکان می‌دهد یک هدف را برای مدت توقف GC مشخص کنیم. با این حال، گاهی اوقات، به ندرت، مکث جمع کننده بیش از 50 میلی ثانیه است که می تواند منجر به تشخیص خطای اشتباه شود. برای جلوگیری از این اتفاق، هماهنگ کننده خرابی یک گره از راه دور را هنگامی که اولین پیام ضربان قلب از آن ناپدید می شود، گزارش نمی کند، فقط در صورتی که چندین مورد پشت سر هم ناپدید شده باشند. به این ترتیب ما موفق شدیم خرابی گره هماهنگ کننده را در 200 تشخیص دهیم. ام‌اس.

اما درک سریع اینکه کدام گره از کار افتاده است کافی نیست. ما باید کاری در این مورد انجام دهیم.

رزرو

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

NewSQL = NoSQL+ACID

فرض کنید می خواهیم یک تراکنش را در گروه 50 اجرا کنیم. بیایید از قبل طرح جایگزینی را تعیین کنیم، یعنی در صورت شکست هماهنگ کننده اصلی، کدام گره ها تراکنش های گروه 50 را اجرا می کنند. هدف ما حفظ عملکرد سیستم در صورت خرابی مرکز داده است. بیایید تعیین کنیم که اولین ذخیره یک گره از یک مرکز داده دیگر باشد و ذخیره دوم یک گره از یک سوم باشد. این طرح یک بار انتخاب می شود و تا زمانی که توپولوژی خوشه تغییر نکند، یعنی تا زمانی که گره های جدید وارد آن شوند، تغییر نمی کند (که بسیار به ندرت اتفاق می افتد). روش انتخاب یک استاد فعال جدید در صورت عدم موفقیت نسخه قبلی همیشه به این صورت خواهد بود: ذخیره اول به استاد فعال تبدیل می شود و اگر از کار افتاده باشد ذخیره دوم به استاد اصلی تبدیل می شود.

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

اما چگونه مشتریان متوجه خواهند شد که کدام استاد اکنون کار می کند؟ ارسال اطلاعات به هزاران مشتری در 50 میلی ثانیه غیرممکن است. زمانی ممکن است که مشتری درخواستی را برای باز کردن یک تراکنش ارسال کند، اما هنوز نمی‌داند که این اصلی دیگر کار نمی‌کند و زمان درخواست به پایان می‌رسد. برای جلوگیری از این اتفاق، مشتریان به صورت سوداگرانه درخواستی را برای باز کردن یک تراکنش به گروه اصلی و هر دو ذخایر او می‌فرستند، اما تنها کسی که در حال حاضر مستر فعال است به این درخواست پاسخ می‌دهد. کلاینت تمام ارتباطات بعدی را در داخل تراکنش فقط با Master فعال انجام می دهد.

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

نحوه انجام معامله

فرض کنید مشتری درخواستی را به هماهنگ کننده ارسال کرده است تا یک تراکنش برای فلان موجودیت با فلان کلید اولیه باز شود. هماهنگ کننده این موجودیت را قفل می کند و آن را در جدول قفل در حافظه قرار می دهد. در صورت لزوم، هماهنگ کننده این موجودیت را از حافظه می خواند و داده های حاصل را در حالت تراکنش در حافظه هماهنگ کننده ذخیره می کند.

NewSQL = NoSQL+ACID

هنگامی که مشتری می خواهد داده ها را در یک تراکنش تغییر دهد، درخواستی را به هماهنگ کننده ارسال می کند تا موجودیت را اصلاح کند و هماهنگ کننده داده های جدید را در جدول وضعیت تراکنش در حافظه قرار می دهد. این کار ضبط را کامل می کند - هیچ ضبطی در ذخیره سازی انجام نمی شود.

NewSQL = NoSQL+ACID

هنگامی که مشتری داده های تغییر یافته خود را به عنوان بخشی از یک تراکنش فعال درخواست می کند، هماهنگ کننده به صورت زیر عمل می کند:

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

بنابراین، مشتری می تواند تغییرات خود را بخواند، اما سایر مشتریان این تغییرات را نمی بینند، زیرا آنها فقط در حافظه هماهنگ کننده ذخیره می شوند، آنها هنوز در گره های Cassandra نیستند.

NewSQL = NoSQL+ACID

هنگامی که مشتری commit را ارسال می کند، وضعیتی که در حافظه سرویس بود توسط هماهنگ کننده در یک دسته ثبت شده ذخیره می شود و به عنوان یک دسته ثبت شده به حافظه Cassandra ارسال می شود. فروشگاه‌ها هر کاری که لازم است انجام می‌دهند تا اطمینان حاصل شود که این بسته به صورت اتمی (کاملاً) اعمال می‌شود و پاسخی را به هماهنگ‌کننده برمی‌گرداند که قفل‌ها را آزاد کرده و موفقیت معامله را به مشتری تأیید می‌کند.

NewSQL = NoSQL+ACID

و برای بازگشت، هماهنگ کننده فقط باید حافظه اشغال شده توسط حالت تراکنش را آزاد کند.

در نتیجه بهبودهای فوق، ما اصول ACID را اجرا کردیم:

  • اتمی بودن. این تضمین می کند که هیچ تراکنش به طور جزئی در سیستم ثبت نمی شود؛ یا تمام عملیات فرعی آن تکمیل می شود یا هیچ یک تکمیل نمی شود. ما از طریق دسته ثبت شده در Cassandra به این اصل پایبند هستیم.
  • ثبات. هر تراکنش موفق، طبق تعریف، فقط نتایج معتبر را ثبت می کند. اگر پس از باز کردن یک تراکنش و انجام بخشی از عملیات، مشخص شد که نتیجه نامعتبر است، بازگشت مجدد انجام می شود.
  • انزوا. هنگامی که یک تراکنش اجرا می شود، تراکنش های همزمان نباید بر نتیجه آن تأثیر بگذارد. تراکنش های رقیب با استفاده از قفل های بدبینانه روی هماهنگ کننده جدا می شوند. برای خواندن خارج از تراکنش، اصل جداسازی در سطح Read Committed رعایت می شود.
  • پایداری. صرف نظر از مشکلات در سطوح پایین تر - خاموشی سیستم، خرابی سخت افزار - تغییرات ایجاد شده توسط یک تراکنش با موفقیت انجام شده باید با از سرگیری عملیات حفظ شود.

خواندن بر اساس نمایه ها

بیایید یک جدول ساده بگیریم:

CREATE TABLE photos (
id bigint primary key,
owner bigint,
modified timestamp,
…)

دارای شناسه (کلید اصلی)، مالک و تاریخ اصلاح است. شما باید یک درخواست بسیار ساده ارائه دهید - داده های مالک را با تاریخ تغییر "برای روز آخر" انتخاب کنید.

SELECT *
WHERE owner=?
AND modified>?

برای اینکه چنین پرس و جوی به سرعت پردازش شود، در یک DBMS کلاسیک SQL باید یک فهرست بر اساس ستون (مالک، اصلاح شده) بسازید. ما می توانیم این کار را به راحتی انجام دهیم، زیرا ما اکنون ضمانت اسید داریم!

ایندکس در C*One

یک جدول منبع با عکس ها وجود دارد که شناسه رکورد در آن کلید اصلی است.

NewSQL = NoSQL+ACID

برای ایندکس، C*One یک جدول جدید ایجاد می کند که کپی اصلی است. کلید همان عبارت شاخص است و همچنین شامل کلید اصلی رکورد از جدول منبع است:

NewSQL = NoSQL+ACID

اکنون پرس و جو برای "مالک برای روز آخر" را می توان به عنوان یک انتخاب از جدول دیگر بازنویسی کرد:

SELECT * FROM i1_test
WHERE owner=?
AND modified>?

هماهنگی داده ها در عکس های جدول منبع و جدول فهرست i1 به طور خودکار توسط هماهنگ کننده حفظ می شود. تنها بر اساس طرح داده، زمانی که تغییری دریافت می‌شود، هماهنگ‌کننده تغییری را نه تنها در جدول اصلی، بلکه در کپی‌ها ایجاد و ذخیره می‌کند. هیچ اقدام اضافی روی جدول فهرست انجام نمی شود، لاگ ها خوانده نمی شوند و هیچ قفلی استفاده نمی شود. یعنی افزودن ایندکس ها تقریباً هیچ منبعی را مصرف نمی کند و عملاً تأثیری بر سرعت اعمال تغییرات ندارد.

با استفاده از ACID، ما توانستیم ایندکس های SQL مانند را پیاده سازی کنیم. آنها سازگار، مقیاس پذیر، سریع، قابل ترکیب هستند و در زبان پرس و جو CQL تعبیه شده اند. هیچ تغییری در کد برنامه برای پشتیبانی از ایندکس ها لازم نیست. همه چیز به همان سادگی در SQL است. و مهمتر از همه، ایندکس ها بر سرعت اجرای تغییرات در جدول تراکنش اصلی تاثیری ندارند.

چی شد

ما سه سال پیش C*One را توسعه دادیم و آن را به صورت تجاری راه اندازی کردیم.

در نهایت چه چیزی به دست آوردیم؟ بیایید این را با استفاده از مثال زیرسیستم پردازش و ذخیره عکس، یکی از مهم ترین انواع داده ها در یک شبکه اجتماعی، ارزیابی کنیم. ما در مورد بدنه خود عکس ها صحبت نمی کنیم، بلکه در مورد انواع متا اطلاعات صحبت می کنیم. اکنون Odnoklassniki حدود 20 میلیارد چنین رکوردی دارد، سیستم 80 هزار درخواست خواندن در ثانیه، تا 8 هزار تراکنش ACID در ثانیه مرتبط با اصلاح داده ها را پردازش می کند.

هنگامی که ما از SQL با ضریب تکرار = 1 (اما در RAID 10) استفاده کردیم، اطلاعات فرا اطلاعاتی عکس در یک خوشه بسیار در دسترس از 32 ماشین که مایکروسافت SQL Server را اجرا می‌کنند (به علاوه 11 نسخه پشتیبان) ذخیره می‌شد. 10 سرور نیز برای ذخیره بک آپ اختصاص داده شد. در کل 50 ماشین گران قیمت. در همان زمان، سیستم با بار نامی، بدون ذخیره کار می کرد.

پس از مهاجرت به سیستم جدید، ضریب تکرار = 3 - یک کپی در هر مرکز داده دریافت کردیم. این سیستم متشکل از 63 گره ذخیره سازی کاساندرا و 6 ماشین هماهنگ کننده، در مجموع 69 سرور است. اما این ماشین ها بسیار ارزان تر هستند، هزینه کل آنها حدود 30٪ هزینه یک سیستم SQL است. در همان زمان، بار در 30٪ نگه داشته می شود.

با معرفی C*One، تأخیر نیز کاهش یافت: در SQL، یک عملیات نوشتن حدود 4,5 میلی ثانیه طول کشید. در C*One - حدود 1,6 میلی ثانیه. مدت تراکنش به طور متوسط ​​کمتر از 40 میلی ثانیه است، تعهد در 2 میلی ثانیه تکمیل می شود، مدت زمان خواندن و نوشتن به طور متوسط ​​2 میلی ثانیه است. صدک 99 - فقط 3-3,1 میلی ثانیه، تعداد وقفه ها 100 برابر کاهش یافته است - همه اینها به دلیل استفاده گسترده از حدس و گمان است.

در حال حاضر، بیشتر گره های SQL Server از کار افتاده اند؛ محصولات جدید تنها با استفاده از C*One در حال توسعه هستند. ما C*One را برای کار در ابر خود تطبیق دادیم یک ابری، که امکان سرعت بخشیدن به استقرار خوشه های جدید، ساده سازی پیکربندی و خودکارسازی عملیات را فراهم کرد. بدون کد منبع، انجام این کار بسیار دشوارتر و دست و پا گیرتر خواهد بود.

اکنون ما در حال کار بر روی انتقال سایر امکانات ذخیره سازی خود به ابر هستیم - اما این یک داستان کاملاً متفاوت است.

منبع: www.habr.com

اضافه کردن نظر