使用 Memorystore 快取資料

在處理某些工作時,高效能且可擴充的網路應用程式通常會優先使用記憶體內建的分散式資料快取,或是以此替代完善的永久儲存空間。建議您使用 Memorystore for Redis 做為快取服務。請注意,Memorystore for Redis 不提供免費層級。詳情請參閱 Memorystore 定價

開始之前,請確認應用程式不會超出 Memorystore for Redis 配額

使用記憶體快取的時機

工作階段資料、使用者偏好設定和其他網頁查詢傳回的資料都很適合使用快取。一般來說,如果經常執行的查詢會傳回一組結果,且不需要立即顯示在應用程式中,您就可以快取結果。如此一來,後續要求就可以先查看快取,然後只有在找不到結果、或是暫存的結果因為逾期而消除的情況下,才去查詢資料庫。

如果您只將值儲存在 Memorystore,而不另於永久儲存空間備份;您就必須確保值過期並從快取中移除時,您的應用程式仍可保持正常運作。舉例來說,如果使用者的工作階段會因為資料突然消失,變成運作不良,那麼資料除了存放在 Memorystore,可能也應該儲存在資料庫。

瞭解 Memorystore 權限

與 Google Cloud 服務的每次互動都必須經過授權。舉例來說,如要與 Memorystore 代管的 Redis 資料庫互動,應用程式必須提供有權存取 Memorystore 的帳戶憑證。

根據預設,應用程式會提供 App Engine 預設服務帳戶的憑證,該帳戶已獲授權可存取與應用程式位於相同專案中的資料庫。

如果符合下列任一條件,您必須使用可明確提供憑證的替代驗證技術:

  • 應用程式和 Memorystore 資料庫位於不同的Google Cloud 專案。

  • 您已變更指派給預設 App Engine 服務帳戶的角色。

如要瞭解其他驗證技術,請參閱設定伺服器對伺服器正式版應用程式的驗證作業一文。

使用 Memorystore 總覽

如要在應用程式中使用 Memorystore,請按照下列步驟操作:

  1. 設定 Memorystore for Redis,這需要您在 Memorystore 上建立 Redis 執行個體,並建立應用程式用來與 Redis 執行個體通訊的無伺服器虛擬私有雲存取。

  2. 安裝 Redis 用戶端程式庫,並使用 Redis 指令快取資料。

    Memorystore for Redis 與所有適用於 Redis 的用戶端程式庫相容。

    Go

    本指南說明如何使用 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. 在 Memorystore 中建立 Redis 執行個體

    系統提示您選取 Redis 執行個體的地區時,請選取與 App Engine 應用程式相同的地區

  2. 請記下您建立的 Redis 執行個體 IP 位址和通訊埠編號。 您會在程式碼中建立 Redis 用戶端時使用這項資訊。

  3. 將 App Engine 連線至虛擬私有雲網路。您的應用程式只能透過虛擬私有雲連接器與 Memorystore 通訊。

    請務必按照「設定應用程式以使用連接器」一文所述,將虛擬私有雲連線資訊新增至 app.yaml 檔案。

安裝依附元件

Go

如要在應用程式於 App Engine 中執行時,讓應用程式使用 redigo 用戶端程式庫,請將該程式庫新增至應用程式的依附元件。舉例來說,如果您使用 go.mod 檔案宣告依附元件,請在 go.mod 檔案中新增下列程式碼:

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

進一步瞭解如何為 Go 應用程式指定依附元件

Java

如要在應用程式於 App Engine 中執行時提供 Jedis 用戶端程式庫,請將程式庫新增至應用程式的依附元件。舉例來說,如果您使用 Maven,請在 pom.xml 檔案中新增下列依附元件:

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

Node.js

如要在應用程式於 App Engine 中執行時提供 node_redis 用戶端程式庫,請將程式庫新增至應用程式的 package.json 檔案。

例如: <0x0

{
  "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

如要讓應用程式在 App Engine 中執行時可使用 PHPRedis 用戶端程式庫,請將 redis.so 擴充功能新增至應用程式的 php.ini 檔案。例如: <0

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

如要進一步瞭解如何在 App Engine 中啟用 PHP 擴充功能,請參閱可動態載入的擴充功能

Python

如要在應用程式於 App Engine 中執行時提供 redis-py 用戶端程式庫,請在應用程式的 requirements.txt 檔案中新增下列程式碼:

  redis

部署應用程式時,App Engine Python 3 執行階段會自動上傳應用程式 requirements.txt 檔案中的所有程式庫。

進行本機開發時,建議您在虛擬環境 (例如 venv) 中安裝依附元件。

Ruby

如要讓應用程式在 App Engine 中執行時可使用 redis-rb 用戶端程式庫,請將該程式庫新增至應用程式的 Gemfile 檔案。

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

  gem "redis-rb"

建立 Redis 用戶端

如要與 Redis 資料庫互動,程式碼必須建立 Redis 用戶端,以管理與 Redis 資料庫的連線。以下各節說明如何使用 Redis 用戶端程式庫建立 Redis 用戶端。

指定環境變數

Redis 用戶端程式庫會使用兩個環境變數,組裝 Redis 資料庫的網址:

  • 用於識別您在 Memorystore 中建立的 Redis 資料庫 IP 位址的變數。
  • 用於識別您在 Memorystore 中建立的 Redis 資料庫通訊埠編號的變數。

建議您在應用程式的 app.yaml 檔案中定義這些變數,而不是直接在程式碼中定義。這樣一來,您就能更輕鬆地在不同環境 (例如本機環境和 App Engine) 中執行應用程式。如要進一步瞭解環境變數,請參閱app.yaml參考頁面

Go

舉例來說,在 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 並建立用戶端

Go

定義 REDISHOSTREDISPORT 環境變數後,請使用下列程式碼行匯入 redigo 程式庫、建立連線集區,然後從集區擷取 Redis 用戶端:


// Command redis 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.hostredis.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

定義 REDISHOSTREDISPORT 環境變數後,您可以使用下列程式碼行匯入 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(REDISPORT, REDISHOST);
client.on('error', err => console.error('ERR:REDIS:', err));

// create a server
http
  .createServer((req, res) => {
    // increment the visit counter
    client.incr('visits', (err, reply) => {
      if (err) {
        console.log(err);
        res.status(500).send(err.message);
        return;
      }
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.end(`Visitor number: ${reply}\n`);
    });
  })
  .listen(8080);

PHP

定義 REDIS_HOSTREDIS_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

定義 REDISHOSTREDISPORT 環境變數後,請使用下列程式碼行匯入 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。不過,redis-py 現在建議使用 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 說明文件中的指示操作。請注意,目前無法在 Windows 本機上執行 Redis。

如要進一步瞭解如何測試應用程式,請參閱「測試及部署您的應用程式」。

部署您的應用程式

應用程式在本機開發伺服器中順利執行後,請按照下列步驟操作:

  1. 在 App Engine 上測試應用程式

  2. 如果應用程式順利執行,請使用流量分割,逐步增加更新後應用程式的流量。在將更多流量導向更新後的應用程式之前,請密切監控應用程式,確認是否有任何資料庫問題。