חיבור למופע Redis מאפליקציה בסביבה רגילה של App Engine

אפשר להתחבר למופע Redis מסביבת App Engine רגילה באמצעות חיבור לרשת (VPC) מאפליקציית serverless.

הגדרה

אם כבר התקנתם את Google Cloud CLI ויצרתם מופע Redis, אתם יכולים לדלג על השלבים האלה.

  1. מתקינים את ה-CLI של gcloud ומפעילים אותו:

    gcloud init
    
  2. פועלים לפי המדריך למתחילים כדי ליצור מכונת Redis. חשוב לשים לב לאזור, לכתובת ה-IP ולפורט של מכונת Redis.

הגדרת חיבור לרשת (VPC) מאפליקציית serverless

כדי להתחבר מאפליקציית App Engine לרשת ה-VPC המורשית של מופע Redis, צריך להגדיר חיבור לרשת (VPC) מאפליקציית serverless.

  1. כדי למצוא את הרשת המורשית של מופע Redis, מריצים את הפקודה:

    gcloud redis instances describe [INSTANCE_ID] --region [REGION]
    
  2. פועלים לפי ההוראות במאמר יצירת מחבר כדי ליצור מחבר של Serverless VPC Access. חשוב ליצור את המחבר באותו אזור שבו נמצאת האפליקציה, ולוודא שהמחבר מצורף לרשת ה-VPC המורשית של מופע Redis. כדאי לזכור את שם המחבר.

אפליקציה לדוגמה

אפליקציית שרת ה-HTTP לדוגמה הזו יוצרת חיבור למופע Redis מאפליקציה בסביבה הרגילה של App Engine.

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

המשך

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

באפליקציה לדוגמה הזו, מוסיפים 1 למונה Redis בכל פעם שניגשים לנקודת הקצה /.

המשך

האפליקציה הזו משתמשת בלקוח github.com/gomodule/redigo/redis. מריצים את הפקודה הבאה כדי להתקין אותו:

go get github.com/gomodule/redigo/redis@latest

אפליקציה לדוגמה:


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

האפליקציה הזו מבוססת על Jetty 3.1 servlet.

הוא משתמש בספרייה Jedis:

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

המחלקות AppServletContextListener משמשות ליצירת מאגר חיבורים ל-Redis לטווח ארוך:


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

המחלקות VisitCounterServlet הן סרוולט אינטרנטי שמגדיל מונה 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

האפליקציה הזו משתמשת במודול 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"
  }
}

קוד אפליקציה לדוגמה:

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

Python

האפליקציה הזו משתמשת ב-Flask לאירוח אתרים ובחבילת redis-py כדי לתקשר עם מופע Redis.

Flask==3.1.3; python_version >= '3.9'
gunicorn==23.0.0
redis==6.0.0
Werkzeug==3.1.8; python_version >= '3.9'

קוד אפליקציה לדוגמה:

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)

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

כדי לגשת למופע Redis, צריך להגדיר את אפליקציית App Engine כך שתשתמש במחבר Serverless VPC Access, ולספק את פרטי החיבור של מופע Redis.

  1. אם עדיין אין לכם אפליקציה, אתם צריכים ליצור אפליקציית App Engine.

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

    המשך

    מעדכנים את הקובץ gae_standard_deployment/app.yaml:

    runtime: go125
    
    # 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>'

    פרטים נוספים זמינים במאמר קובץ ההגדרות app.yaml.

    Java

    מעדכנים את הקובץ gae_standard_deployment/appengine-web.xml כדי לציין את המחבר שלכם לחיבור לרשת (VPC) מאפליקציית serverless:

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

    מעדכנים את הקובץ src/main/resources/application.properties עם כתובת ה-IP והיציאה של מופע Redis:

    redis.host=REDIS_HOST_IP
    redis.port=6379

    מידע נוסף על הגדרת האפליקציה זמין במאמר appengine-web.xml Reference.

    Node.js

    מעדכנים את הקובץ 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>'

    פרטים נוספים זמינים במאמר קובץ ההגדרות app.yaml.

    Python

    מעדכנים את הקובץ 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>'

    פרטים נוספים זמינים במאמר קובץ ההגדרות app.yaml.

פריסת האפליקציה בסביבה הרגילה של App Engine

כדי לפרוס את האפליקציה:

  1. מעתיקים את קובצי ההגדרות הנדרשים לספריית קובצי המקור:

    המשך

    מעתיקים את הקבצים app.yaml ו-go.mod לספריית קובצי המקור:

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

    Java

    מעתיקים את הקובץ appengine-web.xml לספריית קובצי המקור:

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

    Node.js

    מעתיקים את הקובץ app.yaml לספריית קובצי המקור:

    cp gae_standard_deployment/app.yaml .
    

    Python

    מעתיקים את הקובץ app.yaml לספריית קובצי המקור:

    cp gae_standard_deployment/app.yaml .
    
  2. מריצים את פקודת הפריסה:

    המשך

    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
    

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