עד מתי אפשר להשתמש ב-MSMQ? ── החלטת ה-migration של תור legacy ש"אפילו לא מוגדר כ-deprecated"

· עודכן בתאריך: · · Windows, .NET, C#, MSMQ, תור הודעות, טכנולוגיית legacy, migration, מערכות מידע

“שמעתי ש-MSMQ הופסק. גם המערכת שפועלת אצלנו היום חייבת לעבור migration מיד?” — כשמתחזקים מערכת קיימת, זו הנקודה הראשונה שצריך להפריד.

האם MSMQ (Microsoft Message Queuing) ממשיך להתקיים כ-OS feature, והאם יש API להשתמש בו מ-.NET — אלה שתי שאלות נפרדות. נכון ל-יולי 2026, נקודת הייחוס של המאמר הזה, MSMQ לא מופיע ברשימות ה-deprecated הרשמיות ונשאר Windows optional feature. ספריית התקן System.Messaging, לעומת זאת, היא .NET Framework בלבד ולא הועברה ל-.NET (Core ואילך).123

הבעיה היא אפוא לא ש-MSMQ יפסיק לעבוד מחר. היא שמימוש התור הופך למכשול כשמעלים את האפליקציה ל-.NET.

המאמר הזה מיועד למפתחים שמתחזקים ומעבירים מערכות שמשתמשות ב-MSMQ, ולמחלקות מערכות מידע שקובעות את המדיניות. הוא מתחיל בקביעת העובדות ובתנאים להמשך השימוש, ומשם ממשיך ל-inventory, לבחירת יעד ה-migration, למימוש table queue ולהליך ה-cutover.

לגבי migration מ-VB6 או מ-.NET Framework בכללותו, ראו “מדריך מעשי ל-migration מ-VB6 ל-.NET” ו-“צ’קליסט לפני migration מ-.NET Framework ל-.NET”. המאמר הזה מתמקד בחלק של התור.

1. קודם השורה התחתונה: להפריד בין ההישרדות של ה-OS לבין ה-migration של האפליקציה

אם מעלים את האפליקציה ל-.NET, הכלל הוא להעביר גם את התור. אם ממשיכים בינתיים על .NET Framework, אפשר לבחור להמשיך להשתמש ב-MSMQ לאחר בדיקת תקופת התמיכה של ה-OS ותנאי התפעול. אימוץ MSMQ בפרויקט חדש אינו מומלץ.4

לקרוא לפי מה שצריך להחליט עכשיו

מה שצריך להחליט עכשיו נקודת ההתחלה של ההחלטה היכן זה מכוסה
האם הסיפור ש”הופסק” נכון להפריד בין מצב ה-OS feature למצב ה-managed API פרק 1: העובדות
האם אפשר להמשיך להשתמש בזה כמו שהוא בינתיים לבדוק את תקופת התמיכה של ה-OS, ואם אפשר להקים ניטור, recovery ומסירה פרק 2: תנאים להמשך שימוש
רוצים להחליף אותו יחד עם ה-migration ל-.NET לבצע inventory של תלויות, פורמטים, DTC ועמידות לעבודה offline פרק 3: inventory, פרק 4: יעדי migration
אפשר להשאיר את זה עם P/Invoke או CoreWCF להפריד בין היכולת לקרוא לזה לבין היכולת לקחת על עצמכם את נטל התחזוקה סעיף 1.3: ה-managed API ו-P/Invoke, סעיף 1.4: CoreWCF
RabbitMQ או Azure Service Bus לפני הבחירה הזו, לבדוק אם table queue באותו DB עסקי מספיק פרק 4: בחירה לפי דרישה, פרק 5: ה-table queue
רוצים להעביר ל-DB שלנו את ה-SQL של ההוצאה מהתור לבדוק את רמת ה-isolation, את הגדרת ה-snapshot ואת ה-lock hints סעיף 5.3: SQL והגדרות ה-DB
צריך לשמר את סדר העיבוד של התור להפריד בין סדר ההוצאה מהתור לבין סדר ההשתקפות בנתוני ה-business סעיף 5.4: עיבוד מקבילי וסדר
רוצים להעלות את ה-worker ל-production לבדוק את גבול הטרנזקציה, ואת האיסוף מחדש, ה-retry והניקוי סעיף 5.5: ה-worker, סעיף 5.6: תפעול
איך מבצעים cutover למערכת חיה להחליט על פורמט ההודעה, על הצד המקבל, על טיפול בכפילויות ועל מה עושים עם הודעות שנשארו פרק 6: הליך ה-cutover

אם קוראים את המאמר ברצף, הסדר הוא אימות מה ממשיך להתקיים → תנאים להמשך שימוש → inventory → יעד migration → מימוש → cutover. כשחוזרים להחלטה מאוחר יותר, ראו הסיכום בפרק 7.

1.1. לבדוק על איזו שכבה נשאלת השאלה

התשובה ל”האם אפשר עוד להשתמש ב-MSMQ” שונה משכבה לשכבה, כך: נקודת הייחוס למצבים האלה היא יולי 2026, כמו בראש המאמר.

שכבה מצב נוכחי המשמעות המעשית
OS feature (Windows optional feature) קיים. הוא נכלל בגרסאות Windows client ו-Windows Server הנוכחיות, ולא הוכרז על deprecation12 מפעילים והוא עדיין עובד. אפשר להמשיך להשתמש בו כל עוד ה-OS נתמך
Win32 native API (MQSendMessage וכדומה) מתועד וזמין5 המסלול לקרוא לו מ-.NET דרך P/Invoke נשאר פתוח. אבל את ה-formatters ואת האינטגרציה הטרנזקציונית תכתבו ותתחזקו בעצמכם
System.Messaging ב-.NET Framework זמין, אבל מיועד רק ל-.NET Framework 1.1 עד 4.8.13 שם רצות המערכות הקיימות. מעבר לזה אין כלום
ה-managed API הרשמי ב-.NET (Core ואילך) לא קיים. הוא לא נכלל גם ב-Windows Compatibility Pack6 ברגע שמעלים את האפליקציה ל-.NET, חלק התור צריך להיבנות מחדש
ה-MSMQ binding של WCF → CoreWCF.MSMQ קיים port בהובלת הקהילה. ל-CoreWCF עצמו יש מדיניות תמיכה, אבל מימוש ה-MSMQ תלוי ב-port קהילתי של System.Messaging7 שמיש להארכת החיים של שירות WCF שנקרא דרך תור (הצד המקבל), אבל הוא לא תחליף לצד השולח ולא API תור כללי
אימוץ חדש לא מומלץ כי אין מסלול רשמי קדימה

1.2. מה שלא הוגדר כ-deprecated הוא ה-OS feature

ברשימת “Deprecated features” של Windows client מופיעים ערכים כמו NTLM, VBScript ו-WordPad, אבל אין ערך ל-MSMQ. כך גם ב-“Features Removed or No Longer Developed” של Windows Server, כולל ה-tab של Windows Server 2025.12

