最近搞了个毫秒级返回百亿数据!我都做了啥! 上

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
云数据库 RDS PostgreSQL,高可用系列 2核4GB
简介: 最近搞了个毫秒级返回百亿数据!我都做了啥! 上



近年来公司业务迅猛发展,数据量爆炸式增长,随之而来的的是海量数据查询等带来的挑战,我们需要数据量在十亿,甚至百亿级别的规模时依然能以秒级甚至毫秒级的速度返回,这样的话显然离不开搜索引擎的帮助,在搜索引擎中,ES(ElasticSearch)毫无疑问是其中的佼佼者,连续多年在 DBRanking 的搜索引擎中评测中排名第一,也是绝大多数大公司的首选,那么它与传统的 DB 如 MySQL 相比有啥优势呢,ES 的数据又是如何生成的,数据达到 PB 时又是如何保证 ES 索引数据的实时性以更好地满足业务的需求的呢。

本文会结合我司在 ES 上的实践经验与大家谈谈如何构建准实时索引的一些思路,希望对大家有所启发。

MySQL 的不足

MySQL 架构天生不适合海量数据查询,它只适合海量数据存储,但无法应对海量数据下各种复杂条件的查询,有人说加索引不是可以避免全表扫描,提升查询速度吗,为啥说它不适合海量数据查询呢,有两个原因:

1、加索引确实可以提升查询速度,但在 MySQL 中加多个索引最终在执行 SQL 的时候它只会选择成本最低的那个索引,如果没有索引满足搜索条件,就会触发全表扫描,而且即便你使用了组合索引,也要符合最左前缀原则才能命中索引,但在海量数据多种查询条件下很有可能不符合最左前缀原则而导致索引失效,而且我们知道存储都是需要成本的,如果你针对每一种情况都加索引,以 innoDB 为例,每加一个索引,就会创建一颗 B+ 树,如果是海量数据,将会增加很大的存储成本,之前就有人反馈说他们公司的某个表实际内容的大小才 10G, 而索引大小却有 30G!这是多么巨大的成本!所以千万不要觉得索引建得越多越好。

2、有些查询条件是 MySQL 加索引都解决不了的,比如我要查询商品中所有 title 带有「格力空调」的关键词,如果你用 MySQL 写,会写出如下代码

SELECT * FROM product WHERE title like '%格力空调%'

这样的话无法命中任何索引,会触发全表扫描,而且你不能指望所有人都能输对他想要的商品,是人就会犯错误,我们经常会犯类似把「格力空调」记成「格空间」的错误,那么 SQL 语句就会变成:

SELECT * FROM product WHERE title like '%格空调%'

这种情况下就算你触发了全表扫描也无法查询到任何商品,综上所述,MySQL 的查询确实能力有限。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

ES 简介

与其说上面列的这些点是 MySQL 的不足,倒不如说 MySQL 本身就不是为海量数据查询而设计的,术业有专攻,海量数据查询还得用专门的搜索引擎,这其中 ES 是其中当之无愧的王者,它是基于 Lucene 引擎构建的开源分布式搜索分析引擎,可以提供针对 PB 数据的近实时查询,广泛用在全文检索、日志分析、监控分析等场景。

它主要有以下三个特点:

  • 轻松支持各种复杂的查询条件: 它是分布式实时文件存储,会把每一个字段 都编入索引(倒排索引),利用高效的倒排索引,以及自定义打分、排序能力与丰富的分词插件等,能实现任意复杂查询条件下的全文检索需求
  • 可扩展性强:天然支持分布式存储,通过极其简单的配置实现几百上千台服务器的分布式横向扩容,轻松处理 PB 级别的结构化或非结构化数据。
  • 高可用,容灾性能好:通过使用主备节点,以及故障的自动探测与恢复,有力地保障了高可用

我们先用与 MySQL 类比的形式来理解 ES 的一些重要概念

image.png

