שמירת נתונים במטמון באמצעות Memorystore

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

לפני שמתחילים, חשוב לוודא שהאפליקציה לא תחרוג ממכסות השימוש ב-Memorystore for Redis.

מתי כדאי להשתמש במטמון זיכרון

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

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

הסבר על ההרשאות ב-Memorystore

כל אינטראקציה עם שירות Google Cloud צריכה להיות מאושרת. לדוגמה, כדי לבצע אינטראקציה עם מסד נתונים של Redis שמתארח ב-Memorystore, האפליקציה צריכה לספק את פרטי הכניסה של חשבון שיש לו הרשאה לגשת ל-Memorystore.

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

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

  • האפליקציה ומסד הנתונים של Memorystore נמצאים בGoogle Cloud פרויקטים שונים.

  • שיניתם את התפקידים שהוקצו לחשבון השירות שמוגדר כברירת מחדל ב-App Engine.

מידע על טכניקות אימות חלופיות זמין במאמר הגדרת אימות לאפליקציות ייצור מסוג Server to Server.

סקירה כללית על השימוש ב-Memorystore

כדי להשתמש ב-Memorystore באפליקציה:

  1. מגדירים את Memorystore for Redis. לשם כך צריך ליצור מכונת Redis ב-Memorystore וליצור חיבור לרשת (VPC) מאפליקציית serverless שהאפליקציה משתמשת בו כדי לתקשר עם מכונת Redis.

  2. מתקינים ספריית לקוח ל-Redis ומשתמשים בפקודות Redis כדי לשמור נתונים במטמון.

    ‫Memorystore for Redis תואם לכל ספריית לקוח של Redis.

    המשך

    במדריך הזה מוסבר איך להשתמש בספריית הלקוח redigo כדי לשלוח פקודות Redis מהאפליקציה.

    Java

    במדריך הזה מוסבר איך להשתמש בספריית הלקוח Jedis כדי לשלוח פקודות Redis מהאפליקציה. לפרטים על השימוש ב-Jedis, אפשר לעיין ב-Jedis wiki.

    Node.js

    במדריך הזה מוסבר איך להשתמש בספריית הלקוח node_redis כדי לשלוח פקודות Redis מהאפליקציה.

    PHP

    במדריך הזה נסביר איך להשתמש בספריית הלקוח PHPRedis כדי לשלוח פקודות Redis מהאפליקציה.

    Python

    במדריך הזה מוסבר איך להשתמש בספריית הלקוח redis-py 3.0 כדי לשלוח פקודות Redis מהאפליקציה.

    Ruby

    במדריך הזה מוסבר איך להשתמש בספריית הלקוח redis-rb כדי לשלוח פקודות Redis מהאפליקציה.

  3. בודקים את העדכונים.

  4. פריסת האפליקציה ב-App Engine.

הגדרה של Memorystore for Redis

כדי להגדיר את Memorystore for Redis:

  1. יצירת מכונת Redis ב-Memorystore.

    כשמוצגת בקשה לבחור אזור למופע Redis, בוחרים באותו אזור שבו נמצאת אפליקציית App Engine.

  2. חשוב לשים לב לכתובת ה-IP ולמספר היציאה של מופע Redis שיוצרים. תשתמשו במידע הזה כשיוצרים לקוח Redis בקוד.

  3. חיבור של App Engine לרשת VPC. האפליקציה יכולה לתקשר עם Memorystore רק דרך מחבר VPC.

    חשוב להוסיף את פרטי החיבור ל-VPC לקובץ app.yaml כמו שמתואר במאמר הגדרת השימוש במחבר באפליקציה.

יחסי תלות בהתקנות

המשך

כדי שספריית הלקוח redigo תהיה זמינה לאפליקציה כשמריצים אותה ב-App Engine, צריך להוסיף את הספרייה לתלות של האפליקציה. לדוגמה, אם משתמשים בקובץ go.mod כדי להצהיר על יחסי תלות, מוסיפים את השורה הבאה לקובץ go.mod:

module github.com/GoogleCloudPlatform/golang-samples/tree/master/memorystore/redis