deprecated הוא שלב רשמי שמציין שהפיתוח הפעיל הסתיים ושהרכיב עשוי להיות מוסר בעדכון עתידי. הוא שונה מ-removed. לגבי MSMQ, מה שהמאמר הזה אימת הוא שאפילו ההכרזה הזו על deprecation לא יצאה.

1.3. מה שמעכב את ה-migration ל-.NET הוא ה-managed API

ה-reference של System.Messaging.MessageQueue מכסה את .NET Framework 1.1 עד 4.8.1, ואין מהדורה שמיועדת ל-.NET (Core ואילך). Windows Compatibility Pack (החבילה Microsoft.Windows.Compatibility) מספק כ-20,000 API ומכסה תחומים כמו ה-Registry, WMI, Windows services ו-EventLog, אבל הוא לא כולל את System.Messaging.36

אם משאירים את זה עם P/Invoke, לוקחים על עצמכם גם את תחזוקת ה-wrapper

גם ה-native API לא נעלם. קריאה ל-Win32 API כמו MQSendMessage ו-MQReceiveMessage מ-.NET דרך P/Invoke אפשרית כשלעצמה. אבל את ה-formatters ואת האינטיגרציה הטרנזקציונית ש-System.Messaging סיפק, תכתבו עכשיו כ-wrapper משלכם ותתחזקו אותו. זה אינו המסלול המרכזי של ה-migration; זו דרך להאריך את חייו של MSMQ כשחייבים להשאיר אותו.5

1.4. לשפוט את CoreWCF כמסלול לצד המקבל של WCF

ל-WCF ב-.NET Framework היה binding שמשתמש ב-MSMQ. כמסלול לארח אותו על .NET קיים ה-MSMQ transport של CoreWCF (CoreWCF.MSMQ).7

המטרה שלו, לעומת זאת, היא הצד המקבל של שירות WCF שנקרא דרך תור. הוא לא API תור כללי שמחליף את System.Messaging וגם לא תחליף ל-client בצד השולח.

ל-CoreWCF עצמו יש מדיניות תמיכה של Microsoft. מימוש ה-MSMQ transport, לעומת זאת, תלוי ב-port קהילתי של גרסת ה-.NET Framework של System.Messaging, ואי אפשר להתייחס לתלות הזו כאל משהו שנושא את אותה אחריות. אמצו אותו רק אחרי בדיקה אם הגרסה שבה אתם משתמשים נכללת בתמיכה, ומי יתחזק את החלק התלוי.7

לכן “MSMQ כבר הופסק” ו”אי אפשר להעביר אותו כמו שהוא ל-.NET” אינם אומרים את אותו דבר. בחירה ב-P/Invoke או ב-CoreWCF דוחה את החלפת התור, במחיר של נטל התחזוקה של המסלול הזה.

ב-diagram, solid line מציינת relation שתמיד מתקיים ו-dashed line מציינת relation מותנה (התנאים מופיעים בהסבר של כל relation ב-detail page). הרשימה המלאה של ה-relations (סה”כ 28, כולל evidence ו-certainty) וההגדרות של ה-concepts המרכזיים נמצאות ב-detail page של ה-knowledge map (ביפנית). Data: JSON-LD / Turtle

2. להמשיך להשתמש או לא: ההחלטה נגזרת מתוכנית ה-migration ל-.NET וממערך התפעול

2.1. למה “זה עובד” אינו מספיק כדי להחליט

.NET Framework 4.8 נתמך כ-Windows component, לפי מחזור החיים של ה-OS שאליו הוא מותקן. לכן אפשר לתכנן להמשיך להריץ אותו בתוך תקופת התמיכה הזו.4

מה שרוצים לאמת אינו רק אם זה רץ. אם תלות ב-MSMQ משאירה את כל האפליקציה על .NET Framework, נשארים גם נטלי התחזוקה וה-migration הבאים.

נטל ההשפעה על החלטת ה-migration
השגת מתחזקים והעברת האחריות פחות ופחות מהנדסים יודעים להסביר System.Messaging ו-DTC, ולכן עולה עלות החקירה מחדש של התצורה
מגבלות runtime וספריות חלק מתחביר C# חדש זמין בעדכון compiler בלבד, אבל שיפורי ביצועים ב-runtime וספריות תקן חדשות אינם בהישג יד. עוד ועוד חבילות מפסיקות לתמוך ב-.NET Framework כיעד
תלות ב-DTC אם משאירים את החלק שמקבע יחד “הוצאה מהתור” ו”עדכון ה-DB”, משדרגים רק את השוליים והבעיה הקשה ביותר נשארת לאחרונה
פורמטי serialization ישנים המימוש של BinaryFormatter הוסר מה-runtime ב-.NET 9, ולכן ה-migration צריך לכסות גם את הפורמט8

הבעיה של אובדן המתחזק מטופלת גם ב”תחזוקת מערכת בלי קוד מקור ובלי תיעוד”. פניות בנושא MSMQ נוטות להתחיל בהערכת migration ולא בתקלה, דווקא מפני שמה שמקשה הוא התלויות ומסירת האחריות האלה ולא ההתנהגות עצמה.

“זה רץ כל עוד ה-OS תומך” ו”ככל שמחכים יותר ה-migration נעשה קל יותר” הן שתי טענות שונות. גם כשלא מבצעים migration בינתיים, כדאי לבצע inventory של הנקודות הקשות מראש.

2.2. אם לא מבצעים migration בינתיים, למלא ארבעה תנאי מינימום

כשאין תוכנית להעלות את האפליקציה ל-.NET ומשאירים אותה ללא שינוי בינתיים משיקולי עלות-תועלת — מה שמכונה לעיתים הקפאת המצב — הדבר סביר בתנאים מסוימים. ההנחה, לעומת זאת, היא שכל ארבעת הדברים הבאים מתקיימים.

בדיקה תנאי מינימום מה עושים בפועל מה קורה אם לא
לציין את MSMQ במפורש בהליכי ה-kitting וה-restore MSMQ הוא Windows optional feature. כתבו את שלבי ההפעלה דרך “Turn Windows features on or off” או דרך DISM/PowerShell במסמך הקמת הסביבה מישהו שוכח להפעיל אותו בהחלפת מחשב או שרת, וביום המעבר שום דבר לא עובד בלי סיבה נראית לעין
לנטר את אורך התור להקים ניטור ספים והתראה על מספר ההודעות המצטברות. לכלול גם את ה-dead-letter queue ואת ה-journal queue כשהצד המקבל נעצר שום דבר לא מחזיר שגיאה, ההודעות פשוט נערמות, והעבודה העסקית נעצרת בלי שאף אחד שם לב
לתעד את התצורה לתעד את נתיבי התור, ההרשאות, האם יש טרנזקציות, את ה-formatter ואת הגדרות ה-journal (טבלת ה-inventory בסעיף 3.2 מתאימה כמו שהיא) הערכת migration עתידית תתחיל שוב מהחקירה, ודיוקה ומאמץ העבודה נפגעים
לבחון את ההחלטה פעם בשנה לבדוק פעם בשנה אם הוא הופיע ברשימת deprecation, ואם עדכון OS שינה את ההתנהגות שלו מגלים את זה רק אחרי שההודעה על deprecation מתפרסמת

