赞
踩
废话不多说,直接上干货!
虽然没有通用最优解,但是的确存在一些通用的优化策略。
所谓通用最小化,其实很好理解,比如:能用short
绝不用int
, 能用1
,绝不用2
。对于搜索引擎级海量数据的优化,在数据存储每个bit尤为珍贵。了解 ES 内部存储机制的小伙伴一定知道,其底层对倒排表的存储涉及多种压缩算法,其可谓对每个bit的使用发挥到了极致!举个例子:
上面图片为英汉词典中截取的一个小片段,英汉词典按照字典序排列,这就很方便我们去发现问题。假设业务要求我们要存储一个英汉对照
的数据表,常规思想就是为其创建两个字段,分别是“word_en” 、“word_cn”,当然名字是无所谓的。但是这带来一个很大的问题:搜索引擎级的数据往往是海量数据,以亿为单位计算的,这就要求我们必须在各个方面都不能用这种常规手段来处理。比如
在数据结构层面,基于B+Trees
的数据结构就非常不适用于全文检索这种场景,因为B+Trees
中节点大小是固定的,当索引字段单位数据量增大,就会造成节点分离,进而大大加深树的深度,造成不可估量的磁盘IO。这一点我将用一篇单独的文章详细讲解,此处不再赘述。
其次,海量数据可能涉及大量所谓“重复数据”,这里的重复不仅仅指的是keyword
层面或term
层面,而要精细到char
这种级别,简单来说,就是你不能说我两个字段值全完重叠才算做重复,而是如果 Term B 以 Term A 为前缀后缀,或者 Term B 和 Term A 有公共前缀或后缀均可视为重复存储
。
试想,假如列中包含 10亿 数量级的数据,而英文字幕无非就 26 个,也就是说,字幕 a 我们可能重复存储了上亿次。即便是中文,ES在底层存储的时候,也会通过ASCII来转移为字幕,最终以 bits 存储,这种问题,数量级越大,越严重。实际上这些有公共前缀或公共后缀的地方,我们完全可以只存储一次以追求更高的效率。
ES 在数据存储上采用的 FST 这种模型,在保证了高性能的前体系,带来了指数级的存储效率提升,一个TB级的索引,通过FST存储,其构建的模型大小在1GB左右。比如下 图二 就是 图一中词项字典的 FST模型。图三为其二进制序列的十进制展示。
这里我们只借鉴思想,而不探究原理,FST 的模型建立过程非常负责,我在关于倒排索引
的讲解中会有详细的介绍。
当然,此法则绝不仅仅局限于在数据存储层面,思想层面的应用还可扩展更多层面,如索引拆分,模型拆分,数据结构优化,业务分离等。
举个例子:在上述字符串的重复存储问题上面,我们做一下思维扩展,aabbc 和 aabbdd 很容易想到其中 aabb 为其公共前缀,但如果是数字呢? 比如数字 5和数字 10 ,他们的公共前缀又是什么?答案是 5 ,10 = 5 + 5,那如果是 2、5、10 呢,这种情况,他们的存储方式就变成了 2、3、5。因为 2 是 2、 5、10 公共前缀,而 5 是 5 和 10 的公共前缀,讲到这里,不知道你是否理解了这种 通用最小化
层面的思想。借助这种设计思想,其实还有很多可以扩展的地方。这里不一一列举了,因为我也列举不完。如果你真正理解了,你就会发现,这样的例子遍布于和我们生活息息相关的各个场景。
面向对象的语言开发者可能在刚接触语言开发的时候,听到最多的一句话就是“面向对象”,什么是封装、继承、多态,如何提高代码的复用效率。代码封装,不仅仅为了代码美观,还可以大大减小最终生成文件的大小,提高代码的存储和执行效率。这本质也是“通用最小化法则”的一种实现。
再举一个例子:很多重负载业务,都追求极致的轻量化代码或组件,一个典型的例子,我们用 beats 代替 logstash,最重要的原因就是 beats 的轻量化处理,使其在代替 logstash 完成数据采集的前提下,不挤占业务系统的资源。Logstash 虽然功能强大,但是其基于 Jvm 的特性就导致了其需要通过大量的资源占用来换取。
职责单一的设计是为了当某个单一职责所负责的业务的集群或节点无法满足其业务需要的时候,方便定向扩展。而且可以更均衡的分配所有资源。常见的操作如:
功能分离:本质上也利于服务的轻量化,我曾和百度负责搜索引擎开发的一位朋友聊天的时候聊过这个话题,百度内部就是基于 Elasticsearch 的源码做修改,删除对其业务不需要的代码,对 ES做轻量化处理
业务分离:最简单常见的场景,当我在全文检索服务的时候,应避免在服务期间去执行大量的聚合分析。
读写分离:常见操作,不做过多解释
冷热分离:基于索引生命周期管理策略下的性能动态分配策略。
写性能调优是建立在对Elasticsearch的写入原理之上。ES 数据写入具有一定的延时性,这是为了减少频繁的索引文件产生。默认情况下 ES 每秒生成一个 segment 文件,当达到一定阈值的时候 会执行merge,merge 过程发生在 JVM中,频繁的生成 Segmen 文件可能会导致频繁的触发 FGC,导致 OOM。为了避免避免这种情况,通常采取的手段是降低 segment 文件的生成频率,手段有两个,一个是 增加时间阈值,另一个是增大 Buffer的空间阈值,因为缓冲区写满也会生成 Segment 文件。
生产经常面临的写入可以分为两种情况:
高频低量:高频的创建或更新索引或文档一般发生在 处理 C 端业务的场景下。
低频高量:一般情况为定期重建索引或批量更新文档数据。
在搜索引擎的业务场景下,用户一般并不需要那么高的写入实时性。比如你在网站发布一条征婚信息,或者二手交易平台发布一个商品信息。其他人并不是马上能搜索到的,这其实也是正常的处理逻辑。这个延时的过程需要处理很多事情,业务层面比如:你的信息需要后台审核。你发布的内容在搜索服务中需要建立索引,而且你的数据可能并不会马上被写入索引,而是等待要写入的数据达到一定数量之后,批量写入。这种操作优点类似于我们快递物流的场景,只有当快递数量达到一定量级的时候,比如能装满整个车的时候,快递车才会发车。因为反正是要跑一趟,装的越多,平均成本越低。这和我们数据写入到磁盘的过程是非常相似的,我们可以把一条文档数据看做是一个快递,而快递车每次发车就是向磁盘写入数据的一个过程。这个过程不宜太多,太多只会降低性能,就是体现在运输成本上面。而对于我们数据写入而言就是体现在我们硬件性能损耗上面。
以下为常见 数据写入的调优手段,写入调优均以提升写入吞吐量和并发能力为目标,而非提升写入实时性。
目的是减小数据写入磁盘的频率,减小磁盘IO频率。
refresh_interval
的参数值目的是减少segment文件的创建,减少segment的merge次数,merge是发生在jvm中的,有可能导致full GC,增加refresh会降低搜索的实时性。
ES的 refresh 行为非常昂贵,并且在正在进行的索引活动时经常调用,会降低索引速度,这一点在索引写入原理中介绍过,了解索引的写入原理,可以关注我的博客Elastic开源社区
。
默认情况下,Elasticsearch 每秒定期刷新索引,但仅在最近 30 秒内收到一个或多个搜索请求的索引上。
如果没有搜索流量或搜索流量很少(例如每 5 分钟不到一个搜索请求)并且想要优化索引速度,这是最佳配置。此行为旨在在不执行搜索的默认情况下自动优化批量索引。建议显式配置此配置项,如 30秒。
本质也是减小refresh的时间间隔,因为导致segment文件创建的原因不仅有时间阈值,还有buffer空间大小,写满了也会创建。 默认最小值 48MB< 默认值 JVM 空间的10% < 默认最大无限制
当需要单次写入大量数据的时候,建议关闭副本,暂停搜索服务,或选择在检索请求量谷值区间时间段来完成。
具体可通过则设置index.number_of_replicas
为0
以加快索引速度。没有副本意味着丢失单个节点可能会导致数据丢失,因此数据保存在其他地方很重要,以便在出现问题时可以重试初始加载。初始加载完成后,可以设置index.number_of_replicas
改回其原始值。
大多数操作系统尝试将尽可能多的内存用于文件系统缓存,并急切地换掉未使用的应用程序内存。这可能导致部分 JVM 堆甚至其可执行页面被换出到磁盘。
交换对性能和节点稳定性非常不利,应该不惜一切代价避免。它可能导致垃圾收集持续几分钟而不是几毫秒,并且可能导致节点响应缓慢甚至与集群断开连接。在Elastic分布式系统中,让操作系统杀死节点更有效。
发送批量请求的单个线程不太可能最大化 Elasticsearch 集群的索引容量。为了使用集群的所有资源,应该从多个线程或进程发送数据。除了更好地利用集群的资源外,还有助于降低每个 fsync 的成本。
确保注意TOO_MANY_REQUESTS (429)
响应代码(EsRejectedExecutionException
使用 Java 客户端),这是 Elasticsearch 告诉我们它无法跟上当前索引速度的方式。发生这种情况时,应该在重试之前暂停索引,最好使用随机指数退避。
与调整批量请求的大小类似,只有测试才能确定最佳工作线程数量是多少。这可以通过逐渐增加线程数量来测试,直到集群上的 I/O 或 CPU 饱和。
max_result_window
参数max_result_window是分页返回的最大数值,默认值为10000。max_result_window本身是对JVM的一种保护机制,通过设定一个合理的阈值,避免初学者分页查询时由于单页数据过大而导致OOM。
在很多业务场景中经常需要查询10000条以后的数据,当遇到不能查询10000条以后的数据的问题之后,网上的很多答案会告诉你可以通过放开这个参数的限制,将其配置为100万,甚至1000万就行。但是如果仅仅放开这个参数就行,那么这个参数限制的意义有何在呢?如果你不知道这个参数的意义,很可能导致的后果就是频繁的发生OOM而且很难找到原因,设置一个合理的大小是需要通过你的各项指标参数来衡量确定的,比如你用户量、数据量、物理内存的大小、分片的数量等等。通过监控数据和分析各项指标从而确定一个最佳值,并非越大越好
首先要明确一点:鱼和熊掌不可兼得。读写性能调优在很多场景下是只能二选一的。牺牲 A 换 B 的行为非常常见。索引本质上也是通过空间换取时间。写生写入实时性就是为了提高检索的性能。
当你在二手平台或者某垂直信息网站发布信息之后,是允许有信息写入的延时性的。但是检索不行,甚至 1 秒的等待时间对用户来说都是无法接受的。满足用户的要求甚至必须做到10 ms以内。
搜索引擎最擅长的事情是从海量数据中查询少量相关文档,而非单次检索大量文档。非常不建议动辄查询上万数据。如果有这样的需求,建议使用滚动查询
鉴于默认http.max_content_length
设置为 100MB,Elasticsearch 将拒绝索引任何大于该值的文档。您可能决定增加该特定设置,但 Lucene 仍然有大约 2GB 的限制。
即使不考虑硬性限制,大型文档通常也不实用。大型文档对网络、内存使用和磁盘造成了更大的压力,即使对于不请求的搜索请求也是如此,_source
因为 Elasticsearch_id
在所有情况下都需要获取文档的文件系统缓存有效。对该文档进行索引可能会占用文档原始大小的倍数的内存量。Proximity Search(例如短语查询)和高亮查询也变得更加昂贵,因为它们的成本直接取决于原始文档的大小。
有时重新考虑信息单元应该是什么是有用的。例如,您想让书籍可搜索的事实并不一定意味着文档应该包含整本书。使用章节甚至段落作为文档可能是一个更好的主意,然后在这些文档中拥有一个属性来标识它们属于哪本书。这不仅避免了大文档的问题,还使搜索体验更好。例如,如果用户搜索两个单词foo
and bar
,则不同章节之间的匹配可能很差,而同一段落中的匹配可能很好。
批量请求将产生比单文档索引请求更好的性能。但是每次查询多少文档最佳,不同的集群最佳值可能不同,为了获得批量请求的最佳阈值,建议在具有单个分片的单个节点上运行基准测试。首先尝试一次索引 100 个文档,然后是 200 个,然后是 400 个等。在每次基准测试运行中,批量请求中的文档数量翻倍。当索引速度开始趋于平稳时,就可以获得已达到数据批量请求的最佳大小。在相同性能的情况下,当大量请求同时发送时,太大的批量请求可能会使集群承受内存压力,因此建议避免每个请求超过几十兆字节。
很多人会忽略对 Elasticsearch 数据建模的重要性。
nested属于object类型的一种,是Elasticsearch中用于复杂类型对象数组的索引操作。Elasticsearch没有内部对象的概念,因此,ES在存储复杂类型的时候会把对象的复杂层次结果扁平化为一个键值对列表。
特别是,应避免连接。Nested 可以使查询慢几倍,Join 会使查询慢数百倍。两种类型的使用场景应该是:Nested针对字段值为非基本数据类型的时候,而Join则用于 当子文档数量级非常大的时候。
关于数据建模,在我的博客中有详细的讲解,此处不再赘述
Lucene的数据的fsync是发生在OS cache的,要给OS cache预留足够的内从大小,详见JVM调优。
利用查询中的模式来优化数据的索引方式。例如,如果所有文档都有一个price
字段,并且大多数查询 range 在固定的范围列表上运行聚合,可以通过将范围预先索引到索引中并使用聚合来加快聚合速度。
query和filter的主要区别在: filter是结果导向的而query是过程导向。query倾向于“当前文档和查询的语句的相关度”而filter倾向于“当前文档和查询的条件是不是相符”。即在查询过程中,query是要对查询的每个结果计算相关性得分的,而filter不会。另外filter有相应的缓存机制,可以提高查询效率。
避免单页数据过大,可以参考百度或者淘宝的做法。es提供两种解决方案 scroll search 和 search after。关于深度分页的详细原理,推荐阅读:详解Elasticsearch深度分页问题
并非所有数值数据都应映射为数值字段数据类型。Elasticsearch为 查询优化数字字段,例如integer
or long。如果不需要范围查找,对于 term查询而言,keyword 比 integer 性能更好。
Scripting是Elasticsearch支持的一种专门用于复杂场景下支持自定义编程的强大的脚本功能。相对于 DSL 而言,脚本的性能更差,DSL能解决 80% 以上的查询需求,如非必须,尽量避免使用 Script
本质上,索引结构取决于业务,业务场景不同,最合适的索引结构也不同。下面我通过一个真实案例来解释这个问题。
某公司是提供SMS短信服务的,其主要客户群体为各大银行系统。其业务主要是为客户提供短信发送服务,并且要保存所有
痛点1:索引过于庞大
就短信发送而言并无太大问题,但是在项目初期,在建立短信发送记录的时候,并没有在意索引结构的设计,而是将新产生的数据不断的追加到一个索引中,但是后期发现短信发送量级过于庞大,每天都产生上几百GB甚至是TB级的日志数据,后期的优化手段就是把一个单独的大索引,通过Rollover Index,每天创建一个单独的索引。短期内问题得到了解决
痛点2:索引太多
基于滚动索引的方案在短期内的确解决了单个索引过大而带来的性能下降,因为80%的请求都是基于近期数据而言的,但是随着时间周期的拉长,剩余 20% 的请求的问题日渐突出。的确存在一部分用户,需要查询跨周期范围特别大的数据,比如,工商银行某客户要查询 近三年以来短信发送记录,造成的结果就是需要跨一千多个索引来查询。这种问题,时间越久,就越明显。
对于这种需求场景而言,其实索引结构设计的很不合理。固然我们很容易将数据与时间绑定,但是当前场景明显是一个垂直业务。换句话说,索引的拆分不应该简单按照时间进行拆分,而是按照业务属性和业务量。比如:我们要查询某个手机号3年的发送记录,在执行查询之前,我们就能通过手机号的自身属性确定查询范围,如手机号归属地、所属运营商等。北京的手机号,无论如何也不会涉及到上海的数据。所以中国移动在北京和上海的数据就可以保存两个索引,因为他们是互不相干的。中国移动和中国联通的数据,也是互不相干的。这样一来,一个索引可能就被拆成了几百个索引,在检索之前,就可以根据业务属性迅速确定要查询的所有数据所在的索引。
在此基础之上通过Rollover index来创建索引还要根据总量以及每日增量来确定索引创建的粒度大小。这个大小要结合群节点数量、结构分配及角色分配、分片数量的最优原则来确定。比如如果数据量每天很少,那就把粒度改成周或者月,让索引有一个更加合理的大小,过大过小都不好。至于索引和分片大小数量的优化配置,我的文章里有单独介绍。
举个例子:当我们80%的请求落在近三个月,90%的请求都落在近一个月,那其实我们有必要为近三个月单独创建一个索引或者将数据缓存起来,又或者通过冷热集群的方案来做隔离部署。把更优质的资源分配给更需要性能的业务数据上。
然而问题到此还未真正的彻底解决,因为时序数据是具有流转性特点的。近三个月的数据,其实等过三个月就变成了历史数据,本身部署在 hot phase 的数据就要相应的流转到 warm,也有可能需要对数据压缩或冷冻,以提高数据的存储效率降低成本。
有些数据大概率常年是不需要提供任何查询,但是我们不能丢弃,比如银行的转账记录可能要保存十年甚至三十年。平常这些数据我们是用不到的,对于普通用户一般能查询半年或一年,最多三年的记录。但是这些数据因为有可能涉及犯罪信息,当国家或政府需要银行协助调查的时候,银行有保存义务,需要协助国家调查提供数据。这种事情发生的概率很小但是它的确存在。所以可以通过快照或可搜索快照保存。这样代价就很低。
到这里其实还能继续优化,因为即便索引已经合理拆分,但是如果随着业务的继续膨胀以及时间周期的不断拉长,仍然会面临以上问题,那么解决方案就是我们通过数据流来创建索引的生命周期管理策略,数据流在开源社区或者我的个人博客中有单独的讲解,此处不再赘述。
es的默认配置是一个非常合理的默认配置,绝大多数情况下是不需要修改的,如果不理解某项配置的含义,没有经过验证就贸然修改默认配置,可能造成严重的后果。比如max_result_window这个设置,默认值是1W,这个设置是分页数据每页最大返回的数据量,冒然修改为较大值会导致OOM。ES没有银弹,不可能通过修改某个配置从而大幅提升ES的性能,通常出厂配置里大部分设置已经是最优配置,只有少数和具体的业务相关的设置,事先无法给出最好的默认配置,这些可能是需要我们手动去设置的。关于配置文件,如果你做不到彻底明白配置的含义,不要随意修改。
jvm heap分配:7.x 版本默认1GB,这个值太小,很容易导致OOM。Jvm heap大小不要超过物理内存的50%,最大也不要超过32GB(compressed oop),它可用于其内部缓存的内存就越多,但可供操作系统用于文件系统缓存的内存就越少,heap过大会导致GC时间过长
相同角色的节点,避免使用差异较大的服务器配置,
避免使用“超大杯”服务器(SS:Super Server),比如128核CPU,1 T的内存,2T的固态硬盘。这样可能会产生较大的资源浪费。
等量的配置,使用较少的物理机好于使用较多的虚拟机。比如一个一个五台4核16G的物理机,好于10甚至11台2核8G的虚拟机,这里不仅仅是虚拟机本身可能也会消耗一部分性能的问题,也涉及数据安全的问题。
避免在同一台服务器上部署多个节点,会增加集群管理的难度。
分片产生的目的是为了实现分布式,而分布式的好处之一就是实现“高可用性”(还包括高性能如提高吞吐量等会在后面内容展开讲),分片的分配策略极大程度上都是围绕如何提高可用性而来的,如分片分配感知、强制感知等。
互联网开发没有“银弹”,分片的数量分配也没有适用于所有场景的最佳值,创建分片策略的最佳方法是使用您在生产中看到的相同查询和索引负载在生产硬件上对生产数据进行基准测试。分片的分配策略主要从两个指标来衡量:即数量和单个分片的大小。
根据业务量不同,内存的需求也不同,一般生产建议不要少于16G。ES是比较依赖内存的,并且对内存的消耗也很大,内存对ES的重要性甚至是高于CPU的,所以即使是数据量不大的业务,为了保证服务的稳定性,在满足业务需求的前提下,我们仍需考虑留有不少于20%的冗余性能。一般来说,按照百万级、千万级、亿级数据的索引,我们为每个节点分配的内存为16G/32G/64G就足够了,太大的内存,性价比就不是那么高了。
对于ES来说,磁盘可能是最重要的了,因为数据都是存储在磁盘上的,当然这里说的磁盘指的是磁盘的性能。磁盘性能往往是硬件性能的瓶颈,木桶效应中的最短板。ES应用可能要面临不间断的大量的数据读取和写入。生产环境可以考虑把节点冷热分离,“热节点”使用SSD做存储,可以大幅提高系统性能;冷数据存储在机械硬盘中,降低成本。另外,关于磁盘阵列,可以使用raid 0。
CPU对计算机而言可谓是最重要的硬件,但对于ES来说,可能不是他最依赖的配置,因为提升CPU配置可能不会像提升磁盘或者内存配置带来的性能收益更直接、显著。当然也不是说CPU的性能就不重要,只不过是说,在硬件成本预算一定的前提下,应该把更多的预算花在磁盘以及内存上面。通常来说单节点cpu 4核起步,不同角色的节点对CPU的要求也不同。服务器的CPU不需要太高的单核性能,更多的核心数和线程数意味着更高的并发处理能力。现在PC的配置8核都已经普及了,更不用说服务器了。
ES是天生自带分布式属性的,并且ES的分布式系统是基于对等网络的,节点与节点之间的通信十分的频繁,延迟对于ES的用户体验是致命的,所以对于ES来说,低延迟的网络是非常有必要的。因此,使用扩地域的多个数据中心的方案是非常不可取的,ES可以容忍集群跨多个机房,可以有多个内网环境,支持跨AZ部署,但是不能接受多个机房跨地域构建集群,一旦发生了网络故障,集群可能直接GG,即使能够保证服务正常运行,维护这样(跨地域单个集群)的集群带来的额外成本可能远小于它带来的额外收益。
集群需要多少种配置(内存型/IO型/运算型),每种配置需要多少数量,通常需要和产品运营和运维测试商定,视业务量和服务器的承载能力而定,并留有一定的余量。
一个合理的ES集群配置应不少于5台服务器,避免脑裂时无法选举出新的Master节点的情况,另外可能还需要一些其他的单独的节点,比如ELK系统中的Kibana、Logstash等。
架构层面,非一言两语可详述,推荐阅读:从单机到百万节点:Elasticsearch高可用集群架构部署方案
合理的分配角色和每个节点的配置,在部署集群的时候,应该根据多方面的情况去评估集群需要多大规模去支撑业务。这个是需要根据在你当前的硬件环境下测试数据的写入和搜索性能,然后根据你目前的业务参数来动态评估的,比如:业务数据的总量、每天的增量、查询的并发以及QPS以及峰值的请求量。
节点并非越多越好,会增加主节点的压力
分片并非越多越好,从deep pageing 的角度来说,分片越多,JVM开销越大,负载均衡(协调)节点的转发压力也越大,查询速度也越慢。单个分片也并非越大越好,一般来说单个分片大小控制在30-50GB。
doc_values:正排索引,对于不需要聚合的字段,关闭正排索引可节省资源,提高查询速度
fielddata:可以理解为“runtime_doc_values”,doc_value 为 index time 正排索引。fielddata会消耗JVM空间,如果执行大量数据的聚合使用 fielddata,会造成 OOM
尽量不要使用 dynamic mapping
ignore_above:字段保留的长度,越小越好
调整_source字段,通过include和exclude过滤
store:开辟另一块存储空间,可以节省带宽
注意:_sourse设置为false,则不存储源数据,可以节省磁盘,并且不影响搜索。但是禁用_source必须三思而后行,禁用后将导致以下后果:
高亮失效
reindex失效,原本可以修改的mapping部分参数将无法修改,并且无法升级索引
无法查看元数据和聚合搜索
影响索引的容灾能力
禁用_all字段:_all字段的包含所有字段分词后的Term,作用是可以在搜索时不指定特定字段,从所有字段中检索,ES 6.0之前需要手动关闭
关闭 Norms 字段:计算评分用的,如果你确定当前字段将来不需要计算评分,设置false可以节省大量的磁盘空间,有助于提升性能。常见的比如filter和agg字段,都可以设为关闭。
关闭 index_options(谨慎使用,高端操作):此设置用于在index time过程中哪些内容会被添加到倒排索引的文件中,例如TF,docCount、postion、offsets等,减少option的选项可以减少在创建索引时的CPU占用率,不过在实际场景中很难确定业务是否会用到这些信息,除非是在一开始就非常确定用不到,否则不建议删除
enabled:是否创建倒排索引,对于不需要查询的字段,关闭正排索引可节省资源,提高查询速度。
推荐阅读:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。