۳۰ شهریور ۱۴۰۳

Techboy

اخبار و اطلاعات روز تکنولوژی

Apache Flink 101: راهنمای توسعه دهندگان

استاندارد واقعی برای پردازش جریانی بلادرنگ گاهی اوقات پیچیده و یادگیری آن دشوار است. با درک این اصول اصلی شروع کنید.

استاندارد واقعی برای پردازش جریانی بلادرنگ گاهی اوقات پیچیده و یادگیری آن دشوار است. با درک این اصول اصلی شروع کنید.

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

نام خانوارهایی مانند آمازون، نتفلیکس، و اوبر برای تامین انرژی خطوط لوله داده‌ای که در مقیاس فوق‌العاده در قلب کسب‌وکارشان اجرا می‌شوند، به Flink متکی هستند. اما Flink همچنین نقش کلیدی در بسیاری از شرکت‌های کوچک‌تر با شرایط مشابه برای توانایی واکنش سریع به رویدادهای تجاری مهم دارد.

apache flink 101 01

Flink برای چه استفاده می شود؟ موارد استفاده رایج به سه دسته تقسیم می شوند.

خطوط انتقال جریان داده

تجزیه و تحلیل بیدرنگ

برنامه‌های مبتنی بر رویداد

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

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

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

برخی از نمونه‌ها عبارتند از:

  • جریان ETL
  • مصرف دریاچه داده
  • خطوط یادگیری ماشین

برخی از نمونه‌ها عبارتند از:

  • عملکرد کمپین تبلیغاتی
  • مصرف اندازه‌گیری و صورت‌حساب
  • نظارت شبکه
  • مهندسی ویژگی

برخی از نمونه‌ها عبارتند از:

  • تشخیص کلاهبرداری
  • نظارت و اتوماسیون فرآیند کسب و کار
  • حصار جغرافیایی

خطوط انتقال جریان داده

تجزیه و تحلیل بیدرنگ

برنامه‌های مبتنی بر رویداد

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

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

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

برخی از نمونه‌ها عبارتند از:

  • جریان ETL
  • مصرف دریاچه داده
  • خطوط یادگیری ماشین

برخی از نمونه‌ها عبارتند از:

  • عملکرد کمپین تبلیغاتی
  • مصرف اندازه‌گیری و صورت‌حساب
  • نظارت شبکه
  • مهندسی ویژگی

برخی از نمونه‌ها عبارتند از:

  • تشخیص کلاهبرداری
  • نظارت و اتوماسیون فرآیند کسب و کار
  • حصار جغرافیایی

و چه چیزی Flink را خاص می کند؟

  • پشتیبانی قوی از حجم کاری جریان داده در مقیاس مورد نیاز شرکت های جهانی.
  • ضمانت‌های قوی برای درستی یک‌بار و بازیابی شکست.
  • پشتیبانی از جاوا، Python، و SQL، با پشتیبانی یکپارچه برای پردازش دسته‌ای و جریانی.
  • Flink یک پروژه منبع باز بالغ از بنیاد نرم افزار Apache است و دارای یک انجمن بسیار فعال و حامی است.

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

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

هدف این مقاله این است که سفر یادگیری Flink را با طرح اصول اصلی طراحی آن بسیار آسان‌تر کند.

Flink مظهر چند ایده بزرگ است

جریان‌ها

Flink چارچوبی برای ساخت برنامه‌هایی است که جریان‌های رویداد را پردازش می‌کنند، جایی که یک جریان یک دنباله رویدادها محدود یا نامحدود است.

apache flink 101 02

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

apache flink 101 03

کدی که با استفاده از یکی از APIهای Flink می نویسید، نمودار کار را توصیف می کند، از جمله رفتار اپراتورها و اتصالات آنها.

ساخت زیرساخت ابری قابل برنامه ریزی برای توسعه دهندگان

پردازش موازی

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

apache flink 101 04

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

در Flink SQL این کار را با GROUP BYtransaction_id انجام می‌دهید، در حالی که در DataStream API از keyBy(event -> event.transaction_id)  برای تعیین این گروه‌بندی استفاده می‌کنید. ، یا پارتیشن بندی در هر صورت، این در نمودار کار به عنوان یک شبکه کاملاً متصل بین دو مرحله متوالی نمودار نشان داده می‌شود.

