大数据概念

大数据(Big Data):指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产

主要解决,海量数据的存储和海量数据的分析计算问题。并且有以下特点。

  • Volune(大量)
    截至目前,人类生的所有印刷材料的数据量是200PB,而历史上全人类总共说过的话的数据量大约是5EB。当前,典型个人计算机硬盘的容量为TB量级,而一些大企业的数据量已经接近EB量级。

  • Velocity(高速)
    这是大数据区分于传统数据挖掘的最显著持征。根据IC的”数字宇宙”的报告,预计到2020年,全球数据使用量将达到3.22B。在如此海量的数据面前,处理数据的效率就是企业的生命。

  • Variety(多样)
    这种类型的多样性也让数据被分为结构化数据和非结构化数据。相对于以往便于存储的以数据库文本为主的结构化数据,非结构化数据越来越多,包括网络日志、音频、视频、图片、地理位置信息等,这些多类型的数据对数据的处理能力提出了更高要求。

  • Value(低价值密度)
    如何快速对有价值数据“提纯”成为目前大数据背景下待解决的难题。

hadoop

概念

  1. Hadoop是一个由Apache基金会所开发的分布式系统基础架构。
  2. 主要解决,海量数据的存储 *和海量数据的分析计算*问题。
  3. 广义上来说,Hadoop通常是指一个更广泛的概念——Hadoop生态圈。

    大数据技术生态体系

Hadoop三大发行版本:ApacheClouderaHortonworks
Apache版本最原始(最基础)的版本,对于入门学习最好。
Cloudera在大型互联网企业中用的较多。
Hortonworks文档较好。

HDFS

产生背景

随着数据量越来越大,在一个操作系统存不下所有的数据,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统。HDFS只是分布式文件管理系统中的一种

定义

HDFS(Hadoop Distributed File System),它是一个文件系统,用于存储文件,通过目录树来定位文件;其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色。
HDFS的使用场景:适合一次写入,多次读出的场景,且不支持文件的修改。适合用来做数据分析,并不适台用来做网盘应用。

优缺点

  • 优点:
    1. 高容错性:
      数据自动保存多个副本。它通过增加副本的形式,提高容错性。
      某一个副本丢失以后,它可以自动恢复。
    2. 适合处理大数据:
      数据规模:能够处理数据规模达到GB、TB、甚至PB级别的数据。
      文件规模:能够处理百万规模以上的文件数量,数量相当之大。
    3. 可构建在廉价机器上
      通过多副本机制,提高可靠性。
  • 缺点:
    1. 不适合低延时数据访问,比如毫秒级的存储数据,是做不到的。
    2. 无法高效的对大量小文件进行存储。
      存储大量小文件的话,它会占用NameNode大量的内存来存储文件目录和块信息。这样是不可取的,因为NameNode的内存总是有限的。
      小文件存储的寻址时间会超过读取时间,它违反了HDFS的设计目标。
      为什么块的大小不能设置太小,也不能太大?
      HDFS的块设置太小,会增加寻址时间,程序一直在找块的开始位置。
      如果块设置的太大,从磁盘传输数据的时间会明显大于定位这个块开始位置所需的时间。导致程序在处理这块数据时,会非常慢。
      总结:HDFS块的大小设置主要取决于磁盘传输速率。
    3. 不支持并发写入、文件随机修改。
      一个文件只能有一个写,不允许多个线程同时写;
      仅支持数据append(追加),不支持文件的随机修改。

HDFS架构概述

  1. NameNode(nn):
    它是个主管、管理者
    存储文件的元数据,如文件名,文件目录结构,文件属性(生成时间、副本数、文件权限)。
    配置副本策略。
    处理客户端读写请求。
    每个文件的块列表和块所在的DataNode等。
  2. DataNode(dn):
    在本地文件系统存储文件块发据。
    存储实际的数据块。
    执行数据块的读/写操作。
    块数据的校验和。
  3. Secondary NameNo de(2nn):
    用来监控HDFS状态的辅助后台程序,每隔一段时间获取HDFS元数据的快照。
    并非NarneNode的热备。当NameNode挂掉的时候,它并不能马上替换NameNode并提供服务。
    辅助NameNode,分担其I作量,比如定期合并F simageE dits,并推送给NameNode。
    在紧急情况下,可辅助恢复NameNode。
  4. Client:就是客户端。
    文件切分。文件上传HDFS的时候,Client将文件切分 成一个一个的Block,然后进行上传;
    与NameNode交互,获取文件的位置信息;
    与DataNode交互,读取或者写入数据;
    Clien提供一些命令 来管理HDFS,比如lameNode格式化;
    Client可以通过一些命令来访问HDFS,比如对HDFS增删直改操作;

