从零开始大数据

之前较少涉及的,但对于一个成熟的应用,其推荐系统,用户画像乃至金融、智慧城市、医疗等等领域都离不开大数据。
大数据不再仅仅指代“巨大的数据量”,而是一套通过采集、存储、处理和分析,从海量的、复杂的、非结构化的数据中获取洞察力和价值的技术体系。
通常用 5V 特征来描述它:大量(Volume)、高速(Velocity)、多样(Variety)、低价值密度(Value)和真实性(Veracity)。

大数据平台本质上就是对海量数据从采集、存储、计算、应用、管理、运维的多方位、多维度的组合研究设计,从而建设合理、高效的大数据平台架构。

常说的大数据技术,其实起源于Google在2004年前后发表的三篇论文,也就是我们经常听到的“三驾马车”,分别是分布式文件系统GFS大数据分布式计算框架MapReduceNoSQL数据库系统BigTable

搜索引擎主要就做两件事情,一个是网页抓取,一个是索引构建,而在这个过程中,有大量的数据需要存储和计算。Google的思路是部署一个大规模的服务器集群,通过分布式的方式将海量数据存储在这个集群上,然后利用集群上的所有机器进行数据计算。 这样,Google其实不需要买很多很贵的服务器,它只要把这些普通的机器组织到一起,就非常厉害了。

Hadoop的诞生

Lucene开源项目的创始人Doug Cutting正在开发开源搜索引擎Nutch,阅读了Google的论文后,他非常兴奋,紧接着就根据论文原理初步实现了类似GFS和MapReduce的功能。

两年后的2006年,Doug Cutting将这些大数据相关的功能从Nutch中分离了出来,然后启动了一个独立的项目专门开发维护大数据技术,这就是后来赫赫有名的Hadoop,主要包括Hadoop分布式文件系统HDFS和大数据计算引擎MapReduce。

MapReduce操作

MapReduce 是由 Google 提出的一种分布式计算编程模型,主要用于处理大规模数据集。它的核心思想是“分而治之”:将一个巨大的任务拆分成很多个小任务,分发到多台服务器上并行处理,最后再汇总结果。

名字由两个核心操作组成:

  • Map(映射):把一组数据转换成另一组中间数据(通常是键值对 Key-Value)。
  • Reduce(归约):对中间数据进行汇总、过滤或合并。

以最经典的“词频统计(Word Count)”为例,流程分为五个阶段:

  1. Input Splitting(输入分片):

    将原始文件切分成固定大小的块(Split),分发给不同的计算节点。

  2. Mapping(映射阶段):

    每个节点读取自己分到的块,解析成 $\langle Key, Value \rangle$ 形式。例如:看到 “Apple” 就记为 $\langle Apple, 1 \rangle$。

  3. Shuffling & Sorting(混洗与排序 - 核心环节):

    系统将所有节点产出的相同 Key 的数据收集到一起。例如把所有的 $\langle Apple, 1 \rangle$ 都发往同一个 Reduce 节点,并按 Key 排序。

  4. Reducing(归约阶段):

    对相同 Key 的一组数据进行逻辑处理。例如:把一堆 $\langle Apple, 1 \rangle$ 加起来,得到 $ \langle Apple, 3 \rangle $。

  5. Output(输出结果):

    将最终结果写入分布式存储系统(如 HDFS)

将 Shuffle 分为两个部分:Shuffle WriteShuffle Read

第一部分:在 Map 端(Shuffle Write)

Shuffle 的准备工作是在 Map 任务结束前做的。

  1. 数据分区(Partitioning):Map 处理完数据后,根据 Key 计算它该去哪个 Reduce 节点。
  2. 溢写(Spilling):数据先写入内存缓冲区,当缓冲区快满时,在内存中进行排序(Sort),然后写入本地磁盘存为临时文件。
  3. 合并(Merge):将多个小的溢写文件合并成一个大的分区文件。

第二部分:在 Reduce 端(Shuffle Read)

真正的“混洗”动作(数据搬运)发生在 Reduce 任务启动时

  1. 拉取(Fetching/Copying):Reduce 任务通过 HTTP 请求,从各个远程 Map 节点的磁盘上拉取属于自己的那部分数据。
  2. 合并与排序(Merge Sort):由于数据来自多个 Map,Reduce 端的内存会将拉取到的数据再次进行归并排序。

在 Shuffle 的过程中,伴随着多次排序操作。Sort 阶段并不是为了让最终结果有序,而是为了辅助聚合。如果数据是有序的(例如:所有“苹果”都在一起,接着是所有“香蕉”),Reduce 只需要扫描一遍(线性扫描)就能完成统计,而不需要在内存中开辟巨大的哈希表。

用MapReduce进行大数据编程太麻烦了,于是便开发了Pig。Pig是一种脚本语言,使用类SQL的语法,开发者可以用Pig脚本描述要对大数据集上进行的操作,Pig经过编译后会生成MapReduce程序,然后在Hadoop上运行。Hive支持使用SQL语法来进行大数据计算,比如说你可以写个Select语句进行数据查询,然后Hive会把SQL语句转化成MapReduce的计算程序。这样,熟悉数据库的数据分析师和工程师便可以无门槛地使用大数据进行数据分析和处理了。

随后,众多Hadoop周边产品开始出现,大数据生态体系逐渐形成,其中包括:专门将关系数据库中的数据导入导出到Hadoop平台的Sqoop;针对大规模日志进行分布式收集、聚合和传输的Flume;MapReduce工作流调度引擎Oozie等。

在Hadoop早期,MapReduce既是一个执行引擎,又是一个资源调度框架,服务器集群的资源调度管理由MapReduce自己完成。但是这样不利于资源复用,也使得MapReduce非常臃肿。于是一个新项目启动了,将MapReduce执行引擎和资源调度分离开来,这就是Yarn。2012年,Yarn成为一个独立的项目开始运营,随后被各类大数据产品支持,成为大数据平台上最主流的资源调度系统

image-20260110165833797

Hadoop会等数据到齐在shuffle&sort阶段存入磁盘后,再通过reduce聚合

Spark的诞生

使用MapReduce进行机器学习计算的时候性能非常差,因为机器学习算法通常需要进行很多次的迭代计算,而MapReduce每执行一次Map和Reduce计算都需要重新启动一次作业,带来大量的无谓消耗。还有一点就是MapReduce主要使用磁盘作为存储介质,而2012年的时候,内存已经突破容量和成本限制,成为数据运行过程中主要的存储介质.Spark一经推出,立即受到业界的追捧,并逐步替代MapReduce在企业应用中的地位。

MapReduce 的流程是线性的、固定的(Map -> Reduce),而 Spark 的流程是基于 DAG(有向无环图)的动态优化

Spark 意识到 MapReduce 这种“频繁写盘”的 Shuffle 太慢了,于是做了改进:

  1. Shuffle Write & Read:Spark 尽可能在内存中处理 Shuffle 数据。只有当内存实在放不下时,才会溢写磁盘。
  2. 不强制排序:MapReduce 要求 Reduce 接收到的数据必须是有序的。但 Spark 认为,如果你只是做求和(Reduce),其实不需要全局排序。Spark 允许使用 Bypass Merge SortTungsten 优化,跳过不必要的排序步骤,极大地提升了速度。

程序员之前必须写复杂的代码(RDD 算子)来处理数据。Spark SQL 的出现带来了三个核心优势:

  • 降低门槛:懂 SQL 的数据分析师可以直接上手,不需要精通 Java 或 Scala。
  • 性能优化:它内置了一个极其聪明的“大脑”——Catalyst 优化器,能自动帮你寻找最快的计算路径。
  • 统一接入:它可以同时读取 JSON、Parquet、CSV、Hive、MySQL 等各种来源的数据,并把它们当成统一的“表”来操作。

Apache Flink 是一个开源的分布式流处理引擎。它的核心定位是:在任何规模下,处理无界(实时)和有界(离线)数据流。

Flink 认为一切数据都是以“流”的形式存在的。它把数据分为两类:

  • 无界数据流 (Unbounded Streams):有始无终。比如用户的点击日志、传感器的实时温度。这种数据必须实时处理,否则就失去了价值。
  • 有界数据流 (Bounded Streams):有始有终。比如过去一年的销售账单。这类数据可以“批处理”,但 Flink 认为批处理只是流处理的一个特例。

Flink 的运行主要依靠两个核心组件:

  1. JobManager (大脑)
    • 接收任务,编排计算流程。
    • 决定什么时候制作“快照”(Checkpoint)。
    • 在某个节点挂掉时,指挥大家从上一个状态恢复。
  2. TaskManager (肌肉)
    • 实际干活的节点。
    • 数据在这里流动、计算、存储。
维度Hadoop (MapReduce)Spark (Micro-batch)Flink (Native Stream)
计算时机数据全部到齐并存入硬盘后。等待微小的时间窗口(如 1s)数据到齐。数据产生的瞬间。
任务开销每次处理都要重新启动进程、申请资源。每次微批都要进行任务调度和分发。任务长驻执行,没有启动开销。
中间结果写入硬盘(慢)。存在内存,但需要 Shuffle 排序(较慢)。内存增量计算,直接流转(极快)。
端到端延迟小时 / 分钟级。秒级(通常 500ms - 2s)。毫秒级(< 10ms)。

离线批处理与实时流式处理

一般说来,像MapReduce、Spark这类计算框架处理的业务场景都被称作批处理计算,因为它们通常针对以“天”为单位产生的数据进行一次计算,然后得到需要的结果,这中间计算需要花费的时间大概是几十分钟甚至更长的时间。因为计算的数据是非在线得到的实时数据,而是历史数据,所以这类计算也被称为大数据离线计算

在大数据领域,还有另外一类应用场景,它们需要对实时产生的大量数据进行即时计算,比如对于遍布城市的监控摄像头进行人脸识别和嫌犯追踪。这类计算称为大数据流计算,相应地,有Storm、Flink、Spark Streaming等流计算框架来满足此类大数据应用的场景。 流式计算要处理的数据是实时在线产生的数据,所以这类计算也被称为大数据实时计算

在典型的大数据的业务场景下,数据业务最通用的做法是,采用批处理的技术处理历史全量数据,采用流式计算处理实时新增数据。而像Flink这样的计算引擎,可以同时支持流式计算和批处理计算

除了大数据批处理和流处理,NoSQL系统处理的主要也是大规模海量数据的存储与访问,所以也被归为大数据技术。 NoSQL曾经在2011年左右非常火爆,涌现出HBase、Cassandra等许多优秀的产品,其中HBase是从Hadoop中分离出来的、基于HDFS的NoSQL系统。

