【发布时间】:2011-11-20 00:31:39
【问题描述】:
我对 StackOverflow 比较陌生,不确定是否适合提出设计问题。网站给了我一个提示“您提出的问题似乎很主观,很可能已关闭”。也许应该在programmers.stackexchange.com 上询问。请告诉我。
无论如何.. 我正在进行的项目之一是在线调查引擎。这是我在GAE 上的第一个大型商业项目。
我需要您就如何收集统计数据并有效地将其记录在 DataStore 中而不会让我破产的建议。初始要求是:
- 用户完成调查后,客户端发送对列表 [ID (int) + PercentHit (double)]。此列表显示此用户的答案与参考回答者的预定义答案(由 ID 标识)的匹配程度。我称它们为“目标 ID”。
- 调查的创建者希望查看给定 ID 在过去一小时、特定时间范围内或从调查开始时的汇总百分比。
- 有些调查可能有成千上万的目标/参考回答者。
所以我创建了实体
public class HitsStatsDO implements Serializable
{
@Id
transient private Long id;
transient private Long version = (long) 0;
transient private Long startDate;
@Parent transient private Key parent; // fake parent which contains target id
@Transient int targetId;
private double avgPercent;
private long hitCount;
}
但是为每个用户的每个目标编写 HitsStatsDO 会提供大量数据。例如,我进行了一项针对 3000 个目标的调查,一周内约有 400 万人回答了该调查,第一天有 30 万人参加了调查。即使我们假设他们在 24 小时内均匀地回答它,它也会给我们大约 1040 次写入/秒。显然它达到了 Datastore 的并发写入限制。
我决定收集一小时的数据并将其保存,这就是为什么HitsStatsDO 中有avgPercent 和hitCount。 GAE 实例是无状态的,所以我不得不使用dynamic backend instance。
我有这样的东西:
// Contains stats for one hour
private class Shard
{
ReadWriteLock lock = new ReentrantReadWriteLock();
Map<Integer, HitsStatsDO> map = new HashMap<Integer, HitsStatsDO>(); // Key is target ID
public void saveToDatastore();
public void updateStats(Long startDate, Map<Integer, Double> hits);
}
并用分片映射当前小时和前一小时(不会在这里停留很长时间)
private HashMap<Long, Shard> shards = new HashMap<Long, Shard>(); // Key is HitsStatsDO.startDate
所以我每小时一次将前一小时的 Shard 转储到 Datastore。
另外,我有 class LifetimeStats 将 Map<Integer, HitsStatsDO> 保存在 memcached 中,其中 map-key 是目标 ID。
还在我的backend shutdown hook method 中,我将未完成小时的统计信息转储到数据存储区。
这里只有一个主要问题 - 我只有一个后端实例 :) 它提出了以下问题,我想听听您的意见:
- 我可以在不使用后端实例的情况下执行此操作吗?
- 如果一个实例不够用怎么办?
- 如何在多个动态后端实例之间拆分数据?这很难,因为我不知道我有多少,因为随着负载的增加,Google 会创建新的。
- 我知道我可以启动确切数量的驻留后端实例。但是有多少? 2、5、10 ?如果我一周没有任何负担怎么办。持续运行 10 个后端实例太昂贵了。
- 当后端实例死机/重新启动时,我该如何处理来自客户端的数据?
需要注意的一点是,我不能改变太多客户。目前它是嵌入到客户网页中的 JavaScript。我可以以某种方式更改 RPC,但在架构上我不能用 Google Docs 表单替换客户端。
非常感谢您的想法。
【问题讨论】:
标签: google-app-engine architecture scalability backend software-design