Spring Boot业务系统如何实现海量数据高效实时搜索

2023-09-15 10:34:20

1.概述

我们都知道随着业务系统的发展和使用,数据库存储的业务数据量会越来越大,逐渐成为了业务系统的瓶颈。在阿里巴巴开发手册中也建议:单表行数超过500万行或者单表容量超过2GB才推荐进行分库分表,如果预计三年后数据量根本达不到这个级别,请不要在创建表时就分库分表。数据库最终都是存储在磁盘上,随着数据量变大,会导致数据操作变得缓慢,无论是计算还是IO,但是话又说回来,单表数据量大就一定要进行分库分表操作吗?答案是否定的,因为分库分表本身是一个“很重”的操作,这里就不卖关子了,直接来看看分库分表带来的以下问题和挑战:

  • 重构适配系统 本身我们的业务系统不可能一开始开发上线的时候就会分库分表,都是随着系统使用和时间推移数据量日益膨胀才考虑的,进行分库分表我们业务服务项目代码需要从单一数据库表适配成多库多表,这是一次极其繁重的重构任务,还涉及到数据迁移、备份、扩容等操作问题,该任务上线链路之长、风险之大不言而喻,这也是很多小公司即使数据量上来了也不会马上分库分表的原因吧。

  • 事务问题 目前数据库只能够实现本地事务,也就是在同一个数据库中,可以允许一组操作要么全都正确执行,要么都不执行,从而确保数据库的一致性。单从分区角度出发,实际上仍然是一张表,一个库中,它不会存在事务一致性的问题,但是会使得事务变得非常复杂。而分库分表会涉及到分布式事务,目前数据库并不支持跨库事务,所以在这一块需要解决分布式事务可能带来的不一致性

  • 分页、排序、聚合函数问题 分页需要按照执行的字段进行排序,当排序字段就是分片字段的时候,通过分片规则就比较容易定位到指定的分片;当排序字段并非分片字段的时候,就需要在不同分区、分表中进行排序并且返回,然后再将不同分区、分表中返回的结果集进行汇总和再次排序,最终得到返回结果。取得页数越多,性能受影响也就越大。因为在分区、分表的时候都已经限定了分片字段,而其他字段是跟着分片的字段被分到不同的区域或者表中,这样各个分区、分表中的数据可能是随机的,为了排序的准确性,需要将所有分区、分表节点的前的数据都排好序做合并,最后进行整体排序,这样的操作是非常耗费CPU和内存资源的,所以在分区、分表的情况下、分页数越大,系统的性能也会越差。同样、在使用聚合函数,如Max、Min、Sum、Count进行计算的时候,也会像排序那样在每个分区、分表执行相应的函数,然后再将各个分区、分表的结果集进行汇总和再次计算,最终将结果返回。

  • 全局主键避免重复 单表主键id自增能够保证id不重复,但是分库分表之后,多张表就不能保证主键id不重复了,这时候就要使用分布式id算法进行生成。

  • 数据迁移、扩容问题 随着数据持续增加分表后还需要进行动态新增表时,这个时候就要考虑数据迁移以及扩容问题。一般做法是先读出历史数据,然后按照指定的分表规则再将数据写入各个分表中。这本身就是繁杂之事。

当然以上问题并不是说分库分表是一个不可取的方案,现在分库分表方案在很多公司系统都有应用的,这里想表达的是需要根据个人公司业务系统数据特点,综合评估做权衡来选择解决数据量大的实施方案。

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用

Github地址:https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent

微信公众号Shepherd进阶笔记

交流探讨qun:Shepherd_126

2.业务数据量大的解决方案

2.1 数据归档

来分析一个美团业务场景:我们日常每天点外卖,平时会去查看一年前的订单,看看一年前吃了什么吗?答案是几乎不会,或者说这种查询的请求量比较小,出现这种请求大概是有人问你很早之前点的那家外卖好吃,但是你不喜欢记不得了,你帮她查找一下的场景吧~~。由此可见,我们可以根据这一特点进行数据历史归档,即数据做冷、热区分存储。当然这个区分时限要根据自身系统数据特点来指定时限是一年还是半年…这样就能保证我们高频查询的热数据量不大了。

在查询历史数据表时,可以限制查询条件如必须选择日期范围,日期范围不能超过N个月等等从而减轻查询压力。处理历史存量数据比较简单,因为历史数据一般不会变更了,所以一般只需要两个步骤进行归档:

  • 迁移满足限定数据到指定历史归档表
  • 根据主键分批删除业务原表数据,从而降低业务数据量

这里需要强调一下,不能一次性删除所有数据,因为数据量太大可能会引发超时,锁表,长事务等问题,而是应该根据ID分批删除,例如每次删除500或1000条数据。操作步骤如下:

SELECT MAX(id) AS maxId FROM t WHERE create_time < '指定时间'

查出满足归档条件的数据最大id,接下来就可以分批归档和删除了,初始化 startId=0,每次归档500条

select * into t_bak from t where id > startId and id <= maxId limit 500

查询归档表获取最大id:maxBakId,赋值给startId方便下次分批归档删除