大数据应用发展

搜索引擎

Google也是我们公认的大数据鼻祖,它存储着全世界几乎所有可访问的网页,数目可能超过万亿规模,全部存储起来大约需要数万块磁盘。为了将这些文件存储起来,Google开发了GFS(Google文件系统),将数千台服务器上的数万块磁盘统一管理起来,然后当作一个文件系统,统一存储所有这些网页文件

Google得到这些网页文件是要构建搜索引擎,需要对所有文件中的单词进行词频统计,然后根据PageRank算法计算网页排名。这中间,Google需要对这数万块磁盘上的文件进行计算处理。当然,也正是基于这些需求,Google又开发了MapReduce大数据计算框架。

数据仓库

曾经我们在进行数据分析与统计时,仅仅局限于数据库,在数据库的计算环境中对数据库中的数据表进行统计分析。并且受数据量和计算能力的限制,我们只能对最重要的数据进行统计和分析。这里所谓最重要的数据,通常指的都是给老板看的数据和财务相关的数据。

而Hive可以在Hadoop上进行SQL操作,实现数据统计与分析。也就是说,我们可以用更低廉的价格获得比以往多得多的数据存储与计算能力。我们可以把运行日志、应用采集数据、数据库数据放到一起进行计算分析,获得以前无法得到的数据结果,企业的数据仓库也随之呈指数级膨胀

此外还有数据挖掘和机器学习

移动计算而不是移动数据

传统的软件计算处理模型,都是“输入 -> 计算 -> 输出”模型。也就是说,一个程序给它传入一些数据也好,它自己从某个地方读取一些数据也好,总是先有一些输入数据,然后对这些数据进行计算处理,最后得到输出结果。

但是在互联网大数据时代,需要计算处理的数据量急速膨胀。一来是因为互联网用户数远远超过传统企业的用户,相应产生了更大量的数据;二来很多以往被忽视的数据重新被发掘利用,比如用户在一个页面的停留时长、鼠标在屏幕移动的轨迹都会被记录下来进行分析.

正因为如此,传统的计算处理模型不能适用于大数据时代的计算要求。如何解决PB级数据进行计算的问题呢?

问题的解决思路其实跟大型网站的分布式架构思路是一样的,采用分布式集群的解决方案,用数千台甚至上万台计算机构建一个大数据计算处理集群,利用更多的网络带宽、内存空间、磁盘容量、CPU核心数去进行计算处理。

网站实时处理通常针对单个用户的请求操作,虽然大型网站面临大量的高并发请求,比如天猫的“双十一”活动。但是每个用户之间的请求是独立的,只要网站的分布式系统能将不同用户的不同业务请求分配到不同的服务器上,只要这些分布式的服务器之间耦合关系足够小,就可以通过添加更多的服务器去处理更多的用户请求及由此产生的用户数据。这也正是网站系统架构的核心原理。

大数据计算处理通常针对的是网站的存量数据,也就是刚才我提到的全部用户在一段时间内请求产生的数据,这些数据之间是有大量关联的,比如购买同一个商品用户之间的关系,这是使用协同过滤进行商品推荐;比如同一件商品的历史销量走势,这是对历史数据进行统计分析。网站大数据系统要做的就是将这些统计规律和关联关系计算出来,并由此进一步改善网站的用户体验和运营决策

为了解决这种计算场景的问题,技术专家们设计了一套相应的技术架构方案。最早的时候由Google实现并通过论文的方式发表出来,随后根据这些论文,开源社区开发出对应的开源产品,并得到业界的普遍支持和应用。这段历史我们在前面的“预习”中已经讨论过了。

这套方案的核心思路是,既然数据是庞大的,而程序要比数据小得多,将数据输入给程序是不划算的,那么就反其道而行之,将程序分发到数据所在的地方进行计算,也就是所谓的移动计算比移动数据更划算

两台计算机要想合作构成一个系统,必须要在技术上重新架构。这就是现在互联网企业广泛使用的负载均衡、分布式缓存、分布式数据库、分布式服务等种种分布式系统。

当这些分布式技术满足互联网的日常业务需求时,对离线数据和存量数据的处理就被提了出来,当时这些分布式技术并不能满足要求,于是大数据技术就出现了。

移动计算程序到数据所在位置进行计算是如何实现的呢?

1.将待处理的大规模数据存储在服务器集群的所有服务器上,主要使用HDFS分布式文件存储系统,将文件分成很多块(Block),以块为单位存储在集群的服务器上。

2.大数据引擎根据集群里不同服务器的计算能力,在每台服务器上启动若干分布式任务执行进程,这些进程会等待给它们分配执行任务。

3.使用大数据计算框架支持的编程模型进行编程,比如Hadoop的MapReduce编程模型,或者Spark的RDD编程模型。应用程序编写好以后,将其打包,MapReduce和Spark都是在JVM环境中运行,所以打包出来的是一个Java的JAR包。

4.用Hadoop或者Spark的启动命令执行这个应用程序的JAR包,首先执行引擎会解析程序要处理的数据输入路径,根据输入数据量的大小,将数据分成若干片(Split),每一个数据片都分配给一个任务执行进程去处理

5.任务执行进程收到分配的任务后,检查自己是否有任务对应的程序包,如果没有就去下载程序包,下载以后通过反射的方式加载程序。走到这里,最重要的一步,也就是移动计算就完成了。

6.加载程序后,任务执行进程根据分配的数据片的文件地址和数据在文件内的偏移量读取数据,并把数据输入给应用程序相应的方法去执行,从而实现在分布式服务器集群中移动计算程序,对大规模数据进行并行处理的计算目标

HDFS优点

  1. 高可用 副本机制

  2. 适合超大规模数据集

  3. 流式数据访问
  4. 移动计算而非移动数据 HDFS 配合 MapReduce,将计算代码发往数据所在的服务器进行处理。“数据不动,代码动”,这极大地减少了网络带宽的压力。

垂直伸缩和水平伸缩

如果一个文件的大小超过了一张磁盘的大小,你该如何存储?

单机时代,主要的解决方案是RAID;分布式时代,主要解决方案是分布式文件系统。大规模数据存储都需要解决几个核心问题,主要有以下三个方面。

1.数据存储容量的问题。既然大数据要解决的是数以PB计的数据计算问题,而一般的服务器磁盘容量通常1~2TB,那么如何存储这么大规模的数据呢?

2.数据读写速度的问题。一般磁盘的连续读写速度为几十MB,以这样的速度,几十PB的数据恐怕要读写到天荒地老。

3.数据可靠性的问题。磁盘大约是计算机设备中最易损坏的硬件了,通常情况一块磁盘使用寿命大概是一年,如果磁盘损坏了,数据怎么办?

在大数据之前,对应的解决方案就是RAID技术。将多块普通磁盘组成一个阵列,共同对外提供服务。主要是为了改善磁盘的存储容量、读写速度,增强磁盘的可用性和容错能力。在RAID之前,要使用大容量、高可用、高速访问的存储系统需要专门的存储设备,这类设备价格要比RAID的几块普通磁盘贵几十倍。

RAID技术包含RAID0,RAID1,RAID10,RAID5以及RAID6.

首先,先假设服务器有N块磁盘,RAID 0是数据在从内存缓冲区写入磁盘时,根据磁盘数量将数据分成N份,这些数据同时并发写入N块磁盘,使得数据整体写入速度是一块磁盘的N倍;读取的时候也一样,因此RAID 0具有极快的数据读写速度。但是RAID 0不做数据备份,N块磁盘中只要有一块损坏,数据完整性就被破坏,其他磁盘的数据也都无法使用了。

RAID 1是数据在写入磁盘时,将一份数据同时写入两块磁盘,这样任何一块磁盘损坏都不会导致数据丢失,插入一块新磁盘就可以通过复制数据的方式自动修复,具有极高的可靠性。

结合RAID 0和RAID 1两种方案构成了RAID 10,它是将所有磁盘N平均分成两份,数据同时在两份磁盘写入,相当于RAID 1;但是平分成两份,在每一份磁盘(也就是N/2块磁盘)里面,利用RAID 0技术并发读写,这样既提高可靠性又改善性能。不过RAID 10的磁盘利用率较低,有一半的磁盘用来写备份数据。

RAID 3可以在数据写入磁盘的时候,将数据分成N-1份,并发写入N-1块磁盘,并在第N块磁盘记录校验数据,这样任何一块磁盘损坏(包括校验数据磁盘),都可以利用其他N-1块磁盘的数据修复.任何磁盘数据的修改,都会导致第N块磁盘重写校验数据。频繁写入的后果是第N块磁盘比其他磁盘更容易损坏,需要频繁更换,所以RAID 3很少在实践中使用,因此在上面图中也就没有单独列出。

RAID 5是使用更多的方案。RAID 5和RAID 3很相似,但是校验数据不是写入第N块磁盘,而是螺旋式地写入所有磁盘中。这样校验数据的修改也被平均到所有磁盘上,避免RAID 3频繁写坏一块磁盘的情况。

image-20260110221228715

1.数据存储容量的问题。RAID使用了N块磁盘构成一个存储阵列,如果使用RAID 5,数据就可以存储在N-1块磁盘上,这样将存储空间扩大了N-1倍。

2.数据读写速度的问题。RAID根据可以使用的磁盘数量,将待写入的数据分成多片,并发同时向多块磁盘进行写入,显然写入的速度可以得到明显提高;同理,读取速度也可以得到明显提高。不过,需要注意的是,由于传统机械磁盘的访问延迟主要来自于寻址时间,数据真正进行读写的时间可能只占据整个数据访问时间的一小部分,所以数据分片后对N块磁盘进行并发读写操作并不能将访问速度提高N倍。

3.数据可靠性的问题。使用RAID 10、RAID 5或者RAID 6方案的时候,由于数据有冗余存储,或者存储校验信息,所以当某块磁盘损坏的时候,可以通过其他磁盘上的数据和校验数据将丢失磁盘上的数据还原。

在计算机领域,实现更强的计算能力和更大规模的数据存储有两种思路,一种是升级计算机,一种是用分布式系统。前一种也被称作“垂直伸缩”(scaling up),通过升级CPU、内存、磁盘等将一台计算机变得更强大;后一种是“水平伸缩”(scaling out),添加更多的计算机到系统中,从而实现更强大的计算能力。

HDFS