文件的写入

  1. 客户端通过Distributed FileSystem模块向NameNode请求上传文件,NameNode检查目标文件是否已存在,父目录是否存在。
  2. NameNode返回是否可以上传。
  3. 客户端请求第一个 Block上传到哪几个DataNode服务器上。
  4. NameNode返回3个DataNode节点,分别为dn1、dn2、dn3。
  5. 客户端通过FSDataOutputStream模块请求dn1上传数据,dn1收到请求会继续调用dn2,然后dn2调用dn3,将这个通信管道建立完成。
  6. dn1、dn2、dn3逐级应答客户端。
  7. 客户端开始往dn1上传第一个Block(先从磁盘读取数据放到一个本地内存缓存),以Packet为单位,dn1收到一个Packet就会传给dn2,dn2传给dn3;dn1每传一个packet会放入一个应答队列等待应答。
  8. 当一个Block传输完成之后,客户端再次请求NameNode上传第二个Block的服务器。(重复执行3-7步)。

节点距离计算

两个节点到达最近的共同祖先的距离总和。
在HDFS写数据的过程中,NameNode会选择距离待上传数据最近距离的DataNode接收数据
例如,假设有数据中心d1机架r1中的节点n1。该节点可以表示为/d1/r1/n1。利用这种标记,这里给出四种距离描述,如图

机架感知(副本存储节点选择)

官方文档:http://hadoop.apache.org/docs/r2.7.2/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html#Data_Replication
第一个副本在Client所处的节点上。
如果客户端在集群外,随机选一个。
第二个副本和第一个副本位于相同机架,随机节点。
第三个副本位于不同机架,随机节点。

YARN架构概述

MapReduce概述

MapReduce是一个分布式运算程序的编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架。
MapReduce核心功能是将用户编写的业务逻辑代码自带默认组件整合成一个完整的分布式运算程序,并发运行在一个Hadoop集群上。

优缺点

  • 优点
    1. 易于编程:
      它简单的实现一些接口,就可以完成一个分布式程序,这个分布式程序可以分布到大量廉价的PC机器上运行。
    2. 良好的扩展性:
      当你的计算资源不能得到满足的时候,你可以通过简单的增加机器来扩展它的计算能力。
    3. 高容错性:
      MapReduce设计的初衷就是使程序能够部署在廉价的PC机器上,这就要求它具有很高的容错性。比如其中一台机器挂了,它可以把上面的计算任务转移到另外一个节点上运行,不至于这个任务运行失败,而且这个过程不需要人工参与,而完全是由Hadoop内部完成的。
    4. 适合PB级以上海量数据的离线处理:
      可以实现上千台服务器集群并发工作,提供数据处理能力。
  • 缺点
    1. 不擅长实时计算
      无法像MySQL一样,在毫秒或者秒级内返回结果。
    2. 不擅长流式计算
      流式计算的输入数据是动态的,而MapReduce的输入数据集是静态的,不能动态变化。这是因为MapReduce自身的设计特点决定了数据源必须是静态的。
    3. 不擅长DAG(有向图)计算
      多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。在这种情况下,MapReduce并不是不能做,而是使用后,每个MapReduce作业的输出结果都会写入到磁盘,会造成大量的磁盘IO,导致性能非常的低下。

核心思想

  1. 分布式的运算程序往往需要分成至少2个阶段。
  2. 第一个阶段的MapTask并发实例,完全并行运行,互不相干。
  3. 第二个阶段的ReduceTask并发实例互不相干,但是他们的数据依赖于上一个阶段的所有MapTask并发实例的输出。
  4. MapReduce编程模型只能包含一个Map阶段和一个Reduce阶段,如果用户的业务逻辑非常复杂,那就只能多个MapReduce程序,串行运行。
    总结:分析WordCount数据流走向深入理解MapReduce核心思想。

MapReduce架构流程

  1. Map阶段并行处理输入数据
  2. Reduce阶段对Map结果进行汇总

zookeeper

