尝试在Redis Cloud中使用Mercari/Datastore的Memcache集成代替GAE/Go1.12

简而言之

在上一篇文章中,我已经完成了GAE第二代/Go的构建,请使用Redis Cloud替代Memcache。所以,今天我将尝试构建Redis Cloud。

2019年度版的文章(用GAE第二代+Go v1.12构建echo服务器的方法)可以在以下链接中找到:
https://qiita.com/br_branch/items/a26480a05ecb97ac20b3

这次的源代码也在以下链接中公开。
https://github.com/brbranch/GaeGo112EchoSample/tree/feature/redis

本文的目标

最终,我们将使上一次使用的 mercari/datastore 和 redis 进行协同操作。

注册 Redis Cloud

image.png

我們將在這裡進行註冊,點擊「免費開始」。當您註冊郵箱後,將收到激活郵件,點擊該郵件後需要輸入姓名、密碼等資訊。完成所有步驟後,您將被轉至這樣的畫面。

image.png

首先选择Essentials,然后选择免费的30MB计划。
保持免费,似乎不需要进行信用卡注册之类的步骤。

image.png

当前步骤为创建数据库。
由于此处可公开查看,请确保密码设置为强密码(考虑直接使用此页面预先填入的密码是否可行)。

image.png

据说,数据淘汰策略是指当内存达到最大容量时如何清除数据的方法。详细信息可在此处找到:
https://redis-documentasion-japanese.readthedocs.io/ja/latest/topics/lru-cache.html#eviction-policies

以下是引述的内容。

    • noeviction : メモリ使用量が制限に達しており、クライアントが追加のメモリを要求するコマンド(DEL やいくつかの例外を除く、ほとんどの書き込みコマンド)を実行しようとした場合はエラーを返す。

 

    • allkeys-lru : 新しいデータのためにスペースを空けるため、もっとも最近使われていない(LRU)キーから削除するよう試みる。

 

    • volatile-lru : 新しいデータのためにスペースを空けるため、もっとも最近使われていない(LRU)キーから削除するよう試みる。ただし、 expire set が指定されたキーのみを対象とする。

 

    volatile-random : 新しいデータのためにスペースを空けるため、ランダムなキーを選んで削除する。ただし、 expire set が指定されたキーのみを対象とする。

首先,由于只有30MB的空间,我觉得使用allkeys-lru可能是个不错的选择。

這樣做後,設定已完成。
因此,我們試著從上次創建的倉庫中呼叫。

我想尝试一下玩一会儿。 (Wǒ .)

所以,首先我們來玩一玩,一邊看著Google的文件。

使用Redis Labs Redis保存应用程序数据的缓存

我打算先尝试保存和获取字符串。

package client

import "github.com/gomodule/redigo/redis"

type redisClient struct {
    pool *redis.Pool
}

var client *redisClient = nil

func GetRedisClient() *redisClient {
    if client == nil {
        panic("client is not initialized")
    }
    return client
}

func InitRedis(endpoint string, password string) {
    if client != nil {
        return
    }
    client = &redisClient{}
    client.pool = &redis.Pool{
        Dial: func() (redis.Conn, error) {
            conn, err := redis.Dial("tcp", endpoint)
            if password == "" {
                return conn, err
            }
            if err != nil {
                return nil, err
            }
            // パスワードが設定されてる場合のみ認証する(ローカルだと不要なため)
            if password != "" {
                if _, err := conn.Do("AUTH", password); err != nil {
                    conn.Close()
                    return nil, err
                }
            } else if (strings.HasPrefix(endpoint, "redis")) {
                // EndpointがRedusCloudなのにパスワードがないのはおかしい
                conn.Close()
                return nil, errors.New("invalid password.")
            }
            return conn, nil
        },
    }
}

func (c *redisClient) GetConnection() redis.Conn {
    return c.pool.Get()
}

func (c *redisClient) PutString(key string, value string) error {
    con := c.GetConnection()
    defer con.Close()
    _, err := con.Do("SET", key, value)
    return err
}

