הבלוג של ארתיום
בלוג על לינוקס, תוכנה חופשית, מוזיקה, סלסה, ומה לא!
פיתוח Web בשפת ++C? למה, מדוע, כיצד?
מי שמכיר אותי מהפורומים השונים יודע טוב שאני רחוק מלהיות חסיד של שפות תסריטים כגון php, perl, python או שפות jit כמו C#, Java וחבריהן. אז חיפשתי framework נוח לפיתוח web עם C++/C. הסיבה העיקרית היא -- ביצועים, מערכות תוכן של היום הכתובים ב-php או שפות תסריטים אחרות הנשענות על פתרונות LAMP סטנדרטיים מספקות ביצועים נמוכים שלא מתאימים לאתרים עמוסים. התפתח דיון מעניין בנושא עם מאיר. שהטענות העיקריות היו:
- צוואר בקבוק העיקרי הוא בסיס נתונים ולא שפת תכנות.
- גם עם עושים caching ועובדים עם FastCGI יעיל נשארת בעיית concurrency ו-scalability.
- זמן פיתוח הוא גם יקר, לכן C++/C הן לא השפות המועדפות במקרים אלו.
אז החלטתי לשבור את המוסכנות האלו ולהוכיח שניתן לבנות מערכת תוכן/framework בעל מאפניינים הבאים:
- מאפשרים פיתוח יחסית מהיר וקל.
- מספק ביצועים מעולים שמערכות תוכן קלאסיות היו דורשות פתרונות scaling במקרים אלו.
- מספק כלים ל-scaling פשוטים ושקופים במקרה הצורך.
השאלה שנשאלת כיצד?
קודם כל נתבונן בשאילתה שעוברת במערכת תוכן סטנדרטית מבוססת על Apache, PHP/Perl/Python ו-MySQL (ללא caching).
- הלקוח מבקש דף אינטרנט
- השאילתה מגיעה לשרת מעל TCP/IP שמקצה תהליך ייעודי עבור שפת תסריטים כמו php.
- מנוע php טוען את הקוד ומפענח אותו.
- הקוד טוען את כל הפרמטרים של מערכת התוכן ומתחיל לבצע שאילתות ל-MySQL מעל TCP/IP או unix socket.
- על כל שאילתה MySQL בודק אם היא נמצאת ב-cache (רוב הסיכויים) ומחזיר אותה לאותו תסריט שרץ.
- השלבים 4-5 חוזרים על עצמם כעשר--עשרים פעם.
- התסריט מייצר עמוד ומחזיר אותו לשרת, שדואג לשלוח אותו ללקוח
איזה רכיבים נראים כאן מיותרים או חוזרים לעצמם?
- פיענוח תסריטים בכל פעם מחדש.
- טעינת אותם נתוני קונפיגורציה מחדש
- יצירת קשר עם DB וסגירתו כל פעם מחדש.
- שליפת אותם הנותנים כל פעם מחדש.
- יצירת אותם הדפים (או עם שינויים קלים) בכל פעם מחדש.
אכן, לא מעט... אז כיצד פותרים את הבעיה בד"כ? הרי רוב האתרים העמוסים המבוססים על LAMP יודעים לעמוד ביותר מכמה עשרות שאילתות בשניה?
אז יש כאן שני פתרונות -- בהתאם לעומס:
לאתרים בינוניים -- שימוש אינטנסיבי ב-caching.
אם שני מבקרים מבקשים אותו עמוד, למה ליצור אותו פעמים? בודקים אם עמוד בעל אותם מאפיינים נמצא ב-cache ושולפים אותו בלי להעביר את כל עשרות השאילתות ל-MySQL. הפתרון עובד טוב, כל עוד מדובר באתר בעל דפים כמעט סטטים -- למשל אתר חדשות.
הפתרון הזה הופך להרבה יותר קשה/לא ישים אם מדובר במערכת תוכן כמו פורום שבה אתה רוצה להציג לכל משתמש: מספר הודעות בעמוד שהוא רוצה, לסמן הודעות מאז הביקור האחרון שלו, לבנות תיבה להודעות פרטיות ועוד. במקרה הזה, caching חייב להיות מאוד מורכב -- מה לשמור ומה לא, כך שהפיתוח של מערכת כזו הופך לכלל לא טריוויאלי.
לאתרים גדולים -- scale up בעזרת DataBase Replication ו-memcached.
פתרונות כאלה משמשות את המערכות כמו LiveJournal והן עולות הון מבחינת השקעה בחומרה וגם מבחינת מאמצי פיתוח.
כיצד זה עובד? מאחורי האתר עומדים מספר שרתים שמריצים את התוכנה. כל שאילתה ל-DB קודם כל נבדקת ב-memcached שמהווה מעין שרת hash מהיר שמחזיר נתונים לפי מפתח מסויים, אם הנתונים לא נמצאים ב-cache הם מוכנסים אליו. מכיוון שמדובר במעין in memory db הוא עובד מהר מאוד ביחס ל-DB הקלאסיים כמו MySQL. כמו כן, העבודה מתבצעת מול מספר שרתי MySQL שכל המידע משוכפל -- replication.
כמובן שהפתרון הזה מתאים בעיקר לאתרים גדולים מאוד ועולה הון מבחינת פיתוח ותחזוקה.
עכשיו נשאל את עצמנו את השאלות הבאות:
- מדוע אנחנו צריכים לעבוד עם DB מעל TCP/IP ולא משהו שמור בתוך היישום?
- מדוע אנחנו צריכים להתחיל אותם תהליכים בכל פעם מחדש?
- מדוע אנחנו צריכים לפענח אותו קוד בכל פעם מחדש?
כי ככה התרגלנו, זה נוח לפתח עם php או perl שמכיל כל כך הרבה רכיבים נוחים לטיפול בטקסטים, כי זה נוח לעבוד מול DB ששומר את המצב, כי ככה רוב המערכת נכתבו מלכתחילה, כי ככה העבודה מול DB מתבצעת - מעל רכיבי תקשורת כלשהן.
בואו נסתכל על שיטה חילופית:
- השאילתות מתקבלות דרך fastcgi ל-process יחיד שכבר טען את רוב הנתונים הנחוצים כמו templates, קבצי קונפיגורציה.
- הכל השאילתות עוברות דרך process יחיד ששומר את רוב ה-cache בתוך היישום עצמו כך שכל שליפה מתבצעת במהירות של גישה לזכרון.
- כל הנתונים שמורים בפורמט בינארי פנימי בלי צורך לבצע מערכת לשאילתות SQL.
- המידע נשמר בקבצים בדיסקים במקום להשמר בבסיס נתונים.
- scaling מתבצע ע"י העתקה של קבצים וביצוע replication אליהם (כתיבות בלבד).
- הקוד עצמו הוא קוד C++/C שרץ במהירות מקסימלית שהמעבד נותן.
כמובן, לממש cache שעובד מול הרבה thread-ים, שיודע לקרוא בצורה מהירה מהקבצים הוא משימה כלל לא טריוויאלית, אבל כמו בכל מקרה אחר, מישהו כבר עשה זאת בשבילנו:
קצת על Berkeley DB
זהו בסיס נותנים חופשי שמפותח כיום ע"י Oracle, שלא מבוסס על SQL או שפות שאילתות אחרות, אלא על API לשפות תכנות -- קרי הגישה דרך פונקציותC++/C. זהו בסיס נתונים שתומך ב-replication, יודע לשמור cache גדול ותומך ב-multithreading שכל אחד מה-threads נהנה מה-cache המשותף של אותו ה-process. במצב כזה, הגישה לנתונים שנמצאים ב-cache שוות ערך לקריאה מהזכרון.
כמובן, העבודה איתו לא חביבה כמו עבודה מול SQL עם שאילתות יפות אלא צריך לחשוב על הרבה דברים בעצמך, אבל, בסופו של דבר המהירות שלו, זו שהופכת אותה לכל כך אטרקטיבית. למשל אם MySQL מסוגל לבצע כ-10,000 שאילתות select פשוטות עם שליחה דרך mysql או כ-5,000 עם שליחה דרך perl, אז Berkeley DB מספק כ-200,000 שאילתות בשניה.
אז יופי בואו נכתוב מערכת בעזרת FastCGI Berkeley DB ו-++C?
ברמת העיקרון כן אבל, כאן מסתבר שזה לא כל כך פשוט, אומנם יש ספריות ו-API מוכן הן עבור עבודה עם FastCGI והן עבור עבודה עם BDB, רק שהוא ממש לא פשוט ודורש השקעה נכרת ע"מ להתחיל לעבוד איתו. למשל, אין לך את הכלים הנוחים שמאפשרים לבדוק פרמטרים GET/PUT אלא אתה צריך לפענח אותם, אתה צריך להפוך %20 לרווח, יש עשרות פרמטרים עדינים שצריך לדעת לקבוע, ה-API עצמו של BDB הוא חזק אבל לא כל כך נוחה לשימוש ברוב המקרים ודורש השקעה רבה בקריאת התיעוד, יצירת אינדקסים לא פשוטה כמו CREATE INDEX ועוד.
אז מה שנדרש זה Framework.
שיספק API ידידותי למפתח: יצור בסיסי נתונים ויגדיר אינדקסים, יטפל ב-replication, יפתח socket-ים של FastCGI ויעלה thread-ים שיטפלו בהם, יפענח שאילתות, יבצע דחיסת gzip במקרה הצורך, יספק תשובות נכונות עבור השרת, יספק תשתית ליצירת templates ויסיר רוב התעסקות ב-html מקוד ++C ועוד.
כל מה שיישאר לפתח זה: להגדיר מבני נתונים, אינדקסים עבורם, לכתוב HTML עבור הדפים ולממש לוגיקת היישום ב++C -- המאמץ שכבר אמורה להיות הרבה יותר פשוטה אחרי שיסירו את הכל הרכיבים התשתיתיים מסביב.
בצורה כזו, ניתן יהיה לפתח מערכות תוכן בצורה פשוטה שיוכלו להתמודד בעומסים גדולים גם על תשתיות דלות.
אז איפה אני נמצא?
הכנתי מעטפת נוכחה ראשונית ל-Berkeley DB, הכנתי ממשקים ומימשתי חלק מהם עבור עבודה עם טקסטים. בתקווה שאוכל להעלות בקרוב אב טיפוס ראשוני של מערכת תוכן פשוטה ולבצע מדידות ביצועים שיראו שלא צריך 5--10 שרתים על מנת לספק אתר שעומד בכ-800--1000 שאילתות של דפים דינמיים בשניה, אלא מה שצריך זה לא לפחד להסתכל מחוץ לקופסת LAMP או ASP.NET+MSSQL.
תגובות
למה להמציא את הגלגל ולא לעבוד עם firebird או sqlite?
אגב, אם משתמשים ב־fastcgi שכתוב בשפה דינאמית, עדיין נמנעים מ־3, ואולי גם מ־4.
בכל מקרה לא ברור לי למה שלב 4 חוזר על עצמו עשרות פעמים במהלך טעינת דף אחד.
יש ייתרון בשפה מקומפלת, השאלה אם C++ היא השפה האידיאלית?
בסה"כ היא כן תיקח זמן פיתוח יקר עקב בעיות שהיא יוצרת, כמו דליפות זיכרון, ועוד.
למשל בפסקל אתה צריך לדאוג פחות כי יש שם גרביג קולקטור...
הנה לא אומר שפסקל זו התשובה, ואני גם לא מכיר מספיק שפות תכנות.
חוץ מיזה פריים וורק טוב שניתן לבנות בו בקלות דברים ניתן בכל שפה, תמיד הרי ניתן לעשות פונקציה שתקל עליך לעשות דברים.
אז למה לא אסמבלר אם כבר? (טוב נראה שהגזמתי עכשיו)
אבל גם בו, יש פרים וורקים ויש כאלה מיוחדים שכוללים תחברירים פשוטים כמו if וכו שמתורגמים לאסמבלי ועוד (ששאר הקוד הוא אסמבלי)
זה הרי ייתן את היעילות הכי טובה. ----________-
ויכול להיות שכל מה שרשמתי כרגע זה שטויות, רציתי לבטל תגובה זו ע"י יציאה מהתאב, אולם מכיוון שלאפיפני יש מנגנון הסטוריה מתוחכם, הוא זבכ גם שעשיתי בק בתב אחר, אחרי שאת התב המקורי בוטל...
----________-
תוספת:
במוצרי אמבדד נהוג לכתוב את כול האפליקציות כולל השרת בסי, כך שזה לא רעיון מופרך.
אני בהחלט מסכים: אם כותבים יישום FastCGI נטו אז אין בזה צורך.
אבל מה שתיארתי זו הגישה הפופולרית. כלומר אני דיברתי על גישה סטנדרטית כמו שהיא קיימת ב-phpBB למשל או ב-WP.
אני השוויתי ביצועים של MySQL ו-sqlite3 וביצועים של BerkeleyDB - ההבדל הוא בסדרי גודל. כלומר אם אני מצליח לבצע כ-200,000--300,000 שליפות בשניה בעזרת BerkeleyDB, אז בעזרת sqlite3 או MySQL אני מגיע רק ל-10,000. זה באמת לא מספיק מהיר. יש Overhead של parsing של שאילתות sql הפיכה של אחת לשניה ועוד. לעומת זאת ב-BDB אתה פשוט נותן מפתח בינארי (למשל int) ומקבל רשומה מהזכרון. ההבדל הוא משמעותי.
צודק טעות שלי - רק 5. קרי שליחת שאילתות מעל TCP/IP פיענוח שלהן בצד DB שליחת תשובה ופענוח התשובה בתוך היישום.
בשביל להמנע מדליפות זכרון יש מספיק כלים בייחוד ב-++C כמו שימוש ב-STL עובדה עם auto_ptr ועוד. עם עובדים בצורה שיטתית לא יהיו בעיות. בסה"כ היישום הוא יחסית פשוט.
בנוסף, יש היום מספיק כלים לאיתור דליפות זכרון.
אין ספק שהרבה יותר קל לכתוב ב-Java או ב-Python. אבל... אין ספק שלא צריך לפחד ב-++C ומ-C.
אולי דברים השתנו בשנים האחרונות... אבל עד כמה שידוע לי ב-Packal אין GC. לפחות בתקופה שתכנתתי בו הייתי צריך לשחרר זכרון באופן מפורש.
בנוסף ל-GC למיניהם יש מספר בעיות - שימוש יתר בזכרון בגלל אי שחרורו בזמן. - בזבוז זמן יקר על GC שפוגע בביצועים.
לגבי Assembler - התשובה פשוטה: דורש ידע טוב של חומרה והבנה של pipeline על מנת לכתוב קוד יעיל יותר מהקומפיילר, בנוסף הקוד מאוד לא פורטבילי.
בנוסף, אני פחות התייחסתי לשפות תכנות מאשר לגישה עצמה.
כלומר במקום לעובד עם: SQL בתור סביבה ששומרת מצב המערכת, ותסריט שמורץ בכל פעם מחדש על כל שאילתה
לעבוד עם: תסריט/תכנה ששומר את ה-cache בזכרון, שולף מידע ממנו תוך כדי הימנעות מקסימלית מכפילות וכל ה-overhead שנמצא ב-parsing, TCP/IP ועוד.
אני מניח שבאותה מידה ניתן להשתמש ב-#C או ב-Java -- שפות jit, רק שזה לא אומר שהן תהיינה פשוטות יותר מ-++C. (איטיות כן).
נדב, דבר ראשון לפסקל אין GC. :) פשוט אתה לא צריך לנהל דברים בסיסיים כל כך כמו מחרוזות, אלא אם אתה ממש רוצה.. אני חושב שיש הרבה יותר פתרונות מאשר להשתמש בשפה כמו C/++ (או פסקל). למשל אם המידע כמו הבלוג הזה, לא באמת משתנה אחרי שיצרת אותו מידי "שנייה", למה לא ליצור מצב סטטי שלו שנשמר בתור HTML נקי ? כל שינוי ישכתב את הדף הסטטי... דבר נוסף שאפשר לחשוב עליו, הוא sql cache, פרוקסי, שימוש ב AJAX וניתוח מידע של XML כדוגמת XSL בצד הלקוח במקום במצד השרת... וכמובן שאפשר להמשיך...
ד"א יש framework לFPC שנקרא PSP אשר נותן יכולת לעבוד עם CGI בצורה די יפה, כולל אפשרות לשלב בתוך זה Wiki מאוד בסיסי ועוד. בדלפי (7) וLazarus יש כמה כלים ממש נחמדים ליצור דפים דינמיים, כאשר התכנות שלך הוא RAD ומתורגם למצב שאתה מעוניין בו בסופו של דבר... גם יש לי תרגום מלא של היכולת לפתח מודולים לאפצ'י 1 ו2 בFPC... אבל שוב, אני מעדיף פרל, פיתון רובי ואם ממש אין ברירה אז PHP הרבה לפני שאני אבחר בפסקל, C , +C או ג'אווה בתור השפה שלי ליצור CGI.
בסופו של דבר אתה עדיין צריך לתכנת את אותו הדבר. אם היום יש המון פיתוח מבוסס על #C אז מדוע לא ++C שבייוחד הוא לא שונה בהרבה מ-++C.
התייחסתי לזה -- נכון, כשמדובר בבלוג אפשר לעשות דבר כזה, אבל ברגע שאתה רוצה מערכת תוכן קצת יותר מורכבת זה הופך להרבה יותר בעייתי.
גם לזה התייחסתי: - SQL יודע לעשות caching לרוב הדברים בעצמו - יש פתרונות כמו memcached שעוזרים בבניית cache.
בכל אופן... פתרונות כאלה הם טובים, אבל הם כבר נושאים מורכבים בפני עצמם. שדורשים הרבה עבודה והבנה, כך ששפות תכנות מאבדות את המשמעות מבחינת קלות הפיתוח.
"אני מעדיף פרל, פיתון רובי ואם ממש אין ברירה אז PHP"
מה רע בPHP?
אתה מתעלם לחלוטין מAPC, הוא נותן לך אפשרות CACHING לזיכרון (דומה לmemcached אבל יותר מהיר ופועל רק על שרת אחד) וגם שומר את קוד הphp בצורת bytecode בזכרון.
APC או פתרונות bytecode caching משפרות ביצועים אולי פי שתיים (לפחות זה מה שקרה עם phpBB/wp). והן לא עובדות כמו memcached, התפקיד של memcached הוא שונה לחלוטין.
כאן יש משהו: http://www.baryudin.com/articles/software/c-plus-plus-web-development-framework.html
ראיתי את המאמר, ואפילו רציתי להתייחס אליו באחד הכתבות בנושא.
הרעיון וודאי איננו חדש, הבעיה היא, שאף אחד עדיין לא קם ולא אמר: "אין סביבת פיתוח נוחה לאתרי אינטרנט ב-++C, בואו נכתוב אותה"
תמיד יש חלקים שונים שעושים את העבודה בצורה זו או אחרת: יש cgicc יש ספריות boost, יש בסיסי נתונים שונים עם ממשקים נוחים יותר או פחות. למעשה יש את כל הכלים לעשות את העבודה - רק העניין הוא שעבור web development framework נדרש קצת יותר. זה כמו ההבדל בין כתיבת יישום web ב-python והכתיבה שלו בסביבת Django.
יש אפילו סביבות מוכנות כמו wt/klone שנעשו במטרות שונות לחלוטין.
יש כלים, רק צריך לחבר אותם ביחד, להחיות אותם ולהשאיר למפתח להתרכז בתוכן ולא בכל מה שמסביב.
הוסף תגובה:
חובה לאפשר JavaScript כדי להגיב.