概述

  • Zookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。

  • 特点

    1. Zookeeper:一个领导者(Leader),多个跟随者(Follower)组成的集群。
    2. 集群中只要有半数以上首点存活,Zookeeper集群就能正常服务。
    3. 全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的。
    4. 更新请求顺序进行,来自同一个Client的更新请求按其发送顺序依次执行。
    5. 数据更新原子性,一次数据更新要么成功,要么失败。
    6. 实时性,在一定时间范围内,Client能读到最新数据。
  • 结构
    ZooKeeper效据模型的结构与Unix文件系统很类似,整体上可以看作是一棵树,每个节点称做一个ZNode,每一个ZNode默认能够存储IMB的数据,每个ZNode都可以通过其路径唯一标识。

工作原理

节点类型

Stat结构体

  1. czxid-创建节点的事务zxid
    每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。
    事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前发生。
  2. ctime - znode被创建的毫秒数(从1970年开始)
  3. mzxid - znode最后更新的事务zxid
  4. mtime - znode最后修改的毫秒数(从1970年开始)
  5. pZxid-znode最后更新的子节点zxid
  6. cversion - znode子节点变化号,znode子节点修改次数
  7. dataversion - znode数据变化号
  8. aclVersion - znode访问控制列表的变化号
  9. ephemeralOwner- 如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0。
  10. dataLength- znode的数据长度
  11. numChildren - znode子节点数量

监听器原理

选举机制

  1. 半数机制:集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器。
  2. Zookeeper虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的。
  3. 以一个简单的例子来说明整个选举的过程。
    假设有五台服务器组成的Zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动,来看看会发生什么(如图)
    • 服务器1启动,发起一次选举。服务器1投自己一票。此时服务器1票数一票,不够半数以上(3票),选举无法完成,服务器1状态保持为LOOKING;
    • 服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交换选票信息:此时服务器1发现服务器2的ID比自己目前投票推举的(服务器1)大,更改选票为推举服务器2。此时服务器1票数0票,服务器2票数2票,没有半数以上结果,选举无法完成,服务器1,2状态保持LOOKING
    • 服务器3启动,发起一次选举。此时服务器1和2都会更改选票为服务器3。此次投票结果:服务器1为0票,服务器2为0票,服务器3为3票。此时服务器3的票数已经超过半数,服务器3当选Leader。服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING;
    • 服务器4启动,发起一次选举。此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器3为3票,服务器4为1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为FOLLOWING;
    • 服务器5启动,同4一样当小弟。

写数据流程

kafka

概述

在流式计算中,Kafka一般用来缓存数据,Storm通过消费Kafka的数据进行计算。

什么是Kafka

  1. Apache Kafka是一个开源消息系统,由Scala写成。是由Apache软件基金会开发的一个开源消息系统项目。
  2. Kafka最初是由LinkedIn公司开发,并于2011年初开源。2012年10月从Apache Incubator毕业。该项目的目标是为处理实时数据提供一个统一、高通量、低等待的平台。
  3. Kafka是一个分布式消息队列。Kafka对消息保存时根据Topic进行归类,发送消息者称为Producer,消息接受者称为Consumer,此外kafka集群有多个kafka实例组成,每个实例(server)称为broker。
  4. 无论是kafka集群,还是consumer都依赖于zookeeper集群保存一些meta信息,来保证系统可用性。

Kafka架构

详细版
精简版

  1. Producer :消息生产者,就是向kafka broker发消息的客户端;
  2. Consumer :消息消费者,向kafka broker取消息的客户端;
  3. Topic :可以理解为一个队列;
  4. Consumer Group (CG):这是kafka用来实现一个topic消息的广播(发给所有的consumer)和单播(发给任意一个consumer)的手段。一个topic可以有多个CG。topic的消息会复制(不是真的复制,是概念上的)到所有的CG,但每个partion只会把消息发给该CG中的一个consumer。如果需要实现广播,只要每个consumer有一个独立的CG就可以了。要实现单播只要所有的consumer在同一个CG。用CG还可以将consumer进行自由的分组而不需要多次发送消息到不同的topic;
  5. Broker :一台kafka服务器就是一个broker。一个集群由多个broker组成。一个broker可以容纳多个topic;
  6. Partition:为了实现扩展性,一个非常大的topic可以分布到多个broker(即服务器)上,一个topic可以分为多个partition,每个partition是一个有序的队列。partition中的每条消息都会被分配一个有序的id(offset)。kafka只保证按一个partition中的顺序将消息发给consumer,不保证一个topic的整体(多个partition间)的顺序;
  7. Offset:kafka的存储文件都是按照offset.kafka来命名,用offset做名字的好处是方便查找。例如你想找位于2049的位置,只要找到2048.kafka的文件即可。当然the first offset就是00000000000.kafka。