apache flink 101 05

ایالت

اپراتورهایی که روی جریان‌های پارتیشن بندی شده با کلید کار می‌کنند، می‌توانند از ذخیره‌سازی وضعیت کلید/مقدار توزیع‌شده Flink استفاده کنند تا هر آنچه را که می‌خواهند به‌طور بادوام ادامه دهند. وضعیت هر کلید برای یک نمونه خاص از یک اپراتور محلی است و از جای دیگری قابل دسترسی نیست. توپولوژی های فرعی موازی هیچ چیز مشترکی ندارند – این برای مقیاس پذیری نامحدود بسیار مهم است.

apache flink 101 06

یک کار Flink ممکن است به طور نامحدود در حال اجرا باقی بماند. اگر یک کار Flink به طور مداوم کلیدهای جدید (به عنوان مثال، شناسه تراکنش) ایجاد کند و چیزی را برای هر کلید جدید ذخیره کند، آن کار به دلیل استفاده از مقدار نامحدودی از وضعیت خطر انفجار را دارد. هر یک از APIهای Flink بر اساس ارائه راه‌هایی سازماندهی شده‌اند که به شما کمک می‌کند از انفجارهای فراری در حالت اجتناب کنید.

زمان

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

Flink تمایز مهمی بین دو مفهوم مختلف از زمان ایجاد می‌کند:

  • زمان پردازش (یا ساعت دیواری)، که از زمان واقعی روز که یک رویداد در حال پردازش است، به دست می‌آید.
  • زمان رویداد، که بر اساس مهرهای زمانی ثبت شده با هر رویداد است.

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

  • پنجره زمانی پردازش وقتی دقیقه تمام شد تکمیل می‌شود. این کاملاً ساده است.
  • پنجره زمانی رویداد تکمیل می شود زمانی که همه رویدادهایی که در آن دقیقه رخ داده اند پردازش شوند. این می تواند مشکل باشد، زیرا Flink نمی تواند چیزی در مورد رویدادهایی که هنوز پردازش نکرده است بداند. بهترین کاری که می‌توانیم انجام دهیم این است که در مورد اینکه یک جریان ممکن است خارج از نظم باشد، فرض کنیم و این فرض را به صورت اکتشافی به کار ببریم.

نقطه بازرسی برای بازیابی شکست

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

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

معماری سیستم Flink

برنامه‌های Flink در خوشه‌های Flink اجرا می‌شوند، بنابراین قبل از اینکه بتوانید یک برنامه Flink را وارد تولید کنید، به یک خوشه برای استقرار آن نیاز دارید. خوشبختانه، در طول توسعه و آزمایش، شروع به کار با اجرای Flink به صورت محلی در محیط توسعه یکپارچه مانند JetBrains IntelliJ یا در Docker آسان است.

خوشه Flink دارای دو نوع مؤلفه است: مدیر شغل و مجموعه ای از مدیران وظیفه. مدیران وظیفه برنامه های شما را (به صورت موازی) اجرا می کنند، در حالی که مدیر شغل به عنوان دروازه ای بین مدیران وظیفه و دنیای خارج عمل می کند. برنامه‌ها به مدیر شغل ارسال می‌شوند، که منابع ارائه‌شده توسط مدیران وظیفه را مدیریت می‌کند، نقاط بازرسی را هماهنگ می‌کند، و در قالب معیارها در خوشه قابل مشاهده است.

8 فریمورک جاوا برای دنیای ابری

apache flink 101 07

تجربه توسعه دهنده Flink

تجربه‌ای که به‌عنوان یک توسعه‌دهنده Flink خواهید داشت، تا حدی بستگی به این دارد که کدام یک از API‌هایی را که انتخاب می‌کنید: API قدیمی‌تر و سطح پایین‌تر DataStream یا APIهای جدیدتر، Table و SQL.

apache flink 101 08