הבחינה השנתית בפרט היא ההליך שמונע מהודעה על deprecation או מהשפעה של עדכון OS לחמוק מכם. לקראת מעבר של בעל התפקיד, השאירו מאחור לא רק את התצורה אלא גם את הליך ה-recovery. שמירה על המצב הקיים בלי למלא את התנאים האלה נושאת את הסיכון שאף אחד לא ידע לא שנעצר ולא למה.

3. inventory: לתעד למה הפקדנו את MSMQ

3.1. להבין את היכולות ואת המונחים

ב-MSMQ, הצד השולח כותב הודעה לתור, ואפליקציה על אותה מכונה או על מכונה אחרת מוציאה אותה כשנוח לה. יש שלוש תכונות עיקריות שצריך להעביר ליעד ה-migration.

זה נצבר גם כשהצד המקבל נעצר: store-and-forward

גם כשהיעד מושבת, ההודעות נצברות מקומית ונמסרות כשהמערכת מתאוששת. זה עוזר בקווי תקשורת לא אמינים בין אתרים, אבל זה לא מבטיח באופן בלתי מותנה עמידות לאורך restart.

קובעים יחד את התור ואת עדכון ה-DB: טרנזקציות

פעולות על התור יכולות להיות טרנזקציוניות, ועם MS DTC (Distributed Transaction Coordinator) אפשר לקבע את ההוצאה מהתור ואת עדכון ה-DB בטרנזקציה מבוזרת אחת.

היה אפשר להשתמש בו בלי להתקין middleware נוסף: כלול ב-OS

מכיוון שלא היה צורך להתקין middleware נוסף, הוא היה נפוץ בשנות ה-2000 לחיבור בין מערכות הזמנות ורכש, לעיבוד אסינכרוני של דוחות ולחיבור בין שלבי ייצור בקו הייצור. השילוב של שלוש התכונות האלה הוא הסיבה שהוא עוד כאן.

להבדיל בין הצטברות, עמידות וטרנזקציות לפי המונחים

המונחים שמשמשים בטבלאות ההחלטה נקבעים גם כאן.9

מונח מה זה אומר, ולמה בודקים
private queue / public queue private נרשם רק במחשב המקומי ואינו מפורסם ל-Active Directory. מפנים אליו בצורה כמו .\private$\QueueName. public נרשם ב-directory וניתן לאתר אותו בכל ה-domain. במערכות business קטנות ובינוניות, private הוא המקרה הנפוץ
הודעת express מצב השליחה המוגדר כברירת מחדל. היא מוחזקת ב-memory גם בדרך וגם אחרי המסירה, ולכן מהירה, אבל היא אובדת אם המחשב הזה או שירות ה-Message Queuing נעצרים
הודעת recoverable נשמרת על הדיסק במחשב השולח, בכל מחשב שמעביר אותה הלאה ובצד היעד, ולכן שורדת restart. הצד השולח מציין אותה במפורש
תור טרנזקציוני מטפל רק בהודעות טרנזקציוניות. התכונה נקבעת ביצירת התור ולא ניתנת לשינוי אחר כך. הודעות טרנזקציוניות נשמרות לדיסק
DTC שירות ה-Windows שמתאם טרנזקציה שחוצה כמה משאבים, כמו MSMQ ובסיס נתונים. איך מחליפים את העקביות בין התור ל-DB היא נקודת המחלוקת ב-migration
journal queue / dead-letter queue תורי מערכת ש-MSMQ יוצר. הראשון שומר עותקים של הודעות שנשלחו ושל הודעות שהוצאו, והשני מחזיק הודעות שלא ניתן היה למסור. יש לנטר את גידול הנפח מכך שמשאירים אותם בלי טיפול

“זה יכול להצטבר כשהצד המקבל עצור” ו”זה שורד restart של MSMQ או של המכונה” הם שני דברים שונים. יש לבדוק בנפרד אם מסתמכים רק על הראשון, או שצריך גם את השני באמצעות הודעות recoverable או טרנזקציוניות.

3.2. להוציא את התלויות מהקוד, מהתצורה ומהתפעול

לא לסיים את ה-inventory בהפניות לספריות בלבד

מתחילים בחיפוש הפניות ל-System.Messaging ובמקומות שבהם נוצר MessageQueue. עם זאת, היעדר הפניה כזו אינו כשלעצמו בסיס למסקנה ש-MSMQ אינו בשימוש. יש לבדוק את מסלולי הקריאה בנפרד.

מסלול קריאה מה לחפש בקוד ובתצורה
ספרייה של .NET Framework הפניות ל-System.Messaging, ומקומות שבהם נוצר MessageQueue
תצורת WCF netMsmqBinding / msmqIntegrationBinding
native API ו-COM פונקציות native כמו MQSendMessage מהמשפחה MQ*, וקריאות דרך COM

לתעד, לכל תור, את הבסיס להחלטת ה-migration

השתמשו בטבלה הבאה כדי לתעד את מה שמצאתם, שורה לכל תור. המטרה אינה לבדוק ולסיים, אלא להשאיר את התוצאות כבסיס למדיניות ה-migration ולמסמכי האישור הפנימיים.

היבט איפה מסתכלים מה מתעדים איך זה משפיע על ההחלטה
(a) נתיב התור מחרוזת הנתיב שמועברת ל-MessageQueue, יעד החיבור בקבצי התצורה, ציון FormatName: מקומי או מרוחק, private או public, שם המכונה שמולה אם זה מרוחק או בין אתרים, יש להחליט אם נדרש store-and-forward (השורה “עמידות לעבודה offline” בסעיף 4.2). אם הכל מקומי, זה נופל לשורה הראשונה של סעיף 4.2
(b) טרנזקציות ו-Recoverable מקום יצירת התור (האם הוא טרנזקציוני), האם מצוין Recoverable בשליחה, האם משתמשים ב-DTC האם התור טרנזקציוני, האם צוין Recoverable, האם DTC משתתף אם יש DTC, ה-table queue הוא כמעט ודאי יעד ה-migration. אם אין לא זה ולא זה, יש לרשום במפורש שהמערכת הנוכחית מופעלת בהנחה שהודעות נעלמות ב-restart
(c) formatter המקומות שבהם מצוינים XmlMessageFormatter / BinaryMessageFormatter / ActiveXMessageFormatter ה-formatter שבשימוש, והטיפוס של גוף ההודעה אם מדובר ב-BinaryFormatter, החלפה ב-JSON היא הכרחית (סעיף 6.1). זה הופך לגורם העיקרי במאמץ ה-migration8
(d) journal ותור הודעות שלא נמסרו מאפייני התור (האם ה-journal מופעל), תוכן תורי המערכת, נוהל התפעול האם משתמשים ב-journal, והאם מישהו באמת מסתכל על ה-dead-letter queue איך מטפלים בהודעות שנכשלו הופך לדרישת תכן ליעד ה-migration (טבלת parking, במקרה של table queue)
(e) מי יוצר ומוחק אותם ה-installer, סקריפטי הפריסה, הקריאה ל-Create בעליית האפליקציה מי יוצר אותם, ומי מגדיר את ההרשאות בזמן ה-migration זה הופך ישירות ל”מי מספק את התורים החדשים”. אם בוחרים בהקפאת המצב, יש להעביר את זה להליך ה-kitting (פרק 2)

