פתח שירות מטמון כללי לשיפור הביצועים

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

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

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

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

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

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

בנה את המטמון

מספיק אנלוגיות של ארונות תיוק: בואו נעבור לאתרים. שרתי אתרים צריכים להתמודד גם עם מטמון. שרתים מקבלים שוב ושוב בקשות למידע, זהות לבקשות אחרות. למשימה הבאה שלך, עליך לבנות יישום אינטרנט עבור אחת החברות הגדולות בעולם. לאחר ארבעה חודשים של פיתוח, כולל לילות רבים ללא שינה והרבה יותר מדי ג'ולט קולטות, היישום נכנס לבדיקות פיתוח עם 1,000 משתמשים. בדיקת הסמכה של 5,000 משתמשים וביצוע הפקה של 20,000 משתמשים לאחר מכן עוקבות אחר בדיקות הפיתוח. עם זאת, לאחר שתקבל שגיאות מחוץ לזיכרון בזמן שרק 200 משתמשים בודקים את היישום, בדיקות הפיתוח נעצרות.

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

עם זאת, מתעוררות מספר שאלות הכוללות מורכבות כמו:

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

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

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

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

דרישות

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

  1. כל יישום Java יכול לגשת לשירות המטמון.
  2. ניתן למקם חפצים במטמון.
  3. ניתן לחלץ אובייקטים מהמטמון.
  4. אובייקטים במטמון יכולים לקבוע בעצמם מתי יפוג, וכך לאפשר גמישות מרבית. שירותי מטמון שמסתיימים את כל האובייקטים באמצעות אותה נוסחת תפוגה אינם מספקים שימוש אופטימלי באובייקטים במטמון. גישה זו איננה מספקת במערכות רחבות היקף שכן, למשל, רשימת מוצרים עשויה להשתנות מדי יום, ואילו רשימת מיקומי החנויות עשויה להשתנות רק פעם בחודש.
  5. חוט רקע שעובר בעדיפות נמוכה מסיר אובייקטים שנשמרו במטמון.
  6. ניתן לשפר את שירות המטמון מאוחר יותר באמצעות מנגנון טיהור שפחות נעשה שימוש לאחרונה (LRU) או בשימוש בתדירות נמוכה ביותר (LFU).

יישום

כדי לעמוד בדרישה 1, אנו מאמצים סביבת Java טהורה במאה אחוז. על ידי מתן הציבור getואת setשיטות בשירות במטמון, אנו מקיימים דרישות 2 ו 3 גם כן.

לפני שאמשיך לדיון בדרישה 4, אזכיר בקצרה כי נענה על דרישה 5 על ידי יצירת שרשור אנונימי במנהל המטמון; חוט זה מתחיל בבלוק הסטטי. כמו כן, אנו מספקים דרישה 6 על ידי זיהוי הנקודות בהן יתווסף מאוחר יותר קוד ליישום האלגוריתמים LRU ו- LFU. אפרט יותר על דרישות אלה בהמשך המאמר.

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

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

  1. לכל האובייקטים חייבת להיות שיטה ציבורית הנקראת isExpired(), המחזירה ערך בוליאני.
  2. לכל האובייקטים חייבת להיות שיטה ציבורית הנקראת getIdentifier(), המחזירה אובייקט שמבדיל את האובייקט מכל האחרים במטמון.

הערה: לפני שתקפוץ ישר לקוד, עליך להבין שאתה יכול ליישם מטמון בדרכים רבות. מצאתי יותר מתריסר יישומים שונים. Enhydra ו- Caucho מספקים משאבים מצוינים המכילים מספר יישומי מטמון.

תמצא את קוד הממשק לשירות המטמון של מאמר זה ברשימה 1.

רישום 1. Cacheable.java

/ ** * כותרת: מטמון תיאור: ממשק זה מגדיר את השיטות, אותן יש ליישם על ידי כל האובייקטים המעוניינים להציב במטמון. * * זכויות יוצרים: זכויות יוצרים (c) 2001 * חברה: JavaWorld * שם קובץ: Cacheable.java @ מחבר Jonathan Lurie @version 1.0 * / ממשק ציבורי Cacheable {/ * בכך שהוא דורש מכל האובייקטים לקבוע את התפוגות שלהם, האלגוריתם מופשט מה שירות מטמון, ובכך מספק גמישות מרבית מכיוון שכל אובייקט יכול לאמץ אסטרטגיית תפוגה שונה. * / בוליאני ציבורי isExpired (); / * שיטה זו תבטיח ששירות המטמון לא אחראי לזיהוי ייחודי של אובייקטים המונחים במטמון. * / אובייקט getIdentifier אובייקט (); }

