写点什么

架构师训练营 1 期第 6 周:技术选型(二) - 总结

用户头像
piercebn
关注
发布于: 2020 年 11 月 01 日

本文主要介绍分布式关系数据库实现方式、CAP原理及NoSQL数据库架构、分布式一致性算法和相关产品Zookeeper、搜索引擎架构,以便我们在实际分布式场景中应用的同时,了解其工作原理和解决问题的思路。



一、分布式关系数据库实现方式

MySQL复制

MySQL主从复制:实现读写分离

  • 分布式关系数据库最简单的就是主从复制,数据库通过主从复制实现读写分离

  • 用户发起命令提交到主服务器上,命令会更新到本地数据集中,同时记录Binlog,然后从服务器上的Log获取线程会不断的获取主服务器中binlog数据并同步到从服务器中的Relay Log上,最终从服务器上的SQL执行线程将重复执行Relay Log中的命令并保存到从服务器的本地数据集中,实现了主从复制,主服务器上的数据和从服务器上的就完全数据一致了,即实现了主服务器上写入数据,从服务器上可以读到一致的数据

MySQL一主多从复制:实现读的高可用

  • 将一台主服务器的数据,通过Binlog和Relay Log的同步的方式同步到多台从服务器上

  • 多个读从服务器可以承担不同的职责,比如,一台承担业务的读操作;一台专门给数据分析师使用,提交一些计算难度和工作量比较大的SQL命令;一台可以专门做数据复制备份用,什么也不做,万一主服务器或其他从服务器故障或丢失数据时,保留一份完整的备份记录,从而也实现了读操作的高可用

一主多从复制的优点

  • 分摊负载

  • 专机专用

  • 便于冷备

  • 高可用:读的高可用

MySQL主主复制:实现写的高可用

  • 两台主服务器,任意一台服务器上的数据变更,都会通过Binlog和Relay Log同步的方式,同步到另外一台服务器上去,就可以实现写操作的高可用,当一台主服务器宕机时,就可以把写操作迁移到另一台主服务器上进行

  • 一般情况下,实现主主服务的集群,针对集群中的主服务器同样也会构建自己的主从复制集群实现主从复制,从而实现读写的高可用

MySQL主主失效恢复

MySQL主主失效的维护过程

MySQL复制注意事项

  • 主主复制的两个数据库不能并发写入。避免引起写入逻辑错误导致数据冲突不一致。另一台主服务器作为写入备份使用,当工作的主服务器宕机时,通过专门的切换过程控制,切换到写入备份服务器工作。不允许进行同时写操作。

  • 复制只是增加了数据的读并发处理能力没有增加写并发能力存储能力

  • 更新表结构会导致巨大的同步延迟。导致其他数据操作同步就会被停滞,可能导致主从数据库中的数据严重不一致。实践中,是关闭自动表结构同步,通过人工方式实现表结构的同步。

数据分片

数据分片实现关系数据库的高并发写能力和提升存储能力

  • 将一张数据库的表,分成若干片,分别存储到不同数据库上,将数据的写操作和存储分布到多台数据库服务器上,从而实现分布式数据库通过增加服务器,提升并发处理能力的设计目标

数据分片实现方式

  • 硬编码实现数据分片:代码中硬编码选择分片,将数据写入不同的服务器

  • 映射表外部存储:专门的存储用户id和服务器的映射关系,写入时查找映射表,找到对应服务器

数据分片的挑战

  • 需要大量的额外代码处理逻辑因此变得更加复杂。分片代码和处理逻辑代码在一起管理,管理困难。

  • 无法执行多分片的联合查询。

  • 无法使用数据库的事务。

  • 随着数据的增长,如何增加更多的服务器。增加更多服务器涉及数据迁移,分片逻辑变更问题,会带来挑战

  • 常见的实现方式是使用分布式关系数据库中间件来完成,将相关分片选择和分发的逻辑提取出来单独处理。