和RAID在多个磁盘上进行文件存储及并行读写的思路一样,HDFS是在一个大规模分布式服务器集群上,对数据分片后进行并行读写及冗余存储。因为HDFS可以部署在一个比较大的服务器集群上,集群中所有服务器的磁盘都可供HDFS使用,所以整个HDFS的存储空间可以达到PB级容量。

image-20260110224001978

HDFS的关键组件有两个,一个是DataNode,一个是NameNode。

DataNode负责文件数据的存储和读写操作,HDFS将文件数据分割成若干数据块(Block),每个DataNode存储一部分数据块,这样文件就分布存储在整个HDFS服务器集群中。应用程序客户端(Client)可以并行对这些数据块进行访问,从而使得HDFS可以在服务器集群规模上实现数据并行访问,极大地提高了访问速度。

在实践中,HDFS集群的DataNode服务器会有很多台,一般在几百台到几千台这样的规模,每台服务器配有数块磁盘,整个集群的存储容量大概在几PB到数百PB

NameNode负责整个分布式文件系统的元数据(MetaData)管理,也就是文件路径名、数据块的ID以及存储位置等信息,相当于操作系统中文件分配表(FAT)的角色。HDFS为了保证数据的高可用,会将一个数据块复制为多份(缺省情况为3份),并将多份相同的数据块存储在不同的服务器上,甚至不同的机架上。这样当有磁盘损坏,或者某个DataNode服务器宕机,甚至某个交换机宕机,导致其存储的数据块不能访问的时候,客户端会查找其备份的数据块进行访问。

和RAID一样,数据分成若干数据块后存储到不同服务器上,可以实现数据大容量存储,并且不同分片的数据可以并行进行读/写操作,进而实现数据的高速访问

1.数据存储故障容错

磁盘介质在存储过程中受环境或者老化影响,其存储的数据可能会出现错乱。HDFS的应对措施是,对于存储在DataNode上的数据块,计算并存储校验和(CheckSum)。在读取数据的时候,重新计算读取出来的数据的校验和,如果校验不正确就抛出异常,应用程序捕获异常后就到其他DataNode上读取备份数据。

2.磁盘故障容错

如果DataNode监测到本机的某块磁盘损坏,就将该块磁盘上存储的所有BlockID报告给NameNode,NameNode检查这些数据块还在哪些DataNode上有备份,通知相应的DataNode服务器将对应的数据块复制到其他服务器上,以保证数据块的备份数满足要求。

3.DataNode故障容错

DataNode会通过心跳和NameNode保持通信,如果DataNode超时未发送心跳,NameNode就会认为这个DataNode已经宕机失效,立即查找这个DataNode上存储的数据块有哪些,以及这些数据块还存储在哪些服务器上,随后通知这些服务器再复制一份数据块到其他服务器上,保证HDFS存储的数据块备份数符合用户设置的数目,即使再出现服务器宕机,也不会丢失数据。

4.NameNode故障容错

NameNode是整个HDFS的核心,记录着HDFS文件分配表信息,所有的文件路径和数据块存储信息都保存在NameNode,如果NameNode故障,整个HDFS系统集群都无法使用;如果NameNode上记录的数据丢失,整个集群所有DataNode存储的数据也就没用了

NameNode采用主从热备的方式提供高可用服务

image-20260110231002183

集群部署两台NameNode服务器,一台作为主服务器提供服务,一台作为从服务器进行热备,两台服务器通过ZooKeeper选举,主要是通过争夺znode锁资源,决定谁是主服务器。而DataNode则会向两个NameNode同时发送心跳数据,但是只有主NameNode才能向DataNode返回控制信息

正常运行期间,主从NameNode之间通过一个共享存储系统shared edits来同步文件系统的元数据信息。当主NameNode服务器宕机,从NameNode会通过ZooKeeper升级成为主服务器,并保证HDFS集群的元数据信息,也就是文件分配表信息完整一致。

对于一个软件系统而言,性能差一点,用户也许可以接受;使用体验差,也许也能忍受。但是如果可用性差,经常出故障导致不可用,那就比较麻烦了;如果出现重要数据丢失,那开发工程师绝对是摊上大事了。

而分布式系统可能出故障地方又非常多,内存、CPU、主板、磁盘会损坏,服务器会宕机,网络会中断,机房会停电,所有这些都可能会引起软件系统的不可用,甚至数据永久丢失。

常用的保证系统可用性的策略有冗余备份失效转移降级限流。虽然这3种策略你可能早已耳熟能详,但还是有一些容易被忽略的地方。

比如冗余备份,任何程序、任何数据,都至少要有一个备份,也就是说程序至少要部署到两台服务器,数据至少要备份到另一台服务器上。此外,稍有规模的互联网企业都会建设多个数据中心,数据中心之间互相进行备份,用户请求可能会被分发到任何一个数据中心,即所谓的异地多活,在遭遇地域性的重大故障和自然灾害的时候,依然保证应用的高可用。

当要访问的程序或者数据无法访问时,需要将访问请求转移到备份的程序或者数据所在的服务器上,这也就是失效转移。失效转移你应该注意的是失效的鉴定,像NameNode这样主从服务器管理同一份数据的场景,如果从服务器错误地以为主服务器宕机而接管集群管理,会出现主从服务器一起对DataNode发送指令,进而导致集群混乱,也就是所谓的“脑裂”。这也是这类场景选举主服务器时,引入ZooKeeper的原因。ZooKeeper的工作原理,我将会在后面专门分析。

当大量的用户请求或者数据处理请求到达的时候,由于计算资源有限,可能无法处理如此大量的请求,进而导致资源耗尽,系统崩溃。这种情况下,可以拒绝部分请求,即进行限流;也可以关闭部分功能,降低资源消耗,即进行降级。限流是互联网应用的常备功能,因为超出负载能力的访问流量在何时会突然到来,你根本无法预料,所以必须提前做好准备,当遇到突发高峰流量时,就可以立即启动限流。而降级通常是为可预知的场景准备的,比如电商的“双十一”促销,为了保障促销活动期间应用的核心功能能够正常运行,比如下单功能,可以对系统进行降级处理,关闭部分非重要功能,比如商品评价功能。

总结

HDFS是如何通过大规模分布式服务器集群实现数据的大容量、高速、可靠存储、访问的。

1.文件数据以数据块的方式进行切分,数据块可以存储在集群任意DataNode服务器上,所以HDFS存储的文件可以非常大,一个文件理论上可以占据整个HDFS服务器集群上的所有磁盘,实现了大容量存储。

2.HDFS一般的访问模式是通过MapReduce程序在计算时读取,MapReduce对输入数据进行分片读取,通常一个分片就是一个数据块,每个数据块分配一个计算进程,这样就可以同时启动很多进程对一个HDFS文件的多个数据块进行并发访问,从而实现数据的高速访问。

3.DataNode存储的数据块会进行复制,使每个数据块在集群里有多个备份,保证了数据的可靠性,并通过一系列的故障容错手段实现HDFS系统中主要组件的高可用,进而保证数据和整个系统的高可用。

MapReduce

MapReduce编程模型只包含Map和Reduce两个过程,map的主要输入是一对值,经过map计算后输出一对值;然后将相同Key合并,形成;再将这个输入reduce,经过计算输出零个或多个对。

MapReduce又是非常强大的,不管是关系代数运算(SQL计算),还是矩阵运算(图计算),大数据领域几乎所有的计算需求都可以通过MapReduce编程来实现。

以WordCount程序为例,一起来看下MapReduce的计算过程。

WordCount主要解决的是文本处理中词频统计的问题,就是统计文本中每一个单词出现的次数。如果只是统计一篇文章的词频,几十KB到几MB的数据,只需要写一个程序,将数据读入内存,建一个Hash表记录每个词出现的次数就可以了

MapReduce版本WordCount程序的核心是一个map函数和一个reduce函数。

map函数的输入主要是一个对,在这个例子里,Value是要统计的所有文本中的一行数据,Key在一般计算中都不会用到。

Map 的输入(Input Key-Value)

对于最常见的文本文件,Map 任务在读取一个 Split 时,默认生成的键值对是这样的:

  • Key:该行起始位置在整个文件中的 偏移量(Offset),类型通常是 LongWritable
  • Value:该行的 文本内容(Content),不包括换行符,类型通常是 Text

Map 的输出(Intermediate Key-Value)由写的代码决定的。Map 函数的作用就是:解析输入的 Value,提取出你感兴趣的信息,并重新组织成新的 Key 和 Value。

在你的“字符频次统计”任务中,逻辑通常是:

  1. 拿到 Value(一整行字符串)。
  2. 切割字符串,遍历每个单词/字符。
  3. 输出新的键值对。

Map 逻辑处理后输出:

  • <"Apple", 1>
  • <"Orange", 1>
  • <"Banana", 1>
  • <"Apple", 1>

MapReduce计算框架会将这些收集起来,将相同的word放在一起,形成>这样的数据,然后将其输入给reduce函数。

当所有的 Map 任务运行完,Shuffle 阶段会将所有 Map 输出的、相同 Key 的数据汇聚在一起。这时数据的形态发生了变化:

  • Key:唯一的单词。
  • Value:一个包含所有“1”的列表(Iterable)

reduce函数的计算过程是,将这个集合里的1求和,再将单词(word)和这个和(sum)组成一个,也就是输出。每一个输出就是一个单词和它的词频统计总和。

image-20260111223422302

具体流程

在Map阶段为每个数据块分配一个Map计算任务,然后将所有map输出的Key进行合并,相同的Key及其对应的Value发送给同一个Reduce任务去处理。通过这两个阶段,工程师只需要遵循MapReduce编程模型就可以开发出复杂的大数据计算程序。