4. יעדי migration: לבחור לפי התכונות שצריך, לא לפי שמו של מוצר יורש

4.1. לסדר את הדרישות בארבע שאלות

מתוצאות ה-inventory, יש לקבוע את ארבעת הדברים הבאים.

  1. האם הצד המקבל הוא תהליך שמעדכן את בסיס הנתונים העסקי שלנו?
  2. האם התור הטרנזקציוני ואת עדכון ה-DB קובעים יחד דרך DTC?
  3. האם הצד השולח והצד המקבל נמצאים על מכונות שונות או באתרים שונים? האם באמת משתמשים ב-store-and-forward?
  4. מה נפח ההודעות? בקנה מידה של כמה אלפים עד כמה עשרות אלפים ביום, אופייני למערכות business רבות, ה-throughput של המועמדים לבדו לא יכריע את הבחירה.

4.2. האפשרות הראשונה לכל תכונה

לעבודה אסינכרונית שמעדכנת את ה-DB העסקי של אותה מערכת, כדאי להסתכל קודם על table queue. בוחרים broker כשיש דרישה ש-table queue לא מספק, כמו הפצה לכמה מערכות או routing.

התכונה שבשימוש האפשרות הראשונה הנמקה ומזהירים
עיבוד אסינכרוני בתוך מערכת אחת (הצד המקבל מעדכן את ה-DB) table queue בבסיס הנתונים “הוצאת ההודעה מהתור” ו”עדכון נתוני ה-business” ניתנים לקבע באותה טרנזקציה מקומית, ולכן DTC כבר לא נחוץ. ה-backup, הניטור והתפעול הקיימים מתאימים כמו שהם. גם בצד השולח אפשר לקבע “עדכון נתוני ה-business והוספת שורת ההודעה” בטרנזקציה אחת, וזה מה שמכונה Outbox pattern
DTC עושה אטומיות בין התור לעדכון ה-DB table queue בבסיס הנתונים החלפת הטרנזקציה המבוזרת בטרנזקציה מקומית היא מהות ה-migration הזה. תורי ענן לא יכולים להשתתף ב-DTC, ולכן כל עוד הדרישה הזו קיימת, broker הוא עיקוף
חיבור ב-loose coupling בין כמה מערכות וכמה שפות RabbitMQ (on-premises) / Azure Service Bus (אפשר בענן) fan-out של הפצה ו-routing הם החוזק של broker. עם RabbitMQ יש לתקצב את העומס של הפעלה עצמית (יתירות ועדכונים)
עמידות offline בין אתרים (store-and-forward) Azure Service Bus וכדומה בתוספת תכן resend, או table queue בכל אתר בתוספת סנכרון יש מעט מנגנונים שמחליפים באופן שקוף את “לאצור מקומית בצד השולח ולמסור אחר כך” של MSMQ. יש לשנות את התכן כך שהאחריות לאצור בצד השולח תינתן במפורש לאפליקציה
מפיק וצרכן בתוך אותו process תור in-memory כמו .NET Channels מקרה שבו מלכתחילה לא היה צורך בתור בין processים. להשאיר את זה בתוך ה-process, ואם צריך עמידות — לעבור ל-table queue
בשימוש רק כתקשורת בין processים בין שירותי Windows IPC כמו named pipes אבל ההחלפה הזו תקפה רק כששני הצדדים רצים תמיד באותו זמן. MSMQ מקבל שליחה כשהצד המקבל עצור (גם בהודעת express) ומוסר אותה אחר כך, ואילו pipe נכשל מיד. אם מסתמכים על הצטברות כשהצד המקבל עצור, יש לממש resend ו-buffering באפליקציה או להשאיר תור. לבחירה ראו “טבלת החלטה ל-IPC ב-Windows

למה דווקא table queue עולה קודם

“להסתכל קודם על table queue” כאן אינו רצון להמליץ על בסיס נתונים לכל מטרה. הסיבה היא שבעיבוד אסינכרוני שמעדכן את אותו DB, נפוץ במערכות business מתקופת MSMQ, העקביות נשארת פשוטה, ואפשר להשתמש ב-backup, בניטור ובתפעול הקיימים.

דרישות שמתאימות ל-broker, ומה שצריך לתכנן מעליהן

RabbitMQ ו-Azure Service Bus מתאימים להפצה ב-loose coupling בין כמה מערכות וכמה שפות, ל-fan-out ול-routing. הם גם שווים בחינה כשנדרש throughput גבוה. עם זאת, מכיוון שהתור ובסיס הנתונים העסקי הופכים למשאבים נפרדים, אין להעביר כמו שהוא את האטומיות של MSMQ+DTC: יש לתכנן idempotency ו-resend באפליקציה. יש לכלול בהערכה גם ניטור, יתירות, patching והדרכת עובדים ל-middleware החדש.

מעבר לא בהכרח אומר שמותר לוותר על ההצטברות בזמן עצירה

יש לבדוק אם עמידות offline והצטברות כשהצד המקבל עצור הן דרישות שמותר לוותר עליהן. בחירה בתור בענן לא מוסיפה אוטומטית הצטברות מקומית בצד השולח, ומעבר ל-named pipes לא משמר באותה צורה המתנה כשהצד שמולו עצור. אם צריך את זה, יש לתת לאפליקציה resend ו-buffering, או להשאיר תור.

5. ה-table queue: לקבע את פעולת התור ואת העדכון העסקי באותו DB

הפרק הזה מתחיל במשמעות של לאחד את שניהם ל-DB אחד. משם הוא עובר לפי הסדר להנחות היסוד של SQL ההוצאה, לעד כמה הסדר מובטח, לטרנזקציה של ה-worker ולעיבוד שמוסיפים ל-production.

5.1. למה DTC הופך למיותר

ב-MSMQ, התור וה-DB הם משאבים נפרדים. כדי לקבע יחד “הוצא מהתור” ו”עדכן את נתוני ה-business” נדרשה טרנזקציה מבוזרת דרך DTC.

אם הופכים את התור לטבלה באותו בסיס נתונים של נתוני ה-business, שניהם הופכים לעדכונים באותו DB. את ההוצאה, עדכון ה-business ורישום ההשלמה אפשר לקבע בטרנזקציה מקומית אחת. החלפת טרנזקציה מבוזרת במקומית היא לב ה-migration הזה.