func (c *redisClient) GetString(key string) (string, error) {
    con := c.GetConnection()
    defer con.Close()

    return redis.String(con.Do("GET", key))
}

主要程序(main.go)只记录与上次相比的差异部分。

func main() {
    // Endpointを指定
    redisAddr := os.Getenv("REDIS_ADDR")
    // Passwordを指定
    redisPass := os.Getenv("REDIS_PASS")

    client.InitRedis(redisAddr, redisPass)

    // Redisの操作
    e.GET("/redis/put/:name", func(e echo.Context) error {
        name := e.Param("name")
        if err := client.GetRedisClient().PutString("test", name); err != nil {
            log.Fatalf("faield to get redis (reason: %v)", err)
            return e.String(http.StatusInternalServerError, "error")
        }
        return e.String(http.StatusOK, fmt.Sprintf("put redis: %s", name))
    })
    e.GET("/redis/get", func(e echo.Context) error {
        if name, err := client.GetRedisClient().GetString("test"); err != nil {
            log.Fatalf("faield to get redis (reason: %v)", err)
            return e.String(http.StatusInternalServerError, "error")
        } else {
            return e.String(http.StatusOK, fmt.Sprintf("get redis: %s", name))
        }
    })
}

通过实际操作 http://localhost:8080/redis/put/hogehoge ,成功保存了字符串。

image.png
image.png

顺便提一句,好像无法从Redis的网站上确认内部情况。
嗯,没办法咯。

据说在上述代码的Do()函数中,第一个参数是要发送给Redis的命令。
Google文档中提到的INCR是一个用于增加数值的命令。
还有很多其他定义的命令。

Redis 命令列表
https://symfoware.blog.fc2.com/blog-entry-521.html

在本地启动Redis。

虽然以前提到的方法已经足够了,但还是希望在本地环境中进行设置。
所以,我在阅读https://redis.io/topics/quickstart的同时尝试操作了一下。

追加(2019/09/11)

不必特意制作,brew中已经有了。
在Mac上安装Redis
https://qiita.com/checkpoint/items/58b9b0193c0c46400eeb

$ brew install wget # デフォルトだとMacはwget入ってないので(もう入ってたら当然不要)
$ wget http://download.redis.io/redis-stable.tar.gz
--2019-09-07 16:52:52--  http://download.redis.io/redis-stable.tar.gz
︙
2019-09-07 16:52:59 (316 KB/s) - `redis-stable.tar.gz へ保存完了 [2014657/2014657]
$ tar xvzf redis-stable.tar.gz
x redis-stable/
x redis-stable/INSTALL
x redis-stable/sentinel.conf
x redis-stable/deps/
x redis-stable/deps/update-jemalloc.sh
x redis-stable/deps/jemalloc/
︙
$ cd redis-stable
$ make
︙
# ビルドがちゃんと全部成功してるかテストする
$ make test

\o/ All tests passed without errors!

Cleanup: may take some time... OK

在中国,我们只需要一个选择。如果测试通过且没有错误,最后一个 make test 是可选的。生成的内容会存放在 redis-stable/src 文件夹中,所以请确保将其添加到 PATH 中。

执行并协调

在将PATH设置完成后,按照以下方式执行。

$ redis-server
65327:C 07 Sep 2019 17:03:47.270 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
65327:C 07 Sep 2019 17:03:47.270 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=65327, just started
65327:C 07 Sep 2019 17:03:47.270 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
65327:M 07 Sep 2019 17:03:47.272 * Increased maximum number of open files to 10032 (it was originally set to 4864).
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 5.0.5 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 65327
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

65327:M 07 Sep 2019 17:03:47.273 # Server initialized
65327:M 07 Sep 2019 17:03:47.273 * Ready to accept connections

因此,我们将使用 localhost:6379 将环境变量 REDIS_ADDR 传递并执行。 在这种情况下,密码是不必要的(可以使用空字符串)。

image.png
image.png

我从本地正确地获取了。
顺便提一下,刚刚安装的工具中还有一个叫做“redis-cli”的工具,你也可以在那里获取保存的内容。

$ redis-cli
127.0.0.1:6379> get test
"localRedisTest"

你进来了啊(´・ω・`)