מידע נוסף על ציון תלות באפליקציית Go

Java

כדי שספריית הלקוח של Jedis תהיה זמינה לאפליקציה שלכם כשהיא פועלת ב-App Engine, צריך להוסיף את הספרייה לתלות של האפליקציה. לדוגמה, אם משתמשים ב-Maven, מוסיפים את התלות הבאה לקובץ pom.xml:

<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>5.1.0</version>
</dependency>

Node.js

כדי שספריית הלקוח node_redis תהיה זמינה לאפליקציה כשמריצים אותה ב-App Engine, מוסיפים את הספרייה לקובץ package.json של האפליקציה.

לדוגמה:

{
  "name": "memorystore-redis",
  "description": "An example of using Memorystore(Redis) with Node.js",
  "version": "0.0.1",
  "private": true,
  "license": "Apache Version 2.0",
  "author": "Google Inc.",
  "engines": {
    "node": ">=16.0.0"
  },
  "dependencies": {
    "redis": "^4.0.0"
  }
}

מידע נוסף על ציון תלות באפליקציית Node.js

PHP

כדי שספריית הלקוח PHPRedis תהיה זמינה לאפליקציה כשמריצים אותה ב-App Engine, מוסיפים את התוסף redis.so לקובץ php.ini של האפליקציה. לדוגמה:

; Enable the Redis extension on App Engine
extension=redis.so

מידע נוסף על הפעלת תוספי PHP ב-App Engine זמין במאמר בנושא תוספים שאפשר לטעון באופן דינמי.

Python

כדי להפוך את ספריית הלקוח redis-py לזמינה לאפליקציה כשמריצים אותה ב-App Engine, מוסיפים את השורה הבאה לקובץ requirements.txt של האפליקציה:

  redis

זמן הריצה של Python 3 ב-App Engine יעלה אוטומטית את כל הספריות בקובץ requirements.txt של האפליקציה כשפורסים את האפליקציה.

לפיתוח מקומי, מומלץ להתקין תלות בסביבה וירטואלית כמו venv.

Ruby

כדי שספריית הלקוח redis-rb תהיה זמינה לאפליקציה כשמריצים אותה ב-App Engine, צריך להוסיף את הספרייה לקובץ Gemfile של האפליקציה.

  source "https://cloud.google.com/memorystore"

  gem "redis-rb"

יצירת לקוח Redis

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

ציון משתני סביבה

ספריית הלקוח של Redis משתמשת בשני משתני סביבה כדי ליצור את כתובת ה-URL של מסד הנתונים של Redis:

  • משתנה לזיהוי כתובת ה-IP של מסד הנתונים של Redis שיצרתם ב-Memorystore.
  • משתנה לזיהוי מספר היציאה של מסד הנתונים של Redis שיצרתם ב-Memorystore.

מומלץ להגדיר את המשתנים האלה בקובץ app.yaml של האפליקציה במקום להגדיר אותם ישירות בקוד. כך קל יותר להריץ את האפליקציה בסביבות שונות, כמו סביבה מקומית ו-App Engine. מידע נוסף על משתני סביבה זמין בדף העזר בנושא app.yaml.

המשך

לדוגמה, מוסיפים את השורות הבאות לקובץ app.yaml:

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

Java

לדוגמה, מוסיפים את השורות הבאות לקובץ app.yaml:

  env_variables:
       redis.host: '10.112.12.112'
       redis.port: '6379'

Node.js

לדוגמה, מוסיפים את השורות הבאות לקובץ app.yaml:

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

PHP

לדוגמה, מוסיפים את השורות הבאות לקובץ app.yaml:

  env_variables:
       REDIS_HOST: '10.112.12.112'
       REDIS_PORT: '6379'

Python

לדוגמה, מוסיפים את השורות הבאות לקובץ app.yaml:

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

Ruby

לדוגמה, מוסיפים את השורות הבאות לקובץ app.yaml:

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

ייבוא של Redis ויצירת הלקוח

המשך