消息队列

  1. 点对点模式(一对一,消费者主动拉取数据,消息收到后消息清除)
    点对点模型通常是一个基于拉取或者轮询的消息传送模型,这种模型从队列中请求信息,而不是将消息推送到客户端。这个模型的特点是发送到队列的消息被一个且只有一个接收者接收处理,即使有多个消息监听者也是如此。
  2. 发布/订阅模式(一对多,数据生产后,推送给所有订阅者)
    发布订阅模型则是一个基于推送的消息传送模型。发布订阅模型可以有多种不同的订阅者,临时订阅者只在主动监听主题时才接收消息,而持久订阅者则监听主题的所有消息,即使当前订阅者不可用,处于离线状态。
  • 为什么需要消息队列
    1. 解耦:
        允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
    2. 冗余:消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。
    3. 扩展性:
      因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。
    4. 灵活性 & 峰值处理能力:
      在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
    5. 可恢复性:
      系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
    6. 顺序保证:
      在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。(Kafka保证一个Partition内的消息的有序性)
    7. 缓冲:
      有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
    8. 异步通信:
      很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。

生产过程分析以及原理

写入方式

producer采用推(push)模式将消息发布到broker,每条消息都被追加(append)到分区(patition)中,属于顺序写磁盘(顺序写磁盘效率比随机写内存要高,保障kafka吞吐率)。

分区(Partition)

消息发送时都被发送到一个topic,其本质就是一个目录,而topic是由一些Partition Logs(分区日志)组成,其组织结构如下图所示:

每个Partition中的消息都是有序的,生产的消息被不断追加到Partition log上,其中的每一个消息都被赋予了一个唯一的offset值。

  • 分区的原因
    1. 方便在集群中扩展,每个Partition可以通过调整以适应它所在的机器,而一个topic又可以有多个Partition组成,因此整个集群就可以适应任意大小的数据了;
    2. 可以提高并发,因为可以以Partition为单位读写了。
  • 分区的原则
    1. 指定了patition,则直接使用;
    2. 未指定patition但指定key,通过对key的value进行hash出一个patition;
    3. patition和key都未指定,使用轮询选出一个patition。

副本(Replication)

同一个partition可能会有多个replication(对应 server.properties 配置中的 default.replication.factor=N)。没有replication的情况下,一旦broker 宕机,其上所有 patition 的数据都不可被消费,同时producer也不能再将数据存于其上的patition。引入replication之后,同一个partition可能会有多个replication,而这时需要在这些replication之间选出一个leader,producer和consumer只与这个leader交互,其它replication作为follower从leader 中复制数据。

写入流程

  1. producer先从zookeeper的 “/brokers/…/state”节点找到该partition的leader
  2. producer将消息发送给该leader
  3. leader将消息写入本地log
  4. followers从leader pull消息,写入本地log后向leader发送ACK
  5. leader收到所有ISR中的replication的ACK后,增加HW(high watermark,最后commit 的offset)并向producer发送ACK

HBase

HBase是一种分布式、可扩展、支持海量数据存储的 NoSQL数据库
官方文档:http://hbase.apache.org/book.html#_preface

数据模型

