本文已整理致个人 github 地址 https://github.com/allentofight/easy-cs,欢迎你们 star 支持一下html
近年来公司业务迅猛发展,数据量爆炸式增加,随之而来的的是海量数据查询等带来的挑战,咱们须要数据量在十亿,甚至百亿级别的规模时依然能以秒级甚至毫秒级的速度返回,这样的话显然离不开搜索引擎的帮助,在搜索引擎中,ES(ElasticSearch)毫无疑问是其中的佼佼者,连续多年在 DBRanking 的搜索引擎中评测中排名第一,也是绝大多数大公司的首选,那么它与传统的 DB 如 MySQL 相比有啥优点呢,ES 的数据又是如何生成的,数据达到 PB 时又是如何保证 ES 索引数据的实时性以更好地知足业务的需求的呢。node
本文会结合我司在 ES 上的实践经验与你们谈谈如何构建准实时索引的一些思路,但愿对你们有所启发。本文目录以下mysql
MySQL 架构天生不适合海量数据查询,它只适合海量数据存储,但没法应对海量数据下各类复杂条件的查询,有人说加索引不是能够避免全表扫描,提高查询速度吗,为啥说它不适合海量数据查询呢,有两个缘由:git
一、加索引确实能够提高查询速度,但在 MySQL 中加多个索引最终在执行 SQL 的时候它只会选择成本最低的那个索引,若是没有索引知足搜索条件,就会触发全表扫描,并且即使你使用了组合索引,也要符合最左前缀原则才能命中索引,但在海量数据多种查询条件下颇有可能不符合最左前缀原则而致使索引失效,并且咱们知道存储都是须要成本的,若是你针对每一种状况都加索引,以 innoDB 为例,每加一个索引,就会建立一颗 B+ 树,若是是海量数据,将会增长很大的存储成本,以前就有人反馈说他们公司的某个表实际内容的大小才 10G, 而索引大小却有 30G!这是多么巨大的成本!因此千万不要以为索引建得越多越好。github
二、有些查询条件是 MySQL 加索引都解决不了的,好比我要查询商品中全部 title 带有「格力空调」的关键词,若是你用 MySQL 写,会写出以下代码sql
SELECT * FROM product WHERE title like '%格力空调%'
这样的话没法命中任何索引,会触发全表扫描,并且你不能期望全部人都能输对他想要的商品,是人就会犯错误,咱们常常会犯相似把「格力空调」记成「格空间」的错误,那么 SQL 语句就会变成:数据库
SELECT * FROM product WHERE title like '%格空调%'
这种状况下就算你触发了全表扫描也没法查询到任何商品,综上所述,MySQL 的查询确实能力有限。编程
与其说上面列的这些点是 MySQL 的不足,倒不如说 MySQL 自己就不是为海量数据查询而设计的,术业有专攻,海量数据查询还得用专门的搜索引擎,这其中 ES 是其中当之无愧的王者,它是基于 Lucene 引擎构建的开源分布式搜索分析引擎,能够提供针对 PB 数据的近实时查询,普遍用在全文检索、日志分析、监控分析等场景。服务器
它主要有如下三个特色:架构
轻松支持各类复杂的查询条件
: 它是分布式实时文件存储,会把每个字段都编入索引(倒排索引),利用高效的倒排索引,以及自定义打分、排序能力与丰富的分词插件等,能实现任意复杂查询条件下的全文检索需求可扩展性强
:自然支持分布式存储,经过极其简单的配置实现几百上千台服务器的分布式横向扩容,轻松处理 PB 级别的结构化或非结构化数据。高可用,容灾性能好
:经过使用主备节点,以及故障的自动探活与恢复,有力地保障了高可用咱们先用与 MySQL 类比的形式来理解 ES 的一些重要概念
经过类比的形式不难看出 ES 的如下几个概念
一、 MySQL 的数据库(DataBase)至关于 Index(索引),数据的逻辑集合,ES 的工做主要也是建立索引,查询索引。
二、 一个数据库里会有多个表,一样的一个 Index 也会有多个 type
三、 一个表会有多行(Row),一样的一个 Type 也会有多个 Document。
四、 Schema 指定表名,表字段,是否创建索引等,一样的 Mapping 也指定了 Type 字段的处理规则,即索引如何创建,是否分词,分词规则等
五、 在 MySQL 中索引是须要手动建立的,而在 ES 一切字段皆可被索引,只要在 Mapping 在指定便可
那么 ES 中的索引为什么如此高效,能在海量数据下达到秒级的效果呢?它采用了多种优化手段,最主要的缘由是它采用了一种叫作倒排索引的方式来生成索引,避免了全文档扫描,那么什么是倒排索引呢,经过文档来查找关键词等数据的咱们称为正排索引,返之,经过关键词来查找文档的形式咱们称之为倒排索引,假设有如下三个文档(Document)
要在其中找到含有 comming 的文档,若是要正排索引,那么要把每一个文档的内容拿出来查找是否有此单词,毫无疑问这样的话会致使全表扫描,那么用倒排索引会怎么查找呢,它首先会将每一个文档内容进行分词,小写化等,而后创建每一个分词与包含有此分词的文档以前的映射关系,若是有多个文档包含此分词,那么就会按重要程度即文档的权重(一般是用 TF-IDF 给文档打分)将文档进行排序,因而咱们能够获得以下关系
这样的话咱们我要查找全部带有 comming 的文档,就只需查一次,并且这种状况下查询多个单词性能也是很好的,只要查询多个条件对应的文档列表,再取交集便可,极大地提高了查询效率。
画外音:这里简化了一些流程,实际上还要先根据单词表来定位单词等,不过这些流程都很快,能够忽略,有兴趣的读者能够查阅相关资料了解。
除了倒排索引外,ES 的分布式架构也自然适合海量数据查询,来看下 ES 的架构
一个 ES 集群由多个 node 节点组成,每一个 index 是以分片(Shard,index 子集)的数据存在于多个 node 节点上的,这样的话当一个查询请求进来,分别在各个 node 查询相应的结果并整合后便可,将查询压力分散到多个节点,避免了单个节点 CPU,磁盘,内存等处理能力的不足。
另外当新节点加入后,会自动迁移部分分片至新节点,实现负载均衡,这个功能是 ES 自动完成的,对比一个下 MySQL 的分库分表须要开发人员引入 Mycat 等中间件并指定分库分表规则等繁琐的流程是否是一个巨大的进步?这也就意味着 ES 有很是强大的水平扩展的能力,集群可轻松扩展致几百上千个节点,轻松支持 PB 级的数据查询。
固然 ES 的强大不止于此,它还采用了好比主备分片提高搜索吞吐率,使用节点故障探测,Raft 选主机制等提高了容灾能力等等,这些不是本文重点,读者可自行查阅,总之通过上面的简单总结你们只须要明白一点:ES 的分布式架构设计天生支持海量数据查询。
那么 ES 的索引数据(index)是如何生成的呢,接下来咱们一块儿来看看本文的重点
要构建 ES 索引数据,首先得有数据源,通常咱们会使用 MySQL 做为数据源,你能够直接从 MySQL 中取数据而后再写入 ES,但这种方式因为直接调用了线上的数据库查询,可能会对线上业务形成影响,好比考虑这样的一个场景:
在电商 APP 里用的最多的业务场景想必是用户输入关键词来查询相对应的商品了,那么商品会有哪些信息呢,一个商品会有多个 sku(sku 即同一个商品下不一样规格的品类,好比苹果手机有 iPhone 6, iPhone 6s等),会有其基本属性如价格,标题等,商品会有分类(居家,服饰等),品牌,库存等,为了保证表设计的合理性,咱们会设计几张表来存储这些属性,假设有 product_sku(sku 表), product_property(基本属性表),sku_stock(库存表),product_category(分类表)这几张表,那么为了在商品展现列表中展现全部这些信息,就必须把这些表进行 join,而后再写入 ES,这样查询的时候就会在 ES 中获取全部的商品信息了。
这种方案因为直接在 MySQL 中执行 join 操做,在商品达到千万级时会对线上的 DB 服务产生极大的性能影响,因此显然不可行,那该怎么生成中间表呢,既然直接在 MySQL 中操做不可行,可否把 MySQL 中的数据同步到另外一个地方再作生成中间表的操做呢,也就是加一个中间层进行处理,这样就避免了对线上 DB 的直接操做,说到这相信你们又会对计算机界的名言有进一步的体会:没有什么是加一个中间层不能解决的,若是有,那就再加一层。
这个中间层就是 hive
什么是 hive
hive 是基于 Hadoop 的一个数据仓库工具,用来进行数据提取、转化、加载,这是一种能够存储、查询和分析存储在 Hadoop 中的大规模数据的机制,它的意义就是把好写的 hive 的 sql 转换为复杂难写的 map-reduce 程序(map-reduce 是专门用于用于大规模数据集(大于1TB)的并行运算编程模型),也就是说若是数据量大的话经过把 MySQL 的数据同步到 hive,再由 hive 来生成上述的 product_tmp 中间表,能极大的提高性能。hive 生成临时表存储在 hbase(一个分布式的、面向列的开源数据库) 中,生成后会定时触发 dump task 调用索引程序,而后索引程序主要从 hbase 中读入全量数据,进行业务数据处理,并刷新到 es 索引中,整个流程以下
这样构建索引看似很美好,但咱们须要知道的是首先 hive 执行 join 任务是很是耗时的,在咱们的生产场景上,因为数据量高达几千万,执行 join 任务一般须要几十分钟,从执行 join 任务到最终更新至 ES 整个流程经常须要至少半小时以上,若是这期间商品的价格,库存,上线状态(如被下架)等重要字段发生了变动,索引是没法更新的,这样会对用户体验产生极大影响,优化前咱们常常会看到经过 ES 搜索出的中有状态是上线但实际是下架的产品,严重影响用户体验,那么怎么解决呢,有一种可行的方案:创建宽表
既然咱们发现 hive join 是性能的主要瓶颈,那么可否规避掉这个流程呢,可否在 MySQL 中将 product_sku,product_property,sku_stock 等表组合成一个大表(咱们称其为宽表)
这样在每一行中商品涉及到的的数据都有了,因此将 MySQL 同步到 hive 后,hive 就不须要再执行耗时的 join 操做了,极大的提高了总体的处理时间,从 hive 同步 MySQL 再到 dump 到 ES 索引中从原来的半小时以上下降到了几分钟之内,看起来确实不错,但几分钟的索引延迟依然是没法接受的。
为何 hive 没法作到实时导入索引
由于 hive 构建在基于静态批处理的Hadoop 之上,Hadoop 一般都有较高的延迟而且在做业提交和调度的时候须要大量的开销。所以,hive 并不可以在大规模数据集上实现低延迟快速的查询等操做,再且千万级别的数据全量从索引程序导入到 ES 集群至少也是分钟级。
另外引入了宽表,它的维护也成了一个新问题,设想 sku 库存变了,产品下架了,价格调整了,那么除了修改原表(sku_stock,product_categry 等表)记录以外,还要将全部原表变动到的记录对应到宽表中的全部记录也都更新一遍,这对代码的维护是个噩梦,由于你须要在全部商品相关的表变动的地方紧接着着变动宽表的逻辑,与宽表的变动逻辑变动紧藕合!
该如何解决呢?仔细观察上面两个问题,其实都是同一个问题,若是咱们能实时监听到 db 的字段变动,再将变动的内容实时同步到 ES 和宽表中不就解决了咱们的问题了。
怎么才能实时监听到表字段的变动呢?
答案:binlog
来一块儿复习下 MySQL 的主从同步原理
能够看到主从复制的原理关键是 Master 和 Slave 遵循了一套协议才能实时监听 binlog 日志来更新 slave 的表数据,那咱们能不能也开发一个遵循这套协议的组件,当组件做为 Slave 来获取 binlog 日志进而实时监听表字段变动呢?阿里的开源项目 Canal 就是这个干的,它的工做原理以下:
这样的话经过 canal 就能获取 binlog 日志了,固然 canal 只是获取接收了 master 过来的 binlog,还要对 binlog 进行解析过滤处理等,另外若是咱们只对某些表的字段感兴趣,该如何配置,获取到 binlog 后要传给谁? 这些都须要一个统一的管理组件,阿里的 otter 就是干这件事的。
什么是 otter
Otter 是由阿里提供的基于数据库增量日志解析,准实时同步到本机房或异地机房 MySQL 数据库的一个分布式数据库同步系统,它的总体架构以下:
主要工做流程以下
画外音:zookeeper 主要协调节点间的工做,如在跨机房数据同步时,可能要从 A 机房的节点将数据同步到 B 机房的节点,要用 zookeeper 来协调,
你们应该注意到了node 中有 S,E,T,L 四个阶段,它们的主要做用以下
Select 阶段
: 为解决数据来源的差别性,好比接入 canal 获取增量数据,也能够接入其余系统获取其余数据等。
Extract阶段
: 组装数据,针对多种数据来源,mysql,oracle,store,file等,进行数据组装和过滤。
Transform 阶段
: 数据提取转换过程,把数据转换成目标数据源要求的类型
Load 阶段
: 数据载入,把数据载入到目标端,如写入迁移后的数据库, MQ,ES 等
以上这套基于阿里 otter 改造后的数据服务咱们将它称为 DTS(Data Transfer Service),即数据传输服务。
搭建这套服务后咱们就能够经过订阅 MQ 来实时写入 ES 让索引实时更新了,并且也能够经过订阅 MQ 来实现宽表字段的更新,实现了上文中所说的宽表字段更新与原表紧藕合的逻辑,基于 DTS 服务的索引改进架构以下:
注意:「build 数据」这一模块对实时索引更新是透明的,这个模块主要用在更新或插入 MySQL 宽表,由于对于宽表来讲,它是几个表数据的并集,因此并非监听到哪一个字段变动就更新哪一个,它要把全部商品涉及到的全部表数据拉回来再更新到宽表中。
因而,经过 MySQL 宽表全量更新+基于 DTS 的实时索引更新咱们很好地解决了索引延迟的问题,能达到秒级的 ES 索引更新!
这里有几个问题可能你们比较关心,我简单列一下
须要订阅哪些字段
对于 MySQL 宽表来讲因为它要保存商品的完整信息,因此它须要订阅全部字段,可是对于红框中的实时索引更新而言,它只须要订阅库存,价格等字段,由于这些字段若是不及时更新,会对销量产生极大的影响,因此咱们实时索引只关注这些敏感字段便可。
有了实时索引更新,还须要全量索引更新吗
须要,主要有两个缘由:
全量索引更新的数据会覆盖实时索引吗
会,设想这样一种场景,你在某一时刻触发了实时索引,而后此时全量索引还在执行中,还未执行到实时索引更新的那条记录,这样在的话当全量索引执行完以后就会把以前实时索引更新的数据给覆盖掉,针对这种状况一种可行的处理方式是若是全量索引是在构建中,实时索引更新消息能够延迟处理,等全量更新结束后再消费。也正由于这个缘由,全量索引咱们通常会在凌晨执行,因为是业务低峰期,最大可能规避了此类问题。
本文简单总结了我司在 PB 级数据下构建实时 ES 索引的一些思路,但愿对你们有所帮助,文章只是简单提到了 ES,canal,otter 等阿里中间件的应用,但未对这些中间件的详细配置,原理等未做过多介绍,这些中间件的设计很是值得咱们好好研究下,好比 ES 为了提升搜索效率、优化存储空间作了不少工做,再好比 canal 如何作高可用,otter 实现异地跨机房同步的原理等,建议感兴趣的读者能够以后好好研究一番,相信你会受益不浅。
若是你们对方案有些疑问,欢迎你们积极留言探讨,共同进步^_^,更多精品文章,欢迎你们扫码关注「码海」
巨人的肩膀