לא רק הצד המקבל: גם הצד השולח יכול לקבע באותו זמן

גם בצד השולח אפשר להכניס לאותה טרנזקציה את עדכון נתוני ה-business ואת ה-INSERT של שורת ההודעה. זהו ה-Outbox pattern, והוא מונע את חוסר העקביות שבו נתוני ה-business עודכנו אבל הודעה לא יצאה.

מה שבא בהמשך הוא הצורה המינימלית ב-SQL Server. חלק ה-C# הוא pseudocode שמראה את גבול הטרנזקציה; זה אינו מימוש מוגמר לפריסה כמו שהוא.

5.2. הטבלה ששומרת את העבודות

CREATE TABLE dbo.JobQueue (
    Id          BIGINT IDENTITY(1,1) PRIMARY KEY,
    Payload     NVARCHAR(MAX) NOT NULL,                    -- הגוף. נשמר כ-JSON
    Status      TINYINT       NOT NULL DEFAULT 0,          -- 0: ממתין 1: בעבודה 2: הושלם
    EnqueuedAt  DATETIME2     NOT NULL DEFAULT SYSUTCDATETIME(),
    StartedAt   DATETIME2     NULL,                        -- משמש לאיסוף מחדש של שורות שנשארו בעבודה אחרי crash
    RetryCount  INT           NOT NULL DEFAULT 0
);
CREATE INDEX IX_JobQueue_Status ON dbo.JobQueue (Status, Id);

Payload מחזיק את הגוף כ-JSON, ו-Status מבדיל בין ממתין, בעבודה והושלם. StartedAt ו-RetryCount משמשים לאיסוף מחדש ול-retry.

5.3. הוצאת שורה אחת, ובדיקת הגדרות ה-DB

לעדכן את השורה שמוציאים ולקבל את הגוף שלה בהוראת SQL אחת

בהוצאה, מעדכנים שורה אחת ל”בעבודה” ומקבלים את הגוף דרך OUTPUT. READPAST מדלג על מועמדים שאחוזים ב-row lock של worker אחר במקום להמתין להם, וכך כמה processים יכולים לחלוק את העבודה.10

WITH next_job AS (
    SELECT TOP (1) *
    -- READCOMMITTEDLOCK נדרש ב-DB שבו READ_COMMITTED_SNAPSHOT הוא ON (ראו בהמשך).
    -- ב-DB שבו הוא OFF זו אותה התנהגות כמו ברירת המחדל, ולכן השארת ההינט מתאימה לשניהם
    FROM   dbo.JobQueue WITH (READPAST, UPDLOCK, READCOMMITTEDLOCK)
    WHERE  Status = 0
    ORDER  BY Id          -- להוציא לפי סדר ההכנסה. זה מה שהופך את זה ל"תור"
)
UPDATE next_job
SET    Status = 1, StartedAt = SYSUTCDATETIME()
OUTPUT inserted.Id, inserted.Payload;

לבדוק קודם את הגדרת ה-snapshot ואת רמת ה-isolation

כשקוראים את ה-SQL הזה, יש לבדוק את READ_COMMITTED_SNAPSHOT ואת רמת ה-isolation של ה-session.

התיעוד הרשמי קובע שכאשר READ_COMMITTED_SNAPSHOT של ה-DB הוא ON וגם ה-session נמצא ב-READ COMMITTED או שהשאילתה משתמשת גם בהינט READCOMMITTED, אי אפשר לציין READPAST כמו שהוא. התיקון הוא להסיר את הינט ה-READCOMMITTED אם הוא קיים, ולכלול READCOMMITTEDLOCK.10

רמת ה-isolation המוגדרת כברירת מחדל היא READ COMMITTED, ולכן העברה של זה ל-DB שבו ה-snapshot מופעל, בלי טיפול מונע, לא רק חוסמת — הוראת ה-SQL נכשלת בשגיאה. ב-Azure SQL Database הוא ON כברירת מחדל. גם ב-on-premises הוא עשוי להיות מופעל כדי להקל על חסימת קריאות, ולכן בודקים מראש.

SELECT is_read_committed_snapshot_on FROM sys.databases WHERE name = DB_NAME();

ה-SQL להוצאה שלמעלה כולל READCOMMITTEDLOCK, ולכן קורא באמצעות lock ללא קשר להגדרת ה-snapshot. ב-DB שבו ההגדרה OFF, זו אותה התנהגות כמו ברירת המחדל.10

למה ROWLOCK אינו מצוין לצידו, ומהן המגבלות של READPAST

שלא כמו WITH (READPAST, UPDLOCK, ROWLOCK) הנפוץ, כאן ROWLOCK אינו מצוין לצידו. הסיבה היא ש-ROWLOCK ו-READCOMMITTEDLOCK שייכים לאותה קבוצת granularity hints, ואי אפשר לציין את שניהם לאותה טבלה.10

READPAST יכול לדלג רק על row locks, ולא על page locks. בטווח של הדוגמה הזו, שמוציאה שורה אחת דרך index seek עם TOP (1), escalation אינו בעיה מעשית, אבל אם רוצים ROWLOCK מפורש, יש לאמת ש-READ_COMMITTED_SNAPSHOT הוא OFF ואז להסיר דווקא את READCOMMITTEDLOCK.

5.4. סדר ההוצאה מהתור וסדר ההשתקפות בנתוני ה-business הם שני דברים שונים

לציין סדר לשורות המועמדות

אם משמיטים את ה-ORDER BY וכותבים UPDATE TOP (1), איזו שורה תיבחר אינו מוגדר. TOP על UPDATE אינו מסדר את השורות שהוא נוגע בהן, וגם קיום אינדקס על (Status, Id) אינו ערובה לסדר. כדי שעבודות ישנות לא יידחקו שוב ושוב אחורה, הדוגמה מציינת ORDER BY Id.11

בעיבוד מקבילי, הראשון שיצא אינו בהכרח הראשון שנכתב

עם זאת, סדר ההשלמה בעיבוד מקבילי אינו מסודר. בזמן ש-worker A מעבד את עבודה 1, worker B מדלג על השורה הזו דרך READPAST ויכול לקבע את עבודה 2 קודם.

מה מסודר מה לא מסודר
איזו שורה יוצאת מהתור קודם (ORDER BY Id) איזו שורה נכתבת ראשונה לנתוני ה-business

כשסדר ההשתקפות משנה — למשל עדכונים לאותו מספר הזמנה — בוחרים אחת מהשתיים.

איך לשמר את הסדר מה מרוויחים, ומה מקריבים
consumer יחיד מוותרים על מקביליות תמורת סדר. האפשרות הפשוטה ביותר אם היא עדיין מספקת את ה-throughput שנדרש
חלוקה לפי key להצמיד worker למספר הזמנה או ל-key דומה, או להוסיף תנאי שמדלג על שורה כל עוד אותו key נמצא בעבודה. מה שמובטח הוא הסדר בתוך key, ולא הסדר בין keys שונים

