בדף הזה מפורטות הנחיות לשימוש אופטימלי ב-Memorystore for Redis. בדף הזה מפורטות גם בעיות פוטנציאליות שכדאי להימנע מהן.
רשימת תרחישים לפתרון בעיות מופיעה במאמר פתרון בעיות.
ייצוא של RDB
כשמייצאים גיבוי של RDB, פועלים לפי ההנחיות הבאות:
- ייצוא בתקופה של קצב כתיבה נמוך.
- אם מייצאים במהלך תקופה של קצב כתיבה גבוה, צריך להקטין באופן זמני את ההגדרה
maxmemoryל-50% מקיבולת המופע כדי לספק מספיק תקורה לפעולה מוצלחת.
פעולות שצורכות הרבה משאבים
במכונות Redis במסלול הרגיל, הפעולות הבאות משתמשות בזיכרון נוסף למשך הפעולה:
שדרוג גרסה, התאמה לעומס והעברה ידנית ליתירות כשל משתמשים בזיכרון נוסף (במקרים של מופעים ברמת Standard) בגלל שכפול. הפעולות האלה מתבצעות בהתאם לתהליך השכפול שמתואר במאמר התנהגות שדרוג של מופע ברמה רגילה.
פעולות ייבוא וייצוא דורשות זיכרון נוסף בגלל תהליך הפיצול של Redis וניהול הנתונים מסוג copy-on-write שמשויכים לפעולות האלה.
כדי לצמצם את החסרונות של פעולות שדורשות הרבה משאבים, כדאי:
- כדאי להקטין את ההגדרה maxmemory ל-80% מקיבולת המופע למשך הפעולה. כך יש מספיק תקורה כדי שהפעולה תצליח.
- עוקבים אחרי מדד יחס השימוש בזיכרון המערכת, ומוודאים שהמדד הזה מתחת ל-80% לפני שמריצים אחת מהפעולות האלה.
- מומלץ להפעיל את הפעולות האלה בתקופות של תנועה נמוכה במופע (למשל, במהלך הלילה, בסוף השבוע וכו').
- לפני שמריצים את הפעולות האלה, צריך להגדיר לוגיקה של ניסיון חוזר עם השהיה מעריכית לפני ניסיון חוזר (exponential backoff).
פעולות ותרחישים שדורשים ניסיון חוזר ליצירת חיבור
הפעולות והתרחישים הבאים גורמים לניתוק החיבור לרשת בין הרשת שלכם לבין מופע Redis:
- שדרוג גרסה
- הגדלה או הקטנה של נפח הפעילות
- ייבוא
- מעבר ידני לגיבוי
- תחזוקת המערכת
- רוטציה של רשות אישורים למכונות Redis שמופעלת בהן הצפנה בתנועה
- מעבר אוטומטי למצב כשל במקרה חירום
הפעולות האלה משנות את המופע, ולכן נדרש ניתוק זמני של החיבור. לפני שמריצים את הפעולות האלה, צריך להגדיר לוגיקה של ניסיון חוזר עם השהיה מעריכית לפני ניסיון חוזר (exponential backoff) כדי שהאפליקציה תתחבר מחדש באופן אוטומטי ותמשיך לפעול כרגיל.
תחזוקה שוטפת
מכונות Memorystore for Redis עוברות תחזוקה מעת לעת. פרטים נוספים זמינים במדיניות התחזוקה של Memorystore for Redis.
כדי להתכונן לתחזוקה שוטפת, כדאי להטמיע את השיטות המומלצות הבאות:
- הגדרת חלון זמן לתחזוקה שבו יכולים להתבצע עדכוני תחזוקה.
- כדאי לתזמן חלונות תחזוקה לזמנים שבהם נפח התנועה של המופעים נמוך ויש מספיק עומס זיכרון. מידע נוסף זמין במאמר בנושא ההשפעות של עדכוני תחזוקה.
- הפעלת התראות על חלונות זמן לתחזוקה כדי לקבל התראות על תחזוקה שמתקרבת.
- הטמעת לוגיקה לניסיון חוזר עם השהיה מעריכית לפני ניסיון חוזר (exponential backoff).
- במקרים של מכונות וירטואליות במסלול Standard, אפשר לדמות אירוע תחזוקה באמצעות מעבר ידני לגיבוי בעת כשל כדי לראות איך המעבר לגיבוי בעת כשל שנגרם כתוצאה מתחזוקה משפיע על האפליקציה.
- במקרים של מופעים ברמת מסלול בסיסי, אפשר לדמות את ההשפעה של עדכון תחזוקה על ידי שינוי הגודל של המופע לגודל גדול יותר באופן זמני. אחרי שרואים את ההשפעה, אפשר להקטין את הגודל בחזרה לגודל המקורי.
ניהול זיכרון
ניהול הזיכרון יכול להיות מאתגר בגלל פיצול הזיכרון המוכר שמתרחש ב-Redis בקוד פתוח. מומלץ להקטין את ההגדרה maxmemory של המופע כדי להשאיר מרווח ביטחון למקרה של עומס גבוה על הזיכרון.
הדרך הכי טובה לעקוב אחרי העומס על הזיכרון במופע Memorystore היא באמצעות המדד System Memory Usage Ratio. במאמר הזה מוסבר איך לנהל את הזיכרון ב-Memorystore for Redis.
ניהול חיבורים לא פעילים
עם הזמן, יכול להיות שמספר החיבורים למופע Memorystore יגדל אם החיבורים לא יסתיימו כמו שצריך. יכולות להיות לכך השלכות שליליות על הביצועים, במיוחד אם אתם משתמשים בהצפנה במעבר, שמגבילה את מספר החיבורים המקסימלי בהתאם לרמת הקיבולת שלכם. כדי לצמצם את הסיכון הזה, מומלץ להשתמש בtimeout פרמטר ההגדרה של Redis, שמאפשר להגדיר את מספר השניות לפני שחיבורי לקוח לא פעילים יסתיימו אוטומטית.
שמות משאבים ב-Access Transparency
אסור לאחסן מידע אישי רגיש בשמות של משאבי Memorystore for Redis. בשמות של משאבים אנחנו מתכוונים לשמות של מכונות Memorystore for Redis ולמטא-נתונים של מכונות, כמו תגים. אין ערובה לכך שנתונים שמאוחסנים בשמות משאבים מוגנים על ידי Google Cloud Access Transparency, ויכול להיות שהם לא יעמדו בדרישות התאימות של הארגון שלכם ל-Access Transparency.
נדרש חיבור לרשת VPC בסביבות מסוימות בלי שרת (serverless)
בסביבות מסוימות של serverless נדרש מחבר של חיבור לרשת (VPC) מאפליקציית serverless כדי להתחבר ל-Memorystore for Redis. אם רוצים להתחבר באמצעות אחת מהסביבות האלה, צריך להגדיר את המחבר של Serverless VPC Access לפרויקט.
Networking
מומלץ להשתמש במצב החיבור גישה לשירותים פרטיים. ב-Memorystore for Redis יש שני מצבי חיבור: גישה לשירותים פרטיים וקישור ישיר בין רשתות שכנות (direct peering). מצב החיבור של גישה לשירותים פרטיים מפשט את ניהול טווח כתובות ה-IP ומאפשר לכם להשתמש ב-VPC משותף אם אתם רוצים.
אחרי שיוצרים מופע, אי אפשר לשנות את מצב החיבור.
פרטים נוספים זמינים במאמר בנושא רשתות.
מעקב והתראות
מומלץ להשתמש במעקב ובהתראות כי הם מספקים אותות חשובים לגבי השימוש בזיכרון של מופע Redis. הם גם מספקים תובנות לגבי היעילות של תגובת מופע Redis לבקשות מטמון נכנסות.
מומלץ להגדיר את ההתראות הבאות כברירת מחדל:
- הגדרת התראה ב-Cloud Monitoring לגבי השימוש בזיכרון
- הגדרת התראה ב-Cloud Monitoring לגבי יחס השימוש בזיכרון המערכת
שיטות מומלצות לשימוש במעבד (CPU)
שימוש לא תקין בפקודות יקרות של Redis מוביל לזמן אחזור גבוה, לחוסר תגובה או לבעיות בקישוריות. מופעים ברמת Standard מספקים זמינות גבוהה במהלך תוכנית התאוששות מאסון (DR), ומסתמכים על שכפול אסינכרוני בין צמתים ראשיים לצמתים משוכפלים. אם לאחד מהצמתים יש עיבוד פקודות יקר שחוסם את השרשור הראשי של Redis, יכולה להיות לכך השפעה על השכפול. אם הבעיה נמשכת ומתרחש הפסקת זמינות במיקום מסוים, יכול להיות שהנתונים האחרונים שנכתבו במיקום שבו התרחשה הפסקת הזמינות לא יהיו זמינים במיקום אחר.
מומלץ להשתמש ב-Cloud Monitoring כדי להגדיר התראות למדד Main Thread CPU Seconds (redis.googleapis.com/stats/cpu_utilization_main_thread), כדי לוודא שהשימוש במעבד לא חורג מ-0.8 שניות עבור הצומת הראשי או מ-0.5 שניות עבור כל צומת משוכפל, כשהשכפול מוגדר כשכפול לקריאה.
אם מופע Redis חורג מהערכים המומלצים, מומלץ להגדיל את המידה של המופע לרמת קיבולת גבוהה יותר או לפעול לפי ההוראות לפתרון בעיות כדי להימנע מפעולות שדורשות הרבה משאבי CPU.
אם יש במכונה שימוש גבוה במעבד או שהמשאבים של המכונה מוצו (לדוגמה, בגלל יותר מדי חיבורים), יכול להיות שהמכונה תתנהג בצורה לא צפויה, ויכול להיות שחסרים מדדים חיצוניים.
פקודות שדורשות הרבה משאבים
מומלץ מאוד להימנע משימוש בפקודות Redis שצורכות הרבה משאבים. השימוש בפקודות האלה עלול לגרום לבעיות הביצועים הבאות:
- זמן אחזור גבוה ופסק זמן של לקוח
- עומס על הזיכרון שנגרם מפקודות שמגדילות את השימוש בזיכרון
- אובדן נתונים במהלך שכפול וסנכרון של צמתים כי ה-thread הראשי של Redis חסום
- בדיקות תקינות, ניראות ושכפול
בטבלה הבאה מפורטות דוגמאות לפקודות Redis שצורכות הרבה משאבים, ומוצעות חלופות שצורכות פחות משאבים.
| קטגוריה | פקודה שדורשת הרבה משאבים | חלופה יעילה יותר מבחינת משאבים |
|---|---|---|
| הפעלה בכל מרחב המפתחות | KEYS |
SCAN |
| הרצה עבור קבוצת מפתחות באורך משתנה | LRANGE |
הגבלת הגודל של הטווח שמשמש לשאילתה. |
ZRANGE |
הגבלת הגודל של הטווח שמשמש לשאילתה. | |
HGETALL |
HSCAN |
|
SMEMBERS |
SSCAN |
|
| חסימה של הרצת סקריפט | EVAL |
מוודאים שהסקריפט לא פועל ללא הגבלת זמן. |
EVALSHA |
מוודאים שהסקריפט לא פועל ללא הגבלת זמן. | |
| הסרת קבצים וקישורים | DEL |
UNLINK |
| פרסום והרשמה | PUBLISH |
SPUBLISH |
SUBSCRIBE |
SSUBSCRIBE |
שיטות מומלצות לשימוש בלקוח Redis
בקטע הזה מוסבר איך להשתמש בצורה אופטימלית בלקוח Redis.
זיהוי וטיפול בחיבורים שלא מגיבים
מומלץ מאוד להגדיר את אפליקציית הלקוח כך שתזהה חיבורים לא מגיבים ל-Memorystore for Redis. כשמזוהה חיבור שלא מגיב, הלקוח צריך לאפס אותו. כדי לבנות אפליקציה עמידה, מומלץ להשתמש בהגדרות הלקוח הבאות:
- הגדרת פרמטרים של TCP keep-alive: מגדירים את הפרמטרים
TCP keepalive time,TCP keepalive intervalו-TCP keepalive probesכך שהלקוחות יזהו וינתקו באופן יזום חיבורים שלא מגיבים, גם כשהחיבורים לא פעילים. לדוגמה, אם מגדירים את הפרמטרTCP keepalive timeל-30 שניות, את הפרמטרTCP keepalive intervalל-10 שניות ואת הפרמטרTCP keepalive probesל-3, הלקוחות יאפסו חיבורים לא פעילים שלא מגיבים תוך דקה. - הגדרה של פסק זמן למשתמש TCP: צריך להגדיר את פסק הזמן הזה בלקוחות כדי לאפס חיבורים שיש להם בקשות ממתינות ולהפסיק להגיב. לדוגמה, אם מגדירים את הזמן הקצוב לתפוגה ל-15 שניות, הלקוחות מאפסים חיבורים שלא מגיבים שיש להם בקשות בהמתנה אחרי 15 שניות.
שיטות מומלצות ספציפיות ללקוחות
אם תרחיבו את האפליקציות, יכול להיות שתיתקלו בREADONLYשגיאות
בפקודות כתיבה. ב-Memorystore for Redis נעשה שימוש בפריסה שאינה Sentinel עם נקודות קצה סטטיות, והלקוחות לא מקבלים מראש מידע דינמי על התפקידים.
אם אתם משתמשים בחיבור לקוח יחיד גם לנקודות הקצה הראשיות וגם לנקודות הקצה של העותק, יכול להיות שהלקוח ישלח בטעות פקודות כתיבה לעותק לקריאה בלבד. הרפליקה מחזירה שגיאה READONLY כי היא לא יכולה לעבד פקודות כתיבה.
כדי למנוע ניתוב שגוי של פעולות כתיבה, אל תשתמשו בחיבור יחיד גם לקריאה וגם לכתיבה. במקום זאת, מפצלים את הפעולות על ידי יצירת מופעים נפרדים של הלקוח:
- לקוח ראשי: חיבור רק לנקודת הקצה הראשית
- לקוח Replica: מתחבר רק לנקודת הקצה של ה-Replica
בכרטיסיות הבאות מוסבר איך להגדיר את התבניות הנפרדות ב-Go, ב-Java, ב-Node.js וב-Python.
המשך
package main import ( "context" "fmt" "github.com/redis/go-redis/v9" ) func main() { ctx := context.Background() // Initialize the primary client connecting only to the primary endpoint primaryClient := redis.NewClient(&redis.Options{ Addr: "PRIMARY_HOST:6379", Password: "YOUR_PASSWORD", DB: 0, }) defer primaryClient.Close() // Initialize the replica client connecting only to the replica endpoint replicaClient := redis.NewClient(&redis.Options{ Addr: "REPLICA_HOST:6379", Password: "YOUR_PASSWORD", DB: 0, }) defer replicaClient.Close() // Use the primary client for all mutating commands err := primaryClient.Set(ctx, "example_key", "example_value", 0).Err() if err != nil { fmt.Printf("Failed to write to primary: %v\n", err) } // Use the replica client for all read-only commands val, err := replicaClient.Get(ctx, "example_key").Result() if err != nil { fmt.Printf("Failed to read from replica: %v\n", err) } else { fmt.Printf("Successfully read value: %s\n", val) } }
Java
@Bean public RedisConnectionFactory primaryConnectionFactory() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("PRIMARY_HOST", 6379); config.setPassword(RedisPassword.of("YOUR_PASSWORD")); return new LettuceConnectionFactory(config); } @Bean public RedisConnectionFactory replicaConnectionFactory() { RedisStaticMasterReplicaConfiguration config = new RedisStaticMasterReplicaConfiguration("PRIMARY_HOST", 6379); config.addNode("REPLICA_HOST", 6379); config.setPassword(RedisPassword.of("YOUR_PASSWORD")); LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() .readFrom(ReadFrom.REPLICA_PREFERRED) .build(); return new LettuceConnectionFactory(config, clientConfig); } @Bean public RedisTemplate<String, Object> primaryRedisTemplate( @Qualifier("primaryConnectionFactory") RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); return template; } @Bean public RedisTemplate<String, Object> replicaRedisTemplate( @Qualifier("replicaConnectionFactory") RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); return template; }
Node.js
import { createClient } from 'redis'; async function main() { // Initialize the primary client connecting only to the primary endpoint const primaryClient = createClient({ url: 'redis://PRIMARY_HOST:6379', password: 'YOUR_PASSWORD' }); // Initialize the replica client connecting only to the replica endpoint const replicaClient = createClient({ url: 'redis://REPLICA_HOST:6379', password: 'YOUR_PASSWORD' }); primaryClient.on('error', (err) => console.error('Primary Client Error', err)); replicaClient.on('error', (err) => console.error('Replica Client Error', err)); await primaryClient.connect(); await replicaClient.connect(); // Use the primary client for all mutating commands try { await primaryClient.set('example_key', 'example_value'); console.log('Successfully wrote to primary'); } catch (err) { console.error('Failed to write to primary:', err); } // Use the replica client for all read-only commands try { const val = await replicaClient.get('example_key'); console.log(`Successfully read value: ${val}`); } catch (err) { console.error('Failed to read from replica:', err); } await primaryClient.disconnect(); await replicaClient.disconnect(); } main();
Python
import redis def main(): # Initialize the primary client connecting only to the primary endpoint primary_client = redis.Redis( host='PRIMARY_HOST', port=6379, password='YOUR_PASSWORD', decode_responses=True ) # Initialize the replica client connecting only to the replica endpoint replica_client = redis.Redis( host='REPLICA_HOST', port=6379, password='YOUR_PASSWORD', decode_responses=True ) # Use the primary client for all mutating commands try: primary_client.set('example_key', 'example_value') print('Successfully wrote to primary') except redis.RedisError as e: print(f'Failed to write to primary: {e}') # Use the replica client for all read-only commands try: val = replica_client.get('example_key') print(f'Successfully read value: {val}') except redis.RedisError as e: print(f'Failed to read from replica: {e}') if __name__ == '__main__': main()