שיטות מומלצות לשימוש ב-Memorystore for Redis

בדף הזה מפורטות הנחיות לשימוש אופטימלי ב-Memorystore for Redis. בדף הזה מפורטות גם בעיות פוטנציאליות שכדאי להימנע מהן.

רשימת תרחישים לפתרון בעיות מופיעה במאמר פתרון בעיות.

ייצוא של RDB

כשמייצאים גיבוי של RDB, פועלים לפי ההנחיות הבאות:

פעולות שצורכות הרבה משאבים

במכונות Redis במסלול הרגיל, הפעולות הבאות משתמשות בזיכרון נוסף למשך הפעולה:

שדרוג גרסה, התאמה לעומס והעברה ידנית ליתירות כשל משתמשים בזיכרון נוסף (במקרים של מופעים ברמת Standard) בגלל שכפול. הפעולות האלה מתבצעות בהתאם לתהליך השכפול שמתואר במאמר התנהגות שדרוג של מופע ברמה רגילה.

פעולות ייבוא וייצוא דורשות זיכרון נוסף בגלל תהליך הפיצול של Redis וניהול הנתונים מסוג copy-on-write שמשויכים לפעולות האלה.

כדי לצמצם את החסרונות של פעולות שדורשות הרבה משאבים, כדאי:

פעולות ותרחישים שדורשים ניסיון חוזר ליצירת חיבור

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

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

תחזוקה שוטפת

מכונות Memorystore for Redis עוברות תחזוקה מעת לעת. פרטים נוספים זמינים במדיניות התחזוקה של Memorystore for Redis.

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

ניהול זיכרון

ניהול הזיכרון יכול להיות מאתגר בגלל פיצול הזיכרון המוכר שמתרחש ב-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 לבקשות מטמון נכנסות.

מומלץ להגדיר את ההתראות הבאות כברירת מחדל:

שיטות מומלצות לשימוש במעבד (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()