وقتی با Flink’s DataStream API برنامه نویسی می کنید، آگاهانه به این فکر می کنید که زمان اجرای Flink هنگام اجرای برنامه شما چه کاری انجام می دهد. این به این معنی است که شما در حال ایجاد نمودار شغلی یک اپراتور هستید، وضعیتی را که استفاده می‌کنید به همراه انواع درگیر و سریال‌سازی آنها، ایجاد تایمر و اجرای توابع برگشت به تماس برای اجرا در هنگام راه‌اندازی آن تایمرها، و غیره توصیف می‌کنید. انتزاع در DataStream API رویداد است، و توابعی که شما می نویسید، همزمان با رسیدن، یک رویداد را مدیریت می کنند.

از طرف دیگر، وقتی از Flink’s Table/SQL API استفاده می‌کنید، این نگرانی‌های سطح پایین برای شما برطرف می‌شود و می‌توانید مستقیم‌تر روی منطق کسب‌وکارتان تمرکز کنید. انتزاع اصلی جدول است، و شما بیشتر در مورد پیوستن جداول برای غنی سازی، گروه بندی ردیف ها با هم برای محاسبه تجزیه و تحلیل انبوه، و غیره فکر می کنید. یک برنامه ریز/بهینه ساز پرس و جو داخلی SQL از جزئیات مراقبت می کند. برنامه ریز/بهینه ساز کار بسیار خوبی در مدیریت کارآمد منابع انجام می دهد و اغلب کدهای دست نویس را بهتر اجرا می کند.

چند فکر دیگر قبل از پرداختن به جزئیات: اول، لازم نیست DataStream یا Table/SQL API را انتخاب کنید—هر دو API قابل همکاری هستند و می‌توانید آنها را ترکیب کنید. اگر به کمی سفارشی سازی نیاز دارید که در Table/SQL API امکان پذیر نیست، این می تواند راه خوبی باشد. دوم، یکی دیگر از راه‌های خوب برای فراتر از آنچه Table/SQL API ارائه می‌دهد، افزودن برخی قابلیت‌های اضافی در قالب توابع تعریف‌شده توسط کاربر (UDF) است. در اینجا، Flink SQL گزینه های زیادی برای توسعه ارائه می دهد.

ساخت نمودار کار

صرف نظر از اینکه از کدام API استفاده می کنید، هدف نهایی کدی که می نویسید ساختن نمودار شغلی است که زمان اجرا Flink از طرف شما اجرا می کند. این بدان معناست که این APIها حول ایجاد عملگرها و مشخص کردن رفتار و ارتباط آنها با یکدیگر سازماندهی شده اند. با DataStream API شما مستقیماً نمودار کار را می سازید. با Table/SQL API، برنامه ریز SQL Flink از این امر مراقبت می کند.

سریال کردن توابع و داده ها

در نهایت، کدی که به Flink می‌دهید به‌طور موازی توسط کارگران (مدیر وظیفه) در یک کلاستر Flink اجرا می‌شود. برای تحقق این امر، اشیاء تابعی که ایجاد می‌کنید سریال می‌شوند و به مدیران وظیفه ارسال می‌شوند تا در آنجا اجرا شوند. به طور مشابه، خود رویدادها گاهی اوقات نیاز به سریال سازی و ارسال در سراسر شبکه از یک مدیر وظیفه به مدیر دیگر دارند. باز هم، با Table/SQL API لازم نیست به این موضوع فکر کنید.

مدیریت وضعیت

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

با DataStream API شما معمولاً به طور مستقیم وضعیت مورد نیاز برنامه خود را مدیریت می کنید (عملیات پنجره داخلی یک استثنا در این مورد هستند). از سوی دیگر، با Table/SQL API این نگرانی از بین می رود. برای مثال، با توجه به درخواستی مانند مورد زیر، می‌دانید که در جایی در زمان اجرا Flink، برخی از ساختار داده‌ها باید یک شمارنده برای هر URL داشته باشند، اما همه جزئیات برای شما در نظر گرفته شده است.

SELECT url, COUNT(*)
FROM pageviews
GROUP BY url;

تنظیم و راه اندازی تایمر

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