select max(id) from t_bak 

数据删除:

delete from t where id <= maxBakId

重复上面的归档删除操作,直至startId到maxId结束

2.2 读写分离和热点缓存

大部分的业务系统场景都是读多写少,读写比一般都在几十左右,平均每发生几十次查询请求,才有一次更新请求。换句话来说,数据库需要应对的绝大部分请求都是只读查询请求。针对这种情况我们可以通过读写分离方案来降低数据库压力。

主库负责执行应用程序发来的所有数据更新请求,然后异步将数据变更实时同步到所有的从库中去,这样,主库和所有从库中的数据是完全一样的。多个从库共同分担应用的查询请求。

对于一些高频访问的热点数据,我们可以提前预处理使用redis缓存,这样也可以有效降低数据库的压力。

2.3 同步异构数据源

我们知道MySQL会随着数据量增大而查询变慢,那么我们换成其他数据源来完成OLAP查询场景不就得了。特别是在当下大数据时代,现在互联网公司一般都具备与之规模相对应的大数据服务或者平台,那么作为业务开发者要善于应用公司大数据能力,减轻业务数据库压力。比如我们可以把数据同步到ES、HBASE等平台。

使用elasticsearch来实现海量数据搜索就是一个不错的选择,elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。能够达到实时搜索,稳定,可靠,快速,安装使用方便。但是如何实现MySQL数据同步elasticsearch呢?

答案是阿里的开源项目Canal,就是用来解决异构数据源数据同步这个问题的,Canal项目利用了MySQL数据库主从同步的原理,将Canal Server模拟成一台需要同步的从库,从而让主库将binlog日志流发送到Canal Server接口。Canal项目对binlog日志的解析进行了封装,我们可以直接得到解析后的数据,而不需要理会binlog的日志格式。而且Canal项目整合了zookeeper,整体实现了高可用,可伸缩性强

2.4 分库分表

如果通过以上:历史数据归档、数据同步异构数据源、读写分离、热点缓存都不能解决MySQL单表数据压力的,这时我们只能拆分数据表,即把单库单表数据迁移到多库多表中。这也是一线流量互联网公司需要面对的,你试想一下淘宝双11那几天要上架多少商品,产生多少订单,这已经不是前面的方案所能解决了,只能分库分表了。当然分库分表是一个复杂的操作,也不是三言两语就能全面讲清楚的,且也不是我们今天主要议题,所以我这里粗略概述一下,感兴趣的可自行查阅相关资料。

垂直拆分

垂直拆分就是按照业务拆分,我们将电商数据库拆分成三个库,订单库、商品库。支付库,订单表在订单库,商品表在商品库,支付表在支付库。这样每个库只需要存储本业务数据,物理隔离不会互相影响。

水平拆分

按照垂直拆分方案,现在我们已经有三个库了,平稳运行了一段时间。但是随着业务增长,每个单库单表的数据量也越来越大,逐渐到达瓶颈。

这时我们就要对数据表进行水平拆分,所谓水平拆分就是根据某种规则将单库单表数据分散到多库多表,从而减小单库单表的压力。

水平拆分策略有很多方案,最重要的一点是选好ShardingKey也就是按照哪一列进行拆分,怎么分取决于我们访问数据的方式

比如我们可以根据时间范围分片,根据创建时间分配到不同的表中。也可以根据哈希分表,哈希分片可以较为均匀将数据分散在数据库中。我们现在将订单库拆分为4个库编号为[0,3],每个库4张表编号为[0,3],根据分布式id%编号落库,当然也有其他分片方案,这取决于你们公司业务数据特点。

3.如何实时同步数据到elasticsearch支持海量查询

我一开始就强调了分库分表带来的问题,可见今天的重点肯定不是采用分库分表来解决数据量大的问题的,所以我接下来来讲讲我司的解决方案:数据归档+读写分离+同步异构数据源

数据归档可以有效降低数据库数据量,读写分离可以降低单数据库的读写压力,异构数据源es满足日常查询性能要求。

数据归档的操作步骤前面说过了,至于数据库读写分离实现方案等后续有时间再分析一波,今天主要讲讲如何高效实时同步elasticsearch满足查询要求。直接看架构图:

数据同步elasticsearch大概有两种:

1.针对代码中进行数据库的增删改操作时,同时进行elasticsearch的增删改操作。这种方式代码侵入性强,耦合度高,实时性高,改造起来比较痛苦,因为你不能错过任何一个增删改的地方同步操作es,否则就会出现数据不一致问题。

2.利用监听mysql binlog同步,实时性强,对于应用无任何侵入性,且性能更好,不会造成资源浪费。正好阿里巴巴开源的canal就是干这个的,完美解决问题。通过上面的架构图知道可以通过canal client拿到canal server对binlog的解析直接同步到es,但是这种方式处理比较慢,等于我们是一条一条的去同步,很多情况下es的索引表是一张大宽表,是来自MySQL几张表join的信息,这要求我们同步的时候还要根据主键通过join sql语句查出数据再同步,自然就更慢了。所以要使用消息队列kafka进行数据削峰填谷,批量操作是保证实时性的关键。

