7

我正在使用谷歌应用引擎并使用低级 java api 来访问大表。我正在构建一个有 4 层的 SAAS 应用程序:

  • 客户端网络浏览器
  • RESTful 资源层
  • 业务层
  • 数据访问层

我正在构建一个应用程序来帮助管理我的移动汽车美容公司(以及其他类似的公司)。我必须代表这四个独立的概念,但不确定我目前的计划是否是一个好的计划:

  • 约会
  • 订单项
  • 发票
  • 付款

预约: “预约”是指员工为了提供服务而需要在的地点和时间。

行项目: “行项目”是服务、费用或折扣及其相关信息。可能进入约会的行项目示例:

名称:价格:佣金:时间估算   
完整细节,常规尺寸:160 75 3.5 小时
10 美元的全细节优惠券:-10 0 0 小时
高级细节:220 110 4.5 小时
派生总计(非行项目):370 美元 185 美元 8.0 小时

发票: “发票”是客户承诺支付的一个或多个行项目的记录。

付款: “付款”是对已收到的付款的记录。

在此应用程序的先前实现中,生活更简单,我将所有这四个概念都视为 SQL 数据库中的一个表:“约会”。一个“约会”可以有多个行项目、多个付款和一张发票。发票只是根据行项目和客户记录生成的电子邮件或打印件。

10 次中有 9 次,效果很好。当一位客户预约一辆车或几辆车并自己付款时,一切都很美好。但是这个系统在很多条件下都不起作用。例如:

  • 当一位客户预约了一次,但预约中途下雨导致detailer第二天要回来,我需要两次预约,但只有一个line item,一张发票和一份付款。
  • 当办公室的一群客户都决定在同一天完成他们的汽车以获得折扣时,我需要一个预约,但需要多张发票和多笔付款。
  • 当一位客户用一张支票支付两次预约时,我需要两次预约,但只需要一张发票和一张付款。

我能够通过稍微捏造一些东西来处理所有这些异常值。例如,如果一个细部必须在第二天回来,我只需在第二天再预约一个项目,上面写着“完成”,费用将为 0 美元。或者,如果我让一位客户用一张支票支付两次约会,我会在每个约会中放入拆分付款记录。这样做的问题是它为数据不一致创造了巨大的机会。数据不一致可能是一个严重的问题,尤其是对于涉及财务信息的情况,例如第三个示例,其中客户用一张支票支付了两次约会费用。付款必须直接与提供的商品和服务相匹配,以便正确跟踪应收账款。

建议结构:

下面是用于组织和存储这些数据的规范化结构。也许是因为我缺乏经验,我非常重视数据规范化,因为这似乎是避免数据不一致错误的好方法。使用这种结构,可以通过一个操作完成对数据的更改,而不必担心更新其他表。但是,读取可能需要多次读取以及内存中的数据组织。稍后我想,如果存在性能问题,我可以在“约会”中添加一些非规范化字段,以便更快地查询,同时保持“安全”规范化结构完好无损。非规范化可能会减慢写入速度,

表:

Appointment
 start_time
 etc...

Invoice
 due_date
 etc...

Payment
 invoice_Key_List
 amount_paid
 etc...

Line_Item
 appointment_Key_List
 invoice_Key
 name
 price
 etc...

以下是将给定约会列表的所有四个实体(表)联系在一起所需的一系列查询和操作。这将包括关于每次预约安排了哪些服务、每次预约的总费用和天气或每次预约未收到付款的信息。这将是加载日历以进行约会安排或经理获取运营整体视图时的常见查询。

  • 查询“开始时间”字段位于给定范围之间的“约会”列表。
    • 将返回的约会中的每个键添加到列表中。
  • QUERY 对所有“Line_Items”的约会键列表字段包括任何退货约会
    • 将所有行项目中的每个 invoice_key 添加到 Set 集合中。
  • 查询发票集合中的所有“发票”(这可以使用应用引擎在一个异步操作中完成)
    • 将返回的发票中的每个键添加到列表中
  • 查询所有“付款”,其中 invoice_key_list 字段包含与任何返回的发票匹配的键
  • 在内存中重新组织,以便每个约会都反映为其安排的 line_items、总价格、总估计时间以及天气是否已支付。

...如您所见,此操作需要 4 个数据存储查询以及一些内存中的组织(希望内存中会很快)

任何人都可以评论这个设计吗?这是我能想到的最好的,但我怀疑可能有更好的选择或完全不同的设计,我没有想到这可能会在一般情况下或特别是在 GAE(谷歌应用引擎)的优势、劣势和能力下更好地工作.

谢谢!

使用说明

大多数应用程序的读取密集度更高,有些应用程序的写入密集度更高。下面,我描述了一个典型的用例并分解了用户想要执行的操作:

经理接到客户的电话:

  • 读取- 经理加载日历并查找可用时间
  • 写入- 经理向客户查询他们的信息,我将其想象为一系列异步读取,因为经理输入每条信息,例如电话号码、姓名、电子邮件、地址等......或者如果有必要,也许一个在客户端应用程序收集所有信息并提交之后,在最后写入。
  • 写入- 经理记下客户的信用卡信息并将其作为单独的操作添加到他们的记录中
  • 写入- 经理向信用卡收费并验证付款是否通过

经理拨出电话:

  • 读取管理器加载日历
  • 读取经理为他要呼叫的客户加载约会
  • Write Manager 点击“Call”按钮,发起一个调用并写入一个新的 CallReacord 实体
  • 读取呼叫服务器响应呼叫请求并读取 CallRecord 以了解如何处理呼叫
  • 写入呼叫服务器将更新的信息写入呼叫记录
  • 调用关闭时写入,调用服务器向服务器发出另一个请求以更新 CallRecord 资源(注意:此请求不是时间关键的)

