更新AppEngine中每个实例的内存缓存(自动缩放)

问题描述 投票:1回答:3

我正在创建一个架构,其中大多数数据非常稳定,不需要对其进行严格更新,并且不经常更新。此外,该信息的大小并不多(大约几MB)。

因此,当我在Google AppEngine中初始化一个实例(使用GoLang)时,我首先获取所有信息,将其作为热身缓存在内存中,然后使用它,而不是使用Memcached。

但我需要每x次更新一次。因此,我需要一种方法来处理特定实例并更新它的内存缓存。

如果有不同的解决方案:

  • 处理完用户请求后,如果信息已过时,请在后台更新。问题?该用户请求可能需要相当多的时间才能完成。即使我尝试关闭与用户的连接并刷新数据,在该过程完成之前,用户也无法完全处理该请求。此外,如果在我需要更新缓存时发出许多请求,我还应该处理并发性以避免多次执行此操作。
  • 使用Cron + Pub / Sub(类似于这里所做的:https://cloud.google.com/solutions/reliable-task-scheduling-compute-engine,但是有Cloud Engine)。问题是我只能通过我定义的端点URL一次“命中”一个服务实例,因此我无法随意更新所有实例。
  • 杀死并更新实例。出于显而易见的原因,我不太喜欢这个。

使用Basic Sc​​aling可以解决一个特定的实例,但我找不到使用Automatic Sc​​aling的方法,如下所示:https://cloud.google.com/appengine/docs/standard/go/how-instances-are-managed

那么......,你能想象一下优雅的方式来同时更新所有实例的内存状态而不会打扰客户端吗?如何单独点击AppEngine的所有实例来更新内存缓存?

google-app-engine caching go memory-management
3个回答
3
投票

访问所有动态实例通常很麻烦,您不应该依赖它。

而是重新设计并使用不同的方法。

让所有实例都使用内存缓存,但使用缓存数据的到期时间。每当需要这样的数据时,首先检查数据是否仍然有效(检查到期时间),如果是,请继续使用它。如果已过期,则从“某个”位置获取新的实际数据。这个“一些”的地方可能是Memcache或数据存储区,或者可选择两者都喜欢在Memcache中首先尝试,如果没有,那么从Datastore;或者它可能位于完全不同的地方,即使在Google Cloud Platform之外也是如此。获取新数据应包含其到期时间。

此方法不需要您访问动态实例,它们将在过期后自动刷新缓存数据。

如果从多个goroutine访问,则必须同步对缓存数据的访问。最好的方法是使用sync.RWMutex,这样就可以允许多个读取器互不阻塞(频繁操作),并且只有在缓存数据已过期且需要刷新时才获取写锁定。

以下是此类内存缓存的示例实现:

func getFreshData() (data interface{}, expires time.Time, err error) {
    // Implement getting fresh data here:
    return nil, time.Now().Add(time.Minute), nil
}

type cachedData struct {
    sync.RWMutex
    data    interface{}
    expires time.Time
}

var cd = new(cachedData) // zero value is ready to use

func Get() (data interface{}, err error) {
    cd.RLock()
    if time.Now().Before(cd.expires) {
        // We're done: we can use the cached data:
        data = cd.data
        cd.RUnlock()
        return
    }

    cd.RUnlock()

    // Either we don't have cached data or it has expired.
    // Acquire write lock and get data
    cd.Lock()
    defer cd.Unlock()

    // But once we have the write lock, check again, as another competing
    // goroutine might have fetched data before us:
    if time.Now().Before(cd.expires) {
        // Another goroutine fetched fresh data:
        return cd.data, nil
    }

    // Nope, we have to do it ourselves:
    data, expires, err = getFreshData()
    if err == nil {
        // Also put fresh data into the cache:
        cd.data = data
        cd.expires = expires
    } else {
        // There was an error getting it, set a 5 sec timeout to not keep calling:
        cachedData.data = nil
        cachedData.expires = time.Now().Add(5 * time.Second)
    }

    return
}

0
投票

除了icza的答案:

只有在使用手动缩放时才能定位特定实例:

如果使用手动扩展服务,则可以通过包含实例ID来定位并向实例发送请求。实例ID是一个整数,范围从0到运行的实例总数,可以按如下方式指定:

向特定实例中的特定服务和版本发送请求:

https://[INSTANCE_ID]-dot-[VERSION_ID]-dot-[SERVICE_ID]-dot-[MY_PROJECT_ID].appspot.com
http://[INSTANCE_ID].[VERSION_ID].[SERVICE_ID].[MY_CUSTOM_DOMAIN]

注意:配置为自动缩放或基本缩放的服务不支持定位实例。实例ID必须是0之间的整数,直到运行的实例总数。无论您的扩展类型或实例类如何,都无法在不针对该实例中的服务或版本的情况下向特定实例发送请求。

source


0
投票

我会建议:

  1. 使用memcache满足内存需求,从而适用于所有实例
  2. 使用数据存储区备份内存缓存:可以在没有警告的情况下清除内存缓存,以便在内存缓存未命中,从数据存储区中获取并更新内存缓存。
  3. 在请求中,如果数据是旧的,则创建TaskQueue条目。然后执行此操作,让它更新数据存储区和内存缓存。但是,关键是您应该命名任务并在所有实例中选择相同的名称:一次只能存在一个具有给定名称的任务,因此多个实例不会创建多个任务。

在3中,您可以使用返回的旧数据,但会触发更新。如果这不好考虑在app引擎中使用Cron。

© www.soinside.com 2019 - 2024. All rights reserved.