אם לא בוחרים באף אחת מהן, יש לציין במפורש בתכן שב-FIFO גלובלי אין הבטחה בעיבוד מקבילי. אותה בעיה מופיעה גם ב-MSMQ ברגע שמקבלים במקביל. מה שחשוב הוא לא לבצע migration כשברקע עוד ההנחה ש”זה תור, אז זה יוצא לפי הסדר”.

5.5. גבול הטרנזקציה של ה-worker

מה שמסדרים בצד המקבל הוא הטרנזקציה שמכסה את ההוצאה, את עדכון נתוני ה-business ואת רישום ההשלמה. ב-pseudocode שלמטה, שלוש הפעולות מקבלות את אותו connection ואת אותה טרנזקציה, ונקבעות יחד בסוף.

while (!stoppingToken.IsCancellationRequested)
{
    using var tx = connection.BeginTransaction();

    var job = DequeueOne(connection, tx);          // ה-UPDATE ... OUTPUT שלמעלה
    if (job is null)
    {
        tx.Commit();
        await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);  // אין מה להוציא: ממתינים polling interval אחד
        continue;
    }

    ApplyBusinessData(connection, tx, job);        // <- עדכון נתוני ה-business (העבודה שבאמת רצינו לבצע)
    MarkDone(connection, tx, job.Id);              // Status = 2

    tx.Commit();   // "ההוצאה מהתור" ו"עדכון ה-business" נקבעים יחד בשורה אחת
}

נקודת המפתח היא ש-DequeueOne, ApplyBusinessData ו-MarkDone משתמשים באותו connection ובאותה טרנזקציה, ושה-Commit האחרון קובע יחד את ההוצאה ואת עדכון ה-business. אם מחליפים את זה ב-broker, התכונה הזו של אותו DB אובדת, ונדרש תכנון עקביות אחר.

5.6. איסוף מחדש, retry וניקוי שמוסיפים ל-production

מעל הדוגמה המינימלית, יש לתכנן את הפעולות הבאות.

פעולה מה היא עושה
איסוף מחדש של שורות שנשארו בעבודה תהליך recovery שמחזיר שורות ל-Status = 0 כאשר Status = 1 ו-StartedAt ישן מהמרווח שהוגדר
תקרת retry והעברה ל-parking להעביר שורות ש-RetryCount שלהן עבר את התקרה לטבלה נפרדת, או למשהו כמו Status = 9. זה המקום שמקביל ל-dead-letter queue של MSMQ
ניקוי שורות שהושלמו למחוק או לארכב באופן תקופתי שורות עם Status = 2, כדי שהטבלה והאינדקסים שלה לא יתנפחו

במערכות business קטנות ובינוניות, לרוב מספיק טבלה עם polling, או וריאציה מבוססת התראה. לפני שמוסיפים עוד מוצר תור, יש לבדוק אם המימוש הזה כבר מספק את התכונות ואת התפעול שנדרשים.

6. cutover: ליישר קודם את הפורמט, את ההתנהגות ואת הצד המקבל

ב-cutover, מיישרים קודם את פורמט ההודעה ואת הבדיקות של קלט ושל תוצאה. אחר כך בוחרים בין גישור בין ישן לחדש לבין ריקון התור הישן לפני המעבר.

6.1. להחליט על פורמט הודעה שבו ישן וחדש יכולים לדור יחד

ברירת המחדל אחרי ה-migration תהיה JSON. מה שלא מעבירים הלאה הוא serialization מבוסס BinaryFormatter, כמו BinaryMessageFormatter. המימוש של BinaryFormatter הוסר מה-runtime ב-.NET 9, והוא גם אינו מומלץ מטעמי אבטחה.8

אבל זה לא אומר שכל פורמט בינארי פסול. פורמטים שהמפרט שלהם מתוחזק באופן עצמאי, כמו Protocol Buffers ו-MessagePack, יכולים לעבור למערכת החדשה כמו שהם בלי בעיה. את ה-formatter ואת טיפוס הגוף יש לקבוע מראש, ב-inventory בסעיף 3.2.

6.2. לפני שכותבים מחדש, לקבע קלט ותוצאה בבדיקות

עיבוד תורים הוא תחום שבו בקלות נכנסים פגמים תלויי תזמון. יש להכין מראש characterization tests שאומרות “הודעת קלט כזו מפיקה תוצאה כזו”, כך שאפשר יהיה לאמת באופן מכני שההחלפה מפיקה את אותה תוצאה. ראו גם “לקבוע את ההתנהגות ב-characterization tests לפני refactoring”.

6.3. לטפל קודם בצד המקבל, ולתכנן את הגשר סביב אובדן וכפילויות

להעביר קודם את הצד המקבל

קודם מספקים את התור החדש, מריצים מולו את הצד המקבל, ורק אחר כך מחליפים את הצד השולח.

הכנסת גשר קטן שמעביר הודעות מה-MSMQ הישן לתור החדש חוסכת החלפה של כל השולחים בבת אחת. הגשר עצמו יכול להישאר על .NET Framework.

לפצל את הליך המסירה לפי קיום טרנזקציות

אבל אם יש עצירה בין ההוצאה מ-MSMQ לבין הכתיבה לתור החדש, מקבלים אובדן או מסירה כפולה. יש לתכנן כך, לפי סוג תור המקור.

מקור המסירה המינימלית שנדרשת
תור טרנזקציוני לקבל בצד ה-MSMQ באופן טרנזקציוני, כך שאפשר להחזיר אחורה כתיבה שנכשלה. בצד התור החדש לדחות כפילויות לפי message ID ולעבד באופן idempotent
תור לא טרנזקציוני Peek כדי לקרוא, לכתוב באופן idempotent לתור החדש, לאמת שהכתיבה הצליחה, ורק אז להסיר מהתור הישן. כפילויות שנגרמו מעצירה לפני ההסרה נבלעות בצד התור החדש

התכונה הטרנזקציונית של תור נקבעת ביצירתו ואינה ניתנת לשינוי אחר כך. יש לשים לב שאי אפשר להחיל כמו שהוא הליך קבלה טרנזקציוני על תור לא טרנזקציוני.

6.4. אם הגשר אינו משתלם, לרוקן את התור ולעבור

אם האמצעים נגד אובדן וכפילויות בגשר אינם פרופורציונליים לגודל המערכת, אין לכפות דו-קיום של ישן וחדש; כדאי להטות לכיוון של ריקון התור הישן בהשבתה מתוכננת ורק אז מעבר.

מעבר כשההודעות עוד יושבות בתור גורם לעיבוד כפול ולאובדן. ריקון התור לפני המעבר הוא, בסופו של דבר, לרוב גם הדרך הבטוחה וגם המהירה — זו השיפוט המעשי של המאמר הזה.