אחרי שמגדירים את משתני הסביבה REDISHOST ו-REDISPORT, משתמשים בשורות הבאות כדי לייבא את ספריית redigo, ליצור מאגר חיבורים ואז לאחזר לקוח Redis מהמאגר:


// Package main is a basic app that connects to a managed Redis instance.
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/gomodule/redigo/redis"
)

var redisPool *redis.Pool

func incrementHandler(w http.ResponseWriter, r *http.Request) {
	conn := redisPool.Get()
	defer conn.Close()

	counter, err := redis.Int(conn.Do("INCR", "visits"))
	if err != nil {
		http.Error(w, "Error incrementing visitor counter", http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "Visitor number: %d", counter)
}

func main() {
	redisHost := os.Getenv("REDISHOST")
	redisPort := os.Getenv("REDISPORT")
	redisAddr := fmt.Sprintf("%s:%s", redisHost, redisPort)

	const maxConnections = 10
	redisPool = &redis.Pool{
		MaxIdle: maxConnections,
		Dial:    func() (redis.Conn, error) { return redis.Dial("tcp", redisAddr) },
	}

	http.HandleFunc("/", incrementHandler)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	log.Printf("Listening on port %s", port)
	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatal(err)
	}
}

Java

כשמשתמשים בספריית Jedis, מומלץ ליצור JedisPool ואז להשתמש במאגר כדי ליצור לקוח. שורות הקוד הבאות משתמשות במשתני הסביבה redis.host ו-redis.port שהגדרתם קודם כדי ליצור מאגר:


package com.example.redis;

import java.io.IOException;
import java.util.Properties;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@WebListener
public class AppServletContextListener implements ServletContextListener {

  private Properties config = new Properties();

  private JedisPool createJedisPool() throws IOException {
    String host;
    Integer port;
    config.load(
        Thread.currentThread()
            .getContextClassLoader()
            .getResourceAsStream("application.properties"));
    host = config.getProperty("redis.host");
    port = Integer.valueOf(config.getProperty("redis.port", "6379"));

    JedisPoolConfig poolConfig = new JedisPoolConfig();
    // Default : 8, consider how many concurrent connections into Redis you will need under load
    poolConfig.setMaxTotal(128);

    return new JedisPool(poolConfig, host, port);
  }

  @Override
  public void contextDestroyed(ServletContextEvent event) {
    JedisPool jedisPool = (JedisPool) event.getServletContext().getAttribute("jedisPool");
    if (jedisPool != null) {
      jedisPool.destroy();
      event.getServletContext().setAttribute("jedisPool", null);
    }
  }

  // Run this before web application is started
  @Override
  public void contextInitialized(ServletContextEvent event) {
    JedisPool jedisPool = (JedisPool) event.getServletContext().getAttribute("jedisPool");
    if (jedisPool == null) {
      try {
        jedisPool = createJedisPool();
        event.getServletContext().setAttribute("jedisPool", jedisPool);
      } catch (IOException e) {
        // handle exception
      }
    }
  }
}

כדי ליצור לקוח מהמאגר, משתמשים בשיטה JedisPool.getResource(). לדוגמה:


package com.example.redis;

import java.io.IOException;
import java.net.SocketException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@WebServlet(name = "Track visits", value = "")
public class VisitCounterServlet extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    try {
      JedisPool jedisPool = (JedisPool) req.getServletContext().getAttribute("jedisPool");

      if (jedisPool == null) {
        throw new SocketException("Error connecting to Jedis pool");
      }
      Long visits;

      try (Jedis jedis = jedisPool.getResource()) {
        visits = jedis.incr("visits");
      }

      resp.setStatus(HttpServletResponse.SC_OK);
      resp.getWriter().println("Visitor counter: " + String.valueOf(visits));
    } catch (Exception e) {
      resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
    }
  }
}

Node.js

אחרי שמגדירים את משתני הסביבה REDISHOST ו-REDISPORT, אפשר להשתמש בשורות הבאות כדי לייבא את ספריית node_redis וליצור לקוח Redis:

'use strict';
const http = require('http');
const redis = require('redis');