تایمرها همچنین برای اجرای عملیات پنجره سازی مبتنی بر زمان ضروری هستند. هر دو APIهای DataStream و Table/SQL از ویندوز پشتیبانی می‌کنند و تایمرها را از طرف شما ایجاد و مدیریت می‌کنند.

استفاده از کد ویژوال استودیو برای توسعه سی شارپ

موارد استفاده از Flink

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

خط لوله داده جریانی

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

apache flink 101 09

خط لوله جریان متناظر از نظر ظاهری مشابه است، اما تفاوت‌های مهمی دارد:

  • خط لوله جریان همیشه در حال اجرا است.
  • داده‌های تراکنشی در دو بخش به خط لوله جریان تحویل داده می‌شوند: یک بار انبوه اولیه از پایگاه داده و یک جریان ضبط داده تغییر (CDC) که به‌روزرسانی‌های پایگاه داده را از زمان بارگیری انبوه ارائه می‌دهد.
  • نسخه پخش جریانی به‌محض در دسترس قرار گرفتن نتایج جدید به‌طور پیوسته تولید می‌کند.
  • State به صراحت مدیریت می شود تا در صورت خرابی بتوان آن را به خوبی بازیابی کرد. خطوط لوله ETL جریان معمولاً از حالت بسیار کمی استفاده می کنند. منابع داده دقیقاً میزان ورودی دریافت شده را ردیابی می‌کنند، معمولاً به شکل افست‌هایی که رکوردها را از ابتدای جریان‌ها شمارش می‌کنند. سینک ها از تراکنش ها برای مدیریت نوشته های خود در سیستم های خارجی مانند پایگاه های داده یا آپاچی کافکا استفاده می کنند. در حین بازرسی، منابع افست های خود را ثبت می کنند، و سینک ها تراکنش هایی را انجام می دهند که نتایج خواندن دقیقاً تا، اما نه فراتر از آن افست های منبع را به همراه دارد.

برای این مورد، Table/SQL API انتخاب خوبی خواهد بود.

تجزیه و تحلیل بیدرنگ

در مقایسه با برنامه پخش جریانی ETL، برنامه تجزیه و تحلیل جریان چند تفاوت جالب دارد:

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

یک بار دیگر، Table/SQL API معمولاً انتخاب خوبی برای این مورد است.

apache flink 101 10

برنامه های مبتنی بر رویداد

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

apache flink 101 11

Flink می‌تواند برای این برنامه‌ها مناسب باشد، به خصوص اگر به عملکردی نیاز دارید که Flink می‌تواند ارائه دهد. در برخی موارد Table/SQL API همه چیز مورد نیاز شما را دارد، اما در بسیاری از موارد حداقل برای بخشی از کار به انعطاف‌پذیری بیشتر DataStream API نیاز دارید.

از امروز با Flink شروع کنید

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

در مرحله بعدی، دستورالعمل‌های موجود در مستندات Flink، که شما را از طریق آخرین مراحل نصب، بارگیری، نصب و راه‌اندازی می‌کند. نسخه فلینک در مورد موارد استفاده گسترده ای که در مورد آنها بحث کردیم فکر کنید – خطوط لوله داده مدرن، تجزیه و تحلیل بلادرنگ، و ریزسرویس های رویداد محور – و اینکه چگونه می توانند به حل چالش یا ارزش محرک برای سازمان شما کمک کنند.

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

دیوید اندرسون رهبری نرم‌افزار در همراه.

New Tech Forum مکانی را برای رهبران فناوری – از جمله فروشندگان و سایر مشارکت‌کنندگان خارجی – فراهم می‌کند تا فناوری سازمانی نوظهور را در عمق و وسعت بی‌سابقه بررسی و بحث کنند. انتخاب ذهنی است، بر اساس انتخاب ما از فناوری هایی که معتقدیم مهم هستند و برای خوانندگان InfoWorld بیشترین علاقه را دارند. InfoWorld وثیقه بازاریابی را برای انتشار نمی پذیرد و حق ویرایش تمام محتوای ارائه شده را برای خود محفوظ می دارد. همه پرس و جوها را به doug_dineley@foundryco.com.