通过类比的形式不难看出 ES 的以下几个概念1、 MySQL 的数据库(DataBase)相当于 Index(索引),数据的逻辑集合,ES 的工作主要也是创建索引,查询索引。2、 一个数据库里会有多个表,同样的一个 Index 也会有多个 type3、 一个表会有多行(Row),同样的一个 Type 也会有多个 Document。4、 Schema 指定表名,表字段,是否建立索引等,同样的 Mapping 也指定了 Type 字段的处理规则,即索引如何建立,是否分词,分词规则等5、 在 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)如何生成的呢,接下来我们一起来看看本文的重点

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

如何构建 ES 索引

要构建 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 等表)记录之外,还要将所有原表变更到的记录对应到宽表中的所有记录也都更新一遍,这对代码的维护是个噩梦,因为你需要在所有商品相关的表变更的地方紧接着着变更宽表的逻辑,与宽表的变更逻辑变更紧藕合!

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
相关文章
|
存储 测试技术 C++
实践:几十亿条数据分布在几十个节点上的毫秒级实时排序方法
#引子 先简单的问一下, 你如何解决这样的需求: ``` 对一堆数据按某字段排序,获取第100-10条的数据。 ``` 假设你面对的数据是个单节点,简单来说,就是一个mysql数据库, 很自然地用 select a from tb order by a limit 100, 10; ![imag
4369 0
|
缓存 测试技术 持续交付
docker desktop 搜索不到images
【2月更文挑战第23天】
3181 0
|
8月前
|
SQL 关系型数据库 MySQL
基于SQL Server / MySQL进行百万条数据过滤优化方案
对百万级别数据进行高效过滤查询,需要综合使用索引、查询优化、表分区、统计信息和视图等技术手段。通过合理的数据库设计和查询优化,可以显著提升查询性能,确保系统的高效稳定运行。
345 9
|
11月前
|
存储 监控 Java
Zipkin/Pinpoint/SkyWalking全面对比
【11月更文挑战第1天】这里重点从探针的性能、Collector的可扩展性、调用链路分析、完整的应用拓扑、对于科技人员使用友好程度(部署安装、埋点接入、使用管理)几个方面来进行对比。
|
存储 NoSQL Java
redis zset详解:排行榜绝佳选择
新发布的App中,搜索功能使用Redis的有序集合(ZSET)来显示四个热门搜索词。由于应用初期,热门搜索显示的是测试词汇,为提升专业形象,计划删除这些测试词。文章介绍了ZSET的特性,如有序性、唯一性和快速查找,并讲解了如何在命令行中操作ZSET。此外,还分享了利用ZSET实现热搜功能的思路,每次搜索时增加对应词的分数以实现排序。最后,提供了Java代码示例展示了如何在Redisson中操作ZSET数据,以及如何实现热搜词汇功能。
897 1
|
消息中间件 存储 安全
RabbitMQ Streams 详解
RabbitMQ Streams 详解
1058 0
|
关系型数据库
PostgreSQL 百亿级数据范围查询, 分组排序窗口取值 极致优化 case
本文将对一个任意范围按ID分组查出每个ID对应的最新记录的CASE做一个极致的优化体验。优化后性能维持在可控范围内,任意数据量,毫秒级返回,性能平稳可控。比优化前性能提升1万倍。 CASE如下: 有一张数据表,结构: CREATE TABLE target_position
16825 0
|
存储 缓存 druid
基于springboot+jpa 实现多租户动态切换多数据源 - 基于dynamic-datasource实现多租户动态切换数据源
基于springboot+jpa 实现多租户动态切换多数据源 - 基于dynamic-datasource实现多租户动态切换数据源
4139 0
基于springboot+jpa 实现多租户动态切换多数据源 - 基于dynamic-datasource实现多租户动态切换数据源
|
传感器 人工智能 运维
阿里云河源数据中心入选工信部2022年度国家绿色数据中心
阿里云河源数据中心入选工信部2022年度国家绿色数据中心
阿里云河源数据中心入选工信部2022年度国家绿色数据中心