zoukankan      html  css  js  c++  java
  • 《Java架构师的第一性原理》71场景题之搜索引擎ElasticSearch

    1 啥,又要为表增加一列属性?

    需求缘起

    产品第一版:用户有用户名、密码、昵称等三个属性,对应表设计:

    user(uid, name, passwd, nick)

    第二版,产品经理增加了年龄,性别两个属性,表结构可能要变成:

    user(uid, name, passwd, nick, age, sex)

    假设数据量和并发量比较大,怎么变?

    (1)alter table add column?不太可行,锁表时间长

    (2)新表+触发器?如果数据量太大,新表不一定装得下,何况触发器对数据库性能的影响比较高

    (3)让dba来搞?新表,迁移数据,一致性校验,rename?dba真苦逼

    今天分享2个列扩展性设计上几个小技巧

    方案一:版本号+通用列

    以上面的用户表为例,假设只有uid和name上有查询需求,表可以设计为

    user(uid, name, version, ext)

    (1)uid和name有查询需求,必须设计为单独的列并建立索引

    (2)version是版本号字段,它对ext进行了版本解释

    (3)ext采用可扩展的字符串协议载体,承载被查询的属性

    例如,最开始上线的时候,版本为0,此时只有passwd和nick两个属性,那么数据为:

    当产品经理需要扩展属性时,新数据将版本变为1,此时新增了age和sex两个数据,数据变为:

    优点

    (1)可以随时动态扩展属性

    (2)新旧两种数据可以同时存在

    (3)迁移数据方便,写个小程序将旧版本ext的改为新版本的ext,并修改version

    不足

    (1)ext里的字段无法建立索引

    (2)ext里的key值有大量冗余,建议key短一些

    改进

    (1)如果ext里的属性有索引需求,可能Nosql的如MongoDB会更适合

    方案二:通过扩展行的方式来扩展属性

    以上面的用户表为例,可以设计为

    user(uid, key, value)

    初期有name, passwd, nick三个属性,那么数据为:

     未来扩展了age和sex两个属性,数据变为:

    (1)可以随时动态扩展属性

    (2)新旧两种数据可以同时存在

    (3)迁移数据方便,写个小程序可以将新增的属性加上

    (4)各个属性上都可以查询

    不足

    (1)key值有大量冗余,建议key短一些

    (2)本来一条记录很多属性,会变成多条记录,行数会增加很多

    总结

    可以通过“version+ext”或者“key+value”的方式来满足产品新增列的需求。

    2 这才是真正的表扩展方案

    零、缘起

    讨论问题域:

    (1)数据量大、并发量高场景,在线数据库属性扩展

    (2)数据库表结构扩展性设计

    2.1 哪些方案一定是不行

    (1)alter table add column

    要坚持这个方案的,也不多解释了,大数据高并发情况下,一定不可行

    (2)通过增加表的方式扩展,通过外键join来查询

    大数据高并发情况下,join性能较差,一定不可行

    (3)通过增加表的方式扩展,通过视图来对外

    一定不可行。大数据高并发情况下,互联网不怎么使用视图,至少58禁止使用视图

    (4)必须遵循“第x范式”的方案

    一定不可行。互联网的主要矛盾之一是吞吐量,为了保证吞吐量甚至可能牺牲一些事务性和一致性,通过反范式的方式来确保吞吐量的设计是很常见的,例如:冗余数据。互联网的主要矛盾之二是可用性,为了保证可用性,常见的技术方案也是数据冗余。在互联网数据库架构设计中,第x范式真的没有这么重要

    (5)打产品经理

    朋友,这是段子么,这一定不可行

    2.2 哪些方案可行,但文章未提及

    (1)提前预留一些reserved字段

    这个是可以的。但如果预留过多,会造成空间浪费,预留过少,不一定达得到扩展效果。

    (2)通过增加表的方式扩展列,上游通过service来屏蔽底层的细节

    这个也是可以的。Jeff同学提到的UserExt(uid, newCol1, newCol2)就是这样的方案(但join连表和视图是不行的)

    2.3 哪些读者没有仔细看文章

    (1)version+ext太弱了,ext不支持索引

    回复:属于没有仔细看文章,文章也提了如果有强需求索引可以使用MongoDB,它就是使用的json存储(评论中有不少朋友提到,还有其他数据库支持json检索)

    (2)第二种key+value方案不支持索引

    回复:uid可以索引

    2.4 key+value方式使用场景

    服务端,wordpress,EAV,配置,统计项等都经常使用这个方案。

    客户端(APP或者PC),保存个人信息也经常使用这个方案。

    2.5 在线表结构变更

    常见“新表+触发器+迁移数据+rename”方案(pt-online-schema-change),这是业内非常成熟的扩展列的方案。

    user(uid, name, passwd)

    扩展到user(uid, name, passwd, age, sex)为例

    基本原理是:

    (1)先创建一个扩充字段后的新表user_new(uid, name, passwd, age, sex)

    (2)在原表user上创建三个触发器,对原表user进行的所有insert/delete/update操作,都会对新表user_new进行相同的操作

    (3)分批将原表user中的数据insert到新表user_new,直至数据迁移完成

    (4)删掉触发器,把原表移走(默认是drop掉)

    (5)把新表user_new重命名(rename)成原表user

    扩充字段完成。

    优点整个过程不需要锁表,可以持续对外提供服务

    操作过程中需要注意

    (1)变更过程中,最重要的是冲突的处理,一条原则,以触发器的新数据为准,这就要求被迁移的表必须有主键(这个要求基本都满足)

    (2)变更过程中,写操作需要建立触发器,所以如果原表已经有很多触发器,方案就不行(互联网大数据高并发的在线业务,一般都禁止使用触发器)

    (3)触发器的建立,会影响原表的性能,所以这个操作建议在流量低峰期进行

    pt-online-schema-change是DBA必备的利器,比较成熟,在互联网公司使用广泛。

    3 100亿数据1万属性数据架构设计

    3.1 背景描述及业务介绍

    问:什么是数据库扩展的version + ext方案?

    使用ext来承载不同业务需求的个性化属性,使用version来标识ext里各个字段的含义。

    例如上述user表:

    verion=0表示ext里是passwd/nick

    version=1表示ext里是passwd/nick/age/sex

    优点?

    (1)可以随时动态扩展属性,扩展性好

    (2)新旧两种数据可以同时存在,兼容性好

    不足?

    (1)ext里的字段无法建立索引

    (2)ext里的key值有大量冗余,建议key短一些

    问:什么是58同城最核心的数据?

    58同城是一个信息平台,有很多垂直品类:招聘、房产、二手物品、二手车、黄页等等,每个品类又有很多子品类,不管哪个品类,最核心的数据都是“帖子信息”(业务像一个大论坛?)。

    问:帖子信息有什么特点?

    大家去58同城的首页上看看就知道了:

    (1)每个品类的属性千差万别,招聘帖子和二手帖子属性完全不同,二手手机和二手家电的属性又完全不同,目前恐怕有近万个属性

    (2)帖子量很大,100亿级别

    (3)每个属性上都有查询需求(各组合属性上都可能有组合查询需求),招聘要查职位/经验/薪酬范围,二手手机要查颜色/价格/型号,二手要查冰箱/洗衣机/空调

    (4)查询量很大,每秒几10万级别

    如何解决100亿数据量,1万属性,多属性组合查询,10万并发查询的技术难题,是今天要讨论的内容。

    3.2 最容易想到的方案

    每个公司的发展都是一个从小到大的过程,撇开并发量和数据量不谈,先看看

    (1)如何实现属性扩展性需求

    (2)多属性组合查询需求

    最开始,可能只有一个招聘品类,那帖子表可能是这么设计的:

    tiezi(tid,uid, c1, c2, c3)

    那如何满足各属性之间的组合查询需求呢?

    最容易想到的是通过组合索引:

    index_1(c1,c2) index_2(c2, c3) index_3(c1, c3)

    随着业务的发展,又新增了一个房产类别,新增了若干属性,新增了若干组合查询,于是帖子表变成了:

    tiezi(tid,uid, c1, c2, c3, c10, c11, c12, c13)

    其中c1,c2,c3是招聘类别属性,c10,c11,c12,c13是房产类别属性,这两块属性一般没有组合查询需求

    但为了满足房产类别的查询需求,又要建立了若干组合索引(不敢想有多少个索引能覆盖所有两属性查询,三属性查询)

    是不是发现玩不下去了?

    3.3 友商的玩法

    新增属性是一种扩展方式,新增表也是一种方式,有友商是这么玩的,按照业务进行垂直拆分:

    tiezi_zhaopin(tid,uid, c1, c2, c3)

    tiezi_fangchan(tid,uid, c10, c11, c12, c13)

    这些表,这些服务维护在不同的部门,不同的研发同学手里,看上去各业务线灵活性强,这恰恰是悲剧的开始:

    (1)tid如何规范?

    (2)属性如何规范?

    (3)按照uid来查询怎么办(查询自己发布的所有帖子)?

    (4)按照时间来查询怎么办(最新发布的帖子)?

    (5)跨品类查询怎么办(例如首页搜索框)?

    (6)技术范围的扩散,有的用mongo存储,有的用mysql存储,有的自研存储

    (7)重复开发了不少组件

    (8)维护成本过高

    (9)…

    想想看,电商的商品表,不可能一个类目一个表的。

    3.4 58同城的玩法

    【统一帖子中心服务】

    平台型创业型公司,可能有多个品类,例如58同城的招聘房产二手,很多异构数据的存储需求,到底是分还是合,无需纠结:基础数据基础服务的统一,无疑是58同城技术路线发展roadmap上最正确的决策之一,把这个方针坚持下来,@老崔 @晓飞 这些高瞻远瞩的先贤功不可没,业务线会有“扩展性”“灵活性”上的微词,后文看看先贤们如何通过一些巧妙的技术方案来解决的。

    如何将不同品类,异构的数据统一存储起来,采用的就是类似version+ext的方式:

    tiezi(tid,uid, time, title, cate, subcate, xxid, ext)

    (1)一些通用的字段抽取出来单独存储

    (2)通过cate, subcate, xxid等来定义ext是何种含义(和version有点像?)

    (3)通过ext来存储不同业务线的个性化需求

    例如招聘的帖子:

    ext : {“job”:”driver”,”salary”:8000,”location”:”bj”}

    而二手的帖子:

    ext : {”type”:”iphone”,”money”:3500}

     

     58同城最核心的帖子数据,100亿的数据量,分256库,异构数据mysql存储,上层架了一个服务,使用memcache做缓存,就是这样一个简单的架构,一直坚持这这么多年。上层的这个服务,就是58同城最核心的统一服务IMC(Imformation Management Center),注意这个最核心,是没有之一。

    解决了海量异构数据的存储问题,遇到的新问题是:

    (1)每条记录ext内key都需要重复存储,占据了大量的空间,能否压缩存储

    (2)cateid已经不足以描述ext内的内容,品类有层级,深度不确定,ext能否具备自描述性

    (3)随时可以增加属性,保证扩展性

    【统一类目属性服务】

    每个业务有多少属性,这些属性是什么含义,值的约束等揉不到帖子服务里,怎么办呢?

    58同城的先贤们抽象出一个统一的类目、属性服务,单独来管理这些信息,而帖子库ext字段里json的key,统一由数字来表示,减少存储空间。

     如上图所示,json里的key不再是”salary” ”location” ”money” 这样的长字符串了,取而代之的是数字1,2,3,4,这些数字是什么含义,属于哪个子分类,值的校验约束,统一都存储在类目、属性服务里。

     这个表里对帖子中心服务里ext字段里的数字key进行了解释:

    1代表job,属于招聘品类下100子品类,其value必须是一个小于32的[a-z]字符

    4代表type,属于二手品类下200子品类,其value必须是一个short

    这样就对原来帖子表ext里的

    ext : {“1”:”driver”,”2”:8000,”3”:”bj”}

    ext : {”4”:”iphone”,”5”:3500}

    key和value都做了统一约束。

    除此之外,如果ext里某个key的value不是正则校验的值,而是枚举值时,需要有一个对值进行限定的枚举表来进行校验:

    这个枚举校验,说明key=4的属性(对应属性表里二手,手机类型字段),其值不只是要进行“short类型”校验,而是value必须是固定的枚举值。

    ext : {”4”:”iphone”,”5”:3500}这个ext就是不合法的(key=4的value=iphone不合法),合法的应该为

    ext : {”4”:”5”,”5”:3500}

    此外,类目属性服务还能记录类目之间的层级关系:

    (1)一级类目是招聘、房产、二手…

    (2)二手下有二级类目二手家具、二手手机…

    (3)二手手机下有三级类目二手iphone,二手小米,二手三星…

    (4)…

    协助解释58同城最核心的帖子数据,描述品类层级关系,保证各类目属性扩展性,保证各属性值合理性校验,就是58同城另一个统一的核心服务CMC(Category Management Center)

    多提一句,类目、属性服务像不像电商系统里的SKU扩展服务?

    (1)品类层级关系,对应电商里的类别层级体系

    (2)属性扩展,对应电商里各类别商品SKU的属性

    (3)枚举值校验,对应属性的枚举值,例如颜色:红,黄,蓝

    多提一句,类目、属性服务像不像电商系统里的SKU扩展服务?

    (1)品类层级关系,对应电商里的类别层级体系

    (2)属性扩展,对应电商里各类别商品SKU的属性

    (3)枚举值校验,对应属性的枚举值,例如颜色:红,黄,蓝 

    解决了key压缩,key描述,key扩展,value校验,品类层级的问题,还有这样的一个问题没有解决:每个品类下帖子的属性各不相同,查询需求各不相同,如何解决100亿数据量,1万属性的查询需求,是58同城面临的新问题。

    【统一检索服务】

    数据量很大的时候,不同属性上的查询需求,不可能通过组合索引来满足所有查询需求,怎么办呢?

    58同城的先贤们,从一早就确定了“外置索引,统一检索服务”的技术路线:

    (1)数据库提供“帖子id”的正排查询需求

    (2)所有非“帖子id”的个性化检索需求,统一走外置索引

    元数据与索引数据的操作遵循:

    (1)对帖子进行tid正排查询,直接访问帖子服务

    (2)对帖子进行修改,帖子服务通知检索服务,同时对索引进行修改

    (3)对帖子进行复杂查询,通过检索服务满足需求

    这个扛起58同城80%终端请求(不管来自PC还是APP,不管是主页、城市页、分类页、列表页、详情页,很可能这个请求最终会是一个检索请求)的服务,就是58同城另一个统一的核心服务E-search,这个搜索引擎的每一行代码都来自58同城@老崔 @老龚 等先贤们,目前系统维护者,就是“架构师之路”里屡次提到的@龙神 。

    对于这个服务的架构,简单展开说明一下:

     (1)统一的Java代理层集群,其无状态性能够保证增加机器就能扩充系统性能

    (2)统一的合并层C服务集群,其无状态性也能够保证增加机器就能扩充系统性能

    (3)搜索内核检索层C服务集群,服务和索引数据部署在同一台机器上,服务启动时可以加载索引数据到内存,请求访问时从内存中load数据,访问速度很快

    (3.1)为了满足数据容量的扩展性,索引数据进行了水平切分,增加切分份数,就能够无限扩展性能

    (3.2)为了满足一份数据的性能扩展性,同一份数据进行了冗余,理论上做到增加机器就无限扩展性能

    系统时延,100亿级别帖子检索,包含请求分合,拉链求交集,从merger层均可以做到10ms返回。

    58同城的帖子业务,一致性不是主要矛盾,E-search会定期全量重建索引,以保证即使数据不一致,也不会持续很长的时间。

    3.4 总结

     

     文章写了很长,最后做一个简单总结,面对100亿数据量,1万列属性,10万吞吐量的业务需求,58同城的经验,是采用了元数据服务、属性服务、搜索服务来解决的。

    4 深入浅出搜索架构引擎、方案与细节(上)

    4.1 缘起

    《100亿数据1万属性数据架构设计》文章发布后,不少朋友对58同城自研搜索引擎E-search比较感兴趣,故专门撰文体系化的聊聊搜索引擎,从宏观到细节,希望把逻辑关系讲清楚,内容比较多,分上下两期。

    主要内容如下,本篇(上)会重点介绍前三章:

    (1)全网搜索引擎架构与流程

    (2)站内搜索引擎架构与流程

    (3)搜索原理、流程与核心数据结构

    (4)流量数据量由小到大,搜索方案与架构变迁

    (5)数据量、并发量、策略扩展性及架构方案

    (6)实时搜索引擎核心技术

    可能99%的同学不实施搜索引擎,但本文一定对你有帮助。

    4.2 全网搜索引擎架构与流程

    全网搜索的宏观架构长啥样?

    全网搜索的宏观流程是怎么样的?

    全网搜索引擎的宏观架构如上图,核心子系统主要分为三部分(粉色部分):

    (1)spider爬虫系统

    (2)search&index建立索引与查询索引系统,这个系统又主要分为两部分:

    一部分用于生成索引数据build_index

    一部分用于查询索引数据search_index

    (3)rank打分排序系统 

    核心数据主要分为两部分(紫色部分):

    (1)web网页库

    (2)index索引数据

    全网搜索引擎的业务特点决定了,这是一个“写入”和“检索”完全分离的系统:

    【写入】

    系统组成:由spider与search&index两个系统完成

    输入:站长们生成的互联网网页

    输出:正排倒排索引数据

    流程:如架构图中的1,2,3,4

    (1)spider把互联网网页抓过来

    (2)spider把互联网网页存储到网页库中(这个对存储的要求很高,要存储几乎整个“万维网”的镜像)

    (3)build_index从网页库中读取数据,完成分词

    (4)build_index生成倒排索引 

    【检索】

    系统组成:由search&index与rank两个系统完成

    输入:用户的搜索词

    输出:排好序的第一页检索结果

    流程:如架构图中的a,b,c,d

    (a)search_index获得用户的搜索词,完成分词

    (b)search_index查询倒排索引,获得“字符匹配”网页,这是初筛的结果

    (c)rank对初筛的结果进行打分排序

    (d)rank对排序后的第一页结果返回

    4.3 站内搜索引擎架构与流程

    做全网搜索的公司毕竟是少数,绝大部分公司要实现的其实只是一个站内搜索,站内搜索引擎的宏观架构和全网搜索引擎的宏观架构有什么异同?

    以58同城100亿帖子的搜索为例,站内搜索系统架构长啥样?站内搜索流程是怎么样的?

    站内搜索引擎的宏观架构如上图,与全网搜索引擎的宏观架构相比,差异只有写入的地方:

    (1)全网搜索需要spider要被动去抓取数据

    (2)站内搜索是内部系统生成的数据,例如“发布系统”会将生成的帖子主动推给build_data系统

    看似“很小”的差异,架构实现上难度却差很多:全网搜索如何“实时”发现“全量”的网页是非常困难的,而站内搜索容易实时得到全部数据。

    对于spider、search&index、rank三个系统:

    (1)spider和search&index是相对工程的系统

    (2)rank是和业务、策略紧密、算法相关的系统,搜索体验的差异主要在此,而业务、策略的优化是需要时间积累的,这里的启示是:

    a)Google的体验比Baidu好,根本在于前者rank牛逼

    b)国内互联网公司(例如360)短时间要搞一个体验超越Baidu的搜索引擎,是很难的,真心需要时间的积累

    4.4 搜索原理与核心数据结构

    什么是正排索引?

    什么是倒排索引?

    搜索的过程是什么样的?

    会用到哪些算法与数据结构?

    前面的内容太宏观,为了照顾大部分没有做过搜索引擎的同学,数据结构与算法部分从正排索引、倒排索引一点点开始。

    提问:什么是正排索引(forward index)?

    回答:由key查询实体的过程,是正排索引。

    用户表:t_user(uid, name, passwd, age, sex),由uid查询整行的过程,就是正排索引查询。

    网页库:t_web_page(url, page_content),由url查询整个网页的过程,也是正排索引查询。

    网页内容分词后,page_content会对应一个分词后的集合list<item>

    简易的,正排索引可以理解为Map<url, list<item>>,能够由网页快速(时间复杂度O(1))找到内容的一个数据结构。

    提问:什么是倒排索引(inverted index)?

    回答:由item查询key的过程,是倒排索引。

    对于网页搜索,倒排索引可以理解为Map<item, list<url>>,能够由查询词快速(时间复杂度O(1))找到包含这个查询词的网页的数据结构。

    举个例子,假设有3个网页:

    url1 -> “我爱北京”

    url2 -> “我爱到家”

    url3 -> “到家美好”

    这是一个正排索引Map<url, page_content>。

    分词之后:

    url1 -> {我,爱,北京}

    url2 -> {我,爱,到家}

    url3 -> {到家,美好}

    这是一个分词后的正排索引Map<url, list<item>>。

    分词后倒排索引

    我 -> {url1, url2}

    爱 -> {url1, url2}

    北京 -> {url1}

    到家 -> {url2, url3}

    美好 -> {url3}

    由检索词item快速找到包含这个查询词的网页Map<item, list<url>>就是倒排索引。

    正排索引和倒排索引是spider和build_index系统提前建立好的数据结构,为什么要使用这两种数据结构,是因为它能够快速的实现“用户网页检索”需求(业务需求决定架构实现)

    提问:搜索的过程是什么样的?

    假设搜索词是“我爱”,用户会得到什么网页呢?

    (1)分词,“我爱”会分词为{我,爱},时间复杂度为O(1)

    (2)每个分词后的item,从倒排索引查询包含这个item的网页list<url>,时间复杂度也是O(1):

    我 -> {url1, url2}

    爱 -> {url1, url2}

    (3)求list<url>的交集,就是符合所有查询词的结果网页,对于这个例子,{url1, url2}就是最终的查询结果

    看似到这里就结束了,其实不然,分词和倒排查询时间复杂度都是O(1),整个搜索的时间复杂度取决于“求list<url>的交集”,问题转化为了求两个集合交集

    字符型的url不利于存储与计算,一般来说每个url会有一个数值型的url_id来标识,后文为了方便描述,list<url>统一用list<url_id>替代。

    list1和list2,求交集怎么求?

    方案一:for * for,土办法,时间复杂度O(n*n)

    每个搜索词命中的网页是很多的,O(n*n)的复杂度是明显不能接受的。倒排索引是在创建之初可以进行排序预处理,问题转化成两个有序的list求交集,就方便多了。

    方案二:有序list求交集,拉链法

    有序集合1{1,3,5,7,8,9}

    有序集合2{2,3,4,5,6,7}

    两个指针指向首元素,比较元素的大小:

    (1)如果相同,放入结果集,随意移动一个指针

    (2)否则,移动值较小的一个指针,直到队尾

    这种方法的好处是:

    (1)集合中的元素最多被比较一次,时间复杂度为O(n)

    (2)多个有序集合可以同时进行,这适用于多个分词的item求url_id交集

    这个方法就像一条拉链的两边齿轮,一一比对就像拉链,故称为拉链法

    方案三:分桶并行优化

    数据量大时,url_id分桶水平切分+并行运算是一种常见的优化方法,如果能将list1<url_id>和list2<url_id>分成若干个桶区间,每个区间利用多线程并行求交集,各个线程结果集的并集,作为最终的结果集,能够大大的减少执行时间。

    举例:

    有序集合1{1,3,5,7,8,9, 10,30,50,70,80,90}

    有序集合2{2,3,4,5,6,7, 20,30,40,50,60,70}

    求交集,先进行分桶拆分:

    桶1的范围为[1, 9]

    桶2的范围为[10, 100]

    桶3的范围为[101, max_int]

    于是:

    集合1就拆分成

    集合a{1,3,5,7,8,9}

    集合b{10,30,50,70,80,90}

    集合c{}

    集合2就拆分成

    集合d{2,3,4,5,6,7}

    集合e{20,30,40,50,60,70}

    集合e{}

    每个桶内的数据量大大降低了,并且每个桶内没有重复元素,可以利用多线程并行计算:

    桶1内的集合a和集合d的交集是x{3,5,7}

    桶2内的集合b和集合e的交集是y{30, 50, 70}

    桶3内的集合c和集合d的交集是z{}

    最终,集合1和集合2的交集,是x与y与z的并集,即集合{3,5,7,30,50,70}

    方案四:bitmap再次优化

    数据进行了水平分桶拆分之后,每个桶内的数据一定处于一个范围之内,如果集合符合这个特点,就可以使用bitmap来表示集合

     

    如上图,假设set1{1,3,5,7,8,9}和set2{2,3,4,5,6,7}的所有元素都在桶值[1, 16]的范围之内,可以用16个bit来描述这两个集合,原集合中的元素x,在这个16bitmap中的第x个bit为1,此时两个bitmap求交集,只需要将两个bitmap进行“与”操作,结果集bitmap的3,5,7位是1,表明原集合的交集为{3,5,7}

    水平分桶,bitmap优化之后,能极大提高求交集的效率,但时间复杂度仍旧是O(n)

    bitmap需要大量连续空间,占用内存较大

    方案五:跳表skiplist

    有序链表集合求交集,跳表是最常用的数据结构,它可以将有序集合求交集的复杂度由O(n)降至O(log(n))

     

     要求交集,如果用拉链法,会发现1,2,3,4,20,21,22,23都要被无效遍历一次,每个元素都要被比对,时间复杂度为O(n),能不能每次比对“跳过一些元素”呢?

    跳表就出现了:

    集合1{1,2,3,4,20,21,22,23,50,60,70}建立跳表时,一级只有{1,20,50}三个元素,二级与普通链表相同

    集合2{50,70}由于元素较少,只建立了一级普通链表

    如此这般,在实施“拉链”求交集的过程中,set1的指针能够由1跳到20再跳到50,中间能够跳过很多元素,无需进行一一比对,跳表求交集的时间复杂度近似O(log(n)),这是搜索引擎中常见的算法。

    4.5 总结

    文字很多,有宏观,有细节,对于大部分不是专门研究搜索引擎的同学,记住以下几点即可:

    (1)全网搜索引擎系统由spider, search&index, rank三个子系统构成

    (2)站内搜索引擎与全网搜索引擎的差异在于,少了一个spider子系统

    (3)spider和search&index系统是两个工程系统,rank系统的优化却需要长时间的调优和积累

    (4)正排索引(forward index)是由网页url_id快速找到分词后网页内容list<item>的过程

    (5)倒排索引(inverted index)是由分词item快速寻找包含这个分词的网页list<url_id>的过程

    (6)用户检索的过程,是先分词,再找到每个item对应的list<url_id>,最后进行集合求交集的过程

    (7)有序集合求交集的方法

             a)二重for循环法,时间复杂度O(n*n)

             b)拉链法,时间复杂度O(n)

             c)水平分桶,多线程并行

             d)bitmap,大大提高运算并行度,时间复杂度O(n)

             e)跳表,时间复杂度为O(log(n))

    4.6 下章预告

    a)流量数据量由小到大,搜索方案与架构变迁-> 这个应该很有用,很多处于不同发展阶段的互联网公司都在做搜索系统,58同城经历过流量从0到10亿,数据量从0到100亿,搜索架构也不断演化着

    b)数据量、并发量、策略扩展性及架构方案

    c)实时搜索引擎核心技术 -> 站长发布1个新网页,Google如何做到15分钟后检索出来

    5 就是这么迅猛的实现搜索需求

    5.1 缘起

    《深入浅出搜索架构(上篇)》详细介绍了:

    (1)全网搜索引擎架构与流程

    (2)站内搜索引擎架构与流程

    (3)搜索原理与核心数据结构

    本文重点介绍:

    (4)流量数据量由小到大,常见搜索方案与架构变迁

    (5)数据量、并发量、扩展性方案

    只要业务有检索需求,本文一定对你有帮助。

    5.2 检索需求的满足与架构演进

    任何互联网需求,或多或少有检索需求,还是以58同城的帖子业务场景为例,帖子的标题,帖子的内容有很强的用户检索需求,在业务、流量、并发量逐步递增的各个阶段,应该如何实现检索需求呢

    5.2.1 原始阶段-LIKE

    数据在数据库中可能是这么存储的:

    t_tiezi(tid, title, content)

    满足标题、内容的检索需求可以通过LIKE实现:

    select tid from t_tiezi where content like ‘%天通苑%’

    能够快速满足业务需求,存在的问题也显而易见:

    (1)效率低,每次需要全表扫描,计算量大,并发高时cpu容易100%

    (2)不支持分词

    5.2.2 初级阶段-全文索引

    如何快速提高效率,支持分词,并对原有系统架构影响尽可能小呢,第一时间想到的是建立全文索引:

    alter table t_tiezi add fulltext(title,content)

    使用match和against实现索引字段上的查询需求。

    全文索引能够快速实现业务上分词的需求,并且快速提升性能(分词后倒排,至少不要全表扫描了),但也存在一些问题

    (1)只适用于MyISAM

    (2)由于全文索引利用的是数据库特性,搜索需求和普通CURD需求耦合在数据库中:检索需求并发大时,可能影响CURD的请求;CURD并发大时,检索会非常的慢;

    (3)数据量达到百万级别,性能还是会显著降低,查询返回时间很长,业务难以接受

    (4)比较难水平扩展

    5.2.3 中级阶段-开源外置索引

    为了解决全文索的局限性,当数据量增加到大几百万,千万级别时,就要考虑外置索引了。外置索引的核心思路是:索引数据与原始数据分离,前者满足搜索需求,后者满足CURD需求,通过一定的机制(双写,通知,定期重建)来保证数据的一致性。

    原始数据可以继续使用Mysql来存储,外置索引如何实施?Solr,Lucene,ES都是常见的开源方案。

    楼主强烈推荐ES(ElasticSearch),原因是Lucene虽好,但始终有一些不足

    (1)Lucene只是一个库,潜台词是,需要自己做服务,自己实现高可用/可扩展/负载均衡等复杂特性

    (2)Lucene只支持Java,如果要支持其他语言,还是得自己做服务

    (3)Lucene不友好,这是很致命的,非常复杂,使用者往往需要深入了解搜索的知识来理解它的工作原理,为了屏蔽其复杂性,一个办法是自己做服务

    为了改善Lucene的各项不足,解决方案都是“封装一个接口友好的服务,屏蔽底层复杂性”,于是有了ES:

    (1)ES是一个以Lucene为内核来实现搜索功能,提供REStful接口的服务

    (2)ES能够支持很大数据量的信息存储,支持很高并发的搜索请求

    (3)ES支持集群,向使用者屏蔽高可用/可扩展/负载均衡等复杂特性

    目前58到家使用ES作为核心,实现了自己的搜索服务平台,能够通过在平台上简单的配置,实现业务方的搜索需求。

    搜索服务数据量最大的“接口耗时数据收集”需求,数据量大概在7亿左右;并发量最大的“经纬度,地理位置搜索”需求,线上平均并发量大概在600左右,压测数据并发量在6000左右。

    结论:ES完全能满足10亿数据量,5k吞吐量的常见搜索业务需求,强烈推荐。

    5.2.4 高级阶段-自研搜索引擎

    当数据量进一步增加,达到10亿、100亿数据量;并发量也进一步增加,达到每秒10万吞吐;业务个性也逐步增加的时候,就需要自研搜索引擎了,定制化实现搜索内核了。

    5.3 数据量、并发量、扩展性方案

    到了定制化自研搜索引擎的阶段,超大数据量、超高并发量为设计重点,为了达到“无限容量、无限并发”的需求,架构设计需要重点考虑“扩展性”,力争做到:增加机器就能扩容(数据量+并发量)

    58同城的自研搜索引擎E-search初步架构图如下:

     

    (1)上层proxy(粉色)是接入集群,为对外门户,接受搜索请求,其无状态性能够保证增加机器就能扩充proxy集群性能

    (2)中层merger(浅蓝色)是逻辑集群,主要用于实现搜索合并,以及打分排序,业务相关的rank就在这一层实现,其无状态性也能够保证增加机器就能扩充merger集群性能

    (3)底层searcher(暗红色大框)是检索集群,服务和索引数据部署在同一台机器上,服务启动时可以加载索引数据到内存,请求访问时从内存中load数据,访问速度很快

    (3.1)为了满足数据容量的扩展性,索引数据进行了水平切分,增加切分份数,就能够无限扩展性能,如上图searcher分为了4组

    (3.2)为了满足一份数据的性能扩展性,同一份数据进行了冗余,理论上做到增加机器就无限扩展性能,如上图每组searcher又冗余了2份

    如此设计,真正做到做到增加机器就能承载更多的数据量,响应更高的并发量

    5.4 总结

    为了满足搜索业务的需求,随着数据量和并发量的增长,搜索架构一般会经历这么几个阶段:

    (1)原始阶段-LIKE

    (2)初级阶段-全文索引

    (3)中级阶段-开源外置索引

    (4)高级阶段-自研搜索引擎

    6 百度如何能实时检索到15分钟前新生成的网页?

    6.1 缘起

    《深入浅出搜索架构(上篇)》详细介绍了前三章:

    (1)全网搜索引擎架构与流程

    (2)站内搜索引擎架构与流程

    (3)搜索原理与核心数据结构 

    《深入浅出搜索架构(中篇)》介绍了:

    (4)流量数据量由小到大,常见搜索方案与架构变迁

    (5)数据量、并发量、扩展性架构方案

    本篇将讨论:

    (6)百度为何能实时检索出15分钟之前新出的新闻?58同城为何能实时检索出1秒钟之前发布的帖子?搜索引擎的实时性架构,是本文将要讨论的问题。

    6.2 实时搜索引擎架构

    大数据量、高并发量情况下的搜索引擎为了保证实时性,架构设计上的两个要点:

    (1)索引分级

    (2)dump&merge

    索引分级

    深入浅出搜索架构(上篇)》介绍了搜索引擎的底层原理,在数据量非常大的情况下,为了保证倒排索引的高效检索效率,任何对数据的更新,并不会实时修改索引,一旦产生碎片,会大大降低检索效率。 

    既然索引数据不能实时修改,如何保证最新的网页能够被索引到呢?

    索引分为全量库、日增量库、小时增量库

    如下图所述:

    (1)300亿数据在全量索引库中

    (2)1000万1天内修改过的数据在天库中

    (3)50万1小时内修改过的数据在小时库中

     

    当有修改请求发生时,只会操作最低级别的索引,例如小时库。

     

    当有查询请求发生时,会同时查询各个级别的索引,将结果合并,得到最新的数据:

    (1)全量库是紧密存储的索引,无碎片,速度快

    (2)天库是紧密存储,速度快

    (3)小时库数据量小,速度也快 

    数据的写入和读取都是实时的,所以58同城能够检索到1秒钟之前发布的帖子,即使全量库有300亿的数据。

    新的问题来了:小时库数据何时反映到天库中,天库中的数据何时反映到全量库中呢? 

    dump&merge

    这是由两个异步的工具完成的:

    dumper在线的数据导出

    merger将离线的数据合并到高一级别的索引中去

    小时库,一小时一次,合并到天库中去;

    天库,一天一次,合并到全量库中去;

    这样就保证了小时库和天库的数据量都不会特别大;

    如果数据量和并发量更大,还能增加星期库,月库来缓冲。

    6.3 总结

    超大数据量,超高并发量,实时搜索引擎的两个架构要点:

    (1)索引分级

    (2)dump&merge

    如《深入浅出搜索架构(上篇)》中所述,全网搜索引擎分为Spider, Search&Index, Rank三个部分。本文描述的是Search&Index如何实时修改和检索,Spider子系统如何能实时找到全网新生成的网页,又是另外一个问题,未来撰文讲述。

    99 直接读这些牛人的原文

    架构师之路:百度如何能实时检索到15分钟前新生成的网页?

    MySQL 用得好好的,为什么要转 Elasticsearch?

    ElasticSearch 面试 4 连问,你顶得住么?


    作者:沙漏哟
    出处:计算机的未来在于连接
    本文版权归作者和博客园共有,欢迎转载,请留下原文链接
    微信随缘扩列,聊创业聊产品,偶尔搞搞技术
  • 相关阅读:
    javaweb请求编码 url编码 响应编码 乱码问题 post编码 get请求编码 中文乱码问题 GET POST参数乱码问题 url乱码问题 get post请求乱码 字符编码
    windows查看端口占用 windows端口占用 查找端口占用程序 强制结束端口占用 查看某个端口被占用的解决方法 如何查看Windows下端口占用情况
    javaWeb项目中的路径格式 请求url地址 客户端路径 服务端路径 url-pattern 路径 获取资源路径 地址 url
    ServletRequest HttpServletRequest 请求方法 获取请求参数 请求转发 请求包含 请求转发与重定向区别 获取请求头字段
    HttpServletResponse ServletResponse 返回响应 设置响应头设置响应正文体 重定向 常用方法 如何重定向 响应编码 响应乱码
    Servlet主要相关类核心类 容器调用的过程浅析 servlet解读 怎么调用 Servlet是什么 工作机制
    linq查询语句转mongodb
    winddows rabbitmq安装与配置
    Redis For Windows安装及密码
    出现,视图必须派生自 WebViewPage 或 WebViewPage错误解决方法
  • 原文地址:https://www.cnblogs.com/yeahwell/p/14417842.html
Copyright © 2011-2022 走看看