NoSQL历史简介
2013-04-25 23:46 Hejin.Wong 阅读(2649) 评论(1) 编辑 收藏 举报什么是NoSQL
随着用户内容的增长,所生成、处理、分析和归档的数据的规模快速增大,类型也快速增多。此外,一些新数据源也在生成大量数据,比如传感器、全球定位系统(GPS)、自动追踪器和监控系统。这些大数据集通常被称为大数据。数据不仅仅快速增长,而且半结构化和稀疏的趋势也很明显。这样一来,预定义好schema和利用关系型引用的传统数据管理技术就受到了挑战。
在探索海量数据和半结构化数据相关问题的过程中,诞生了一系列新型数据库产品,其中包括列族数据库(column-oriented data store)、键/值数据库和文档数据库,这些数据库统称NoSQL。
今天NoSQL泛指这样一类数据库和数据存储,它们不遵循经典RDBMS原理,且常与Web规模的大型数据集有关。换句话说,NoSQL并不单指一个产品或一种技术,它代表一族产品,以及一系列不同的、有时相互关联的、有关数据存储及处理的概念。
RDBMS假定数据的结构已明确定义,数据是致密的,并且很大程度上是一致的。RDBMS构建在这样的先决条件上,即数据的属性可以预先定义好,它们之间的相互关系非常稳固且被系统地引用(systematically referenced)。它还假定定义在数据上的索引能保持一致性,能统一应用以提高查询的速度。RDBMS可以容忍一定程度的不规律和结构缺乏,但在松散结构的海量稀疏数据面前,传统存储机制和访问方法捉襟见肘。
NoSQL缓解了RDBMS引发的问题并降低了处理海量稀疏数据的难度,但是反过来也被夺去了事务完整性的力量和灵活的索引及查询能力。
总之,NoSQL数据库是非常高效、强大的海量数据存储与处理工具。大部分NoSQL数据库都能很好地适应数据增长,并且能灵活适应半结构化数据和稀疏数据集。
背景和历史
Google建造了大规模可扩展的基础设施,用于支撑Google的搜索引擎和其他应用。其策略是在应用程序栈的每个层面上分别解决问题,旨在建立一套可伸缩的基础设施来并行处理海量数据。为此Google创建了一整套完备的机制,包括分布式文件系统、面向列族的数据存储、分布式协调系统和基于MapReduce的并行算法执行环境。
Google公开发布了一系列论文来解释其基础设施中一些关键的组成部分:
Sanjay Ghemawat、Howard Gobioff和Shun-Tak Leung,“The Google File System”; pub.19th ACM Symposium on Operating Systems Principles, Lake George, NY, October 2003
Jeffrey Dean和Sanjay Ghemawat,“MapReduce: Simplified Data Processing on Large Clusters”; pub. OSDI’04: Sixth Symposium on Operating System Design and Implementation, San Francisco, CA, December 2004。
Fay Chang、Jeffrey Dean、Sanjay Ghemawat、Wilson C. Hsieh、Deborah A. Wallach、Mike Burrows、Tushar Chandra、Andrew Fikes和Robert E. Gruber,“Bigtable: A Distributed Storage System for Structured Data”; pub. OSDI’06: Seventh Symposium on Operating System Design and Implementation, Seattle, WA, November 2006。
Mike Burrows,“The Chubby Lock Service for Loosely-Coupled Distributed Systems”; pub.OSDI’06: Seventh Symposium on Operating System Design and Implementation, Seattle, WA, November 2006。
Google 云端三大天王分別是: The Google File System, MapReduce, Bigtable
下面是中文翻译版
GFS: http://blog.csdn.net/xuleicsu/archive/2005/11/10/526386.aspx
MapReduce: http://blog.csdn.net/active1001/archive/2007/07/02/1675920.aspx
BigTale: http://blog.csdn.net/accesine960/archive/2006/02/09/595628.aspx
Google公布设计理念引起了开源开发者的广泛关注和浓厚兴趣。很快,第一个模仿Google基础设施部分特性的开源软件就开发出来了,它的创建者正是开源搜索引擎Lucene的发明人。紧接着,Lucene的核心开发者们加入了Yahoo!,在那里,依靠众多开源贡献者的支持,参照Google的分布式计算架构,开发者们创建出了一个能够替代Google基础设施所有部分的开源产品,这就是Hadoop及其子项目和相关项目。
Google的论文激发了人们对并行大规模处理和分布式非关系型数据存储的兴趣,一年后,Amazon分享了他们的成功经验。2007年Amazon对外展示了它的分布式高可用、最终一致性数据存储,其名曰Dynamo。
有关Amazon Dynamo的内容可以参考如下论文:
Giuseppe DeCandia, Deniz Hastorun, Madan Jampani, Gunavardhan Kakulapati, Avinash Lakshman, Alex Pilchin, Swami Sivasubramanian, Peter Vosshall, and Werner Vogels,“Dynamo: Amazon’s Highly Available Key/value Store,”in the Proceedings of the 21st ACM Symposium on Operating Systems Principles, Stevenson, WA, October 2007。(中文版:点击下载)
此外,Amazon的CTO Werner Vogels在一篇博文中解释了Amazon Dynamo背后的关键思想,博文地址为:http://www.allthingsdistributed.com/2007/10/amazons_dynamo.html
大数据
就现在而言,任何超过几个TB大小的数据集都可以归为大数据。这是数据集大到开始跨越多个存储单元的典型尺寸,也是传统RDBMS技术开始表现出吃力的尺寸。
随着数据规模的增长和数据创建来源的日趋多元化,以下挑战将日益严峻。
·高效存储和访问大量数据很难。额外要求的容错和备份使事情变得更加复杂。
·操作大数据集涉及大量并行进程。运行过程中要从任何故障中平稳恢复过来,同时还要在合理的时间范围内返回结果,这非常复杂。
·各种不同数据源生成的半结构化和无结构数据的schema和元数据持续不断变化,对它们的管理是一个令人头疼的问题。
可扩展性
可扩展性是一种能力,有了它系统能通过增加资源提高吞吐量进而解决增加的负荷。可扩展性可以通过两种方式实现,一是配置一个大而强的资源来满足额外的需求,二是依靠由普通机器组成的集群。使用大而强的机器通常属于垂直可扩展性。典型的垂直扩展方案是使用配有大量CPU内核且直接挂载大量存储的超级计算机。这类超级计算机通常极其昂贵,属于专有设备。替代垂直扩展的是水平扩展。水平扩展使用商业系统集群,集群随负载的增加而扩展。水平扩展通常需要添加额外的节点来应付额外的负载。大数据以及大规模并行处理数据的需要促使水平扩展得到了广泛的采纳。在Google、Amazon、Facebook、eBay和Yahoo!,水平扩展的基础设施包含数量巨大的服务器,其中一些包含几千甚至几十万台服务器。
对水平扩展集群上分布的数据进行处理是非常复杂的事情。在水平集群上处理大规模数据的方法里,MapReduce模型可能要算是最好的。
MapReduce
MapReduce这种并行编程模型支持在水平集群上对大规模数据集进行分布式处理。MapReduce框架是Google的专利,但其核心思想可以自由分享,一些开源实现已经采纳了这些思想。
MapReduce的创意和灵感来源于函数式编程。map和reduce是函数式编程中两个常用函数。在函数式编程中,map函数对列表的每个元素执行操作或函数。例如,在列表[1, 2, 3, 4]上执行multiple-by-two函数会产生另一个列表[2, 4, 6, 8]。执行这些函数时,原有列表不会被修改。函数式编程认为应当保持数据不可变,避免在多个进程或线程间共享数据。这意味着刚演示过的map函数虽然很简单,却可以通过两个或更多线程在同一个列表上同时执行,线程之间互不影响,因为列表本身没有改变。
与map函数类似,函数式编程中还有一个reduce函数的概念。实际上,reduce在函数式编程中更广为人知的名字是fold函数。reduce或fold函数又称accumulate、compress或者inject函数。reduce或fold函数对数据结构(例如列表)中的所有元素执行一个函数,最终返回单个结果或输出。因此在map函数输出列表[2, 4, 6, 8]上执行reduce求和,会得到单个输出值20。
map和reduce函数可以结合起来处理列表数据,先对列表的每个成员执行一个函数,再对转换生成的列表执行另一个聚合函数。
map和reduce这种简洁的思路可以用在大数据集上,只需稍事修改以适应由元组(tuple)或键/值对组成的集合即可。map函数对集合中的每组键/值对执行函数并产生一个新集合,接着reduce函数对新生成的集合执行聚合以计算最终结果。
假设存在由键/值对组成的集合,key是邮政编码 value是居民的姓名:
[{"94303":"Tom"},{"94303":"Jane"},{"94301":"Arun"},{"94302":"Chen"}]
在上述集合上执行map函数获得特定邮编范围内居民的姓名,输出结果:
[{"94303":["Tom","Jane"]},{"94301":["Arun"]},{"94302":["Chen"]}]
然后执行reduce函数计算特定邮编范围内居民的总数,输出结果:
[{"94303":2},{"94301":1},{"94302":1}]
列举一部分最知名的NoSQL产品,然后按照它们的功能和属性进行分类。
面向列的有序存储
Google Bigtable的数据存储模型支持面向列,这与RDBMS面向行的存储格式截然不同。面向列的存储能高效地存储数据,如果列值不存在就不存储,这样一来遇到null值时就能避免浪费空间。每个数据单元可以看作一组键/值对集合,单元本身通过主标识符(primary identifier)标识,主标识符又叫主键。Bigtable和其他类似产品喜欢称呼这个主键为行键(row-key)。另外,正如本节标题所描述的那样,单元按顺序排列好进行存储。数据单元按行键排序。
假设有一张表存储与人相关的信息。其中包含以下各列:名字(first_name)、姓氏(last_name)、职业(occupation)、邮编(zip_code)和性别(gender)。表中两组数据如下:
//Data 1 first_name:John last_name:Doe zip_code:10001 gender:male //Data 2 first_name:Jane zip_code:94303
第一条数据的行键为1,第二条的行键为2。面向列的有序存储中这两条数据会这样存储:行键1的数据会存储在行键2的前面,且两条数据相互比邻。
每条数据中只有合法的键/值对才会被存储。在类Bigtable的面向列存储中,数据按列族(column-family)存储。列族通常是在配置或启动时定义好,列则不需要预先定义或声明。列可以存储任何数据类型,前提是数据能持久化成byte数组。
假设上面例子中列族name的成员包括列first_name和last_name,列族location的成员包括列zip_code,列族profile的成员包括列gender。这样底层存储由3个存储桶组成:name、location和profile。每个桶只会保存合法的键/值对。
//列族name桶中存储下列值: For row-key:1 first_name:John last_name:Doe For row-key:2 first_name:Jane //列族location存储: For row-key:1 zip_code:10001 For row-key:2 zip_code:94303 //列族profile只有行键1对应的数据值,则它存储: For row-key:1 gender:male
在实际存储中,物理上一条数据的列族并不相互隔离,同一行键的所有数据存储在一起。列族可以看作代表成员列的键,而行键则是代表整条数据的键。
在Bigtable和其他类似产品中,数据按顺序连续存储。当数据逐渐填满一个节点后,它会拆分成多个节点。数据不仅在每个节点上是有序的,而且还跨越多节点形成一个更大的有序集。数据持久化方面会有容错的考虑,每份数据都同时维护3个副本。大部分类Bigtable产品都利用分布式文件系统将数据持久化到磁盘上。分布式文件系统支持将数据存储到集群的多台服务器上。因为有序,数据按行键查找效率极高。数据访问随机性更小,查找也更简单,就是在序列中查找包含数据的节点。数据插入发生在队列尾部,数据更新则原地进行,不过一般是添加数据的一个新版本到指定单元(cell)里,而非原地覆写(in-place overwrite)。每个单元始终维护多个版本,版本属性通常可配置。
HBase
HBase是以Google Bigtable思想为蓝本创建的、广受欢迎的、开源的有序列族存储。HBase中存储的数据可以通过MapReduce进行处理。Hadoop的MapReduce工具可以很容易地用HBase作为数据源或数据接收方。要了解和利用Google基础设施的思想,最佳方法是从学习Hadoop产品族开始。NoSQL的Bigtable存储HBase正是Hadoop家族中的一员。
- 官方在线资源:http://hbase.apache.org。
- 历史:2007年创建于Powerset(现在属于微软),微软收购Powerset前HBase被捐赠给了Apache基金会。
- 技术和语言:Java实现。
- 访问方法:JRuby的shell支持命令行访问。有Thrift1、Avro、REST和protobuf2客户端。对一些编程语言提供支持。发行版本中内置Java API。
- 查询语言:无原生查询语言。由Hive(http://hive.apache.org)提供类SQL接口。
- 开源许可证:Apache License 版本2。
- 使用者:Facebook、StumbleUpon、Hulu、Ning、Mahalo、Yahoo!等。
注1:Thrift是一个软件框架和一种接口定义语言,支持跨语言的服务和API开发。用Thrift生成的服务能在C++、Java、Python、PHP、Ruby、Erlang、Perl、Haskell、C#、Cocoa、Smalltalk和OCaml之间无缝地高效互通。Thrift在2007年由Facebook创建,它现在是Apache孵化器项目。关于它的更多信息请参阅:http://incubator.apache.org/thrift/。
注2:Protocol Buffer简称Protobuf,是Google的数据互换格式。有关其更多信息请参阅:http://code.google.com/p/protobuf/。
Hypertable
- 官方在线资源:www.hypertable.org。
- 历史:2007年创建于Zvents,现在是独立开源项目。
- 技术和语言:C++实现,使用Google RE2正则表达式类库。RE2实现快速高效。Hypertable承诺性能高于HBase,有可能节省处理大量数据的时间及成本。
- 访问方法:支持命令行。支持Thrift接口。基于Thrift接口支持一系列语言。充满创造力的开发者甚至为Hypertable开发了JDBC兼容接口。
- 查询语言:HQL(Hypertable Query Language,Hypertable查询语言)是一种用于查询Hypertable数据的类SQL抽象。Hypertable也有Hive适配器。
- 开源许可证:GNU GPL 版本2。
- 使用者:Zvents、百度(中国最大的搜索引擎)和Rediff(印度最大的门户网站)。
Cloudata
- 官方在线资源:www.cloudata.org。
- 历史:由韩国开发者YK Kwon创建(www.readwriteweb.com/hack/2011/02/open-source-bigtable-cloudata.php)。没有太多有关其起源的公开信息。
- 技术和语言:Java实现。
- 访问方法:支持命令行。支持Thrift、REST和Java API。
- 查询语言:CQL(Cloudata Query Language,Cloudata查询语言),一种类SQL查询语言。
- 开源许可证:Apache License 版本2。
键/值存储
哈希表(HashMap)或关联数组(associative array)是可以容纳键/值对的最简数据结构。这类数据结构极高效,访问数据的时间复杂度为O(1)。键/值对中的键在集合中是唯一值,容易查找,便于访问数据。
键/值存储各不相同:有的数据保存在内存里,有的能把数据持久化到磁盘里。键/值对可以被分散保存到集群节点中。
Oracle的Berkeley DB键/值存储简单强大,它是纯粹的存储引擎,键和值都是字节数组。其核心存储引擎并不关注键或值的含义,只管保存传入的字节数组对,然后返回同样的数据给调用客户端。Berkeley DB支持将数据缓存在内存中,随数据增长将其刷入磁盘。它还支持对键索引,帮助更快地查找和访问。Berkeley DB自20世纪90年代中期就已存在。在BSD4.3迁移到BSD4.4的过程中,它被创建出来替换AT&T的NDBM。到了1996年,软件公司Sleepycat成立,专门负责为Berkeley DB提供支持和维护。
另一种常用的键/值存储是缓存。缓存提供应用中使用最多的数据的内存快照。缓存的目的是减少磁盘I/O。它可以是最简单的映射表,也可以是支持缓存过期策略的健壮系统。作为一种流行策略,缓存广泛应用于计算机软件栈所有层面以提高性能。操作系统、数据库、中间件和各种应用都使用缓存。
像EHCache(http://ehcache.org/)这样健壮的开源分布式缓存系统广泛应用于各类Java应用中,可以将它看作一种NoSQL方案。另一种缓存系统Memcached(http://memcached.org/)在Web应用中非常流行,它是开源的高性能对象缓存系统。Memcached是2003年Brad Fitzpatrick为LiveJournal开发的。除了作为缓存系统,Memcached还帮助实现有效的内存管理,它会创建一个大虚拟池,然后在节点间按需分配内存。这种方式避免了碎片区域的出现,即某个节点有多余的空闲内存,而其他节点却急缺内存。
随着NoSQL运动的势头日益强劲,涌现出一批新的键/值对数据存储。其中一些构建在Memcached API上,一些用Berkeley DB作为底层存储,另一些则提供全新的替代方案。这类键/值存储多包含API,支持Get-Set机制以便获取和设置值;少数,如Redis(http://redis.io/),则提供更丰富的抽象和更强大的API。Redis可被视为数据结构服务器,因为除映射表以外它还提供字符串(字符序列)、列表和集合等数据结构,而且它还支持一套丰富的操作来访问不同类型数据结构的数据。
Membase
(拟并入Couchbase, Couchbase公司成立后开始从CouchDB中获得特性)
- 官方在线资源:www.membase.org/。
- 历史:NorthScale公司(后更名为Membase)于2009年启动该项目。从那时起,Zygna和NHN为其作出了重要贡献。Membase基于Memcached,支持Memcached的文本和二进制协议,它在Memcached基础上增加了很多新特性,包括磁盘持久化、数据复制、在线的集群重新配置和数据动态平衡。Membase的许多核心创建者也是Memcached的贡献者。
- 技术和语言:Erlang、C和C++实现。
- 访问方法:扩展的Memcached兼容API,可替代Memcached。
- 开源许可证:Apache License 版本2。
- 使用者:Zynga、NHN等。
Kyoto Cabinet
- 官方在线资源:http://fallabs.com/kyotocabinet/。
- 历史:Kyoto Cabinet的前身是Tokyo Cabinet(http://fallabs.com/tokyocabinet/)。Kyoto Cabinet的数据库就是包含记录的简单数据文件,每条记录是一对键/值,每个键和值都是一组变长二进制数据。
- 技术和语言:C++实现。
- 访问方法:提供C、C++、Java、C#、Python、Ruby、Perl、Erlang、OCaml和Lua的API。由于协议简单,所在存在非常多的客户端。
- 开源许可证:GNU GPL和GNU LGPL。
- 使用者:Mixi公司。原作者离开Mixi加入Google前,Mixi公司赞助了初期大部分工作。
Redis
- 官方在线资源:http://redis.io/。
- 历史:2009年Salvatore Sanfilippo启动该项目。Salvatore为其初创公司LLOOGG(http://lloogg.com/)开发了Redis。虽然目前仍然是独立项目,不过Redis的主要作者受雇于VMware,该公司赞助了Redis的开发。
- 技术和语言:C实现。
- 访问方法:支持丰富的方法和操作。可以通过Redis命令行接口和一系列得到良好维护的客户端类库进行访问,支持语言包括Java、Python、Ruby、C、C++、Lua、Haskell、AS3等。
- 开源许可证:BSD。
- 使用者:Craigslist。
上面罗列的3个键/值存储快速灵活,支持存储实时数据、短期内频繁使用的数据,甚至还支持数据完全地持久化。
至今为止列举的键/值存储都为数据存储提供了强一致性模型。然而,在分布式环境中其他一些键/值存储强调可用性高于一致性。它们中大部分灵感来自Amazon Dynamo,Amamzon Dynamo也是键/值存储,它承诺卓越的可用行和可扩展性,并成了Amazon分布式容错和高可用性系统的骨干。
Amazon Dynamo推出了大量重要的高可用性思想,其中最重要的是最终一致性(eventual consistency)。最终一致性暗示出节点数据更新过程中,副本可能会出现间歇的不一致。最终一致不是不一致,只是与RDBMS典型的ACID(atomicity、consistency、isolation、durabilty,原子性、一致性、隔离性、持久性)一致性相比更弱。
下面列出Amazon Dynamo的相似产品及其部分重要特点。
Cassandra
- 官方在线资源:http://cassandra.apache.org/。
- 历史:由Facebook开发,2008年开源。Apache Cassandra被捐赠给了Apache基金会。
- 技术和语言:Java实现。
- 访问方法:支持命令行访问。支持Thrift接口和Java API。存在多种语言的客户端,包括Java、Python、Grails、PHP、.NET和Ruby。支持Hadoop集成。
- 查询语言:查询语言规范正在形成中。
- 开源许可证:Apache License 版本2。
- 使用者:Facebook、Digg、Reddit、Twitter等。
Voldemort
- 官方在线资源:http://project-voldemort.com/。
- 历史:2008年由LinkedIn数据与分析组创建。
- 技术和语言:Java实现。可插拔存储引擎,支持Berkeley DB和MySQL。
- 访问方法:集成Thrift、Avro和protobuf(http://code.google.com/p/protobuf/)接口。可以结合Hadoop使用。
- 开源许可证:Apache License 版本2。
- 使用者:LinkedIn。
Riak
- 官方在线资源:http://wiki.basho.com/。
- 历史:创建于Basho,该公司成立于2008年。
- 技术和语言:Erlang实现。另外还使用了一点点C和JavaScript。
- 访问方法:支持基于HTTP的JSON接口和protobuf客户端。有Erlang、Java、Ruby、Python、PHP和JavaScript类库。
- 开源许可证:Apache License 版本2。
- 使用者:Comcast和Mochi Media。
Cassandra、Riak和Voldemort,所有这三个开源产品都提供了开源Amazon Dynamo的能力。Cassandra和Riak的行为和属性分别显现出各自的双重性:Cassandra同时拥有Google Bigtable和Amazon Dynamo的属性,而Riak既是键/值存储又是文档数据库。
文档数据库
文档数据库不是文档管理系统。文档数据库中的文档一词意指文档中松散结构的键/值对集合,通常是JSON(JavaScript Object Notation,JavaScript对象表示法),而非一般意义的文档或表格(尽管它们也能被存储)。
文档数据库把文档当作一个整体,不会将文档分割成多个键/值对。在集合层面上,这使得不同结构的文档可以放在同一个集合里。文档数据库支持文档索引,不仅包括主标识符,还包括文档的属性。当今为数不多的开源文档数据库中,最声名远扬的要数MongoDB和CouchDB。
MongoDB
- 官方在线资源:www.mongodb.org。
- 历史:创建于10gen。
- 技术和语言:C++实现。
- 访问方法:支持JavaScript命令行接口。支持多语言驱动,包括C、C#、C++、Erlang、Haskell、Java、JavaScript、Perl、PHP、Python、Ruby以及Scala。
- 查询语言:类SQL查询语言。
- 开源许可证:GNU Affero GPL(http://gnu.org/licenses/agpl-3.0.html)。
- 使用者:FourSquare、Shutterfly、Intuit、Github等。
CouchDB
- 官方在线资源:http://couchdb.apache.org和www.couchbase.com。大部分作者属于Couchbase公司。
- 历史:始于2005年,2008年被纳入Apache孵化器。
- 技术和语言:主要用Erlang实现,部分C实现,JavaScript执行环境。
- 访问方法:支持REST高于其他一切机制。可以用标准Web工具和客户端访问数据库,和访问Web资源的方法相同。
- 开源许可证:Apache License 版本2。
- 使用者:Apple、BBC、Canonical、Cern等,请参阅:http://wiki.apache.org/couchdb/CouchDBin_the_wild。
图形数据库
图形数据库和XML数据存储,也可以算作NoSQL数据库。列出两种图形数据库:Neo4j和FlockDB。
Neo4j
Neo4j是一个兼容ACID的图形数据库,便于快速遍历图形。
- 官方在线资源:http://neo4j.org。
- 历史:2003年创建于Neo Technologies。(没错,这个数据库在NoSQL这个词儿流行之前就已经存在了。)
- 技术和语言:Java实现。
- 访问方法:支持命令行接口和REST接口。有多种语言客户端,包括Java、Python、Ruby、Clojure、Scala和PHP。
- 查询语言:支持SPARQL协议和RDF查询语言。
- 开源许可证:AGPL。
- 使用者:Box.net。
FlockDB
- 官方在线资源:https://github.com/twitter/flockdb。
- 历史:创建于Twitter,2010年开源。最初是为了存储Twitter上粉丝的邻接表而设计的。
- 技术和语言:Scala实现。
- 访问方法:支持Thrift和Ruby客户端。
- 开源许可证:Apache License 版本2。
- 使用者:Twitter。
当代开发者是在RDBMS的陪伴下成长起来的,所以采用NoSQL既是采纳新技术,也是改变行为习惯。这也意味着作为开发者,我们需要首先学习NoSQL,深入理解它,之后才能对其适用性作出自己的判断。此外,NoSQL中的许多理念非常适合解决大规模可扩展性问题,可用于各种类型的应用。
(完)
声明:本文基于《深入NoSQL》整理。