4.总结

以上全部就是我们对海量数据实时搜索的解决方案浅析,各有利弊。我们可以根据自身的业务数据情况选择合适的方案即可,切勿动不动就来分库分表,显得有点不知深浅。

更多推荐

【校招VIP】java语言考点之嵌套类&内部类

考点介绍:嵌套类&内部类问题在校招面试中经常出现。以在一个类的内部定义另一个类,这种类称为嵌套类(nestedclasses),它有两种类型:静态嵌套类和非静态嵌套类。静态嵌套类使用很少最重要的是非静态嵌套类,也即是被称作为内部类(inner)。java语言考点之嵌套类&内部类-相关题目及解析内容可点击文章末尾链接查看

如何搭建Linux环境

W...Y的主页😊代码仓库分享💕当我们想要搭建一个Linux系统,我们应该怎么使用呢?今天我就带领大家搭建Linux系统!!!目录Linux环境安装双系统(不推荐)powwershell(不推荐)虚拟机+centos7镜像使用云服务器(推荐)XShell下的复制粘贴Linux环境安装双系统(不推荐)在计算机上安装L

Linux MQTT智能家居(源码使用分析)

文章目录前言一、连接服务器1.初始化客户端2.设置端口号设置IP地址3.连接服务器二、发布消息三、订阅消息总结前言本篇文章开始我们来分析一下大佬写的MQTT源码,并且来看看怎么样使用MQTT连接到服务器。MQTT源码地址:源码地址这里找到源码中的test.c进行分析:一、连接服务器1.初始化客户端首先使用mqtt_le

Linux MQTT智能家居(ubantu和ARM中使用MQTT)

文章目录前言一、在ubantu中使用MQTT1.安装cmake2.编译MQTT库二、在ARM中使用MQTT三、使用自己的服务器四、ARM板服务器MQTTX三者关系五、MQTTX的使用六、ARM使用MQTT的方法1.修改MQTT源码2.使用库3.把MQTT源码加入到自己的工程总结前言本篇文章将会带大家在ubantu和AR

elementui-slider 滑动时会重置为0的问题解决

文章目录问题描述:问题排查问题解决总结问题描述:首次打开有elementui-slider的页面,不管滑动哪个滑块,滑动时都会自动归0(划得动,但是会自动回到最左侧0的位置),但是他确实触发了change函数。问题排查尝试了很多方法,包括将slider的值设置为number类型,虽然它本身就是number,用了一个干净

Webserver项目解析

一.webserver的组成部分Buffer类用于存储需要读写的数据Channel类存储文件描述符和相应的事件,当发生事件时,调用对应的回调函数ChannelMap类Channel数组,用于保存一系列的ChannelDispatcher监听器,可以设置为epoll类型/select类型/poll类型。HttpReque

云计算的下一个飞跃:容器编排与Kubernetes最新趋势解析

文章目录容器和容器编排的基础什么是容器?什么是容器编排?Kubernetes的崛起Kubernetes的最新趋势1.云原生生态系统的增长2.云原生安全性3.边缘计算支持4.多集群管理5.GitOps的兴起总结🎉欢迎来到云计算技术应用专栏~探索Java中的静态变量与实例变量☆*o(≧▽≦)o*☆嗨~我是IT·陈寒🍹✨

CDN加速器有哪些?

一、前端使用CDN加速的优缺点1.CDN优点(1).提高网站性能:CDN(内容分发网络)可以将静态资源(如脚本、样式表、图片等)缓存在服务器节点上,并通过就近访问用户,从而提供更快的加载速度和更好的用户体验。(2).减轻源服务器负载:CDN可以将用户请求的静态资源分发到全球各个边缘节点,从而将访问流量分散到多个服务器上

WebGIS开发教程:天地图的地理坐标系和投影坐标系的区别

天地图是⼀个在线地图服务提供商,⽀持多种地图投影和坐标系。在使用天地图时,需要了解地理坐标系、投影坐标系之间的区别。地理坐标系地理坐标系是⼀个用于表示地球表面上点位置的坐标系统,通常使用经度和纬度来表示。地理坐标系是⼀个三维坐标系统,以地球质心为原点,以地球⾚道面和⼀个固定点(例如北极点)为基准面。投影坐标系投影坐标系

【ONE·Linux || 进程间通信】

总言进程间通信:简述进程间通信,介绍一些通信方式,管道通信(匿名、名命)、共享内存等。文章目录总言1、进程间通信简述2、管道2.1、简介2.2、匿名管道2.2.1、匿名管道的原理2.2.2、编码理解:用fork来共享管道2.2.2.1、准备工作一:创建一个管道pipe2.2.2.2、准备工作二:创建子进程,对父子进程构

【Linux 应用】 kworker 进程

1.简介“kworker”是Linux内核的工作线程,用于异步处理工作队列中的任务。这些任务包括处理硬件中断、文件系统事件、管理系统内存等。你可能会看到多个kworker进程,每个进程的名称后面都有一个数字,如“kworker/0:1”、“kworker/1:2”等。这个数字表示了kworker是在哪个CPU核心上运行

热文推荐