7. סיכום

נכון ליולי 2026, נקודת הייחוס של המאמר הזה, MSMQ לא הוגדר רשמית כ-deprecated. להישאר כ-OS feature ולהיות קל ל-migration ל-.NET הם שני דברים שונים. System.Messaging סגור בתוך .NET Framework, ולכן הכלל הוא שאם מעלים את האפליקציה ל-.NET, בוחנים מחדש גם את התור.123

אם ממשיכים להשתמש בו בינתיים, בודקים את תקופת התמיכה של ה-OS ומקימים את הליכי ההקמה וה-recovery, את ניטור התור, את תיעוד התצורה ואת הבחינה השנתית של ההחלטה. הסיכון הגדול של הקפאת המצב אינו רק טכני: הוא שלא נשאר אף אחד שמסוגל לגעת בזה.

אם מבצעים migration, יש לבצע inventory של התלויות ושל פורמטי ההודעות קודם. לעיבוד אסינכרוני שמעדכן את אותו DB עסקי, table queue הוא האפשרות הראשונה. את העקביות שסיפקו MSMQ ו-DTC אפשר להחליף בטרנזקציה מקומית באותו DB. את RabbitMQ או Azure Service Bus שוקלים כשיש דרישה שהפתרון הזה לא מספק, כמו הפצה לכמה מערכות או routing.

מעל זה, בודקים את רמת ה-isolation וה-hints ב-SQL, את סדר ההשתקפות בעבודה מקבילית, ואת האמצעים נגד אובדן וכפילויות בגשר. מה שחשוב הוא לא למהר מפני ש”כנראה הופסק”, אלא להבין במה המערכת שלנו תלויה ולהחליט על סמך זה בין להשאיר לבין לעבור.

מאמרים קשורים

תחומי ייעוץ קשורים

חברת Komura Software LLC מטפלת ב-inventory ובתוכניות migration לתצורות legacy כולל MSMQ, ב-migration מ-.NET Framework ל-.NET, ובשינוי מערכות business שכוללות עיבוד תורים.

מקורות

  1. Microsoft Learn, Deprecated features in the Windows client. הרשימה הרשמית של features ב-Windows client שהפיתוח הפעיל שלהם הסתיים, כלומר features deprecated. על כך שנכון ליולי 2026 הרשימה כוללת את NTLM, VBScript, WordPad ואחרים, בעוד MSMQ (Microsoft Message Queuing) אינו מופיע בה, וכי deprecated הוא שלב שמשמעותו “לא בפיתוח פעיל ועשוי להיות מוסר בעדכון עתידי”, ושיש להבדיל בינו לבין removed.  2 3 4

  2. Microsoft Learn, Features Removed or No Longer Developed in Windows Server. הרשימה הרשמית של features שהוסרו מ-Windows Server ושל features שהפיתוח שלהם הסתיים (deprecated). על כך ש-MSMQ אינו מופיע בה נכון ליולי 2026, כולל ב-tab של Windows Server 2025, ועל כך שרכיבים deprecated ממשיכים להיכלל ב-Windows Server, נשארים נתמכים לשימוש ב-production בהתאם למחזור החיים של המוצר וממשיכים לקבל עדכוני אבטחה ועדכוני איכות.  2 3 4

  3. Microsoft Learn, MessageQueue Class (System.Messaging). על כך שה-reference של המחלקה System.Messaging.MessageQueue מכסה גרסאות מ-.NET Framework 1.1 עד 4.8.1, ושאין מהדורה שמיועדת ל-.NET (Core ואילך).  2 3 4

  4. Microsoft Learn, .NET Framework official support policy. על העובדה ש-.NET Framework 4.8 מוגדר כרכיב של מערכת ההפעלה Windows, והוא נתמך בהתאם למדיניות מחזור החיים של המוצר האב (ה-OS) שעליו הוא מותקן.  2

  5. Microsoft Learn (ארכיון), Message Queuing Functions, MQSendMessage ו-MQReceiveMessage. על העובדה שה-native API של MSMQ ב-Win32 (MQCreateQueue, MQSendMessage, MQReceiveMessage ואחרים) מתועד לאפליקציות C/C++, ומאפשר ליצור תורים ולשלוח ולקבל הודעות בלי לעבור דרך managed API.  2

  6. Microsoft Learn, Use the Windows Compatibility Pack to port code to .NET. על כך ש-Windows Compatibility Pack (החבילה Microsoft.Windows.Compatibility) מספק כ-20,000 API כדי לכסות תלויות ב-API ייעודי ל-.NET Framework בזמן migration ל-.NET, וכי רשימת תחומי הטכנולוגיה שלו (CodeDom, תצורה, Directory Services, Drawing, ODBC, ACL, WCF, ה-Registry, WMI, performance counters, Windows services, EventLog וכדומה) אינה כוללת messaging (System.Messaging).  2

  7. CoreWCF project, CoreWCF.MSMQ (NuGet) ו-CoreWCF 1.4.0 Preview release; Microsoft, CoreWCF Support Policy. על כך ש-CoreWCF הוא פרויקט בהובלת הקהילה שמעביר ל-.NET את צד ה-server של WCF, ושל-Microsoft יש עבורו מדיניות תמיכה רשמית; שתמיכת MSMQ (החבילה CoreWCF.MSMQ) פורסמה כחלק מ-queued transports שלו; ושמימוש ה-MSMQ הזה תלוי ב-port קהילתי של גרסת ה-.NET Framework של ספריית System.Messaging.  2 3

  8. Microsoft Learn, BinaryFormatter migration guide. על העובדה ש-BinaryFormatter הוצא משימוש בהדרגה מטעמי אבטחה, שמאז .NET 9 המימוש שלו הוסר מה-runtime ואי אפשר להשתמש בו כברירת מחדל, וכי כיעדי migration מוצגים פורמטי serialization בטוחים כמו JSON (System.Text.Json).  2 3

  9. Microsoft Learn (ארכיון), Express and Recoverable Messaging, System-Generated Queues, Message Queuing (MSMQ). על העובדות שהודעת express מוחזקת ב-RAM גם בדרך וגם אחרי המסירה, ואובדת כשהמחשב שבו היא נמצאת נעצר או כששירות ה-Message Queuing נעצר; שהודעת recoverable נכתבת לדיסק במחשב השולח ובכל מחשב שמעביר אותה הלאה וגם נשמרת על הדיסק בתור היעד; ש-public queue נרשם ב-directory service ואילו private queue נרשם רק במחשב המקומי ואינו מפורסם ל-directory service; ושה-journal queue, ששומר עותקים של הודעות שהוצאו מתור ושל הודעות שכבר נשלחו, וה-dead-letter queue, שמחזיק הודעות שלא ניתן היה למסור, הם תורי מערכת ש-MSMQ יוצר. 

  10. Microsoft Learn, Table Hints (Transact-SQL). על העובדות ש-READPAST הוא הינט שמדלג על שורות שנאחזות ב-lock של טרנזקציות אחרות בלי לקרוא אותן, שהוא יכול לדלג רק על locks ברמת row ולא על locks ברמת page, ושהוא ניתן לציון רק ברמת ה-isolation READ COMMITTED או REPEATABLE READ. במיוחד לגבי הקביעה ש“כאשר database option ‏READ_COMMITTED_SNAPSHOT מוגדר ל-ON ואחת מהאפשרויות הבאות נכונה — (a) רמת ה-isolation של הטרנזקציה ב-session היא READ COMMITTED, או (b) השאילתה מציינת גם את הטבלה READCOMMITTED כ-hint — אי אפשר לציין את ההינט READPAST. כדי לציין את ההינט READPAST במקרים האלה, יש להסיר את ה-hint של READCOMMITTED אם הוא קיים, ולכלול בשאילתה את ה-hint של READCOMMITTEDLOCK.” ראו באותו עמוד גם את העובדה ש-READCOMMITTEDLOCK גורם לקריאות READ COMMITTED להשתמש ב-lock ללא קשר להגדרת READ_COMMITTED_SNAPSHOT, ושאי אפשר לציין יותר מ-hint אחד מסוג granularity hints (PAGLOCK / NOLOCK / READCOMMITTEDLOCK / ROWLOCK / TABLOCK / TABLOCKX) לטבלה אחת. את ההגדרה בצד ה-DB אפשר לבדוק דרך is_read_committed_snapshot_on ב-sys.databases 2 3 4

  11. Microsoft Learn, TOP (Transact-SQL). על העובדה שכאשר משתמשים ב-TOP עם INSERT, UPDATE, MERGE או DELETE, השורות שאליהן מתייחסים אינן מסודרות בשום סדר, ושכאשר הסדר חשוב יש להשתמש בתת-שאילתה עם TOP ו-ORDER BY. 

מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.

העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.

המאמר קשור ישירות לשירותים הבאים.

שאלות נפוצות

שאלות נפוצות בפניות בנושא המאמר.

האם MSMQ הופסק?
לא. נכון ליולי 2026, MSMQ אינו מופיע לא ברשימת ה-features ה-deprecated של Windows client ולא ברשימת "Features Removed or No Longer Developed" של Windows Server. הוא נכלל כעת גם בגרסאות Windows הנוכחיות כ-Windows optional feature. הסיבה שהניסוח "הופסק" נפוץ היא ערבוב עם המצב בצד ה-.NET: הספרייה הסטנדרטית System.Messaging, שמטפלת ב-MSMQ, קיימת רק ב-.NET Framework ולא הועברה ל-.NET (Core ואילך). ליתר דיוק, המצב הוא שהוא ממשיך להתקיים כ-OS feature, אבל אין דרך רשמית להשתמש בו מ-.NET מודרני.
האם יש דרך להשתמש ב-MSMQ מ-.NET 8 או מ-.NET 10?
אין managed API רשמי. System.Messaging הוא API של .NET Framework עד 4.8.1, והוא אינו נכלל גם ב-Windows Compatibility Pack (החבילה Microsoft.Windows.Compatibility). קריאה ל-native API של MSMQ ב-Win32 (כמו MQSendMessage) דרך P/Invoke אפשרית מבחינה טכנית, אבל משמעותה היא לכתוב ולתחזק בעצמכם wrapper, כולל ה-formatters והאינטגרציה הטרנזקציונית. בצד הקהילה, פרויקט CoreWCF מפרסם חבילה ל-MSMQ transport (החבילה CoreWCF.MSMQ), אבל היא נועדה לארח על .NET מודרני שירותי WCF שנקראים דרך תור — כלומר port של צד ה-server של WCF — והיא אינה API תור כללי שמחליף את System.Messaging וגם לא תחליף ל-client בצד השולח. גם המימוש שלה תלוי ב-port קהילתי של גרסת ה-.NET Framework של System.Messaging. היא יכולה לשמש לבדיקה או להארכת חיים זמנית. ל-CoreWCF עצמו יש מדיניות תמיכה רשמית של מיקרוסופט, אבל ה-port הקהילתי של System.Messaging שמימוש ה-MSMQ הזה תלוי בו אינו זוכה לאותה אחריות, ולכן אם מבססים עליו מערכת business, יש להחליט רק אחרי בדיקה אם הגרסה שבה משתמשים נכללת במדיניות התמיכה ואיך מטופל החלק התלוי. מבחינה מעשית, הדרך הנכונה היא להעביר גם את התור בזמן שמעלים את האפליקציה ל-.NET.
מהו יעד ה-migration המתאים — RabbitMQ או Azure Service Bus?
לפני הדילמה הזו, שקלו להפוך טבלה בבסיס הנתונים לתור. ברוב המערכות business בקנה מידה קטן ובינוני שמשתמשות ב-MSMQ, הצד שמול התור הוא תהליך שמעדכן את בסיס הנתונים של הארגון. במקרה כזה, table queue מאפשר לקבע את "הוצאת ההודעה מהתור" ואת "עדכון נתוני ה-business" באותה טרנזקציה מקומית של נתוני ה-business, ובכך להחליף את העקביות שסיפקו MSMQ יחד עם טרנזקציה מבוזרת (DTC) במנגנון פשוט יותר. רק כשקיימת דרישה ש-table queue לא מספק — למשל הפצה ב-loose coupling בין כמה מערכות או throughput גבוה — יש לעבור לשקול את RabbitMQ אם דרישת ה-on-premises חזקה, או את Azure Service Bus אם אפשר להעביר לענן. הסדר הזה הוא הפחות סביר להיכשל.
האם זו החלטה סבירה להשאיר את המערכת בינתיים על .NET Framework?
באופן מותנה, כן. .NET Framework 4.8 נתמך כ-Windows component, לפי מחזור החיים של ה-OS שאליו הוא מותקן, ו-MSMQ עצמו לא הוגדר כ-deprecated, כך שאפשר לצפות באופן סביר שהוא ימשיך לרוץ. עם זאת, אם בוחרים להשאיר את המצב כמו שהוא, עשו לפחות את ארבעת הדברים האלה: ציינו במפורש את הפעלת ה-feature של Message Queuing בהליך ה-kitting, הקימו ניטור על אורך התור ועל ה-journal, תעדו את פורמט ההודעות ואת הגדרות החיבור, והשאירו הליך recovery למקרה שבעל התפקיד עוזב. הסיכון האמיתי של הקפאת המצב אינו טכני — הוא שלא נשאר אף אחד שמסוגל לגעת בזה.

פרופיל הכותב

עמוד היכרות עם כותב המאמר.

Go Komura

מנהל KomuraSoft LLC

מתמחה בפיתוח תוכנה עבור Windows, ייעוץ טכני וחקירת תקלות, בעיקר בפרויקטים עם מערכות קיימות ובאגים שקשה לשחזר.

קישורים ציבוריים

חזרה לבלוג