逻辑上,HBase 的数据模型同关系型数据库很类似,数据存储在一张表中,有行有列。
但从 HBase 的底层物理存储结构(K-V)来看,HBase 更像是一个 multi-dimensional map。

  1. Name Space
    命名空间,类似于关系型数据库的 DatabBase 概念,每个命名空间下有多个表。HBase
    有两个自带的命名空间,分别是 hbase 和 default,hbase 中存放的是 HBase 内置的表,
    default表是用户默认使用的命名空间。
  2. Region
    类似于关系型数据库的表概念。不同的是,HBase定义表时只需要声明列族即可,不需
    要声明具体的列。这意味着,往HBase写入数据时,字段可以动态、按需指定。因此,和关
    系型数据库相比,HBase能够轻松应对字段变更的场景。
  3. Row
    HBase表中的每行数据都由一个RowKey 和多个Column(列)组成,数据是按照 RowKey
    的字典顺序存储的,并且查询数据时只能根据 RowKey 进行检索,所以RowKey的设计十分重
    要。
  4. Column
    HBase中的每个列都由 Column Family(列族)和 Column Qualifier(列限定符)进行限
    定,例如info:name,info:age。建表时,只需指明列族,而列限定符无需预先定义。
  5. Time Stamp
    用于标识数据的不同版本(version),每条数据写入时,如果不指定时间戳,系统会
    自动为其加上该字段,其值为写入 HBase的时间。
  6. Cell
    由{rowkey, column Family:column Qualifier, time Stamp} 唯一确定的单元。cell 中的数
    据是没有类型的,全部是字节码形式存贮。

架构角色

基本架构

  • Region Server
    Region Server为 Region的管理者,其实现类为 HRegionServer,主要作用如下:
    对于数据的操作:get, put, delete;
    对于 Region的操作:splitRegion、compactRegion。
  • Master
    Master是所有Region Server的管理者,其实现类为 HMaster,主要作用如下:
    对于表的操作:create, delete, alter
    对于RegionServer的操作:分配regions到每个RegionServer,监控每个RegionServer
    的状态,负载均衡和故障转移。
  • Zookeeper
    HBase 通过Zookeeper 来做 Master 的高可用、RegionServer 的监控、元数据的入口以及
    集群配置的维护等工作。
  • HDFS
    HDFS 为HBase 提供最终的底层数据存储服务,同时为HBase 提供高可用的支持。

架构原理

  • StoreFile
    保存实际数据的物理文件,StoreFile 以 HFile 的形式存储在 HDFS 上。每个 Store 会有
    一个或多个 StoreFile(HFile),数据在每个 StoreFile 中都是有序的。
  • MemStore
    写缓存,由于HFile 中的数据要求是有序的,所以数据是先存储在MemStore 中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile。
  • WAL
    由于数据要经 MemStore 排序后才能刷写到 HFile,但把数据保存在内存中会有很高的 概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做 Write-Ahead logfile 的文件 中,然后再写入MemStore中。所以在系统出现故障的时候,数据可以通过这个日志文件重
    建。

写流程

  • Client 先访问 zookeeper,获取 hbase:meta 表位于哪个 Region Server。
  • 访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey, 查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。
  • 与目标 Region Server 进行通讯;
  • 将数据顺序写入(追加)到WAL;
  • 将数据写入对应的 MemStore,数据会在 MemStore 进行排序;
  • 向客户端发送 ack;
  • 等达到 MemStore 的刷写时机后,将数据刷写到 HFile。

读流程

  • Client 先访问 zookeeper,获取 hbase:meta 表位于哪个 Region Server。
  • 访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey, 查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以
    及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。
  • 与目标 Region Server 进行通讯;
  • 分别在 Block Cache(读缓存),MemStore 和 Store File(HFile)中查询目标数据,并将
    查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不
    同的类型(Put/Delete)。
  • 将从文件中查询到的数据块(Block,HFile数据存储单元,默认大小为64KB)缓存到
    Block Cache。
  • 将合并后的最终结果返回给客户端。

Phoenix

Phoenix 是 HBaseSQL Apache顶级开源项目
社区公认的HBase上最合适的SQL层,支持毫秒级到秒级的低延时OLTP和操作型分析查询Phoenix核心能力

SQL引擎层
支持标准SQL 92,转为SQL为HBase API
算子、过滤条件下推到Server端,并行执行
轻量级事务、二级索引、动态列、分页查询等多种SQL层能力
JDBC Driver Metadata管理
集成Spark.Hive.Pig.FlumeMapReduce

架构

实现原理

Phoenix的SQL实现原理主要也是基于一系列的Scan操作来完成,Scan是HBase的批量扫描过程。这一系列的Scan操作也是分散到各台RegionServer上通过Coprocessor来完成。主要用到的是RegionObserver,通过RegionObserver在postScannerOpen Hook中将RegionScanner替换成支持聚合操作的定制化Scanner,在真正执行聚合时,会通过自定的Scan属性传递给RegionScanner,在这个Scan中也可加入一些过滤规则,尽量减少返回Client的结果。