接受的答案:: 前两个答案都非常周到和赞赏。我接受了一个票数很少的人,以便尽可能不完美地平衡他们的曝光率。

4

3 回答 3

9

您指定了您的网站需要提供的两个特定“视图”:

  1. 安排约会。您当前的方案应该可以正常工作 - 您只需要执行您提到的第一个查询。

  2. 操作的整体视图。我不太确定这意味着什么,但是如果您需要执行上面提到的四个查询的字符串来获得它,那么您的设计可以使用一些改进。详情如下。

四个数据存储查询本身并不一定是多余的。您的问题是其中两个查询很昂贵,甚至可能是不可能的。我将遍历每个查询:

  1. 获取约会列表 - 没问题。此查询将能够扫描索引以有效检索您指定日期范围内的约会。

  2. 从 #1 获取每个约会的所有行项目 - 这是一个问题。此查询要求您进行IN查询。 查询在幕后IN被转换为子查询- 因此您最终会从 #1 中获得每个约会键的一个查询!这些将并行执行,因此还不错。主要问题是查询仅限于一小部分值(最多只有 30 个值)。如果 #1 返回的约会键超过 30 个,则此查询将无法执行!NIN

  3. 获取行项目引用的所有发票 - 没问题。你是正确的,这个查询很便宜,因为你可以直接通过键简单地获取所有相关的发票。(注意:这个查询仍然是同步的——我不认为异步是你要找的词)。

  4. 获取 #3 返回的所有发票的所有付款 - 这是一个问题。与 #2 一样,此查询将是一个IN查询,并且如果 #3 返回您需要为其提取付款的中等数量的发票,则该查询将失败。

如果 #1 和 #3 返回的项目数量足够少,那么 GAE 几乎肯定能够在允许的范围内执行此操作。这应该足以满足您的个人需求 - 听起来您主要需要它来工作,并且不需要它来扩展到大量用户(它不会)。

改进建议:

  • 非规范化!尝试将与给定约会相关的 、 和实体的键存储在约会本身的Line_Item列表Invoice中。Payment然后你可以消除你的IN查询。确保这些新ListProperty没有被索引以避免爆炸索引的问题

其他不太具体的改进想法:

  • 根据您的“整体运营视图”将显示的内容,您可能能够拆分所有这些信息的检索。例如,也许您从显示约会列表开始,然后当经理想要有关特定约会的更多信息时,您继续获取与该约会相关的信息。如果您将此交互发生在单个页面上,您甚至可以通过 AJAX 执行此操作。
  • Memcache 是您的朋友 - 使用它来缓存数据存储查询的结果(甚至更高级别的结果),这样您就不必在每次访问时从头开始重新计算它。
于 2010-06-25T18:48:14.717 回答
7

正如您所注意到的,这种设计无法扩展。它需要 4 (!!!) DB 查询来呈现页面。这3个太多了:)

使用 App Engine 数据存储区的流行概念是,您希望在编写某些内容时尽可能多地完成工作,以便在检索和呈现某些内容时几乎不需要做任何事情。与渲染的次数相比,您可能只写了很少几次数据。

规范化同样是您似乎正在努力争取的事情。数据存储在规范化中没有任何价值——这可能意味着更少的数据不一致,但这也意味着读取数据的速度要慢得多(4 次读取?!!)。由于您的数据的读取频率远高于写入频率,因此请针对读取进行优化,即使这意味着您的数据偶尔会在短时间内重复或不同步。

与其考虑数据在存储时的外观,不如考虑您希望数据在向用户显示时的外观。尽可能接近该格式存储,即使这意味着将预渲染的 HTML 存储在数据存储中。读取速度将快如闪电,这是一件好事。

因此,由于您应该针对读取进行优化,因此您的写入通常会增长到巨大的比例。如此庞大,以至于您无法在 30 秒的请求时间限制内适应它。嗯,这就是任务队列的用途。将您认为模型的“基本必需品”存储在数据存储中,然后启动任务队列以将其拉出,生成要呈现的 HTML,并将其放在后台。这可能意味着您的模型立即准备好显示,直到任务完成,因此在这种情况下您需要优雅地降级,即使这意味着在数据完全填充之前“缓慢地”渲染它。任何进一步的阅读都将是闪电般的快速。

总之,我没有任何与您的数据库直接相关的具体建议——这取决于您希望数据在用户看到时的样子。

能给你的是一些关于数据存储的超级有用视频的链接:

  • Brett Slatkin 的2008 年2009 年关于在 App Engine 上构建可扩展的复杂应用程序的演讲,以及今年关于数据管道的精彩演讲(我认为这并不直接适用,但总的来说非常有用)
  • App Engine Under the Covers : App Engine 如何在幕后完成它的工作
  • AppStats:查看您正在执行的数据存储读取次数的好方法,以及减少该次数的一些技巧
于 2010-06-25T18:32:13.893 回答
2

以下是我认为您必须应对的一些应用程序引擎特定因素:

  • 使用不等式查询时,只能对一个属性使用不等式。例如,如果您筛选的应用日期介于 7 月 1 日和 7 月 4 日之间,则您不能同时筛选price > 200

  • 与您可能习惯的 SQL 数据库相比,应用引擎上的事务有点棘手。您只能对同一“实体组”中的实体进行交易。

于 2010-06-25T18:55:47.500 回答