const REDISHOST = process.env.REDISHOST || 'localhost';
const REDISPORT = process.env.REDISPORT || 6379;

const client = redis.createClient({
  url: `redis://${REDISHOST}:${REDISPORT}`,
});

client.on('error', err => console.error('ERR:REDIS:', err));

client
  .connect()
  .then(() => {
    console.log('Connected to Redis');
    http
      .createServer(async (req, res) => {
        try {
          const reply = await client.incr('visits');
          res.writeHead(200, {'Content-Type': 'text/plain'});
          res.end(`Visitor number: ${reply}\n`);
        } catch (err) {
          console.error(err);
          res.statusCode = 500;
          res.end(err.message);
        }
      })
      .listen(8080, () => {
        console.log('Server listening on port 8080');
      });
  })
  .catch(err => {
    console.error('Failed to connect to Redis:', err);
    throw err;
  });

PHP

אחרי שמגדירים את משתני הסביבה REDIS_HOST ו-REDIS_PORT, אפשר להשתמש בשורות הבאות כדי ליצור לקוח Redis:

<?php
/**
 * Copyright 2019 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Only serve traffic from "/"
switch (@parse_url($_SERVER['REQUEST_URI'])['path']) {
    case '/':
        break;
    default:
        http_response_code(404);
        exit('Not Found');
}

// Connect to Memorystore from App Engine.
if (!$host = getenv('REDIS_HOST')) {
    throw new Exception('The REDIS_HOST environment variable is required');
}

# Memorystore Redis port defaults to 6379
$port = getenv('REDIS_PORT') ?: '6379';

try {
    $redis = new Redis();
    $redis->connect($host, $port);
} catch (Exception $e) {
    return print('Error: ' . $e->getMessage());
}

$value = $redis->incr('counter');

printf('Visitor number: %s', $value);

Python

אחרי שמגדירים את משתני הסביבה REDISHOST ו-REDISPORT, משתמשים בשורות הבאות כדי לייבא את הספרייה redis-py וליצור לקוח:

  import redis

  redis_host = os.environ.get('REDISHOST', 'localhost')
  redis_port = int(os.environ.get('REDISPORT', 6379))
  redis_client = redis.Redis(host=redis_host, port=redis_port)

אם השתמשתם בגרסה ישנה יותר של redis-py באפליקציות אחרות, יכול להיות שהשתמשתם במחלקה StrictClient במקום Client. עם זאת, עכשיו מומלץ להשתמש ב-Client במקום ב-StrictClient.

Ruby

אין מידע נוסף על זמן הריצה הזה.

שימוש בפקודות Redis לאחסון ולאחזור נתונים במטמון

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

משימה פקודת Redis
יצירת רשומה במטמון הנתונים והגדרת זמן תפוגה לרשומה
SETNX
MSETNX
אחזור נתונים מהמטמון GET
MGET
החלפת ערכי מטמון קיימים SET
MSET
הגדלה או הקטנה של ערכי מטמון מספריים INCR
INCRBY
DECR
DECRBY
מחיקת רשומות מהמטמון DEL
UNLINK
תמיכה באינטראקציות בו-זמניות עם המטמון מידע נוסף על עסקאות ב-Redis

ב-Python, ספריית הלקוח redis-py מחייבת שכל העסקאות יתבצעו בצינור עיבוד נתונים.

בדיקת העדכונים

כשבודקים את האפליקציה באופן מקומי, מומלץ להריץ מופע מקומי של Redis כדי להימנע מאינטראקציה עם נתוני ייצור (Memorystore לא מספק אמולטור). כדי להתקין ולהריץ את Redis באופן מקומי, צריך לפעול לפי ההוראות במסמכי התיעוד של Redis. שימו לב שכרגע אי אפשר להריץ את Redis באופן מקומי ב-Windows.

במאמר בדיקה ופריסה של האפליקציה תוכלו לקרוא מידע נוסף על בדיקת האפליקציות.

פריסת האפליקציה

אחרי שהאפליקציה פועלת בשרת הפיתוח המקומי ללא שגיאות:

  1. בדיקת האפליקציה ב-App Engine

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