对 memcache 将 AppEngine py2.7 应用程序迁移到 py3 感到困惑

Posted

技术标签:

【中文标题】对 memcache 将 AppEngine py2.7 应用程序迁移到 py3 感到困惑【英文标题】:Confused about memcache migrating AppEngine py2.7 app to py3 【发布时间】:2021-10-24 03:35:18 【问题描述】:

我有一个在旧版 appengine 2.7 中运行的游戏服务器。我已将服务器迁移到 py3/flask 和所有各种位。我已将新服务器连接到 Redis 实例,而旧服务器正在使用 py2.7 的内存缓存。

我正在考虑将我的服务器的 2.7 版本连接到 Redis 内存缓存,因为我已将它作为分阶段迁移运行。这样我就可以在 py3 服务器和 py2 服务器之间拆分流量,它们将使用与我测试等相同的 memcache 服务器。我可以让一些 beta 用户与新服务器交谈并与当前服务器共存。

我的 py2.7 服务器版本与 redis 通信。但我发现使用 Redis 的文档对于存储 ndb 模型有点混乱。看来我不能像使用旧的遗留 memcache 那样直接在 Redis 中填充 ndb 模型类。

在迁移文档中,它说您可以使用 appengine 将 redis 设置为“全局缓存”,示例如下:

client = ndb.Client()
global_cache = ndb.RedisCache.from_environment()

with client.context(global_cache=global_cache):
  books = Book.query()
  for book in books:
      print(book.to_dict())

我不太清楚这意味着什么。我是否需要以我想要缓存的方式构建所有查询,或者是否有一次性设置,然后模型将自动缓存?如果存在,上面的示例会自动从缓存中提取吗?

目前在旧版 memcache 服务器中,我将一堆模型实例(或在我的情况下是特定用户所属的游戏)收集到 python 列表中,并使用类似的键缓存它们

cacheKey = userskey.urlsafe()+"_gamesList"

每当此用户的游戏以某种方式发生更改(更新、删除、添加..等)时,我都会删除 cacheKey.. 下次该用户查询他们的游戏列表时,我会重建缓存。看起来我不能在 Redis 中简单地存储这样的数据。此外,由于该用户可能会与服务器交互,因此我只希望缓存代表该用户的模型。每次该用户与我的服务器交谈时,我都需要使用密钥调用他们的用户帐户

我想我只是对全局缓存以及这一切与旧版 memcache 有何不同感到困惑。目前,我使用旧版 memcache 获得了接近 90% 的缓存命中率,因此尝试使用 Redis 复制它当然是值得的。甚至可能只是一个指向实际示例应用程序的指针也会有所帮助。令人惊讶的是,我没有找到。

【问题讨论】:

【参考方案1】:

正如 rossco 所说,Memorystore Redis 中没有自动缓存。在Legacy Memcache 中,缓存是全局的,默认情况下在应用程序的前端、后端及其所有服务和版本之间共享。 GlobalCache 接口具有相同的描述。但是,从您提供的示例中可以看出,创建了 RedisCache 对象(这是 GlobalCache 的实现)并将其传递给客户端上下文,这与创建旧版 Memcache 客户端的方式不同。关于公开的方法,在以下这些文档中也可以看到差异。

RedisCache Legacy Memcache

一旦客户端上下文被初始化,所有使用该上下文的操作都将使用单个全局缓存对象。使用相同的 global_cache 构造多个上下文,或使用依赖注入来传递相同的上下文将是一次性设置。

【讨论】:

【参考方案2】:

“全局缓存”一词有点令人困惑,它只是意味着可以从项目外部访问 MemoryStore Redis 实例。 如果您想继续使用 NDB,则需要先迁移到 CloudNDB。回答您的一个问题 - 不,MemoryStore 不会自动缓存任何内容。

但是我的建议是 - 如果您所做的只是在 Redis 中存储键值对,为什么不直接使用本机 Redis 功能?

# Connect to Redis (I create a CloudDNS record which points to the private IP)
pool = redis.ConnectionPool(host=<REDIS_HOST>, port=<REDIS_PORT>, db=0)
r = redis.Redis(connection_pool=pool)

然后你可以获取对象并将其设置为字节

import pickle, io

f = io.BytesIO()
pickle.dump(book.to_dict(),f)
f.seek(0)

res = r.set(alias,f.read())
# res will be True or False

outObj = r.get(alias)
if len(outObj) > 0:
    dictObj = pickle.loads(outObj)
else:
   # Cache Miss - get it from the DB and store it in Redis

另外MemoryStore比较贵,我用FakeRedis本地测试

import fakeredis as redis
serv = redis.FakeServer()
r = redis.FakeStrictRedis(server=serv)

更新 - 从您关于 UnicodeDecodeError 的评论中,我不知道您的字典中有哪些数据会导致问题,您可以尝试转换为 json 以及任何不可序列化的内容(如 datetime 对象),只需将它们转换为 str

jsonObj = json.dumps(dictObj, default=str)
f = io.BytesIO()
pickle.dump(jsonObj,f)
f.seek(0)

【讨论】:

感谢您的信息。我自己可以很好地管理它,我确实尝试过 .to_dict() 但 redis 不喜欢 dict 对象,我收到错误“redis.exceptions.DataError: Invalid input of type:'dict'。转换为字节,字符串, int 或 float 先。”我也尝试过使用 json.dumps(dict) 但在日期属性上失败了。 我已更新答案以显示转换为字节 感谢您的持续信息。我可以将它存储在 redis 中而不会出错,但在 .get() 上我得到“UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte” 刚刚添加了另一个转换为 json 但忽略 datetime 对象的建议,看看是否有帮助

以上是关于对 memcache 将 AppEngine py2.7 应用程序迁移到 py3 感到困惑的主要内容,如果未能解决你的问题,请参考以下文章

什么是 appengine_config.py

./manage.py 对 Django 1.4 的测试给出了在 google appengine 上找不到的 Thing2Literal 导入

Appengine Python 内存缓存容量缩减

进行 AppEngine 模型内存缓存的最佳方法是啥?

python appengine_config.py

跨所有实例在 appengine 上存储对象列表