使mercari/datastore和redis进行协作

好了,终于开始正题了。我们将通过将mercari/datastore和redis进行协作,实现Entity的缓存功能。

import (
    client2 "gaego112echosample/client"
    "github.com/labstack/echo"
    datastore2 "go.mercari.io/datastore"
    "go.mercari.io/datastore/clouddatastore"
    "go.mercari.io/datastore/dsmiddleware/rediscache"
    // ...省略
)

func HelloWorld(e echo.Context) error {
    // ... 省略 
    // Redisとの連携を行う
    redisConn := client2.GetRedisClient().GetConnection()
    defer redisConn.Close()
    mw := rediscache.New(redisConn,
        // ログが出るようにする
        rediscache.WithLogger(func(ctx context.Context, format string, args ...interface{}){
            log.Printf(format, args...)
        }),
        // Redisに登録されてるか見るためにログに出力するようにしてる
        rediscache.WithCacheKey(func(key datastore2.Key) string {
            cacheKey := fmt.Sprintf("cache:%s", key.Encode())
            log.Printf("redis cache key: %s", cacheKey)
            return cacheKey
        }),
    )
    client.AppendMiddleware(mw)
    // ... 省略 
}

这样的话,mericari/dtastore 可以不断注入功能。太棒了。

我会实际启动一下看看。

// ターミナル1: redisサーバー
$ redis-server
// ターミナル2: datastoreエミュレーター
$ gcloud beta emulators datastore start --host-port localhost:8059 --project test-project
// ターミナル3: ローカルサーバー
$ env DATASTORE_EMULATOR_HOST=localhost:8059 DATASTORE_PROJECT_ID=test-project REDIS_ADDR=localhost:6379 go run main.go

当尝试访问时,本地服务器会输出以下日志。

2019/09/07 17:40:38 Project ID:
2019/09/07 17:40:38 dsmiddleware/rediscache.SetMulti: incoming len=1
2019/09/07 17:40:38 redis cache key: cache:EgkKBHBvc3QQuWA
2019/09/07 17:40:38 dsmiddleware/rediscache.SetMulti: len=1
2019/09/07 17:40:38 dsmiddleware/rediscache.GetMulti: incoming len=1
2019/09/07 17:40:38 redis cache key: cache:EgkKBHBvc3QQuWA
2019/09/07 17:40:38 dsmiddleware/rediscache.GetMulti: hit=1 miss=0

当尝试使用 redis-cli 获取此 cache:EgkKBHBvc3QQuWA 时…

$ redis-cli
127.0.0.1:6379> get cache:EgkKBHBvc3QQuWA
"\x1b\xff\x83\x02\x01\x01\x0cPropertyList\x01\xff\x84\x00\x01\xff\x82\x00\x005\xff\x81\x03\x01\x01\bProperty\x01\xff\x82\x00\x01\x03\x01\x04Name\x01\x0c\x00\x01\x05Value\x01\x10\x00\x01\aNoIndex\x01\x02\x00\x00\x00\x1e\xff\x84\x00\x01\x01\acontent\x01\x06string\x0c\x06\x00\x04test\x00"

现在已经在缓存中了。太完美了!

尝试之后的感受

我可以毫不费力地根据感觉来构建,没有特别困难的部分。嗯,能够像这样理解并且进行开发是件很好的事情。暂时看来似乎可以用这个方案进行开发。

请看以下提供的建议

使用 Redis Labs Redis 进行应用程序数据的缓存存储。

Mericari / datastore 文档.ja
https://github.com/mercari/datastore/blob/master/doc_ja.go

美利坚/数据存储 文档.ja
https://github.com/mercari/datastore/blob/master/doc_ja.go

Redis 快速入门
https://redis.io/topics/quickstart

使用Redigo的基本方法 (1)
https://qiita.com/riverplus/items/c1df770838d3868c3a13

Redis 命令列表
https://symfoware.blog.fc2.com/blog-entry-521.html

我尝试了使用Golang和Redis玩耍

bannerAds