שימוש נכון ב-TCP וב - NIO
שלום לכולם,
אני רוצה להעלות כאן שאלה, ואשמח לתשובות וזווית ראייה. מדובר בתחום שנגעתי בו לראשונה ברכיב שאני כותב עכשיו, ובעבודה שלי אין לי עם להתייעץ ואין לי שום מושג אם אני צודק או לא (זו אחת הבעיות הקשות במקום בו אני עובד). נתקלתי בויכוח ביני לבין המפתח של הלקוח, וכרגע צריך להחליט מי צודק, ומי יבצע את השינוי בצד שלו. האמת היא שזה לא כזה משנה: הלקוח תמיד צודק ובסוף אנחנו נבצע את השינוי, אבל אשמח לתשובה גם מזווית אישית, כדי לדעת איך לכתוב נכון.
הממשק ביני לבין הלקוח מבוסס על הודעות בינאריות, שעוברות על TCP פשוט. אין שום פרוטוקול יותר גבוה כמו HTTP, AMQP וכו'.
המבנה של ההודעה בנוי מכותרת ומגוף ההודעה.
מבנה הכותרת:
1. magic שמטרתו זיהוי byte-order וזיהוי תחילת הודעה במקרה שמידע הלך לאיבוד (אני יודע שב-TCP מידע לא אמור ללכת לאיבוד)
2. גודל ההודעה
3. meta-data
לאחר מכן מגיע גוף ההודעה שגודלו גודל ההודעה פחות גודל הכותרת (גודל הכותרת קבוע).
אני עובד עם JAVA עם NIO. אדגיש שוב שהבעיה העיקרית שלי היא שאני משתמש לראשונה ב-NIO ולכן אין לי שום דרך לדעת אם אני עובד נכון או לא.
יש לי חוצץ מעגלי בגודל 15MB (לצורך העניין). כאשר מגיע אובייקט מהמערכת שלי אותו אני רוצה לשלוח אני לוקח את האובייקט, יוצר ממנו את הבתים של ההודעה וכותב אותם לאותו חוצץ.
ברקע יש thread שמתעורר ברגע שה - SocketChannel מסמן לי write-ready, ואז אני כותב באמצעות channel.write את כל מה שיש לי בחוצץ באותו רגע. בפועל כמובן ישנה אפשרות שלא אצליח לכתוב את הכל (משתמש במצב של non-blocking) ולכן בפעם הבאה שאתעורר, אכתוב מהנקודה בה הפסקתי עד לנקודה האחרונה בה יש נתונים בחוצץ. בזמן הזה, אני יכול לקבל עוד אובייקטים ולדחוף אותם לחוצץ.
כך נוצר מצב שלפעמים אני כותב הודעה שלמה, לפעמים אני כותב הודעה וחצי, לפעמים חצי הודעה, ואז שלוש הודעות ואז עוד חצי וכו'. המצב הזה קביל בעיני מפני שלוגיקה יותר גבוהה אמורה לדעת לפרק את ההודעות. למעשה ככה אני עובד בקריאה: אוסף בתים לתוך חוצץ אחר ובונה הודעה רק כשקיבלתי את כל הבתים שאני צריך.
אני לא מרשה לעצמי להניח שום דבר על שלמות הגעת ההודעות, מלבד הגעה בסדר הנכון.
אם מישהו קרא עד כאן וחושב שאני עובד בצורה לא נכונה, אשמח אם יעיר לי עוד לפני שיראה איך הצד השני עובד. הרבה יותר חשוב לי לדעת אם אני עובד נכון או לא.
במקרה שיש הודעות מאוד גדולות (אנחנו מגיעים גם ל-100MB להודעה), הודעה לפעמים נקטעת באמצע ורק באירטציה הבאה אני אשלח את החלק השני. כאמור זו לא בעיה בעיני, אבל זה יוצר בעיות בצד השני (של הלקוח):
הלקוח מניח שאנחנו שולחים הודעה אחת כל פעם, ולכן קורא כל מה שיש לי לשלוח. אם הוא מקבל חצי הודעה זה לא נורא כי הוא יודע שהוא יחכה לשאר ההודעה. אם הוא מקבל הודעה ועוד בתים ששייכים להודעה הבאה, הוא פשוט זורק אותם, ומכאן יכנס למעגל אינסופי של שברי הודעות בגלל אותה הנחה.
אחדד: לטענת הלקוח אף על פי ש-TCP לא מיועד לשליחת הודעות אלא לשליחת בתים, הוא מניח שאני שולח כל פעם הודעה אחת בלבד ועל זה הוא מסתמך.השורה התחתונה שלי היא שאני אמור לעבוד בצורה סינכורנית ולשלוח הודעה רק אם סיימתי לשלוח את הקודמת. בעיני מדובר בארכיטקטורה לא נכונה, שתפגע בביצועים ובסקלביליות (יכולים להיות עשרות SocketChannels שכל אחד כותב ללקוח אחר)
וכאן לשאלה הנוספת: האם ההנחה שלי לא נכונה? עד כמה שידוע לי, TCP רשאי לשלוח מידע בכמה "מנות", ואני אמור להסתכל עליו כעל byte-stream ולא להעמיס עליו מבנה של הודעות ששייך לפרוטוקול גבוה יותר.
שלום לכולם,
אני רוצה להעלות כאן שאלה, ואשמח לתשובות וזווית ראייה. מדובר בתחום שנגעתי בו לראשונה ברכיב שאני כותב עכשיו, ובעבודה שלי אין לי עם להתייעץ ואין לי שום מושג אם אני צודק או לא (זו אחת הבעיות הקשות במקום בו אני עובד). נתקלתי בויכוח ביני לבין המפתח של הלקוח, וכרגע צריך להחליט מי צודק, ומי יבצע את השינוי בצד שלו. האמת היא שזה לא כזה משנה: הלקוח תמיד צודק ובסוף אנחנו נבצע את השינוי, אבל אשמח לתשובה גם מזווית אישית, כדי לדעת איך לכתוב נכון.
הממשק ביני לבין הלקוח מבוסס על הודעות בינאריות, שעוברות על TCP פשוט. אין שום פרוטוקול יותר גבוה כמו HTTP, AMQP וכו'.
המבנה של ההודעה בנוי מכותרת ומגוף ההודעה.
מבנה הכותרת:
1. magic שמטרתו זיהוי byte-order וזיהוי תחילת הודעה במקרה שמידע הלך לאיבוד (אני יודע שב-TCP מידע לא אמור ללכת לאיבוד)
2. גודל ההודעה
3. meta-data
לאחר מכן מגיע גוף ההודעה שגודלו גודל ההודעה פחות גודל הכותרת (גודל הכותרת קבוע).
אני עובד עם JAVA עם NIO. אדגיש שוב שהבעיה העיקרית שלי היא שאני משתמש לראשונה ב-NIO ולכן אין לי שום דרך לדעת אם אני עובד נכון או לא.
יש לי חוצץ מעגלי בגודל 15MB (לצורך העניין). כאשר מגיע אובייקט מהמערכת שלי אותו אני רוצה לשלוח אני לוקח את האובייקט, יוצר ממנו את הבתים של ההודעה וכותב אותם לאותו חוצץ.
ברקע יש thread שמתעורר ברגע שה - SocketChannel מסמן לי write-ready, ואז אני כותב באמצעות channel.write את כל מה שיש לי בחוצץ באותו רגע. בפועל כמובן ישנה אפשרות שלא אצליח לכתוב את הכל (משתמש במצב של non-blocking) ולכן בפעם הבאה שאתעורר, אכתוב מהנקודה בה הפסקתי עד לנקודה האחרונה בה יש נתונים בחוצץ. בזמן הזה, אני יכול לקבל עוד אובייקטים ולדחוף אותם לחוצץ.
כך נוצר מצב שלפעמים אני כותב הודעה שלמה, לפעמים אני כותב הודעה וחצי, לפעמים חצי הודעה, ואז שלוש הודעות ואז עוד חצי וכו'. המצב הזה קביל בעיני מפני שלוגיקה יותר גבוהה אמורה לדעת לפרק את ההודעות. למעשה ככה אני עובד בקריאה: אוסף בתים לתוך חוצץ אחר ובונה הודעה רק כשקיבלתי את כל הבתים שאני צריך.
אני לא מרשה לעצמי להניח שום דבר על שלמות הגעת ההודעות, מלבד הגעה בסדר הנכון.
אם מישהו קרא עד כאן וחושב שאני עובד בצורה לא נכונה, אשמח אם יעיר לי עוד לפני שיראה איך הצד השני עובד. הרבה יותר חשוב לי לדעת אם אני עובד נכון או לא.
במקרה שיש הודעות מאוד גדולות (אנחנו מגיעים גם ל-100MB להודעה), הודעה לפעמים נקטעת באמצע ורק באירטציה הבאה אני אשלח את החלק השני. כאמור זו לא בעיה בעיני, אבל זה יוצר בעיות בצד השני (של הלקוח):
הלקוח מניח שאנחנו שולחים הודעה אחת כל פעם, ולכן קורא כל מה שיש לי לשלוח. אם הוא מקבל חצי הודעה זה לא נורא כי הוא יודע שהוא יחכה לשאר ההודעה. אם הוא מקבל הודעה ועוד בתים ששייכים להודעה הבאה, הוא פשוט זורק אותם, ומכאן יכנס למעגל אינסופי של שברי הודעות בגלל אותה הנחה.
אחדד: לטענת הלקוח אף על פי ש-TCP לא מיועד לשליחת הודעות אלא לשליחת בתים, הוא מניח שאני שולח כל פעם הודעה אחת בלבד ועל זה הוא מסתמך.השורה התחתונה שלי היא שאני אמור לעבוד בצורה סינכורנית ולשלוח הודעה רק אם סיימתי לשלוח את הקודמת. בעיני מדובר בארכיטקטורה לא נכונה, שתפגע בביצועים ובסקלביליות (יכולים להיות עשרות SocketChannels שכל אחד כותב ללקוח אחר)
וכאן לשאלה הנוספת: האם ההנחה שלי לא נכונה? עד כמה שידוע לי, TCP רשאי לשלוח מידע בכמה "מנות", ואני אמור להסתכל עליו כעל byte-stream ולא להעמיס עליו מבנה של הודעות ששייך לפרוטוקול גבוה יותר.