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

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

הגדרה

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

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

    gcloud init
    
  2. פועלים לפי המדריך למתחילים כדי ליצור מכונת Redis. חשוב לשים לב לאזור, לכתובת ה-IP ולפורט של מכונת 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 באותה רשת מורשית כמו מופע Redis, ולספק את פרטי החיבור של מופע Redis. כדי למצוא את הרשת המורשית, כתובת ה-IP והיציאה של מופע Redis, מריצים את הפקודה הבאה:

 gcloud redis instances describe [INSTANCE_ID] --region [REGION]
  1. יוצרים אפליקציית App Engine.

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

    המשך

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

    runtime: go
    env: flex
    
    # Update with Redis instance details
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Redis instance network name
    network:
      name: default
    
    runtime_config:
      operating_system: "ubuntu22"
      runtime_version: "1.25"

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

    Java

    מעדכנים את הקובץ gae_flex_deployment/app.yaml כדי לציין את הרשת של מופע Redis:

    runtime: java
    env: flex
    
    # Update with Redis instance network name
    network:
      name: default

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

    redis.host=REDIS_HOST_IP
    redis.port=6379

    מידע נוסף על הגדרת האפליקציה זמין במאמר הגדרת האפליקציה באמצעות app.yaml.

    Node.js

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

    runtime: nodejs
    env: flex
    
    # Update with Redis instance details
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Redis instance network name
    network:
      name: default

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

    Python

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

    runtime: python
    env: flex
    entrypoint: gunicorn -b :$PORT main:app
    
    runtime_config:
      python_version: 3
    
    # Update with Redis instance IP and port
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Redis instance network name
    network:
      name: default

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

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

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

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

    המשך

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

    cp gae_flex_deployment/app.yaml .
    

    Java

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

    mkdir -p src/main/appengine
    cp gae_flex_deployment/app.yaml src/main/appengine/
    

    Node.js

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

    cp gae_flex_deployment/app.yaml .
    

    Python

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

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

    המשך

    gcloud app deploy
    

    התהליך עשוי להימשך מספר דקות.

    Java

    mvn appengine:deploy
    

    התהליך עשוי להימשך מספר דקות.

    Node.js

    gcloud app deploy
    

    התהליך עשוי להימשך מספר דקות.

    Python

    gcloud app deploy
    

    התהליך עשוי להימשך מספר דקות.

אחרי שהפריסה מסתיימת, נכנסים לאפליקציה בכתובת ה-URL הבאה, ומחליפים את [PROJECT_ID] במזהה הפרויקט: Google Cloud

https://[PROJECT_ID].appspot.com

המספר במופע Redis גדל בכל פעם שמבקרים באפליקציה.