原文地址:http://www.infoq.com/cn/news/2011/03/nosql-architecture-practice-2/
(三)纯Nosql架构
-
只使用Nosql作为数据存储。
图 4-纯Nosql架构
-
在一些数据结构、查询关系非常简单的系统中,我们可以只使用Nosql即可以解决存储问题。这样不但可以提高性能,还非常易于扩展。手机凤凰网的前端展示系统就使用了这种方案。
-
在一些数据库结构经常变化,数据结构不定的系统中,就非常适合使用Nosql来存储。比如监控系统中的监控信息的存储,可能每种类型的监控信息都不太一样。这样可以避免经常对MysqL进行表结构调整,增加字段带来的性能问题。
-
这种架构的缺点就是数据直接存储在Nosql中,不能做关系数据库的复杂查询,如果由于需求变更,需要进行某些查询,可能无法满足,所以采用这种架构的时候需要确认未来是否会进行复杂关系查询以及如何应对。
-
非常幸运的是,有些Nosql数据库已经具有部分关系数据库的关系查询特性,他们的功能介于key-value和关系数据库之间,却具有key-value数据库的性能,基本能满足绝大部分web 2.0网站的查询需求。比如:
-
MongoDB就带有关系查询的功能,能解决常用的关系查询,所以也是一种非常不错的选择。下面是一些MongoDB的资料:
- 《视觉中国的Nosql之路:从MysqL到MongoDB》
- 《Choosing a non-relational database; why we migrated from MysqL to MongoDB》
- 最近的一次Mongo Beijing 开发者聚会也有一部分资料。
-
虽然Foursquare使用MongoDB的宕机事件的出现使人对MongoDB的自动Shard提出了质疑,但是毫无疑问,MongoDB在Nosql中,是一个优秀的数据库,其单机性能和功能确实是非常吸引人的。由于上面的例子有详细的介绍,本文就不做MongoDB的使用介绍。
-
Tokyo Tyrant数据库带有一个名为table的存储类型,可以对存储的数据进行关系查询和检索。一个table库类似于MysqL中的一个表。下面我们看一个小演示:
-
我们要存储一批用户信息,用户信息包含用户名(name),年龄(age),email,最后访问时间(lastvisit),地区(area)。下面为写入的演示代码:
<code class="hljs xml has-numbering" style="display: block; padding: 0px; color: inherit; Box-sizing: border-Box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="PHP" style="Box-sizing: border-Box;"><span class="hljs-preprocessor" style="color: rgb(68,68,68); Box-sizing: border-Box;"><?PHP</span>
<span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$tt</span> = <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">new</span> TokyoTyrantTable ( <span class="hljs-string" style="color: rgb(0,136,0); Box-sizing: border-Box;">"127.0.0.1"</span>,<span class="hljs-number" style="color: rgb(0,102,102); Box-sizing: border-Box;">1978</span> );
<span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$tt</span>->vanish ();<span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">//清空 </span>
<span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$id</span> = <span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$tt</span>->genUid ();<span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">//获取一个自增id </span>
<span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">//put方法提供数据写入。 put ( string $key,array $columns ); </span>
<span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$tt</span>->put ( <span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$id</span>,<span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">array</span> (<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"id"</span> => <span class="hljs-variable" style="color: rgb(102,<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"name"</span> => <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"zhangsan"</span>,0); Box-sizing: border-Box;">"age"</span> => <span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">27</span>,0); Box-sizing: border-Box;">"email"</span> => <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"zhangsan@gmail.com"</span>,0); Box-sizing: border-Box;">"lastvisit"</span> =>strtotime ( <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"2011-3-5 12:30:00"</span> ),0); Box-sizing: border-Box;">"area"</span> => <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"北京"</span> ) );
<span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$tt</span>->genUid ();
<span class="hljs-variable" style="color: rgb(102,0); Box-sizing: border-Box;">"lisi"</span>,102); Box-sizing: border-Box;">25</span>,0); Box-sizing: border-Box;">"lisi@126.com"</span>,0); Box-sizing: border-Box;">"lastvisit"</span> => strtotime( <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"2011-3-3 14:40:44"</span> ),0); Box-sizing: border-Box;">"laowang"</span>,102); Box-sizing: border-Box;">37</span>,0); Box-sizing: border-Box;">"laowang@yahoo.com"</span>,0); Box-sizing: border-Box;">"2011-3-5 08:30:12"</span> ),0); Box-sizing: border-Box;">"成都"</span> ) );
<span class="hljs-variable" style="color: rgb(102,0); Box-sizing: border-Box;">"tom"</span>,102); Box-sizing: border-Box;">21</span>,0); Box-sizing: border-Box;">"tom@hotmail.com"</span>,0); Box-sizing: border-Box;">"2010-12-10 13:12:13"</span> ),0); Box-sizing: border-Box;">"天津"</span> ) );
<span class="hljs-variable" style="color: rgb(102,0); Box-sizing: border-Box;">"jack"</span>,0); Box-sizing: border-Box;">"jack@gmail.com"</span>,0); Box-sizing: border-Box;">"2011-02-24 20:12:55"</span> ),0); Box-sizing: border-Box;">"天津"</span> ) );
<span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">//循环打印数据库的所有数据库 </span>
<span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$it</span> = <span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$tt</span>->getIterator ();
<span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">foreach</span> ( <span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$it</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">as</span> <span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$k</span> => <span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$v</span> ) {
print_r ( <span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$v</span> );
}
<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">?></span></span></code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,221,221); list-style: none; text-align: right; background-color: rgb(238,238,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li><li style="Box-sizing: border-Box; padding: 0px 5px;">14</li><li style="Box-sizing: border-Box; padding: 0px 5px;">15</li><li style="Box-sizing: border-Box; padding: 0px 5px;">16</li><li style="Box-sizing: border-Box; padding: 0px 5px;">17</li><li style="Box-sizing: border-Box; padding: 0px 5px;">18</li><li style="Box-sizing: border-Box; padding: 0px 5px;">19</li><li style="Box-sizing: border-Box; padding: 0px 5px;">20</li><li style="Box-sizing: border-Box; padding: 0px 5px;">21</li><li style="Box-sizing: border-Box; padding: 0px 5px;">22</li><li style="Box-sizing: border-Box; padding: 0px 5px;">23</li><li style="Box-sizing: border-Box; padding: 0px 5px;">24</li><li style="Box-sizing: border-Box; padding: 0px 5px;">25</li><li style="Box-sizing: border-Box; padding: 0px 5px;">26</li><li style="Box-sizing: border-Box; padding: 0px 5px;">27</li><li style="Box-sizing: border-Box; padding: 0px 5px;">28</li><li style="Box-sizing: border-Box; padding: 0px 5px;">29</li><li style="Box-sizing: border-Box; padding: 0px 5px;">30</li><li style="Box-sizing: border-Box; padding: 0px 5px;">31</li><li style="Box-sizing: border-Box; padding: 0px 5px;">32</li><li style="Box-sizing: border-Box; padding: 0px 5px;">33</li><li style="Box-sizing: border-Box; padding: 0px 5px;">34</li><li style="Box-sizing: border-Box; padding: 0px 5px;">35</li><li style="Box-sizing: border-Box; padding: 0px 5px;">36</li><li style="Box-sizing: border-Box; padding: 0px 5px;">37</li><li style="Box-sizing: border-Box; padding: 0px 5px;">38</li><li style="Box-sizing: border-Box; padding: 0px 5px;">39</li></ul>
<code class="hljs lasso has-numbering" style="display: block; padding: 0px; color: inherit; Box-sizing: border-Box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-subst" style="color: rgb(0,0); Box-sizing: border-Box;"><?</span>PHP
<span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$tt</span> <span class="hljs-subst" style="color: rgb(0,0); Box-sizing: border-Box;">=</span> <span class="hljs-literal" style="color: rgb(0,102); Box-sizing: border-Box;">new</span> TokyoTyrantTable ( <span class="hljs-string" style="color: rgb(0,102); Box-sizing: border-Box;">$query</span> <span class="hljs-subst" style="color: rgb(0,0); Box-sizing: border-Box;">=</span> <span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$tt</span><span class="hljs-subst" style="color: rgb(0,0); Box-sizing: border-Box;">-></span>getQuery ();
<span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">//查询年龄为21岁的用户 </span>
<span class="hljs-variable" style="color: rgb(102,102); Box-sizing: border-Box;">$query</span><span class="hljs-subst" style="color: rgb(0,0); Box-sizing: border-Box;">-></span>addCond ( “age”,TokyoTyrant<span class="hljs-tag" style="color: rgb(0,102); Box-sizing: border-Box;">::RDBQC_NUMEQ</span>,“<span class="hljs-number" style="color: rgb(0,102); Box-sizing: border-Box;">21</span>” );
print_r ( <span class="hljs-variable" style="color: rgb(102,0); Box-sizing: border-Box;">-></span>search () );
<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">?></span><span class="hljs-markup" style="Box-sizing: border-Box;"></span></code><ul class="pre-numbering" style="Box-sizing: border-Box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li><li style="Box-sizing: border-Box; padding: 0px 5px;">12</li><li style="Box-sizing: border-Box; padding: 0px 5px;">13</li></ul>
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',0); Box-sizing: border-Box;">-></span>getQuery ();
<span class="hljs-variable" style="color: rgb(102,0); Box-sizing: border-Box;">-></span>addCond ( “lastvisit”,102); Box-sizing: border-Box;">::RDBQC_NUMGE</span>,strtotime ( <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"2011-3-5 00:00:00"</span> ) );
print_r ( <span class="hljs-variable" style="color: rgb(102,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li><li style="Box-sizing: border-Box; padding: 0px 5px;">2</li><li style="Box-sizing: border-Box; padding: 0px 5px;">3</li><li style="Box-sizing: border-Box; padding: 0px 5px;">4</li><li style="Box-sizing: border-Box; padding: 0px 5px;">5</li><li style="Box-sizing: border-Box; padding: 0px 5px;">6</li><li style="Box-sizing: border-Box; padding: 0px 5px;">7</li><li style="Box-sizing: border-Box; padding: 0px 5px;">8</li><li style="Box-sizing: border-Box; padding: 0px 5px;">9</li><li style="Box-sizing: border-Box; padding: 0px 5px;">10</li><li style="Box-sizing: border-Box; padding: 0px 5px;">11</li></ul>
(四)以Nosql为数据源的架构
-
数据直接写入Nosql,再通过Nosql同步协议复制到其他存储。根据应用的逻辑来决定去相应的存储获取数据。
图 5 -以Nosql为数据源
-
纯Nosql的架构虽然结构简单,易于开发,但是在应付需求的变更、稳定性和可靠性上,总是给开发人员一种风险难于控制的感觉。为了降低风险,系统的功能不局限在Nosql的简单功能上,我们可以使用以Nosql为数据源的架构。
-
在这种架构中,应用程序只负责把数据直接写入到Nosql数据库就OK,然后通过Nosql的复制协议,把Nosql数据的每次写入,更新,删除操作都复制到MysqL数据库中。同 时,也可以通过复制协议把数据同步复制到全文检索实现强大的检索功能。在海量数据下面,我们也可以根据不同的规则,把数据同步复制到设计好的分表分库的 MysqL中。这种架构:
- 非常灵活。可以非常方便的在线上系统运行过程中进行数据的调整,比如调整分库分表的规则、要添加一种新的存储类型等等。
- 操作简单。只需要写入Nosql数据库源,应用程序就不用管了。需要增加存储类型或者调整存储规则的时候,只需要增加同步的数据存储,调整同步规则即可,无需更改应用程序的代码。
- 性能高。数据的写入和更新直接操作Nosql,实现了写的高性能。而通过同步协议,把数据复制到各种适合查询类型的存储中(按照业务逻辑区分不同的存储),能实现查询的高性能,不像以前MysqL一种数据库就全包了。或者就一个表负责跟这个表相关的所有的查询,现在可以把一个表的数据复制到各种存储,让各种存储用自己的长处来对外服务。
- 易扩展。开发人员只需要关心写入Nosql数据库。数据的扩展可以方便的在后端由复制协议根据规则来完成。
- 这种架构需要考虑数据复制的延迟问题,这跟使用MysqL的master-salve模式的延迟问题是一样的,解决方法也一样。
-
在这种以Nosql为数据源的架构中,最核心的就是Nosql数据库的复制功能的实现。而当前的几乎所有的Nosql都没有提供比较易于使用的复制接口来完成这种架构,对Nosql进行复制协议的二次开发,需要更高的技术水平,所以这种架构看起来很好,但是却不是非常容易实现的。我的开源项目PHPBuffer中有个实现TokyoTyrant复制的例子,虽然是PHP版本的,但是很容易就可以翻译成其他语言。通过这个例子的代码,可以实现从Tokyo Tyrant实时的复制数据到其他系统中。
(五)总结
- 以Nosql为主的架构应该算是对Nosql的一种深度应用,整个系统的架构以及代码都不是很复杂,但是却需要一定的Nosql使用经验才行。