使用 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 維基百科

    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 通訊。

    請務必按照「設定應用程式使用連接器」一文所述,將 VPC 連線資訊新增至 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 檔案。

例如:

{
  "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 檔案。例如:

; 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
取消連結
支援與快取的並行互動 請參閱 Redis 交易的詳細說明。

針對 Python,redis-py 用戶端程式庫要求所有交易都必須在管道中發生。

測試更新

在本機測試應用程式時,建議您執行 Redis 的本機執行個體,以免與實際工作環境中的資料互動 (Memorystore 不提供模擬器)。如要在本機安裝及執行 Redis,請按照 Redis 說明文件中的指示操作。請注意,目前無法在 Windows 本機上執行 Redis。

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

部署您的應用程式

應用程式在本機開發伺服器中執行時,如果沒有發生錯誤,請按照下列步驟操作:

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

  2. 如果應用程式執行時沒有發生錯誤,請使用流量分配功能,逐步增加更新後應用程式的流量。在將更多流量導向更新後的應用程式前,請密切監控應用程式是否有任何資料庫問題。