分布式数据库中间件

  • MyCat

  • 应用程序连接分布式数据库中间件,中间件负责进行分片逻辑的转换,将数据库操作分发给不同的数据服务器去处理,从而实现的数据库的分片操作。

  • 应用程序不用关系分片逻辑,像连接普通数据库一样连接MyCat中间件,由MyCat完成数据分片处理。

  • Cobar架构

  • Cobar系统组件模型



  • SQL解析模块:解析SQL语句中的分片字段和值

  • SQL路由模块:根据解析出来的分片字段计算找到执行服务器的IP地址列表

  • SQL执行代理模块:将SQL提交到映射的一个或多个数据库服务器上

  • 结果合并模块:对服务器返回的结果数据集进行合并,通过通讯模块返回给应用程序

  • 路由配置示例

  • 配置表的路由规则,指定表名,schema,数据库服务器连接列表,分片字段和相关分片逻辑表达式,对应访问的数据库连接

  • 如何做集群伸缩

  • 涉及到数据迁移,实践的一种做法是,在一开始设计分片逻辑时,就对一个较大的数字取模,假设我们长期目标是100服务器,我们就对100取模,配置100条分片规则,对应访问的连接也是100个连接,但是存储的服务器只是两台,即启动100个数据库实例,运行在两台服务器上,当集群进行伸缩的时候,我们只需要把数据库连接的对应的服务器IP地址改变就可以了,规则不需要改变。

  • 首先,添加一个新的服务器到集群中

  • 然后,从集群中每个服务器上选择指定数量的实例,通过主从复制的方式,迁移到新加入的服务器

  • 最后,当新老服务器上的数据库同步完成数据一致后,再修改配置表里的对应的迁移实例的IP地址,数据就可以写入新的服务器里去了,查找也从新的服务器上查找

  • 这种迁移方式,要求在整个分布式数据库建立起来的时候,就规划好最终服务器集群数量,针对最终服务器数量进行分配规则配置,如果想再增加就没办法了

数据库部署方案

  • 单一服务与单一数据库

  • 主从复制实现伸缩

  • 两个Web服务及两个数据库:按照业务进行数据库分布部署,也叫业务分库

  • 综合部署:包括业务分库、主从复制和数据分片的综合部署方式,根据各业务需求和数据压力分别进行对应具体的部署

二、CAP原理及NoSQL数据库架构

关系数据库本身的设计并不是为分布式场景使用的,我们为了在关系数据库上,应用分布式的一些特性,实现集群伸缩,使用了各种手段,但是这些手段在使用过程中会有些别扭,为了更加原生的支持数据存储的分布式特性,提出了另外一种存储方案,对应的数据库为NoSQL数据库,对分布式的支持更加友好。

CAP原理

CAP原理主要关注的是,在一个分布式系统中,系统的数据一致性(Consistency)、可用性(Availability)和分区耐受性(Partition tolerance),这三个特性之间的关系。

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

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

  • 分区耐受性(Partition tolerance):分区耐受性说,即使因为网络原因,部分服务器节点之间消息丢失或者延迟了,系统依然应该是可以操作的(The system continues to operate despite an arbitrary number of  messages being dropped (or delayed) by the network between nodes)。在一个分布式系统中,我们不能保证分区总是可用,也就是说分区耐受性是必然出现的。

  • 当网络分区失效发生的时候,也就是网络不可用的时候,我们要么取消操作,即不返回数据或不写入数据,这样获取的数据就是一致的,但是系统却不可用;要么我们继续写入数据或返回老数据,但是数据的一致性就得不到保证。如果选择了一致性,系统就可能返回一个错误码或者干脆超时,即系统不可用。如果选择了可用性,那么系统总是可以返回一个数据,但是并不能保证这个数据是最新的。

  • 因此CAP原理讲的是,在保证系统分区耐受性的前提下,我们要么接受数据不一致,要么接受系统不可用,数据一致性和系统可用性只能二选一,二者无法同时满足。



CAP原理与数据一致性冲突

  • 当网络通讯发生故障,系统实现了分区障碍时,节点A和节点B,要不两个节点都可用但是数据不一致,要不有一个节点不可用保持单一节点数据一致性

最终一致性:在一致性上进行改进

  • 当我们对服务器进行更新操作的时候,当客户端更新了数据库1的数据,数据库2的数据和数据库1的数据在一段时间内可能不一致,当网络延迟恢复,数据同步完成后,最终数据库2和数据库1的数据是一致的

  • 在服务器数据最终一致之前,用户通过客户端如何获取最终一致的数据,做到用户的最终一致性?

