תרגום והתאמה של Java Microservices: מדריך מעשי . חלקים קודמים של המדריך:
בואו נסתכל על הבעיות המובנות של שירותי מיקרו ב-Java, החל בדברים מופשטים וכלה בספריות קונקרטיות.
כיצד להפוך ג'אווה מיקרו-שירות גמיש?
זכור שכאשר אתה יוצר מיקרו-שירותים, אתה בעצם סוחר בקריאות בשיטת JVM עבור שיחות HTTP סינכרוניות או הודעות אסינכרוניות. בעוד שלרוב מובטחת השלמת קריאת שיטה (ללא כיבוי בלתי צפוי של JVM), קריאת רשת אינה אמינה כברירת מחדל. זה אולי יעבוד, אבל אולי זה לא יעבוד מסיבות שונות: הרשת עמוסה יתר על המידה, כלל חומת אש חדש יושם, וכן הלאה. כדי לראות איך זה משנה, בואו נסתכל על הדוגמה של BillingService.דפוסי חוסן HTTP/REST
נניח שלקוחות יכולים לקנות ספרים אלקטרוניים באתר האינטרנט של החברה שלך. לשם כך, יישמת מיקרו-שירות חיוב שיכול להתקשר לחנות המקוונת שלך כדי ליצור חשבוניות PDF בפועל. לעת עתה, נבצע את הקריאה הזו באופן סינכרוני, דרך HTTP (אם כי הגיוני יותר לקרוא לשירות זה באופן אסינכרוני, מכיוון שיצירת PDF לא חייבת להיות מיידית מנקודת המבט של המשתמש. נשתמש באותה דוגמה בדוגמה הבאה סעיף ותסתכל על ההבדלים).@Service
class BillingService {
@Autowired
private HttpClient client;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
httpClient.send(invoiceRequest(user.getEmail(), invoice), responseHandler());
// ...
}
}
לסיכום, הנה שלוש תוצאות אפשריות של שיחת HTTP זו.
- בסדר: השיחה עברה, החשבון נוצר בהצלחה.
- עיכוב: השיחה עברה, אבל לקח יותר מדי זמן להשלים.
- שְׁגִיאָה. השיחה נכשלה, ייתכן ששלחת בקשה לא תואמת, או שהמערכת לא עובדת.
דפוסי חוסן של הודעות
בואו נסתכל מקרוב על תקשורת אסינכרונית. תוכנית BillingService שלנו עשויה להיראות כעת בערך כך, בהנחה שאנו משתמשים ב-Spring וב-RabbitMQ להעברת הודעות. כדי ליצור חשבון, אנו שולחים כעת הודעה למתווך ההודעות RabbitMQ שלנו, שם ישנם מספר עובדים שמחכים להודעות חדשות. עובדים אלו יוצרים חשבוניות PDF ושולחים אותן למשתמשים המתאימים.@Service
class BillingService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
// преобразует счет, например, в json и использует его How тело messages
rabbitTemplate.convertAndSend(exchange, routingkey, invoice);
// ...
}
}
שגיאות פוטנציאליות נראות קצת אחרת כעת, מכיוון שאינך מקבל עוד תגובות אישור או שגיאה מיידיות כמו שעשית עם חיבור HTTP סינכרוני. במקום זאת, ייתכן שיש לנו שלושה תרחישים פוטנציאליים שעלולים להשתבש, שיכולים להעלות את השאלות הבאות:
- האם ההודעה שלי נמסרה והעובדת השתמשה בה? או שזה אבוד? (המשתמש אינו מקבל חשבונית).
- האם ההודעה שלי נמסרה רק פעם אחת? או נמסר יותר מפעם אחת ועובד רק פעם אחת? (המשתמש יקבל מספר חשבוניות).
- תצורה: מ"האם השתמשתי במפתחות/שמות הניתוב הנכונים עבור הבורסה" ל"האם מתווך ההודעות שלי מוגדר ומתוחזק כהלכה או שהתורים שלו מלאים?" (המשתמש אינו מקבל חשבונית).
- אם אתה משתמש ביישומי JMS כמו ActiveMQ, אתה יכול להחליף מהירות תמורת ערבות של התחייבויות דו-פאזיות (XA).
- אם אתה משתמש ב-RabbitMQ, קרא תחילה את המדריך הזה, ולאחר מכן חשב היטב על אישורים, סובלנות לתקלות ואמינות ההודעות באופן כללי.
- אולי מישהו בקיא בהגדרת שרתי Active או RabbitMQ, במיוחד בשילוב עם clustering ו-Docker (מישהו? ;))
איזו מסגרת תהיה הפתרון הטוב ביותר עבור שירותי מיקרו של Java?
מצד אחד, אתה יכול להתקין אופציה מאוד פופולרית כמו Spring Boot . זה מקל מאוד על יצירת קובצי jar, מגיע עם שרת אינטרנט מובנה כמו Tomcat או Jetty, וניתן להפעיל אותו במהירות ובכל מקום. אידיאלי לבניית יישומי microservice. לאחרונה הופיעו כמה מסגרות מיקרו-שירות מיוחדות, Kubernetes או GraalVM , בהשראת חלקית מתכנות ריאקטיבי. הנה עוד כמה מתמודדים מעניינים: Quarkus , Micronaut , Vert.x , Helidon . בסופו של דבר, תצטרכו לבחור בעצמכם, אבל אנחנו יכולים לתת לכם כמה המלצות שאולי אינן סטנדרטיות לחלוטין: למעט Spring Boot, כל מסגרות המיקרו-שירותים משווקות בדרך כלל כמהירות להפליא, עם הפעלה כמעט מיידית , שימוש נמוך בזיכרון, מדרגיות עד אינסוף. חומרים שיווקיים כוללים בדרך כלל גרפיקה מרשימה שמציגה את הפלטפורמה ליד מגף האביב המופלא או זה לזה. זה, בתיאוריה, חוסך את העצבים של מפתחים התומכים בפרויקטים מדור קודם, שלפעמים לוקח כמה דקות לטעון. או מפתחים שעובדים בענן שרוצים להפעיל/להפסיק כמה מיקרו-מכולות שהם צריכים כרגע תוך 50 אלפיות השנייה. עם זאת, הבעיה היא שזמני ההפעלה (המלאכותיים) הללו של מתכת חשופה וזמני פריסה מחדש כמעט ולא תורמים להצלחת הפרויקט הכוללת. לפחות הם משפיעים הרבה פחות מתשתית מסגרת חזקה, תיעוד חזק, קהילה וכישורי מפתחים חזקים. אז עדיף להסתכל על זה כך: אם עד כה:- אתה נותן ל-ORM שלך להתרוצץ וליצור מאות שאילתות עבור זרימות עבודה פשוטות.
- אתה צריך גיגה-בייט אינסופי כדי להפעיל את המונוליט המורכב למדי שלך.
- יש לך כל כך הרבה קוד והמורכבות היא כל כך גבוהה (אנחנו לא מדברים על מתנעים איטיים כמו Hibernate עכשיו) שליישום שלך לוקח כמה דקות לטעון.
אילו ספריות הן הטובות ביותר עבור קריאות Java REST סינכרוניות?
בצד הטכני ברמה נמוכה, סביר להניח שתקבל אחת מספריות לקוח HTTP הבאות: ה- HttpClient המקורי של Java (מאז Java 11), ה-HttpClient של Apache או OkHttp . שימו לב שאני אומר כאן "כנראה" כי יש אפשרויות אחרות, החל מלקוחות JAX-RS ישנים וטובים ועד ללקוחות WebSocket מודרניים . בכל מקרה, המגמה היא ליצור לקוח HTTP, להתרחק מהתעסקות עם קריאות HTTP בעצמך. כדי לעשות זאת, עליך להעיף מבט בפרויקט OpenFeign ובתיעוד שלו כנקודת התחלה לקריאה נוספת.מהם הברוקרים הטובים ביותר להעברת הודעות Java אסינכרוניות?
סביר להניח שתתקלו ב- ActiveMQ הפופולריים (קלאסי או ארטמיס) , RabbitMQ או קפקא .- ActiveMQ ו- RabbitMQ הם מתווכים מסורתיים ומלאי הודעות. הם כרוכים באינטראקציה של "ברוקר חכם" ו"משתמשים טיפשים".
- מבחינה היסטורית, ל-ActiveMQ היה היתרון של שילוב קל (לבדיקה), אשר ניתן למתן באמצעות הגדרות RabbitMQ/Docker/TestContainer.
- קפקא לא יכול להיקרא מתווך "חכם" מסורתי. במקום זאת, מדובר במאגר הודעות "מטומטם" יחסית (קובץ יומן) שדורש עיבוד של צרכנים חכמים.
באילו ספריות אוכל להשתמש כדי לבדוק שירותי מיקרו?
זה תלוי בערימה שלך. אם פרוסת מערכת אקולוגית של Spring, יהיה זה חכם להשתמש בכלים הספציפיים של המסגרת . אם JavaEE הוא משהו כמו Arquillian . אולי כדאי להעיף מבט ב-Docker ובספריית Testcontainers הטובה באמת , שעוזרת במיוחד להגדיר בקלות ובמהירות מסד נתונים של Oracle עבור בדיקות פיתוח או אינטגרציה מקומיות. לבדיקת מדומה של שרתי HTTP שלמים, עיין ב- Wiremock . כדי לבדוק הודעות אסינכרוניות, נסה ליישם את ActiveMQ או RabbitMQ ולאחר מכן לכתוב בדיקות באמצעות Awaitility DSL . בנוסף, נעשה שימוש בכל הכלים הרגילים שלך - Junit , TestNG עבור AssertJ ו- Mockito . שימו לב שזו אינה רשימה מלאה. אם אינך מוצא את הכלי המועדף עליך כאן, אנא פרסם אותו בקטע התגובות.כיצד לאפשר רישום עבור כל שירותי המיקרו של Java?
רישום במקרה של microservices הוא נושא מעניין ומורכב למדי. במקום שיהיה לך קובץ יומן אחד שאתה יכול לתפעל עם פקודות פחות או grep, כעת יש לך n קובצי יומן, ואתה רוצה שהם לא יהיו מפוזרים מדי. התכונות של המערכת האקולוגית של רישום מתוארים היטב במאמר זה (באנגלית). הקפד לקרוא אותו, ולשים לב לסעיף רישום מרכזי מנקודת מבט של שירותי מיקרו . בפועל, תתקלו בגישות שונות: מנהל המערכת כותב סקריפטים מסוימים האוספים ומשלבים קבצי יומן משרתים שונים לקובץ יומן בודד ומכניס אותם לשרתי FTP להורדה. הפעלת שילובי cat/grep/unig/sort במפגשי SSH מקבילים. זה בדיוק מה שאמזון AWS עושה, ואתה יכול ליידע את המנהל שלך. השתמש בכלי כמו Graylog או ELK Stack (Elasticsearch, Logstash, Kibana)איך המיקרו-שירותים שלי מוצאים זה את זה?
עד עכשיו, הנחנו ששירותי המיקרו שלנו יודעים אחד על השני ומכירים את ה-IPS המקביל. בואו נדבר על תצורה סטטית. אז, המונוליט הבנקאי שלנו [ip = 192.168.200.1] יודע שהוא צריך לדבר עם שרת הסיכון [ip = 192.168.200.2], אשר מקודד בקובץ המאפיינים. עם זאת, אתה יכול להפוך את הדברים לדינמיים יותר:- השתמש בשרת תצורה מבוסס ענן שממנו כל שירותי המיקרו מושכים את התצורות שלהם במקום לפרוס קבצי application.properties בשירותי המיקרו שלהם.
- מכיוון שמופעי השירות שלך יכולים לשנות באופן דינמי את מיקומם, כדאי להסתכל על שירותים שיודעים היכן השירותים שלך חיים, מה ה-IP שלהם וכיצד לנתב אותם.
- עכשיו כשהכל דינמי צצות בעיות חדשות כמו בחירת מנהיג אוטומטית: מי האדון שעובד על משימות מסוימות, כדי לא לעבד אותן פעמיים למשל? מי מחליף מנהיג כשהוא נכשל? על סמך מה מתבצעת ההחלפה?
כיצד לארגן הרשאות ואימות באמצעות מיקרו-שירותי Java?
נושא זה ראוי גם לסיפור נפרד. שוב, האפשרויות נעות בין אימות HTTPS בסיסי מקודד עם מסגרות אבטחה מותאמות אישית להפעלת התקנת Oauth2 עם שרת הרשאות משלה.איך אני יכול לוודא שכל הסביבות שלי נראות אותו הדבר?
מה שנכון לפריסות ללא שירות מיקרו, נכון גם לפריסות עם שירות כזה. נסה שילוב של Docker/Testcontainers ו-Scripting/Ansible.אין שאלה: בקצרה על YAML
בואו נתרחק לרגע מספריות ומנושאים קשורים ונסתכל במהירות על Yaml. פורמט קובץ זה משמש דה פקטו כפורמט ל"כתיבת תצורה כקוד". הוא משמש גם על ידי כלים פשוטים כמו Ansible וענקים כמו Kubernetes. כדי לחוות את הכאב של הזחה של YAML, נסה לכתוב קובץ Ansible פשוט וראה כמה אתה צריך לערוך את הקובץ לפני שהוא יעבוד כמצופה. וזאת למרות שהפורמט נתמך על ידי כל ה-IDEs הגדולים! לאחר מכן, חזור כדי לסיים לקרוא את המדריך הזה.Yaml:
- is:
- so
- great
מה לגבי עסקאות מבוזרות? בדיקת ביצועים? נושאים אחרים?
אולי יום אחד, במהדורות הבאות של המדריך. לעת עתה, זה הכל. תישאר איתנו!בעיות קונספטואליות עם Microservices
מלבד הבעיות הספציפיות של שירותי מיקרו ב-Java, ישנן בעיות נוספות, נניח, כאלו המופיעות בכל פרויקט מיקרו-שירות. הם מתייחסים בעיקר לארגון, צוות וניהול.אי התאמה בין הקצה והקצה האחורי
אי התאמה של Frontend ו- Backend היא בעיה נפוצה מאוד בפרויקטים רבים של מיקרו-שירותים. מה זה אומר? רק שבמונוליטים הישנים והטובים, למפתחי ממשקי אינטרנט היה מקור אחד ספציפי להשגת נתונים. בפרויקטים של שירות מיקרו, למפתחי קצה יש פתאום n מקורות להשיג מהם נתונים. תאר לעצמך שאתה יוצר איזשהו פרויקט IoT (Internet of Things) ב-Java. נניח שאתה מנהל מכונות גיאודטיות ותנורים תעשייתיים ברחבי אירופה. והתנורים האלה שולחים לכם עדכונים שוטפים לגבי הטמפרטורות שלהם וכדומה. במוקדם או במאוחר אולי תרצה למצוא תנורים בממשק המשתמש של הניהול, אולי באמצעות מיקרו-שירותי "חיפוש תנור". בהתאם למידת ההקפדה של עמיתיך האחוריים ליישם חוקי עיצוב או שירות מיקרו-שירותים מונחי תחום, מיקרו-שירות "מצא תנור" עשוי להחזיר רק מזהי תנור ולא נתונים אחרים כגון סוג, דגם או מיקום. לשם כך, מפתחי קצה יצטרכו לבצע קריאה נוספת אחת או אחת (בהתאם למימוש ההחלפה) במיקרו-שירות "קבל נתוני תנור" עם המזהים שקיבלו מהמיקרו-שירות הראשון. ולמרות שזו רק דוגמה פשוטה, אם כי לקוחה מפרויקט אמיתי (!), אפילו היא מדגים את הבעיה הבאה: סופרמרקטים הפכו לפופולריים ביותר. זה בגלל שאיתם לא צריך ללכת ל-10 מקומות שונים כדי לקנות ירקות, לימונדה, פיצה קפואה ונייר טואלט. במקום זאת, אתה הולך למקום אחד. זה קל ומהיר יותר. אותו דבר לגבי מפתחי חזית ומיקרו-שירותים.ציפיות ההנהלה
ההנהלה נמצאת ברושם מוטעה שהם צריכים כעת לגייס אינסוף מפתחים לפרויקט (כולל), שכן מפתחים יכולים כעת לעבוד באופן עצמאי לחלוטין אחד מהשני, כל אחד על המיקרו-שירות שלו. נדרשת רק מעט עבודת אינטגרציה ממש בסוף (זמן קצר לפני ההשקה). למעשה, גישה זו בעייתית ביותר. בפסקאות הבאות ננסה להסביר מדוע."חתיכות קטנות יותר" אינן שוות ל"חתיכות טובות יותר"
זו תהיה טעות גדולה להניח שקוד המחולק ל-20 חלקים יהיה בהכרח באיכות גבוהה יותר מאשר חתיכה אחת שלמה. גם אם ניקח איכות מנקודת מבט טכנית גרידא, ייתכן שהשירותים האישיים שלנו עדיין מריצים 400 שאילתות Hibernate כדי לבחור משתמש ממסד הנתונים, חוצים שכבות של קוד לא נתמך. שוב, אנו חוזרים לציטוט של סיימון בראון: אם לא תצליח לבנות מונוליטים כמו שצריך, יהיה קשה לבנות מיקרו-שירותים מתאימים. לעתים קרובות מאוחר מאוד לדבר על סובלנות תקלות בפרויקטים של שירות מיקרו. עד כדי כך שלפעמים מפחיד לראות איך מיקרו-שירותים עובדים בפרויקטים אמיתיים. הסיבה לכך היא שמפתחי Java לא תמיד מוכנים ללמוד סובלנות תקלות, רשתות ונושאים קשורים אחרים ברמה המתאימה. ה"חלקים" עצמם קטנים יותר, אבל ה"חלקים הטכניים" גדולים יותר. תארו לעצמכם שצוות המיקרו-שירותים שלכם מתבקש לכתוב מיקרו-שירות טכני לכניסה למערכת מסד נתונים, משהו כמו זה:@Controller
class LoginController {
// ...
@PostMapping("/login")
public boolean login(String username, String password) {
User user = userDao.findByUserName(username);
if (user == null) {
// обработка варианта с несуществующим пользователем
return false;
}
if (!user.getPassword().equals(hashed(password))) {
// обработка неверного пароля
return false;
}
// 'Ю-ху, залогинorсь!';
// установите cookies, делайте, что угодно
return true;
}
}
כעת הצוות שלך עשוי להחליט (ואולי אפילו לשכנע את אנשי העסקים) שזה פשוט ומשעמם מדי, במקום לכתוב שירות התחברות, עדיף לכתוב מיקרו-שירות UserStateChanged שימושי באמת ללא כל השלכות עסקיות ממשיות ומוחשיות. ומכיוון שחלק מהאנשים מתייחסים כרגע לג'אווה כמו לדינוזאור, בואו נכתוב את המיקרו-שירות UserStateChanged שלנו ב-Erlang האופנתי. ובואו ננסה להשתמש בעצים אדומים-שחורים איפשהו, כי סטיב יגה כתב שצריך להכיר אותם מבפנים כדי להגיש בקשה לגוגל. מנקודת מבט של אינטגרציה, תחזוקה ועיצוב כולל, זה גרוע כמו כתיבת שכבות של קוד ספגטי בתוך מונוליט בודד. דוגמה מלאכותית ורגילה? זה נכון. עם זאת, זה יכול לקרות במציאות.
פחות חתיכות - פחות הבנה
ואז כמובן עולה השאלה לגבי הבנת המערכת בכללותה, התהליכים ותזרימי העבודה שלה, אבל במקביל אתה, כמפתח, אחראי רק לעבוד על המיקרו-שירות המבודד שלך [95: login-101: updateUserProfile]. זה מתיישב עם הפסקה הקודמת, אבל בהתאם לארגון, רמת האמון והתקשורת שלך, זה יכול להוביל להרבה בלבול, משיכת כתפיים והאשמה אם יש תקלה מקרית בשרשרת המיקרו-שירות. ואין מי שייקח אחריות מלאה על מה שקרה. וזה בכלל לא עניין של חוסר יושר. למעשה, קשה מאוד לחבר בין חלקים שונים ולהבין את מקומם בתמונה הכוללת של הפרויקט.תקשורת ושירות
רמת התקשורת והשירות משתנה מאוד בהתאם לגודל החברה. עם זאת, הקשר הכללי ברור: כמה שיותר, יותר בעייתי.- מי מפעיל את מיקרו-שירות #47?
- האם הם פשוט פרסו גרסה חדשה ולא תואמת של שירות מיקרו? איפה זה תועד?
- עם מי אני צריך לדבר כדי לבקש תכונה חדשה?
- מי יתמוך בשירות המיקרו הזה בארלנג, אחרי שהיחיד שידע את השפה הזו עזב את החברה?
- כל צוותי המיקרו-שירות שלנו עובדים לא רק בשפות תכנות שונות, אלא גם באזורי זמן שונים! איך אנחנו מתאמים את כל זה נכון?
GO TO FULL VERSION