在实践中,这个过程有两个关键问题需要处理。

  • 如何为每个数据块分配一个Map计算任务,也就是代码是如何发送到数据块所在服务器的发送后是如何启动的,启动以后如何知道自己需要计算的数据在文件什么位置(BlockID是什么)。
  • 处于不同服务器的map输出的如何把相同的Key聚合在一起发送给Reduce任务进行处理

    MapReduce运行过程涉及三类关键进程。

    1.大数据应用进程。这类进程是启动MapReduce程序的主入口,主要是指定Map和Reduce类、输入输出文件路径等,并提交作业给Hadoop集群,也就是下面提到的JobTracker进程

    2.JobTracker进程。这类进程根据要处理的输入数据量,命令下面提到的TaskTracker进程启动相应数量的Map和Reduce进程任务,并管理整个作业生命周期的任务调度和监控。这是Hadoop集群的常驻进程,需要注意的是,JobTracker进程在整个Hadoop集群全局唯一。

    3.TaskTracker进程。这个进程负责启动和管理Map进程以及Reduce进程。因为需要每个数据块都有对应的map函数,TaskTracker进程通常和HDFS的DataNode进程启动在同一个服务器。也就是说,Hadoop集群中绝大多数服务器同时运行DataNode进程和TaskTracker进程。

    JobTracker进程和TaskTracker进程是主从关系,主服务器通常只有一台(或者另有一台备机提供高可用服务,但运行时只有一台服务器对外提供服务,真正起作用的只有一台),从服务器可能有几百上千台,所有的从服务器听从主服务器的控制和调度安排。主服务器负责为应用程序分配服务器资源以及作业执行的调度,而具体的计算操作则在从服务器上完成。

    MapReduce的主服务器就是JobTracker,从服务器就是TaskTracker。HDFS的主服务器是NameNode,从服务器是DataNode。

    可重复使用的架构方案叫作架构模式,一主多从可谓是大数据领域的最主要的架构模式。主服务器只有一台,掌控全局;从服务器有很多台,负责具体的事情。这样很多台服务器可以有效组织起来,对外表现出一个统一又强大的计算能力。

    image-20260111232339331

    可以概括如下:

    1.应用进程JobClient将用户作业JAR包存储在HDFS中,将来这些JAR包会分发给Hadoop集群中的服务器执行MapReduce计算。

    2.应用程序提交job作业给JobTracker。

    3.JobTracker根据作业调度策略创建JobInProcess树,每个作业都会有一个自己的JobInProcess树。

    4.JobInProcess根据输入数据分片数目(通常情况就是数据块的数目)和设置的Reduce数目创建相应数量的TaskInProcess。

    5.TaskTracker进程和JobTracker进程进行定时通信。

    6.如果TaskTracker有空闲的计算资源(有空闲CPU核心),JobTracker就会给它分配任务。分配任务的时候会根据TaskTracker的服务器名字匹配在同一台机器上的数据块计算任务给它,使启动的计算任务正好处理本机上的数据,以实现我们一开始就提到的“移动计算比移动数据更划算”。

    7.TaskTracker收到任务后根据任务类型(是Map还是Reduce)和任务参数(作业JAR包路径、输入数据文件路径、要处理的数据在文件中的起始位置和偏移量、数据块多个备份的DataNode主机名等),启动相应的Map或者Reduce进程。

    8.Map或者Reduce进程启动后,检查本地是否有要执行任务的JAR包文件,如果没有,就去HDFS上下载,然后加载Map或者Reduce代码开始执行。

    9.如果是Map进程,从HDFS读取数据(通常要读取的数据块正好存储在本机);如果是Reduce进程,将结果数据写出到HDFS。

    MapReduce数据合并与连接机制

    想要统计相同单词在所有输入数据中出现的次数,而一个Map只能处理一部分数据,一个热门单词几乎会出现在所有的Map中,这意味着同一个单词必须要合并到一起进行统计才能得到正确的结果。

    事实上,几乎所有的大数据计算场景都需要处理数据关联的问题,像WordCount这种比较简单的只要对Key进行合并就可以了,对于像数据库的join操作这种比较复杂的,需要对两种类型(或者更多类型)的数据根据Key进行连接。

    在map输出与reduce输入之间,MapReduce计算框架处理数据合并与连接操作,这个操作有个专门的词汇叫shuffle

    image-20260112101503219

    每个Map任务的计算结果都会写入到本地文件系统,等Map任务快要计算完成的时候,MapReduce计算框架会启动shuffle过程,在Map任务进程调用一个Partitioner接口,对Map产生的每个进行Reduce分区选择,然后通过HTTP通信发送给对应的Reduce进程。这样不管Map位于哪个服务器节点,相同的Key一定会被发送给相同的Reduce进程。Reduce任务进程对收到的进行排序和合并,相同的Key放在一起,组成一个传递给Reduce执行。

    map输出的shuffle到哪个Reduce进程是这里的关键,它是由Partitioner来实现,MapReduce框架默认的Partitioner用Key的哈希值对Reduce任务数量取模,相同的Key一定会落在相同的Reduce任务ID上。从实现上来看的话,这样的Partitioner代码只需要一行。

    分布式计算需要将不同服务器上的相关数据合并到一起进行下一步计算,这就是shuffle

    shuffle是大数据计算过程中最神奇的地方,不管是MapReduce还是Spark,只要是大数据批处理计算,一定都会有shuffle过程,只有让数据关联起来,数据的内在关系和价值才会呈现出来。如果你不理解shuffle,肯定会在map和reduce编程中产生困惑,不知道该如何正确设计map的输出和reduce的输入。

    资源调度框架Yarn

    在MapReduce应用程序的启动过程中,最重要的就是要把MapReduce程序分发到大数据集群的服务器上,在Hadoop 1中,这个过程主要是通过TaskTracker和JobTracker通信来完成。

    ··

    Yarn包括两个部分:一个是资源管理器(Resource Manager),一个是节点管理器(Node Manager)。这也是Yarn的两种主要进程:ResourceManager进程负责整个集群的资源调度管理,通常部署在独立的服务器上;NodeManager进程负责具体服务器上的资源和任务管理,在集群的每一台计算服务器上都会启动,基本上跟HDFS的DataNode进程一起出现。

    具体说来,资源管理器又包括两个主要组件:调度器应用程序管理器

    调度器其实就是一个资源分配算法,根据应用程序(Client)提交的资源申请和当前服务器集群的资源状况进行资源分配。Yarn内置了几种资源调度算法,包括Fair Scheduler、Capacity Scheduler等,你也可以开发自己的资源调度算法供Yarn调用。

    Yarn进行资源分配的单位是容器(Container),每个容器包含了一定量的内存、CPU等计算资源,默认配置下,每个容器包含一个CPU核心。容器由NodeManager进程启动和管理,NodeManger进程会监控本节点上容器的运行状况并向ResourceManger进程汇报。

    应用程序管理器负责应用程序的提交、监控应用程序运行状态等。应用程序启动后需要在集群中运行一个ApplicationMaster,ApplicationMaster也需要运行在容器里面。每个应用程序启动后都会先启动自己的ApplicationMaster,由ApplicationMaster根据应用程序的资源需求进一步向ResourceManager进程申请容器资源,得到容器以后就会分发自己的应用程序代码到容器上启动,进而开始分布式计算

    Yarn的整个工作流程。

    1.向Yarn提交应用程序,包括MapReduce ApplicationMaster、编写的MapReduce程序,以及MapReduce Application启动命令。

    2.ResourceManager进程和NodeManager进程通信,根据集群资源,为用户程序分配第一个容器,并将MapReduce ApplicationMaster分发到这个容器上面,并在容器里面启动MapReduce ApplicationMaster。

    3.MapReduce ApplicationMaster启动后立即向ResourceManager进程注册,并为自己的应用程序申请容器资源。

    4.MapReduce ApplicationMaster申请到需要的容器后,立即和相应的NodeManager进程通信将用户MapReduce程序分发到NodeManager进程所在服务器,并在容器中运行,运行的就是Map或者Reduce任务。

    5.Map或者Reduce任务在运行期和MapReduce ApplicationMaster通信,汇报自己的运行状态,如果运行结束,MapReduce ApplicationMaster向ResourceManager进程注销并释放所有的容器资源。

    MapReduce如果想在Yarn上运行,就需要开发遵循Yarn规范的MapReduce ApplicationMaster,相应地,其他大数据计算框架也可以开发遵循Yarn规范的ApplicationMaster,这样在一个Yarn集群中就可以同时并发执行各种不同的大数据计算框架,实现资源的统一调度管理。

    Hadoop的三个主要组成部分的时候,管HDFS叫分布式文件系统,管MapReduce叫分布式计算框架,管Yarn叫分布式集群资源调度框架

    为什么HDFS是系统,而MapReduce和Yarn则是框架?

    框架在架构设计上遵循一个重要的设计原则叫“依赖倒转原则”,依赖倒转原则是高层模块不能依赖低层模块,它们应该共同依赖一个抽象,这个抽象由高层模块定义,由低层模块实现。

    应用程序启动器ASM始终运行在 ResourceManager 进程内部。AM运行在集群中任意一个 DataNode(NodeManager) 的 Container 里

    用户:提交一个 Java JAR 包给 YARN。

    ASM (在 RM 内部):收到请求,检查资源。

    ASM (在 RM 内部):找 Scheduler 要了一块地(Container)。

    ASM (在 RM 内部):联系某个 NodeManager,说:“在那块地里把这个任务的 ApplicationMaster (AM) 给我跑起来!”

    NodeManager:启动 AM。此时,任务正式开始。

    AM:启动后,再去直接找 Scheduler 要更多的资源跑具体的 Map/Reduce 任务。

    Hive与MapReduce的结合

    Hive的目的就是将SQL生成MapReduce可执行的代码,然后提交Hadoop执行.

    Spark

    RDD是Spark的核心概念,是弹性数据集(Resilient Distributed Datasets)的缩写。RDD既是Spark面向开发者的编程模型,又是Spark自身架构的核心元素。

    大数据计算就是在大规模的数据集上进行一系列的数据计算处理。MapReduce针对输入数据,将计算过程分为两个阶段,一个Map阶段,一个Reduce阶段,可以理解成是面向过程的大数据计算。在用MapReduce编程的时候,思考的是,如何将计算逻辑用Map和Reduce两个阶段实现,map和reduce函数的输入和输出是什么

    而Spark则直接针对数据进行编程,将大规模数据集合抽象成一个RDD对象,然后在这个RDD上进行各种计算处理,得到一个新的RDD,继续计算处理,直到得到最后的结果数据。所以Spark可以理解成是面向对象的大数据计算。我们在进行Spark编程的时候,思考的是一个RDD对象需要经过什么样的操作,转换成另一个RDD对象,思考的重心和落脚点都在RDD上。

    RDD上定义的函数分两种,一种是转换(transformation)函数,这种函数的返回值还是RDD;另一种是执行(action)函数,这种函数不再返回RDD。

    RDD定义了很多转换操作函数,比如有计算map(func)、过滤filter(func)、合并数据集union(otherDataset)、根据Key聚合reduceByKey(func, [numPartitions])、连接数据集join(otherDataset, [numPartitions])、分组groupByKey([numPartitions])等十几个函数。

    跟MapReduce一样,Spark也是对大数据进行分片计算,Spark分布式计算的数据分片、任务调度都是以RDD为单位展开的,每个RDD分片都会分配到一个执行进程去处理。

    RDD上的转换操作又分成两种,一种转换操作产生的RDD不会出现新的分片,比如map、filter等,也就是说一个RDD数据分片,经过map或者filter转换操作后,结果还在当前分片。就像你用map函数对每个数据加1,得到的还是这样一组数据,只是值不同。实际上,Spark并不是按照代码写的操作顺序去生成RDD,比如rdd2 = rdd1.map(func)这样的代码并不会在物理上生成一个新的RDD。物理上,Spark只有在产生新的RDD分片时候,才会真的生成一个RDD,Spark的这种特性也被称作惰性计算。另一种转换操作产生的RDD则会产生新的分片,比如reduceByKey,来自不同分片的相同Key必须聚合在一起进行操作,这样就会产生新的RDD分片

    Spark为什么更高效

    和MapReduce一样,Spark也遵循移动计算比移动数据更划算这一大数据计算基本原则。但是和MapReduce僵化的Map与Reduce分阶段计算相比,Spark的计算框架更加富有弹性和灵活性,进而有更好的运行性能。

    和MapReduce一个应用一次只运行一个map和一个reduce不同,Spark可以根据应用的复杂程度,分割成更多的计算阶段(stage),这些计算阶段组成一个有向无环图DAG,Spark任务调度器可以根据DAG的依赖关系执行计算阶段。

    Spark作业调度执行的核心是DAG,DAG根据应用输入划分不同阶段。整个应用就被切分成哪些阶段,每个阶段的依赖关系也就清楚了。之后再根据每个阶段要处理的数据量生成相应的任务集合(TaskSet),每个任务都分配一个任务进程去处理,Spark就实现了大数据的分布式计算。

    那么Spark划分计算阶段的依据是什么呢?显然并不是RDD上的每个转换函数都会生成一个计算阶段。

    image-20260112152219517

    关于计算阶段的划分从图上就能看出规律,当RDD之间的转换连接线呈现多对多交叉连接的时候,就会产生新的阶段。一个RDD代表一个数据集,图中每个RDD里面都包含多个小块,每个小块代表RDD的一个分片

    一个数据集中的多个数据分片需要进行分区传输,写入到另一个数据集的不同分片中,这种数据分区交叉传输的操作,我们在MapReduce的运行过程中也看到过。Spark也需要通过shuffle将数据进行重新组合,相同Key的数据放在一起,进行聚合、关联等操作,因而每次shuffle都产生新的计算阶段。这也是为什么计算阶段会有依赖关系,它需要的数据来源于前面一个或多个计算阶段产生的数据,必须等待前面的阶段执行完毕才能进行shuffle,并得到数据。

    不同阶段的依赖关系是有向的,计算过程只能沿着依赖关系方向执行,被依赖的阶段执行完成之前,依赖的阶段不能开始执行,同时,这个依赖关系不能有环形依赖,否则就成为死循环了。

    RDDB是窄依赖,而RDDF是宽依赖

    因为RDD B在前面一个阶段,阶段1的shuffle过程中,已经进行了数据分区。分区数目和分区Key不变,就不需要再进行shuffle。

    image-20260112154243266

    计算阶段划分的依据是shuffle,不是转换函数的类型,有的函数有时候有shuffle,有时候没有。

    这种不需要进行shuffle的依赖,在Spark里被称作窄依赖;相反的,需要进行shuffle的依赖,被称作宽依赖。跟MapReduce一样,shuffle也是Spark最重要的一个环节,只有通过shuffle,相关数据才能互相计算,构建起复杂的应用逻辑。

    同样都要经过shuffle,为什么Spark可以更高效呢?

    其实从本质上看,Spark可以算作是一种MapReduce计算模型的不同实现。Hadoop MapReduce简单粗暴地根据shuffle将大数据计算分成Map和Reduce两个阶段,然后就算完事了。而Spark更细腻一点,将前一个的Reduce和后一个的Map连接起来,当作一个阶段持续计算,形成一个更加优雅、高效的计算模型,虽然其本质依然是Map和Reduce。但是这种多个计算阶段依赖执行的方案可以有效减少对HDFS的访问,减少作业的调度执行次数,因此执行速度也更快。

    并且和Hadoop MapReduce主要使用磁盘存储shuffle过程中的数据不同,Spark优先使用内存进行数据存储,包括RDD数据。除非是内存不够用了,否则是尽可能使用内存, 这也是Spark性能比Hadoop高的另一个原因

    总结,Spark为什么更快: 1. MapReduce模型不完全一样,Spark将前一个Reduce而后一个Map连接起来,将一个计算任务拆为多个阶段,减少了对HDFS的访问和作业的调度执行次数。2. Spark优先使用内存,除非内存不够用。而Hadoop在每次Map完后都会将数据写入到磁盘.

    Spark里面的RDD函数有两种,一种是转换函数,调用以后得到的还是一个RDD,RDD的计算逻辑主要通过转换函数完成。

    另一种是action函数,调用以后不再返回RDD。比如count()函数,返回RDD中数据的元素个数;saveAsTextFile(path),将RDD数据存储到path路径下。Spark的DAGScheduler在遇到shuffle的时候,会生成一个计算阶段,在遇到action函数的时候,会生成一个作业(job)。

    RDD里面的每个数据分片,Spark都会创建一个计算任务去处理,所以一个计算阶段会包含很多个计算任务(task)。

    Spark的执行过程

    Spark支持Standalone、Yarn、Mesos、Kubernetes等多种部署方案,几种部署方案原理也都一样,只是不同组件角色命名不同,但是核心功能和运行流程都差不多。

    首先,Spark应用程序启动在自己的JVM进程里,即Driver进程,启动后调用SparkContext初始化执行配置和输入数据。SparkContext启动DAGScheduler构造执行的DAG图,切分成最小的执行单位也就是计算任务。

    image-20260112155140561

    首先,Spark应用程序启动在自己的JVM进程里,即Driver进程,启动后调用SparkContext初始化执行配置和输入数据。SparkContext启动DAGScheduler构造执行的DAG图,切分成最小的执行单位也就是计算任务。

    然后Driver向Cluster Manager请求计算资源,用于DAG的分布式计算。Cluster Manager收到请求以后,将Driver的主机地址等信息通知给集群的所有计算节点Worker。

    Worker收到信息以后,根据Driver的主机地址,跟Driver通信并注册,然后根据自己的空闲资源向Driver通报自己可以领用的任务数。Driver根据DAG图开始向注册的Worker分配任务。

    Worker收到任务后,启动Executor进程开始执行任务。Executor先检查自己是否有Driver的执行代码,如果没有,从Driver下载执行代码,通过Java反射加载后开始执行。

    Spark有三个主要特性:RDD的编程模型更简单,DAG切分的多阶段计算过程更快速,使用内存存储中间计算结果更高效。这三个特性使得Spark相对Hadoop MapReduce可以有更快的执行速度,以及更简单的编程实现。

    BigTable对应的NOSQL

    Google发表GFS、MapReduce、BigTable三篇论文,号称“三驾马车”,开启了大数据的时代。那和这“三驾马车”对应的有哪些开源产品呢?GFS对应的Hadoop分布式文件系统HDFS,以及MapReduce对应的Hadoop分布式计算框架MapReduce。而BigTable就对应NOSQL数据库 HBase.

    HBase为可伸缩海量数据储存而设计,实现面向在线业务的实时数据访问延迟。HBase的伸缩性主要依赖其可分裂的HRegion及可伸缩的分布式文件系统HDFS实现

    在计算机数据存储领域,一直是关系数据库(RDBMS)的天下,以至于在传统企业的应用领域,许多应用系统设计都是面向数据库设计,也就是先设计数据库然后设计程序,从而导致关系模型绑架对象模型,并由此引申出旷日持久的业务对象贫血模型与充血模型之争。

    业务的贫血模型和充血模型

    贫血模型 这是目前国内互联网开发中最常用的模式(常见于 Spring 的三层架构:Controller-Service-Dao)。业务对象(POJO/Entity)只包含属性(Getter/Setter),没有任何业务逻.所有的业务逻辑都写在 Service 层中。

    充血模型 (Rich Domain Model)这是领域驱动设计(DDD)极力推崇的模式。业务对象不仅包含属性,还包含与该对象相关的业务行为(方法)。业务逻辑封装在领域对象内部。Service 只负责编排这些对象。

    关系数据库难以克服的缺陷——糟糕的海量数据处理能力及僵硬的设计约束,从Google的BigTable开始,一系列的可以进行海量数据存储与访问的数据库被设计出来,更进一步说,NoSQL这一概念被提了出来。

    HBase的架构设计

    HBase之所以能够具有海量数据处理能力,其根本在于和传统关系型数据库设计的不同思路。传统关系型数据库对存储在其上的数据有很多约束,学习关系数据库都要学习数据库设计范式,事实上,是在数据存储中包含了一部分业务逻辑。而NoSQL数据库则简单暴力地认为,数据库就是存储数据的,业务逻辑应该由应用程序去处理

    HRegion是HBase负责数据存储的主要进程,应用程序对数据的读写操作都是通过和HRegion通信完成。在HBase中,数据以HRegion为单位进行管理,也就是说应用程序如果想要访问一个数据,必须先找到HRegion,然后将数据读写操作提交给HRegion,由 HRegion完成存储层面的数据操作

    image-20260112160435616

    可伸缩架构

    HRegionServer是物理服务器,每个HRegionServer上可以启动多个HRegion实例。当一个 HRegion中写入的数据太多,达到配置的阈值时,一个HRegion会分裂成两个HRegion,并将HRegion在整个集群中进行迁移,以使HRegionServer的负载均衡。

    每个HRegion中存储一段Key值区间[key1, key2)的数据,所有HRegion的信息,包括存储的Key值区间、所在HRegionServer地址、访问端口号等,都记录在HMaster服务器上为了保证HMaster的高可用,HBase会启动多个HMaster,并通过ZooKeeper选举出一个主服务器。

    应用程序通过ZooKeeper获得主HMaster的地址,输入Key值获得这个Key所在的HRegionServer地址,然后请求HRegionServer上的HRegion,获得所需要的数据。

    image-20260112160808684

    数据写入过程也是一样,需要先得到HRegion才能继续操作。HRegion会把数据存储在若干个HFile格式的文件中,这些文件使用HDFS分布式文件系统存储,在整个集群内分布并高可用。当一个HRegion中数据量太多时,这个HRegion连同HFile会分裂成两个HRegion,并根据集群中服务器负载进行迁移。

    如果集群中有新加入的服务器,也就是说有了新的HRegionServer,由于其负载较低,也会把HRegion迁移过去并记录到HMaster,从而实现HBase的线性伸缩。HBase的核心设计目标是解决海量数据的分布式存储,和Memcached这类分布式缓存的路由算法不同,HBase的做法是按Key的区域进行分片,这个分片也就是HRegion。应用程序通过HMaster查找分片,得到HRegion所在的服务器HRegionServer,然后和该服务器通信,就得到了需要访问的数据。

    可扩展数据模型

    传统的关系数据库为了保证关系运算的正确性,在设计数据库表结构的时候,需要指定表的schema也就是字段名称、数据类型等,并要遵循特定的设计范式。这些规范带来了一个问题,就是僵硬的数据结构难以面对需求变更带来的挑战,有些应用系统设计者通过预先设计一些冗余字段来应对,但显然这种设计也很糟糕

    许多NoSQL数据库使用的列族(ColumnFamily)做到可扩展的护具结构设计。列族最早在Google的BigTable中使用,这是一种面向列族的稀疏矩阵存储格式,

    特性关系型数据库 (行式)NoSQL 列族存储
    模式 (Schema)强 Schema:每行必须有相同的列。稀疏性:同一列族下,每行的列可以完全不同。
    存储方式一整行数据存在一起。同一列族的数据存在一起。
    扩展性纵向扩展(加 CPU/内存)。横向扩展(分布在多台服务器)。
    空值处理空值也占空间(或需要特殊处理)。不存储空值,不占空间。

    高性能存储

    传统的机械式磁盘的访问特性是连续读写很快,随机读写很慢。这是因为机械磁盘靠电机驱动访问磁盘上的数据,电机要将磁头落到数据所在的磁道上,这个过程需要较长的寻址时间。如果数据不连续存储,磁头就要不停地移动,浪费了大量的时间。

    虽然 SSD 很快,但它在物理层面有一个非常“别扭”的限制,这导致它依然存在顺序写比随机写快的现象:

    • 读取(Read):以 Page(页,约 4KB~16KB) 为单位,非常快。
    • 写入(Write):也以 Page 为单位。但有个前提:必须在“干净”的空地上写
    • 擦除(Erase):以 Block(块,包含数百个 Page) 为单位。

    核心矛盾:你不能直接覆盖旧数据。如果你想改写一个 Page,必须先把整个 Block 擦除干净,然后才能写。这种特性被称为 “写前擦除”(Erase-before-write)

    为了提高数据写入速度,HBase使用了一种叫作LSM树的数据结构进行数据存储。LSM树的全名是Log Structed Merge Tree,翻译过来就是Log结构合并树。数据写入的时候以Log方式连续写入,然后异步对磁盘上的多个LSM树进行合并。

    LSM 树并不是一棵像 B+ 树那样巨大的、常驻磁盘的树,它实际上是由内存组件磁盘组件共同构成的多层结构。

    LSM树可以看作是一个N阶合并树。数据写操作(包括插入、修改、删除)都在内存中进行,并且都会创建一个新记录(修改会记录新的数据值,而删除会记录一个删除标志)。这些数据在内存中仍然还是一棵排序树,当数据量超过设定的内存阈值后,会将这棵排序树和磁盘上最新的排序树合并。当这棵排序树的数据量也超过设定阈值后,会和磁盘上下一级的排序树合并。合并过程中,会用最新更新的数据覆盖旧的数据(或者记录为不同版本)。

    在需要进行读操作时,总是从内存中的排序树开始搜索,如果没有找到,就从磁盘 上的排序树顺序查找。

    在LSM树上进行一次数据更新不需要磁盘访问,在内存即可完成。当数据访问以写操作为主,而读操作则集中在最近写入的数据上时,使用LSM树可以极大程度地减少磁盘的访问次数,加快访问速度

    HBase作为Google BigTable的开源实现,完整地继承了BigTable的优良设计。架构上通过数据分片的设计配合HDFS,实现了数据的分布式海量存储;数据结构上通过列族的设计,实现了数据表结构可以在运行期自定义;存储上通过LSM树的方式,使数据可以通过连续写磁盘的方式保存数据,极大地提高了数据写入性能。

    image-20260112165512753

    流式计算

    大数据批处理计算。顾名思义,数据是以批为单位进行计算,比如一天的访问日志、历史上所有的订单数据等。这些数据通常通过HDFS存储在磁盘上,使用MapReduce或者Spark这样的批处理大数据计算框架进行计算,一般完成一次计算需要花费几分钟到几小时的时间。

    此外,还有一种大数据技术,针对实时产生的大规模数据进行即时计算处理,我们比较熟悉的有摄像头采集的实时视频数据、淘宝实时产生的订单数据等。像上海这样的一线城市,公共场所的摄像头规模在数百万级,即使只有重要场所的视频数据需要即时处理,可能也会涉及几十万个摄像头,如果想实时发现视频中出现的通缉犯或者违章车辆,就需要对这些摄像头产生的数据进行实时处理。实时处理最大的不同就是这类数据跟存储在HDFS上的数据不同,是实时传输过来的,或者形象地说是流过来的,所以针对这类大数据的实时处理系统也叫大数据流计算系统

    Storm

    能不能开发一个流处理计算系统,我们只要定义好处理流程和每一个节点的处理逻辑,代码部署到流处理系统后,就能按照预定义的处理流程和处理逻辑执行呢?Storm就是在这种背景下产生的,它也算是一个比较早期的大数据流计算框架。上面的例子如果用Storm来实现,过程就变得简单一些了。

    image-20260112171616823

    Storm运行机制

    nimbus是集群的Master,负责集群管理、任务分配等。supervisor是Slave,是真正完成计算的地方,每个supervisor启动多个worker进程,每个worker上运行多个task,而task就是spout或者bolt。supervisor和nimbus通过ZooKeeper完成任务分配、心跳检测等操作。

    image-20260112171738564

    nimbus是集群的Master,负责集群管理、任务分配等。supervisor是Slave,是真正完成计算的地方,每个supervisor启动多个worker进程,每个worker上运行多个task,而task就是spout或者bolt。supervisor和nimbus通过ZooKeeper完成任务分配、心跳检测等操作。

    Hadoop、Storm的设计理念,其实是一样的,就是把和具体业务逻辑无关的东西抽离出来,形成一个框架,比如大数据的分片处理、数据的流转、任务的部署与执行等,开发者只需要按照框架的约束,开发业务逻辑代码,提交给框架执行就可以了。

    而这也正是所有框架的开发理念,就是将业务逻辑和处理过程分离开来,使开发者只需关注业务开发即可,比如Java开发者都很熟悉的Tomcat、Spring等框架,全部都是基于这种理念开发出来的

    Spark Streaming

    Spark Streaming巧妙地利用了Spark的分片快速计算的特性,将实时传输进来的数据按照时间进行分段,把一段时间传输进来的数据合并在一起,当作一批数据,再去交给Spark去处理。

    如果时间段分得足够小,每一段的数据量就会比较小,再加上Spark引擎的处理速度又足够快,这样看起来好像数据是被实时处理的一样,Spark Streaming主要负责将流数据转换成小的批数据,剩下的就可以交给Spark去做了。

    Flink的架构和Hadoop 1或者Yarn看起来也很像,JobManager是Flink集群的管理者,Flink程序提交给JobManager后,JobManager检查集群中所有TaskManager的资源利用状况,如果有空闲TaskSlot(任务槽),就将计算任务分配给它执行。

    如果要进行流计算,Flink会初始化一个流执行环境StreamExecutionEnvironment,然后利用这个执行环境构建数据流DataStream。

    1
    2
    3
    StreamExecutionEnvironment see = StreamExecutionEnvironment.getExecutionEnvironment();

    DataStream<WikipediaEditEvent> edits = see.addSource(new WikipediaEditsSource());

    如果要进行批处理计算,Flink会初始化一个批处理执行环境ExecutionEnvironment,然后利用这个环境构建数据集DataSet。

    1
    2
    3
    ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

    DataSet<String> text = env.readTextFile("/path/to/file");

    然后在DataStream或者DataSet上执行各种数据转换操作(transformation),这点很像Spark。不管是流处理还是批处理,Flink运行时的执行引擎是相同的,只是数据源不同而已。

    Zookeeper如何保证数据一致性

    在分布式系统里的多台服务器要对数据状态达成一致,其实是一件很有难度和挑战的事情,因为服务器集群环境的软硬件故障随时会发生,多台服务器对一个数据的记录保持一致,需要一些技巧和设计。

    HDFS为了保证整个集群的高可用,需要部署两台NameNode服务器,一台作为主服务器,一台作为从服务器。当主服务器不可用的时候,就切换到从服务器上访问。但是如果不同的应用程序(Client)或者DataNode做出的关于主服务器是否可用的判断不同,那么就会导致HDFS集群混乱。

    CAP原理认为,一个提供数据服务的分布式系统无法同时满足数据一致性(Consistency)、可用性(Availibility)、分区耐受性(Patition Tolerance)这三个条件,

    一致性是说,每次读取的数据都应该是最近写入的数据或者返回一个错误(Every read receives the most recent write or an error),而不是过期数据,也就是说,数据是一致的。

    可用性是说,每次请求都应该得到一个响应,而不是返回一个错误或者失去响应,不过这个响应不需要保证数据是最近写入的(Every request receives a (non-error) response, without the guarantee that it contains the most recent write),也就是说系统需要一直都是可以正常使用的,不会引起调用者的异常,但是并不保证响应的数据是最新的。

    分区耐受性是说,即使因为网络原因,部分服务器节点之间消息丢失或者延迟了,系统依然应该是可以操作的(The system continues to operate despite an arbitrary number of messages being dropped (or delayed) by the network between nodes)。

    当网络分区失效发生的时候,我们要么取消操作,这样数据就是一致的,但是系统却不可用;要么我们继续写入数据,但是数据的一致性就得不到保证。

    对于一个分布式系统而言,网络失效一定会发生,也就是说,分区耐受性是必须要保证的,那么在可用性和一致性上就必须二选一。当网络分区失效,也就是网络不可用的时候,如果选择了一致性,系统就可能返回一个错误码或者干脆超时,即系统不可用。如果选择了可用性,那么系统总是可以返回一个数据,但是并不能保证这个数据是最新的。

    Zookeeper架构与ZAB算法

    ZooKeeper主要提供数据的一致性服务,其实现分布式系统的状态一致性依赖一个叫Paxos的算法。Paxos算法在多台服务器通过内部的投票表决机制决定一个数据的更新与写入

    应用程序连接到任意一台服务器后提起状态修改请求(也可以是获得某个状态锁的请求),从图上看也就是服务器1,会将这个请求发送给集群中其他服务器进行表决。如果某个服务器同时收到了另一个应用程序同样的修改请求,它可能会拒绝服务器1的表决,并且自己也发起一个同样的表决请求,那么其他服务器就会根据时间戳和服务器排序规则进行表决。

    表决结果会发送给其他所有服务器,最终发起表决的服务器也就是服务器1,会根据收到的表决结果决定该修改请求是否可以执行,事实上,只有在收到多数表决同意的情况下才会决定执行。当有多个请求同时修改某个数据的情况下,服务器的表决机制保证只有一个请求会通过执行,从而保证了数据的一致性。

    Paxos算法有点过于复杂、实现难度也比较高,所以ZooKeeper在编程实现的时候将其简化成了一种叫做ZAB的算法(Zookeeper Atomic Broadcast, Zookeeper原子广播)。

    ZAB算法的目的,同样是在多台服务器之间达成一致,保证这些服务器上存储的数据是一致的。ZAB算法的主要特点在于:需要在这些服务器中选举一个Leader,所有的写请求都必须提交给Leader。由Leader服务器向其他服务器(Follower)发起Propose,通知所有服务器:我们要完成一个写操作请求,大家检查自己的数据状态,是否有问题。

    如果所有Follower服务器都回复Leader服务器ACK,即没有问题,那么Leader服务器会向所有Follower发送Commit命令,要求所有服务器完成写操作。这样包括Leader服务器在内的所有ZooKeeper集群服务器的数据,就都更新并保持一致了。如果有两个客户端程序同时请求修改同一个数据,因为必须要经过Leader的审核,而Leader只接受其中一个请求,数据也会保持一致。

    在实际应用中,客户端程序可以连接任意一个Follower,进行数据读写操作。如果是写操作,那么这个请求会被这个Follower发送给Leader,进行如上所述的处理;如果是读操作,因为所有服务器的数据都是一致的,那么这个Follower直接返回自己本地的数据给客户端就可以了。

    对数据进行分类和预测

    上面通过Hadoop,Spark,Flink等对大数据进行存储,计算,查询等分布式计算框架介绍,有了数据之后如何利用呢。 利用数学统计方法,统计数据中的规律,然后利用这些统计规律进行自动化数据处理,使计算机表现出某种智能的特性,而各种数学统计方法,就是大数据算法。

    KNN分类算法

    KNN算法,即K近邻(K Nearest Neighbour)算法,是一种基本的分类算法。其主要原理是:对于一个需要分类的数据,将其和一组已经分类标注好的样本集合进行比较,得到距离最近的K个样本,K个样本最多归属的类别,就是这个需要分类数据的类别

    image-20260112192136936

    数据的距离

    KNN算法的关键是要比较需要分类的数据与样本数据之间的距离,这在机器学习中通常的做法是:提取数据的特征值,根据特征值组成一个n维实数向量空间(这个空间也被称作特征空间),然后计算向量之间的空间距离。空间之间的距离计算方法有很多种,常用的有欧氏距离、余弦距离等。

    文本的特征值

    机器学习的算法需要计算距离,而计算距离还需要知道数据的特征向量,因此提取数据的特征向量是机器学习工程师们的重要工作,有时候甚至是最重要的工作。不同的数据以及不同的应用场景需要提取不同的特征值,我们以比较常见的文本数据为例,看看如何提取文本特征向量。

    文本数据的特征值就是提取文本关键词,TF-IDF算法是比较常用且直观的一种文本关键词提取算法。这种算法是由TF和IDF两部分构成。

    TF是词频(Term Frequency),表示某个单词在文档中出现的频率,一个单词在一个文档中出现得越频繁,TF值越高。

    词频:

    IDF是逆文档频率(Inverse Document Frequency),表示这个单词在所有文档中的稀缺程度,越少文档出现这个词,IDF值越高。

    逆文档频率:

    TF与IDF的乘积就是TF-IDF。

    贝叶斯公式

    为了理解这个公式,我们给每一个项起个名字:

    • $P(A|B)$(后验概率):在看到证据 $B$ 之后,事件 $A$ 发生的概率。
    • $P(A)$(先验概率):在看到证据 $B$ 之前,我们对事件 $A$ 发生概率的初始猜测。
    • $P(B|A)$(似然度):如果假设 $A$ 是真的,那么证据 $B$ 出现的可能性有多大。
    • $P(B)$(标准化常量):证据 $B$ 在所有情况下出现的总概率。

    可以把贝叶斯公式看成一个“认知自动机”

    初始信念 ($P(A)$) + 新的证据 ($P(B|A)$) $\rightarrow$ 更新后的信念 ($P(A|B)$)

    数据之间的关系

    搜索排序

    当我们使用Google进行搜索的时候,通常在搜索的前三个结果里就能找到自己想要的网页内容,而且很大概率第一个结果就是我们想要的网页。而排名越往后,搜索结果与我期望的偏差越大。并且在搜索结果页的上面,会提示总共找到多少个结果。

    那么Google为什么能在十几万的网页中知道我最想看的网页是哪些,然后把这些页面排到最前面呢?

    答案是Google使用了一种叫PageRank的算法,这种算法根据网页的链接关系给网页打分。如果一个网页A,包含另一个网页B的超链接,那么就认为A网页给B网页投了一票,PageRank算法核心包括数量质量

    这样经过一次计算后,每个页面的PageRank分值就会重新分配,重复同样的算法过程,经过几次计算后,根据每个页面PageRank分值进行排序,就得到一个页面重要程度的排名表。根据这个排名表,将用户搜索出来的网页结果排序,排在前面的通常也正是用户想要的结果。

    但是这个算法还有个问题,如果某个页面只包含指向自己的超链接,这样的话其他页面不断给它送分,而自己一分不出,随着计算执行次数越多,它的分值也就越高,这显然是不合理的。这种情况就像下图所示的,A页面只包含指向自己的超链接。

    Google的解决方案是,设想浏览一个页面的时候,有一定概率不是点击超链接,而是在地址栏输入一个URL访问其他页面,表示在公式上,就是

    上面$(1-\alpha)$就是跳转到其他任何页面的概率,通常取经验值0.15(即$\alpha$ 为0.85),因为有一定概率输入的URL是自己的,所以加上上面公式最后一项,其中分母4表示所有网页的总数。

    那么对于$N$个网页,任何一个页面$P_{i}$的PageRank计算公式如下

    公式中,$P{j}\in M(P{i})$表示所有包含有$P{i}$超链接的$P{j}$,$L(P{j})$表示$P{j}$页面包含的超链接数,$N$表示所有的网页总和。

    由于Google要对全世界的网页进行排名,所以这里的N可能是一个万亿级的数字,一开始将所有页面的PageRank值设为1,带入上面公式计算,每个页面都得到一个新的PageRank值。再把这些新的PageRank值带入上面的公式,继续得到更新的PageRank值,如此迭代计算,直到所有页面的PageRank值几乎不再有大的变化才停止。

    关联分析

    在深入关联分析前,需要先了解两个基本概念,一个是支持度,一个是置信度

    支持度是指一组频繁模式的出现概率,比如(啤酒,尿不湿)是一组频繁模式,它的支持度是4%,也就是说,在所有订单中,同时出现啤酒和尿不湿这两件商品的概率是4%。

    置信度用于衡量频繁模式内部的关联关系,如果出现尿不湿的订单全部都包含啤酒,那么就可以说购买尿不湿后购买啤酒的置信度是100%;如果出现啤酒的订单中有20%包含尿不湿,那么就可以说购买啤酒后购买尿不湿的置信度是20%。

    那应该从哪里考虑着手,可以使用最少的计算资源寻找到最小支持度的频繁模式?寻找满足最小支持度的频繁模式经典算法是Apriori算法,Apriori算法的步骤是:

    第1步:设置最小支持度阈值。

    第2步:寻找满足最小支持度的单件商品,也就是单件商品出现在所有订单中的概率不低于最小支持度。

    第3步:从第2步找到的所有满足最小支持度的单件商品中,进行两两组合,寻找满足最小支持度的两件商品组合,也就是两件商品出现在同一个订单中概率不低于最小支持度。

    第4步:从第3步找到的所有满足最小支持度的两件商品,以及第2步找到的满足最小支持度的单件商品进行组合,寻找满足最小支持度的三件商品组合。

    第5步:以此类推,找到所有满足最小支持度的商品组合。

    Apriori算法极大地降低了需要计算的商品组合数目,这个算法的原理是,如果一个商品组合不满足最小支持度,那么所有包含这个商品组合的其他商品组合也不满足最小支持度。所以从最小商品组合,也就是一件商品开始计算最小支持度,逐渐迭代,进而筛选出所有满足最小支持度的频繁模式。

    通过关联分析,可以发现看似不相关商品的关联关系,并利用这些关系进行商品营销,一方面可以为用户提供购买便利;另一方面也能提高企业营收。专栏下一期还会讲到更多发现用户兴趣进行推荐的算法。

    聚类

    分类算法主要解决如何将一个数据分到几个确定类别中的一类里去。分类算法通常需要样本数据训练模型,再利用模型进行数据分类,那么一堆样本数据又如何知道各自的类别呢?样本数据归类一方面可以通过人工手动打标签,另一方面也可以利用算法进行自动归类,即所谓的“聚类”

    K-means是一种在给定分组个数后,能够对数据进行自动归类,即聚类的算法。

    第1步:随机在图中取K个种子点,图中K=2,即图中的实心小圆点。

    第2步:求图中所有点到这K个种子点的距离,假如一个点离种子点X最近,那么这个点属于X点群

    第3步:对已经分好组的两组数据,分别求其中心点。对于图中二维平面上的数据,求中心点最简单暴力的算法就是对当前同一个分组中所有点的X坐标和Y坐标分别求平均值,得到的就是中心点。

    第4步:重复第2步和第3步,直到每个分组的中心点不再移动。这时候,距每个中心点最近的点数据聚类为同一组数据。

    K-means算法原理简单,在知道分组个数的情况下,效果非常好,是聚类经典算法。通过聚类分析我们可以发现事物的内在规律:具有相似购买习惯的用户群体被聚类为一组,一方面可以直接针对不同分组用户进行差别营销,线下渠道的话还可以根据分组情况进行市场划分;另一方面可以进一步分析,比如同组用户的其他统计特征还有哪些,并发现一些有价值的模式。

    预测用户的喜好

    在用户对自己需求相对明确的时候,可以用搜索引擎通过关键字搜索很方便地找到自己需要的信息。但有些时候,搜索引擎并不能完全满足用户对信息发现的需求。一方面,用户有时候其实对自己的需求并不明确,期望系统能主动推荐一些自己感兴趣的内容或商品;另一方面,企业也希望能够通过更多渠道向用户推荐信息和商品,在改善用户体验的同时,提高成交转化率,获得更多营收。而这中间发现用户兴趣和喜好的就是推荐引擎

    主要就是依靠各种推荐算法,常用的推荐算法有:基于人口统计的推荐基于商品属性的推荐基于用户的协同过滤推荐基于商品的协同过滤推荐

    基于人口统计的推荐

    基于用户的属性进行分类,然后根据同类用户的行为进行推荐

    基于人口统计的推荐是相对比较简单的一种推荐算法,根据用户的基本信息进行分类,然后将商品推荐给同类用户。

    这种推荐算法也不依赖商品的数据,和要推荐的领域无关,不管是服装还是美食,不管是电影还是旅游目的地,都可以进行推荐,甚至可以混杂在一起进行推荐。

    当然也正因为这种推荐算法比较简单,对于稍微精细一点的场景,推荐效果就比较差了。因此,在人口统计信息的基础上,根据用户浏览、购买信息和其他相关信息,进一步细化用户的分类信息,给用户贴上更多的标签,比如家庭成员、婚姻状况、居住地、学历、专业、工作等,即所谓的用户画像,根据用户画像进行更精细的推荐,并进一步把用户喜好当做标签完善用户画像,再利用更完善的用户画像进行推荐,如此不断迭代优化用户画像和推荐质量。

    基于商品属性的推荐

    基于商品属性的推荐则是将商品的属性进行分类,然后根据用户的历史行为进行推荐。

    例如电影A和电影D有相似的属性,被划分为同类商品,如果用户A喜欢电影A,那么就可以向用户A推荐电影D,比如给喜欢《星球大战》的用户推荐《星际迷航》。一般来说,相对于基于人口统计的推荐,基于商品属性的推荐会更符合用户的口味,推荐效果相对更好一点。

    但是基于商品属性的推荐需要对商品属性进行全面的分析和建模,难度相对也更大一点,在实践中,一种简单的做法是提取商品描述的关键词和商品的标签作为商品的属性。此外,基于商品属性的推荐依赖用户的历史行为数据,如果是新用户进来,没有历史数据,就没有办法进行推荐了,即存在“冷启动”问题。

    基于用户的协同过滤推荐

    基于用户的协同过滤推荐是根据用户的喜好进行用户分类,常用的就是KNN算法,寻找和当前用户喜好最相近的K个用户,然后根据这些用户的喜好为当前用户进行推荐

    基于用户的协同过滤推荐和基于人口统计的推荐都是将用户分类后,根据同类用户的喜好为当前用户进行推荐。不同的是,基于人口统计的推荐仅仅根据用户的个人信息进行分类,分类的粒度比较大,准确性也较差;而基于用户的协同过滤推荐则根据用户历史喜好进行分类,能够更准确地反映用户的喜好类别,推荐效果也更好一点。今天文章开头举的推荐电影的例子,就是基于用户的协同过滤进行推荐。

    基于商品的协同过滤推荐

    基于商品的协同过滤推荐是根据用户的喜好对商品进行分类,如果两个商品,喜欢它们的用户具有较高的重叠性,就认为它们的距离相近,划分为同类商品,然后进行推荐

    例如,用户A喜欢商品A、商品B和商品D,用户B喜欢商品B、商品C和商品D,那么商品B和商品D的距离最近,划分为同类商品;而用户C喜欢商品B,那么就可以为其推荐商品D。

    商品的分类相对用户的分类更为稳定,通常情况下,商品的数目也少于用户的数目,因此使用基于商品的协同过滤推荐,计算量和复杂度小于基于用户的协同过滤推荐。

    除了上面这些推荐算法,还有基于模型的推荐根据用户和商品数据,训练数学模型,然后进行推荐。在实践中,通常会混合应用多种算法进行推荐,特别是大型电商网站,推荐效果每进步一点,都可能会带来巨大的营收转化,如果你经常在网上购物,肯定也能感受电商网站这些年在推荐方面的巨大进步。

    互联网运营数据指标

    不同的互联网行业关注不同的运营数据,细化来看,复杂的互联网产品关注的运营指标成百上千。但是有一些指标是我们最常用的,这些指标基本反映了运营的核心状态

    新增用户数

    新增用户数是网站增长性的关键指标,指新增加的访问网站的用户数(或者新下载App的用户数),对于一个处于爆发期的网站,新增用户数会在短期内出现倍增的走势,是网站的战略机遇期,很多大型网站都经历过一个甚至多个短期内用户暴增的阶段。新增用户数有日新增用户数、周新增用户数、月新增用户数等几种统计口径。

    用户留存率

    新增的用户并不一定总是对网站(App)满意,在使用网站(App)后感到不满意,可能会注销账户(卸载App),这些辛苦获取来的用户就流失掉了。网站把经过一段时间依然没有流失的用户称作留存用户,留存用户数比当期新增用户数就是用户留存率。

    1
    用户留存率 = 留存用户数 / 当期新增用户数

    计算留存有时间窗口,即和当期数据比,3天前新增用户留存的,称作3日留存;相应的,还有5日留存、7日留存等。新增用户可以通过广告、促销、病毒营销等手段获取

    一般说来,3日留存率能做到40%以上就算不错了。和用户留存率对应的是用户流失率。

    活跃用户数

    用户下载注册,但是很少打开产品,表示产品缺乏黏性和吸引力。活跃用户数表示打开使用产品的用户数,根据统计口径不同,有日活跃用户数、月活跃用户数等。提升活跃是网站运营的重要目标,各类App常用推送优惠促销消息给用户的手段促使用户打开产品。

    PV

    打开产品就算活跃,打开以后是否频繁操作,就用PV这个指标衡量,用户每次点击,每个页面跳转,被称为一个PV(Page View)。PV是网页访问统计的重要指标,在移动App上,需要进行一些变通来进行统计。

    GMV

    GMV即成交总金额(Gross Merchandise Volume),是电商网站统计营业额(流水)、反映网站营收能力的重要指标。和GMV配合使用的还有订单量(用户下单总量)、客单价(单个订单的平均价格)等。

    转化率

    转化率是指在电商网站产生购买行为的用户与访问用户之比

    1
    转化率 = 有购买行为的用户数 / 总访问用户数

    用户从进入网站(App)到最后购买成功,可能需要经过复杂的访问路径,每个环节都有可能会离开:进入首页想了想没什么要买的,然后离开;搜索结果看了看不想买,然后离开;进入商品详情页面,看看评价、看看图片、看看价格,然后离开;放入购物车后又想了想自己的钱包,然后离开;支付的时候发现不支持自己喜欢的支付方式,然后离开…一个用户从进入网站到支付,完成一笔真正的消费,中间会有很大概率流失,网站必须要想尽各种办法:个性化推荐、打折促销、免运费、送红包、分期支付,以留住用户,提高转化率

    A/B测试与灰度发布

    A/B测试将每一次测试当作一个实验。通过A/B测试系统的配置,将用户随机分成两组(或者多组),每组用户访问不同版本的页面或者执行不同的处理逻辑,即运行实验。通常将原来产品特性当作一组,即原始组新开发的产品特性当作另一组,即测试组。

    经过一段时间(几天甚至几周)以后,对A/B测试实验进行分析,观察两组用户的数据指标,使用新特性的测试组是否好于作为对比的原始组,如果效果比较好,那么这个新开发的特性就会在下次产品发布的时候正式发布出去,供所有用户使用;如果效果不好,这个特性就会被放弃,实验结束。

    image-20260112225711967

    A/B测试的系统架构

    A/B测试系统最重要的是能够根据用户ID(或者设备ID)将实验配置参数分发给应用程序,应用程序根据配置参数决定给用户展示的界面和执行的业务逻辑

    在实验管理模块里进行用户分组,比如测试组、原始组,并指定每个分组用户占总用户的百分比;流量分配模块根据某种Hash算法将用户(设备)分配到某个实验组中;一个实验可以有多个参数,每个组有不同的参数值。

    移动App在启动后,定时和A/B测试系统通信,根据自身用户ID或者设备ID获取自己参与的A/B测试实验的配置项,根据配置项执行不同的代码,体验不同的应用特性。应用服务器和A/B测试系统在同一个数据中心,获取实验配置的方式可以更灵活。

    移动App和应用服务器上报实验数据其实就是传统的数据采集,但是在有A/B测试的情况下,数据采集上报的时候需要将A/B测试实验ID和分组ID也上报,然后在数据分析的时候,才能够将同一个实验的不同分组数据分别统计,得到A/B测试的实验数据报告

    灰度发布

    经过A/B测试验证过的功能特性,就可以发布到正式的产品版本中,向所有用户开放。但是有时候在A/B测试中表现不错的特性,正式版本发布后效果却不好。此外,A/B测试的时候,每个功能都应该是独立(正交)的,正式发布的时候,所有的特性都会在同一个版本中一起发布,这些特性之间可能会有某种冲突,导致发布后的数据不理想。

    解决这些问题的手段是灰度发布,即不是一次将新版本发布给全部用户,而是一批一批逐渐发布给用户。在这个过程中,监控产品的各项数据指标,看是否符合预期,如果数据表现不理想,就停止灰度发布,甚至进行灰度回滚,让所有用户都恢复到以前的版本,进一步观察分析数据指标。

    灰度发布系统可以用A/B测试系统来承担,创建一个名叫灰度发布的实验即可,这个实验包含这次要发布的所有特性的参数,然后逐步增加测试组的用户数量,直到占比达到总用户量的100%,即为灰度发布完成。

    灰度发布的过程也叫作灰度放量,灰度放量是一种谨慎的产品运营手段。对于Android移动App产品而言,因为国内存在很多个应用下载市场,所以即使没有A/B测试系统,也可以利用应用市场实现灰度发布。即在发布产品新版本的时候,不是一次在所有应用市场同时发布,而是有选择地逐个市场发布。每发布一批市场,观察几天数据指标,如果没有问题,继续发布下一批市场。

    -------------本文结束感谢您的阅读-------------
    感谢阅读.

    欢迎关注我的其它发布渠道