最终一致性写冲突

  • 简单冲突处理策略:在服务端实现,根据时间戳,进行写覆盖(暂不考虑覆盖合理性)

  • 客户端2的更新操作的时间戳更新,客户端2的操作覆盖客户端1的操作,最终的数据以客户端2为主

  • 客户端冲突解决:客户端自己实现冲突的数据处理或合并

  • 投票解决冲突(Cassandra)

  • 写数据时,数据写入三个节点,其中两个节点数据写入成功,就认为写入成功

  • 读数据时,从三个节点读,其中有两个节点返回数据,就认为得到了数据,根据两个节点返回的数据进行比较决定最新数据是什么,如果故障节点又恢复了,三个节点都返回数据,根据投票进行决定最新数据是什么,比如两个节点数据一致,一个节点数据不一致,会根据投票结果把不一致的节点数据淘汰,选择一致的数据作为最新数据,认为投票多的数据是最终结果

Cassandra分布式架构

  • 多个服务器节点构建成了集群

  • 写操作时,客户端连接时,可连接到任何一个节点上去,把数据写入这个节点,由这个节点去计算,写入的数据应该存储在哪台服务器上,然后把这个操作扩散到其他服务器上去,写入到三个节点,其中两个节点写入成功,就可以返回写成功

  • 读操作也一样,连接到一个服务器节点,由这个节点计算,这样的读操作应该在哪些服务器节点上产生返还,把读操作发给三个服务器节点,三个服务器节点返回数据以后,通过投票来决定最终返回值是什么,其中两个返回数据一致,就可以返回数据读成功

  • 即使有节点失效,网络宕机,Cassandra也可以实现一个最终一致性,数据返回是一个正确的结果,同时也是可用的

  • Cassandra并没有解决CAP原理问题,是符合CAP原理的,当网络分区失效或投票不够选出的时候,比如两个服务器节点宕机时,无法投票选出结果,实际上存储在服务器节点里的数据本身也是不一致的,它只是最终返回给用户的数据是一致的。尽量降低CAP原理对客户端用户操作产生的影响,在满足可用性的前提下,尽量保证数据的一致性

HBase架构

HBase架构如下:

  • HBase的低层文件存储是通过Hadoop的HDFS分布式分布式文件系统来实现的,数据的复制,以及文件系统的高可用,是由Hadoop来支撑的,HBase只要解决数据的分布式存储就可以了

  • HMaster主要做数据分片路由选择的

  • HRegionServer是真正的服务器实例,应用程序通过和HRegionServer通讯去读写数据,HRegionServer里逻辑的区分了若干个HRegion

HBase整个执行过程是:

  • 应用程序想要去读写一个数据的时候,这些数据通常是key-value这样的结构,提供一个key,HMaster会这个key对应服务器的HRegionServer是哪一台,HMaster根据分区范围记录着不同的key的分区对应的HRegionServer是哪些。

  • 如果HMaster宕机的话,整个应用程序就无法通过key去选择HRegionServer了,所以需要有多个HMaster来提供这种数据分片路由选择的实现,多个路由选择如何去最终统一一致的对外提供服务呢?它们还需要一个Zookeeper进行当前的主HMaster的选择

  • 关于Zookeeper工作原理,以及HMaster为什么需要有多个服务器,而一个时间之内,只有一个主服务器对外提供服务,会在后面关于分布式一致性的内容时进行说明

  • 应用程序通过HMaster去返回HRegionServer,应用程序就可以和HRegionServer进行通讯,将请求数据写入到HRegionServer对应的某个HRegion中,HRegion会将数据最终写入到Hadoop分布式文件系统里

  • 应用程序通过Zookeeper返回一个HMaster地址,然后给HMaster输入key,HMaster会返回HRegionServer地址,应用程序就直接和HRegionServer通讯,提供key去查询数据,HRegionServer去访问对应的HRegion处理,返回最终的数据

