我正在使用谷歌应用引擎并使用低leval java api访问Big Table.我正在构建一个包含4层的SAAS应用程序:
客户端Web浏览器
RESTful资源层
业务层
数据访问层
我正在构建一个应用程序来帮助管理我的移动汽车细节公司(以及其他类似的公司).我必须代表这四个独立的概念,但不确定我目前的计划是否合适:
约会
订单项
发票
支付
预约: "预约"是指员工需要提供服务的地点和时间.
订单项: "订单项"是服务,费用或折扣及其相关信息.可能进入约会的订单项示例:
Name: Price: Commission: Time estimate Full Detail, Regular Size: 160 75 3.5 hours $10 Off Full Detail Coupon: -10 0 0 hours Premium Detail: 220 110 4.5 hours Derived totals(not a line item): $370 $185 8.0 hours
发票: "发票"是客户承诺支付的一个或多个订单项的记录.
付款: "付款"是付款的记录.
在本应用程序的先前实现中,生活更简单,我将所有这四个概念视为SQL数据库中的一个表:"约会".一个"约会"可以有多个订单项,多笔付款和一张发票.发票只是通过订单项和客户记录生成的电子邮件或打印件.
十分之九,这很好.当一个客户预约一辆或几辆车并自己付款时,一切都很棒.但是这个系统在很多条件下都不起作用.例如:
当一个客户预约了一个,但约会中途下雨导致细节设计者必须在第二天回来时,我需要两个约会,但只有一个订单项,一个发票和一个付款.
当一组办公室的客户都决定在同一天完成他们的汽车以获得折扣时,我需要一个预约,但需要多个发票和多个付款.
当一个客户通过一张支票支付两张约会时,我需要两次约会,但只需要一张发票和一张付款.
通过稍微捏造一些东西,我能够处理所有这些异常值.例如,如果细节设计者必须在第二天回来,我会在第二天再次预约,其中的一个项目表示"完成",费用为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...
以下是将给定的约会列表将所有四个实体(表)绑定在一起所需的一系列查询和操作.这将包括有关每次预约安排了哪些服务的信息,每次预约的总费用以及每次预约收到的天气或不付款.当加载日历以进行约会安排或管理者获得操作的整体视图时,这将是一个常见的查询.
查询"约会"列表,其中"start_time"字段位于给定范围之间.
将返回约会中的每个键添加到List中.
查询所有"line_Items",其中的appointment_key_List字段包含任何退货约会
将所有订单项中的每个invoice_key添加到Set集合中.
发票组中所有"发票"的查询(这可以使用app引擎在一个异步操作中完成)
将返回的发票中的每个密钥添加到List中
查询所有"付款",其invoice_key_list字段包含与任何已退回发票匹配的密钥
在内存中重新组织,以便每个约会反映为其安排的line_items,总价格,总估计时间和天气是否已经支付.
...正如您所看到的,此操作需要4个数据存储区查询以及一些内存组织(希望内存中的速度非常快)
任何人都可以评论这个设计吗?这是我能想到的最好的,但我怀疑可能有更好的选择或完全不同的设计,我没想到它可能在一般情况下更好或者特别是在GAE(谷歌应用程序引擎)的优势,劣势和能力下.
谢谢!
大多数应用程序更加读取密集型,有些应用程序更加密集.下面,我将描述用户想要执行的典型用例和细分操作:
经理接到客户的电话:
读取 - 管理器加载日历并查找可用的时间
写 - 管理员向客户查询他们的信息,我想这是一系列异步读取,因为经理输入了每一条信息,如电话号码,姓名,电子邮件,地址等......或者如有必要,可能是一个在客户端应用程序收集了所有信息之后写到最后,然后提交.
写入 - 管理员记下客户的信用卡信息,并将其作为单独的操作添加到他们的记录中
写 - 经理收取信用卡并验证付款是否通过
经理拨打电话:
读取管理器加载日历
阅读管理器为他想要呼叫的客户加载预约
写入管理器单击"呼叫"按钮,启动呼叫并写入新的CallReacord实体
读取呼叫服务器响应呼叫请求并读取CallRecord以了解如何处理呼叫
写呼叫服务器将更新的信息写入CallRecord
在呼叫关闭时写入,呼叫服务器向服务器发出另一个请求以更新CallRecord资源(注意:此请求不是时间关键的)
接受的答案:: 前两个答案都非常周到和赞赏.为了尽可能不完美地平衡他们的曝光率,我接受了投票少的人.
您指定了网站需要提供的两个特定"视图":
安排预约.您当前的方案应该可以正常工作 - 您只需要执行您提到的第一个查询.
整体运营观点.我不确定这会带来什么,但如果您需要执行上面提到的四个查询字符串来获得此信息,那么您的设计可能会使用一些改进.详情如下.
四个数据存储区查询本身并不一定是过分的.在你的情况下,问题是两个查询是昂贵的,甚至可能是不可能的.我将通过每个查询:
获取约会列表 - 没问题.此查询将能够扫描索引以有效地检索您指定的日期范围内的约会.
获取#1中每个约会的所有订单项 - 这是一个问题.此查询要求您执行IN
查询. IN
查询会N
在幕后转换为子查询 - 因此您最终会从#1的每个约会密钥中获得一个查询!这些将并行执行,因此不是那么糟糕.主要问题是IN
查询仅限于一小部分值(最多只有30个值).如果#1返回的约会密钥超过30个,则此查询将无法执行!
获取订单项引用的所有发票 - 没问题.你这个查询很便宜是正确的,因为你可以直接通过密钥获取所有相关的发票.(注意:此查询仍然是同步的 - 我不认为异步是您正在寻找的单词).
获取#3返回的所有发票的所有付款 - 这是一个问题.与#2一样,此查询将是一个IN
查询,如果#3返回您需要获取付款的中等数量的发票,则该查询将失败.
如果#1和#3返回的项目数量足够小,那么GAE几乎肯定能够在允许的限制范围内执行此操作.这应该足以满足您的个人需求 - 听起来您最需要它才能工作,并且不需要扩展到大量用户(它不会).
改进建议:
非规范化!尝试存储密钥Line_Item
,Invoice
以及Payment
相关的名单上任命本身就是一个给定的约会实体.然后,您可以消除您的IN
查询.确保这些新ListProperty
的没有索引,以避免问题的爆发指数
其他不太具体的改进想法:
根据您的"整体操作视图"将要显示的内容,您可以拆分检索所有这些信息.例如,您可能首先显示约会列表,然后当经理想要了解有关特定约会的更多信息时,您可以继续获取与该约会相关的信息.如果您在单个页面上进行此交互,您甚至可以通过AJAX执行此操作.
Memcache是您的朋友 - 使用它来缓存数据存储区查询的结果(甚至更高级别的结果),这样您就不必在每次访问时从头开始重新计算它.
正如您所注意到的,此设计无法扩展.它需要4(!!!)DB查询来呈现页面.这太多了:)
使用App Engine数据存储区的主流概念是,您希望在编写内容时尽可能多地完成工作,这样在检索和呈现内容时几乎不需要执行任何操作.大概数次写入数据,与渲染的次数相比.
规范化同样是你似乎正在努力的事情.数据存储区在归一化中没有任何价值 - 它可能意味着数据不一致,但这也意味着读取数据的速度会慢得多(4次读取?!!).由于您的数据读取的频率比写入的频率高得多,因此优化读取,即使这意味着您的数据偶尔会被复制或不同步.
不要考虑数据在存储时的外观,而是考虑数据在向用户显示时的外观.尽可能接近该格式存储,即使这意味着在数据存储区中存储预呈现的HTML.读取将是闪电般的,这是一件好事.
因此,既然您应该优化读取,那么您的写入通常会增长到巨大的比例.如此巨大,以至于您无法在30秒的时间限制内完成请求.那就是任务队列的用途.将您认为模型的"必需品"存储在数据存储区中,然后触发任务队列将其拉出,生成要呈现的HTML,并将其放在后台.这可能意味着您的模型可以立即显示,直到任务完成,因此在这种情况下您需要优雅降级,即使这意味着在数据完全填充之前将其呈现为"缓慢的方式".任何进一步的读取都会很快.
总之,我没有任何与您的数据库直接相关的具体建议 - 这取决于您希望数据在用户看到时的样子.
我可以给你一些链接到有关数据存储的一些超级有用的视频:
Brett Slatkin在2008年和2009年关于在App Engine上构建可扩展的复杂应用程序的讨论,以及今年关于数据管道的一个很好的应用程序(我认为这不是直接适用的,但在一般情况下非常有用)
封面下的App Engine:App Engine如何在幕后完成它的工作
AppStats:查看您正在执行的数据存储读取数量的好方法,以及减少该数量的一些提示