Menghubungkan ke instance Redis dari aplikasi lingkungan standar App Engine

Anda dapat terhubung ke instance Redis dari lingkungan standar App Engine menggunakan Akses VPC Serverless.

Penyiapan

Jika telah menginstal Google Cloud CLI dan membuat instance Redis, Anda dapat melewati langkah-langkah ini.

  1. Instal gcloud CLI dan lakukan inisialisasi:

    gcloud init
    
  2. Ikuti Panduan Memulai Cepat untuk membuat instance Redis. Catat zona, alamat IP, dan port instance Redis.

Mengonfigurasi Akses VPC Serverless

Untuk terhubung dari aplikasi App Engine ke jaringan VPC resmi instance Redis, Anda harus menyiapkan Akses VPC Serverless.

  1. Temukan jaringan yang diizinkan instance Redis Anda dengan menjalankan perintah:

    gcloud beta redis instances describe [INSTANCE_ID] --region [REGION]
    
  2. Ikuti petunjuk di Membuat konektor untuk membuat konektor Akses VPC Tanpa Server. Pastikan Anda membuat konektor di region yang sama dengan aplikasi, dan pastikan konektor dilampirkan ke jaringan VPC resmi instance Redis. Ingat nama konektor.

Contoh aplikasi

Contoh aplikasi server HTTP ini membuat koneksi ke instance Redis dari aplikasi lingkungan standar App Engine.

Clone repositori untuk bahasa pemrograman yang Anda inginkan dan buka folder yang berisi kode contoh:

Go

git clone https://github.com/GoogleCloudPlatform/golang-samples
cd golang-samples/memorystore/redis

Java

git clone https://github.com/GoogleCloudPlatform/java-docs-samples
cd java-docs-samples/memorystore/redis

Node.js

git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples
cd nodejs-docs-samples/memorystore/redis

Python

git clone https://github.com/GoogleCloudPlatform/python-docs-samples
cd python-docs-samples/memorystore/redis

Aplikasi contoh ini menambahkan penghitung Redis setiap kali endpoint / diakses.

Go

Aplikasi ini menggunakan klien github.com/gomodule/redigo/redis. Instal dengan menjalankan perintah berikut:

go get github.com/gomodule/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

Aplikasi ini berbasis servlet Jetty 3.1.

Library ini menggunakan library Jedis:

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

Class AppServletContextListener digunakan untuk membuat kumpulan koneksi Redis yang berumur panjang:


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
      }
    }
  }
}

Class VisitCounterServlet adalah servlet web yang menambahkan penghitung Redis:


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

Aplikasi ini menggunakan modul redis.

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

'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);

Python

Aplikasi ini menggunakan Flask untuk penayangan web dan paket redis-py untuk berkomunikasi dengan instance Redis.

Flask==3.0.3
gunicorn==22.0.0
redis==5.0.1
Werkzeug==3.0.3
import logging
import os

from flask import Flask
import redis

app = Flask(__name__)

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


@app.route("/")
def index():
    value = redis_client.incr("counter", 1)
    return f"Visitor number: {value}"


@app.errorhandler(500)
def server_error(e):
    logging.exception("An error occurred during a request.")
    return (
        """
    An internal error occurred: <pre>{}</pre>
    See logs for full stacktrace.
    """.format(
            e
        ),
        500,
    )


if __name__ == "__main__":
    # This is used when running locally. Gunicorn is used to run the
    # application on Google App Engine and Cloud Run.
    # See entrypoint in app.yaml or Dockerfile.
    app.run(host="127.0.0.1", port=8080, debug=True)

Menyiapkan aplikasi untuk deployment

Untuk mengakses instance Redis, aplikasi App Engine harus dikonfigurasi untuk menggunakan konektor Akses VPC Serverless, dan Anda harus memberikan detail koneksi instance Redis.

  1. Jika Anda belum memilikinya, buat aplikasi App Engine.

  2. Perbarui konfigurasi aplikasi untuk menentukan konektor Akses VPC Serverless serta alamat IP dan port instance Redis Anda:

    Go

    Update file gae_standard_deployment/app.yaml:

    runtime: go111
    
    # Update with Redis instance details
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Serverless VPC Access connector details
    vpc_access_connector:
      name: 'projects/<PROJECT_ID>/locations/<REGION>/connectors/<CONNECTOR_NAME>'

    Lihat File Konfigurasi app.yaml untuk mengetahui detail selengkapnya.

    Java

    Perbarui file gae_standard_deployment/appengine-web.xml untuk menentukan konektor Akses VPC Serverless Anda:

    <appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
      <runtime>java8</runtime>
      <threadsafe>true</threadsafe>
      <vpc-access-connector>
        <name>projects/[PROJECT_ID]/locations/[REGION]/connectors/[CONNECTOR_NAME]</name>
      </vpc-access-connector>
    </appengine-web-app>

    Kemudian, perbarui file src/main/resources/application.properties dengan alamat IP dan port instance Redis Anda:

    redis.host=REDIS_HOST_IP
    redis.port=6379

    Untuk informasi selengkapnya tentang cara mengonfigurasi aplikasi, lihat Referensi appengine-web.xml.

    Node.js

    Update file gae_standard_deployment/app.yaml:

    runtime: nodejs10
    
    # Update with Redis instance details
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Serverless VPC Access connector details
    vpc_access_connector:
      name: 'projects/<PROJECT_ID>/locations/<REGION>/connectors/<CONNECTOR_NAME>'

    Lihat File Konfigurasi app.yaml untuk mengetahui detail selengkapnya.

    Python

    Update file gae_standard_deployment/app.yaml:

    runtime: python37
    entrypoint: gunicorn -b :$PORT main:app
    
    # Update with Redis instance details
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Serverless VPC Access connector details
    vpc_access_connector:
      name: 'projects/<PROJECT_ID>/locations/<REGION>/connectors/<CONNECTOR_NAME>'

    Lihat File Konfigurasi app.yaml untuk mengetahui detail selengkapnya.

Men-deploy aplikasi ke lingkungan standar App Engine

Untuk men-deploy aplikasi:

  1. Salin file konfigurasi yang diperlukan ke direktori sumber:

    Go

    Salin file app.yaml dan go.mod ke direktori sumber:

    cp gae_standard_deployment/{app.yaml,go.mod} .
    

    Java

    Salin file appengine-web.xml ke direktori sumber:

    mkdir -p src/main/webapp/WEB-INF
    cp gae_standard_deployment/appengine-web.xml src/main/webapp/WEB-INF/
    

    Node.js

    Salin file app.yaml ke direktori sumber:

    cp gae_standard_deployment/app.yaml .
    

    Python

    Salin file app.yaml ke direktori sumber:

    cp gae_standard_deployment/app.yaml .
    
  2. Jalankan perintah deploy:

    Go

    gcloud app deploy
    

    Java

    mvn package appengine:stage
    gcloud app deploy target/appengine-staging/app.yaml
    

    Node.js

    gcloud app deploy
    

    Python

    gcloud app deploy
    

Setelah deployment selesai, perintah akan menghasilkan URL tempat Anda dapat membuka aplikasi. Jika Anda membuka URL ini, Anda akan melihat jumlah pada instance Redis bertambah setiap kali halaman dimuat.