关注点

  • 关注点1:NoSQL系统对数据的集群伸缩非常友好的,在这样的集群里添加服务器,删除服务器都是非常容易的

  • Cassandra集群中添加一个服务器,随便启动一个服务器,向集群中注册,集群中根据路由算法就会写一部分数据进来,任何一台服务器失效都没有关系,因为数据是写三份的,丢一份数据对系统没有什么影响,可以继续对外提供服务,同时丢失的这台服务器上的数据,还有其他的服务器上面,会迁移一部分数据过来,所以集群的伸缩在Cassandra里面很容易

  • HBase自己并不存储数据,所有的数据都是存储在更低层Hadoop分布式文件系统里的,真正要去读写数据的时候,它是通过HDFS去进行读写的,当HRegionServer不够用的时候,需要进行伸缩的时候,就需要加一台服务器把相关的一些key分配给新的HRegionServer就可以了,而HRegionServer只需要去访问对应的HDFS文件系统就好了,甚至在HBase的架构里面,都不会涉及到数据的迁移,因为HRegionServer本身不存取数据的,它去读对应的HDFS里面的数据,HDFS存储的数据不变,增加一个服务器去读HDFS就可以了

  • 关注点2:HBase的数据存储,牺牲了部分读性能,用来大幅提升写性能

  • 存储在一个叫做Log结构的合并数(Log Structed Merge Tree,LSM树)上的

  • 对应关系数据库中的B+树,B+树是随机进行读写操作的

  • 而LSM树是以Log结构顺序进行写入的,依然是一种树的结构,因为传统的磁盘在进行Log格式写入的时候,连续的进行数据写入的时候,它的效率会更高,利用这个特性HBase使用了LSM进行数据写入

  • 数据在内存中先以树的形式进行存储,当它超出内存范围的时候它会把它合并到硬盘中,而硬盘中的树以多棵树的方式存在,当每棵树的规模比较大的时候,它会进一步的进行合并,最终构建成一个Log结构的一棵大树,这棵树存储着NoSQL里存储的数据

  • 读取的时候稍微麻烦,需要合并磁盘中历史数据和内存中最近修改操作,读取数据时需要先看是否命中内存,否则需要访问较多的磁盘文件,所以读性能相比关系数据库降低了,随着磁盘中的树定期合并,合并成一棵大树,读性能可以得到了一定的优化。

关于事务处理:ACID与BASE

传统关系数据库事务处理能力比较强,叫ACID

  • 原子性(Atomicity): 事务要么全部完成,要么全部取消。 如果事务崩溃,状态回到事务之前(事务回滚)。

  • 隔离性(Isolation): 如果2个事务T1和T2同时运行,事务T1和T2最终的结果是相同的,不管T1和T2谁先结束,隔离性主要依靠锁实现。

  • 持久性(Durability): 一旦事务提交,不管发生什么(崩溃或者出错),数据要保存在数据库中。

  • 一致性(Consistency): 只有合法的数据(依照关系约束和函数约束)才能写入数据库。

NoSQL数据库的事务处理能力,叫BASE

  • 基本可用(Basically Available)系统在出现不可预知故障时,允许损失部分可用性,如响应时间上的损失或功能上的损失。

  • Soft state(弱状态)软状态,指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。

  • Eventually consistent(最终一致性)指系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态,因此最终一致性的本质是需要系统保证数据能够达到一致,而不需要实时保证系统数据的强一致性。

他们的事务处理能力和策略是完全不一样的

三、分布式一致性算法和相关产品Zookeeper

分布式一致性问题

主服务器选择的问题

  • 当关系数据库使用主主复制的时候,哪一个主服务器才是我们当前应该访问的主服务器呢?主主复制并不是两个服务器都能够同时对外提供写操作,在一个时间范围之内,只有一个主服务器对外提供服务,哪个才是当前活动的主服务器呢?我们需要进行选择。

  • 在讲NoSQL HBase的时候,也讲到了HMaser可能有多个,我们只能有一个HMaster是活动的,并对外提供服务,其他的HMaster是备份的,主要是为了提供可用性,当主服务器宕机的时候,其他的服务器HMaster可以提供同样的服务,以此来提高系统的可用性,但是哪一个服务器是我们当前应该选择的呢?这是分布式系统中比较重要的一个点,这种问题就是分布式一致性问题。