כל אובייקט המונח במטמון - Stringלמשל, א 'חייב להיות עטוף בתוך אובייקט המיישם את Cacheableהממשק. רישום 2 הוא דוגמה למחלקת עטיפה כללית הנקראת CachedObject; הוא יכול להכיל כל אובייקט הדרוש להצבתו בשירות המטמון. שים לב כי מחלקת עטיפה זו מיישמת את Cacheableהממשק שהוגדר ברשימה 1.

רישום 2. CachedManagerTestProgram.java

/ ** * כותרת: מטמון * תיאור: עטיפה כללית של אובייקט מטמון. מיישם את ממשק ה- Cacheable * משתמש בסטרטגיית TimeToLive לתוקף CacheObject. * זכויות יוצרים: זכויות יוצרים (c) 2001 * חברה: JavaWorld * שם קובץ: CacheManagerTestProgram.java * @ המחבר ג'ונתן לוריא * @version 1.0 * / מחלקה ציבורית CachedObject מיישמת Cacheable {// ++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++ ++++ / * משתנה זה ישמש לקביעת פג תוקפו של האובייקט. * / java.util.Date dateofExpiration = null; מזהה אובייקט פרטי = null; / * זה מכיל את "הערך" האמיתי. זה האובייקט שצריך לשתף. * / אובייקט אובייקט ציבורי = null; // ++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++ CachedObject (אובייקט אובייקט, מזהה אובייקט, int minutesToLive) {this.object = obj; זֶה.מזהה = id; // minutesToLive של 0 פירושו שהוא יחיה ללא הגבלת זמן. אם (minutesToLive! = 0) {dateofExpiration = java.util.Date חדש (); java.util.Calendar cal = java.util.Calendar.getInstance (); cal.setTime (dateofExpiration); cal.add (cal.MINUTE, minutesToLive); dateofExpiration = cal.getTime (); }} // +++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++ בוליאני ציבורי is Expired () {// זכור שאם הדקות לחיות אפסות אז היא חיה לנצח! אם (dateofExpiration! = null) {// תאריך התפוגה מושווה. if (dateofExpiration.before (new java.util.Date ())) {System.out.println ("CachedResultSet.is Expired: Expired from Cache! EXPIRE TIME:" + dateofExpiration.toString () + "CURRENT TIME:" + ( חדש java.util.Date ()). toString ()); לחזור אמיתי; } אחר {System.out.println ("CachedResultSet.isExpired:פג תוקף לא מהמטמון! "); החזר שקר;}} אחר // זה אומר שהוא חי לנצח! החזר שקר;} // +++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++ אובייקטים ציבוריים לקבל מזהה () {מזהה החזרה;} // ++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++}

CachedObjectבכיתה חושפת שיטת בנאי שלוקחת שלושה פרמטרים:

public CachedObject (אובייקט אובייקט, מזהה אובייקט, int minutesToLive) 

הטבלה שלהלן מתארת ​​את הפרמטרים הללו.

תיאורי פרמטרים של בנאי CachedObject
שֵׁם סוּג תיאור
Obj לְהִתְנַגֵד האובייקט המשותף. הוא מוגדר כאובייקט המאפשר גמישות מרבית.
Id לְהִתְנַגֵד Idמכיל מזהה ייחודי המבדיל את objהפרמטר מכל שאר האובייקטים השוכנים במטמון. שירות המטמון אינו אחראי להבטחת ייחודם של האובייקטים במטמון.
minutesToLive Int The number of minutes that the obj parameter is valid in the cache. In this implementation, the caching service interprets a value of zero to mean that the object never expires. You might want to change this parameter in the event that you need to expire objects in less than one minute.

The constructor method determines the expiration date of the object in the cache using a time-to-live strategy. As its name implies, time-to-live means that a certain object has a fixed time at the conclusion of which it is considered dead. By adding minutesToLive, the constructor's int parameter, to the current time, an expiration date is calculated. This expiration is assigned to the class variable dateofExpiration.

כעת, isExpired()השיטה חייבת לקבוע בפשטות אם dateofExpirationהתאריך והשעה הנוכחיים או אחריהם. אם התאריך הוא לפני השעה הנוכחית, והאובייקט במטמון נחשב לפג, isExpired()השיטה מחזירה נכון; אם התאריך הוא אחרי השעה הנוכחית, האובייקט במטמון לא פג, isExpired()ומחזיר שקר. כמובן, אם dateofExpirationהוא null, וזה יהיה המקרה אם minutesToLiveהיה אפס, isExpired()השיטה תמיד מחזירה שקר, מה שמצביע על כך שהאובייקט במטמון חי לנצח.