分布式系统脑裂

  • 在一个分布式系统中,不同服务器获得了互相冲突的数据信息或者执行指令,导致整个集群陷入混乱,数据损坏,称作分布式系统脑裂。

分布式一致性问题的解决方案

数据库主主备份场景下的解决方案:

  • 需要专门的应用,进行判断选择主服务器,决定主服务器的那个应用本身也需要是高可用的,高可用的策略就是,要把应用部署在多个服务器上,任何一个服务器停机,都不会影响系统提供服务。

  • 主服务器选择的应用如果部署多个服务器,该如何保证它的服务器之间数据一致呢?这是分布式系统中的一个难点,如何保证分布式系统高可用的情况下,有多台服务器,这些服务器又是保持数据状态一致的?我们的解决方案的主要产品就是Zookeeper,这样一个分布式一致性的应用服务

分布式一致性算法Paxos

  • 三个角色:Proposer提案者、Acceptor接受者、Learner学习者

  • 当我们想要去修改某个状态,比如要选择某个服务器是Master,那么A服务Proposer提出来,我要成为主服务器,B服务也向Proposer提出来,我是主服务器,Proposer就会向Accepter提出投票请求,让Accepter决定由哪个服务器成为主服务器,Accepter收到了这些请求以后,就会投票,只能投出一个来,最后,得到投票多的那个向Proposer提交请求的服务(比如A服务)会成为主服务器,这个决策一旦定下来,它们之间就全部同意,把它发给Learner说,我们已经决定了让A服务成为主服务器,Learner就知道A服务是主服务器,B服务的提案就被驳回了,所有客户端都通过Learner去问,当前的主服务器是哪个,Learner就会返回是A服务,所有客户端就得到A服务是主服务器。

  • 在角色里面,比如说我们有多个Accepter,其中一个Accepter宕机了,只要剩下的Accepter投票能够投出来,最终的结果还是一致的,所以这里面分布式一致性算法都有一个投票的过程,由投票来决定最终的状态,或者某一个数据的状态是什么样子的,通过投票来决定最终一致性,这个一致在整个集群中是统一的。

  • 通过分布式一致性算法,既实现了高可用,又实现了一致性,多个服务器之间状态是一致的,是通过投票来进行选择和决定的。

  • 事实上Paxos算法部署的时候,通常一台服务器既是一个Proposer,又是一个Accepter,又是一个Learner,一个客户端程序连接任何一台服务器,那么这台服务器就变成Proposer了,这台服务器会向所有其他的服务器(Accepter角色)发起提案,大家一起来投票,要不要同意这个提案,同意这个提案,决定下来,就交给Learner,所有的服务器也都是一个Learner,所有服务器接收到这个信息,最后投票出来的结果是个决定,大家就都同意了,因为有投票的过程,其中任何一台服务器宕机,都不会影响到最终的一致性

  • Proposer提交的提案请求是以排队的方式逐一进行的,如果前一个提案通过了,后一个提案和前一个提案有冲突,就会被驳回,不会同时有两个提案在被投票,这样保证这个集群投票过程中它的状态的一致性,以及状态检查的一致性,能够正确的进行投票。

  • 在分布式系统应用场景中,这种加锁、选主(选择Master),要求统一一致的场景,通过这样一个提案Accepter,完成它的一致性决策

  • 三个阶段:Prepare,Accept,Learn

  • 第一阶段:Prepare 阶段。Proposer 向 Acceptors 发出 Prepare 请求,Acceptors 针对 收到的 Prepare 请求进行 Promise 承诺。

  • 第二阶段:Accept 阶段。Proposer 收到多数 Acceptors 承诺的 Promise 后,向 Acceptors 发出 Propose 请求,Acceptors 针对收到的 Propose 请求进行 Accept 处理。

  • 第三阶段:Learn 阶段。Proposer 在收到多数 Acceptors 的 Accept 之后,标志着本次 Accept 成功,决议形成,将形成的决议发送给所有 Learners。

  • Proposer 生成全局唯一且递增的 Proposal ID (可使用时间戳加Server ID),向所有 Acceptors 发送 Prepare 请求,这里无需携带提案内容,只携带 Proposal ID 即可。 Acceptors 收到 Prepare 和 Propose 请求后

  • 不再接受 Proposal ID 小于等于当前请求的 Prepare 请求。

  • 不再接受 Proposal ID 小于当前请求的 Propose 请求。

  • Paxos协议是比较知名且比较早的一个分布式一致性协议,但在实践中Paxos算法相对比较复杂一点,整个的开发复杂度也比较大,在Zookeeper中并没有直接使用Paxos协议,对Paxos协议做了一些简化改造,核心思路依然是通过投票来决定状态的一致性。Zookeeper使用的是Zap协议。

Zap协议与Zookeeper

  • 角色:Leader和Follower两种角色

  • 请求提交给Leader,由Leader向所有Follower发出Propose提案,Follower自己去决定提案能不能接受,如果接受的话就ACK确认,Leader接收到多数Follower的ACK确认以后,就通知Follower去Commit刚才的提案,然后确认把它更新到状态里去,然后所有的Follower的状态就一致了,其他的请求进入Follower的时候,不管连接的哪个Follower,得到的数据状态都是一致的。通过这种简化过程,也可以实现数据的一致性。

  • Zookeeper集群中,有一个Leader,其他的都是Follower,客户端程序连接任何一个Follower,提交一个Request请求,Follower收到这个Request请求以后,把这个Request提交给了Leader,Leader把这个Request构建成一个提案,发送给所有的Follower,所有的Follower投票来决定是不是要接收,如果要接收就发送一个ACK,Leader收到多数投票以后,就决定提案通过了,发送一个Commit对刚才的提案进行确认,Follower收到确认后,会把它的提案状态更新到服务器里去,这样的话所有的Follower状态就一致了。应用程序连接任意一个Follower都可以得到状态一致的内容。

Zookeeper架构与应用场景

  • Zookeeper架构,具体部署如下,有5个服务器,一个Leader,四个Follower,本身高可用,通过投票确定数据状态一致性

  • Zookeeper的树状记录结构

  • 通过树状结构记录数据,叶子节点最终路径里面进行数据值的记录

  • Zookeeper API

  • Zookeeper应用场景

  • 配置管理:管理key-value配置项

  • 选Master:MySQL主主复制选主

  • 集群管理(负载均衡与失效转移)

  • Zookeeper的性能

  • 大部分都是写的时候的性能,大大低于读的性能

  • 服务器越多,读的性能越好,写的性能越差

四、搜索引擎架构

互联网搜索引擎的整体架构

整体架构

  • 通过网络爬虫把内容爬到,然后把内容去重存储起来,基于这些页面内容构建倒排索引,计算每个页面的链接关系,给每个页面进行打分,基于倒排索引和连接关系,进行检索,构建排序后的页面内容,根据搜索词把页面内容返回给用户。

  • 全网搜索引擎主要分为两个部分:网络爬虫,倒排索引,以及相关的检索模块

爬虫系统架构

  • 通过种子URL去访问互联网,获取页面内容,首先进行页面内容存储,然后解析页面中包含的其他超链接,把解析出的URL放入到待下载URL队列,网络下载从待下载URL获取链接,去互联网下载网页内容,然后存储页面内容,同时对已经爬取过的URL放入已下载URL集合,每次爬取时检查是否已下载过,如果已经下载过,就不进行下载了。不断地下载、解析、待下载URL、下载。

  • 如果说所有的网络都是互连的,所有的页面总有一个链接指向它,那我们通过有限的几个种子URL,通过一步步的链接、下载、解析,就可以获得全网的所有的互联网页面内容。

  • 爬虫禁爬协议:大家共同遵守的一个约定

文档矩阵与倒排索引

  • 爬下来的内容是万亿级规模,如何快速的搜索到万亿级规模里面我想要的内容呢?搜索引擎主要是通过一个倒排索引实现的。

  • 有倒排索引,也就有所谓的正排索引,正排索引是文档里面包含了哪些词,文档对应的词有哪些,倒排索引反过来,是词对应的文档有哪些

  • 建立文档和词的矩阵

  • 根据文档矩阵,构建倒排索引。倒排索引记录着,词和出现这个词的文档列表有哪些

  • 快速搜索。比如搜索“后端技术”,就可以把这个搜索查询拆分成两个搜索词:后端和技术,出现“后端”的是四个文档,出现“技术”的是三个文档,然后对两个结果进行交集处理,就知道2和4是出现“后端技术”的文档,然后将结果展现给用户。

  • 通过词直接拿到了文档列表,看起来是一个类似哈希表的结果,词也许很多几百万,几千万也没关系,它构建的是一个哈希表,我们拿到词,立刻就去访问到对应的列表,相关的列表进行交集的处理,我们就得到最终的结果页面了,整个处理过程会非常快。即使有几万亿文档也没有关系,我们也可以快速的查找到,我们要查询的搜索词出现在哪些文档中,把这些文档快速的列举给用户。所以搜索引擎可以通过倒排索引非常快的进行搜索查询

文档与倒排索引:记录文档ID

  • 根据文档内容,构建倒排索引。文档越大,内容越多,词越多,性能的优势体现的越明显。

带词频的倒排索引:记录词频数

  • 除了简单的记录下来一个单词在哪些文档中出现这个倒排索引,在倒排索引列表中还可以记录下来出现词频的数目,一个词在一个文档中出现的次数越多,说明这个词对于这个文档越重要。我们可以利用这个词的词频数进行一些排序等操作。

带词频和位置的倒排索引:记录文档出现位置

  • 在文档列表中还可以记录单词在文档中出现的位置。文档中所有的词,通过分词处理以后,总共100个词,位置就有100个编号,每个词出现在编号的哪个位置都记录下来,可以判断检索的单词在文档中是否挨在一起,可以决定排序的优先级更高。

Lucene架构

Lucene架构

  • 应用程序想要利用搜索引擎优化数据访问或数据存储,我们的解决方案目前比较成熟的是Lucene搜索引擎开源项目,它和典型的搜索引擎架构是非常一样的

  • 把数据源构建倒排索引,把查询词在倒排索引中进行查询,最后获得搜索结果。

  • 数据源首先通过一个分析器进行分词,把数据源里的文档内容,通过分词变成一些索引,构建起来倒排索引,倒排索引通过存储存储在一个索引存储里去。

  • 当我们需要查询语句时,我们需要通过分词器将查询语句拆分成一些词,通过查询分析器,提交给搜索模块进行搜索,搜索模块会把这些词查询倒排索引多个词,每个词返回一个倒排列表,然后把倒排列表经过排序、合并,最后返回一个搜索结果

Lucene倒排索引

  • 比如对“elsatic search”、“lucene”、“solr”这三个词构建倒排表

Lucene索引文件准实时更新

  • 倒排索引是所有文档的倒排索引,当我们增加新文档的时候,这个新文件可能会影响到很多个索引的词,这些索引词的列表都需要更新,当我们删除一个文件的时候,也会影响到多个词,当我们一个文档中的内容更新了,也会影响到整个的倒排索引的结构,如果重新把倒排索引构建一次,数据量比较大的情况下计算时间会比较长,性能会比价差,效率会比较低。

  • Lucene如何解决这个问题,当文档更新的时候,可以快速的去做到索引的重新构建。要想快速的重新构建,就不能吧所有的索引重新构建一次,Lucene使用的是分段的概念,只更新一部分的段,加快索引的构建。

  • Lucene引入了的概念,将一个索引文件拆分成多个子文件,每个子文件叫做段,每个段都是一个独立可被搜索的数据集,搜索引擎搜索的时候,是在每个段里分别进行搜索的,把搜索出来的结果进行一些合并,也就是说一个词的倒排索引可能在多个段中出现,对多个段都要进行检索,对检索结果进一步的进行合并处理

  • 当我们想新增一个文档的时候,我们会增加一个新的段,在这个新的段里构建新的索引,这样的话我们就不用对原来的段进行变更,进行修改,进行重建,可以很快的建立一个新段,把新的文档写在新的段中。

  • 删除的时候并不是说把倒排索引中出现的文档直接删掉,而是增加一个.del文件,文件里存储被删除的文档,当我们去索引搜索出来结果列表,拿到一个倒排列表的时候,我们去检查一下这个列表里的内容,是否在.del中出现,如果在.del中出现,说明文档已经被删掉了,把相关文档删掉就可以了,我们并不需要去修改倒排索引。

  • 更新会把它拆分新增删除两部分,先是把它放在del中,然后再新加一个文档id,然后把新的内容当成一个新的文档放在新的段里去,我们也不需要修改原来的段,加快索引的构建速度。

  • 但是这些段,如果不断的增加的话,段太多了,每一次查找都要在所有的段中进行查找,结果还要对所有的段返回的结果进行一些合并,查询效率就比较低,虽然更新速度快了,查询的速度下降了,为了解决这些问题,达到一个好的折中点,所以定期要对这些段进行合并,把删掉的就真的删掉了,更新的就真的更新了,新增的也合并到一个大的段里面,合并完了以后,在原始的段里就可以快速的进行查找。通过不断的合并,减少段的数量,提升查询的性能

  • Lucene的主要问题在于,它不支持分布式集群,Lucene只能用一台服务器构建索引,当我们数据量比较大的时候,一台服务器就不够用了,或当我们需要高可用的时候,这一台服务器宕机的时候,所有的倒排索引内容都丢失了,也不能提供搜索服务了。所以为了提高搜索引擎的性能以及它的可用性,可以处理更大的数据规模,我们现在较常使用的其实是ElasticSearch。

ElasticSearch架构

ElasticSearch架构

  • ElasticSearch的里面包装的是Lucene,只是把Lucene这样一个单机的搜索引擎,包装成一个分布式集群,通过集群的方式对外提供服务

  • ElasticSearch相对Lucene来说它的主要特点

  • 引入了索引的分片,不同的分片存储在不同的服务器上,实现了分布式

  • 4台服务器,8个分片,每台服务器存储2个分片,存储更多的数据内容,实现一个更大规模的搜索引擎集群

  • 索引实现备份,实现高可用

  • 每一个分片有一个自己的备份,这个分片存储在别的服务器上,当一个服务器宕机的时候,比如Node2宕机了,分片2对应的副本2是在Node3里面也有的,Node3还可以对外提供服务,整个数据不会丢失,系统是高可用的

  • 整个访问接口API更加简单,更加高级,用起来更加便利

  • ES分片预分配集群扩容

  • ElasticSearch怎么实现分布式集群的,它是如何进行分片的,它的集群规模如何进行扩容

  • 我们在创建一个索引的时候,就要指定好它的分片数目,指定好后,ES就会为我们构建多个分片,当我们只有一台服务器时,多个分片就在一台服务器里面,当分片内容过多,需要扩容的时候,增加了一个服务器Node2,ES就会帮我们吧其中一个分片迁移到Node2里去,两个分片在两个服务器里。如果只有两个分片,如果两个服务器不够用了,增加一个Node3,这个时候就没有办法了,因为分片扩容的单位,必须以分片为单位的,只有两个分片,有三个服务器也没有办法去把一个分片拆分到第三个服务器上了。所以它和我们前面提到的分布式关系数据库一样,已开始就需要做一个预规划,规划好我们将来最多要使用多少台服务器,这个时候最好提供更多的分片,这些分片一开始的时候,在一台服务器上存在,当我们集群进行扩容的时候,这些分片迁移到了新的服务器上,直到最后每个服务器上一个分片,实现了集群规模的最大化。当服务器跟分片数量相等的时候,就没办法增加新的服务器了,也没办法进行集群的扩容了,集群的规模是和分片数目是相关的

  • 我们在实践中使用的搜索引擎,目前最常用的还是ElasticSearch,他们用ElasticSearch甚至会替代NoSQL,因为数据要存储,数据在写入的时候直接写入到ElasticSearch里去,把ElasticSearch本身当成一个存储,它能够通过自己的分片,做到集群的快速伸缩,像NoSQL一样,同时它能够提供快速的搜索和查询服务,同时因为它提供API接口更加便利更加强大,也更愿意使用ElasticSearch。把搜索引擎当做存储来用这种实践越来越多。



发布于: 2020 年 11 月 01 日阅读数: 41
用户头像

piercebn

关注

还未添加个人签名 2019.07.24 加入

还未添加个人简介

评论

发布
暂无评论
架构师训练营 1 期第 6 周:技术选型(二) - 总结