一种自平衡解决数据倾斜的分表方法

news2024/11/25 14:42:55

1、背景

这篇主要描述了B端令牌系统应用数据分表解决业务数据量增大,且存在的数据倾斜问题,主要面向的场景是一对多数据倾斜问题

1)B令牌的业务背景

先简述一下B令牌的业务背景,B令牌系统是用于营销场景中,将许多用户绑定在一个令牌上,再将令牌绑定在促销上,从而实现差异和精准营销,一般情况下一个令牌的生命周期等同于这个促销。

2)B端令牌的结构现状

令牌和令牌用户关系是一个一对多的关系,早期的令牌系统使用jed分库,2个分片,中间进行了一次扩容达到了8个分片,存储的数据行数达到了1.2亿

3)数据和业务现状

1.2亿数据,分布在8个分库中,每个分库平均1500万,但由于分库字段使用的是令牌ID(token_uuid),有得令牌用户少,只有几千到一万,有的令牌用户多,有100万到150万,令牌总数量并不多,只有2万左右,所以导致数据存在倾斜,有的分库有3000多万数据,有的分库可能只有几百万,这已经开始导致数据库读写性能下降。而又因为令牌用户关系表数据结构很简单,虽然数据行数很多,但占用的空间却不大。8个分库总占用量还不足20G。同时令牌的生命周期基本和促销相同,一个令牌服务于一个或几个促销后,就会慢慢过期被弃之不用,后续会继续创建新的令牌。所以这些过期令牌是可以进行归档的。

同时由于B端业务的发展,业务诉求也更多,和业务沟通中了解到,未来会上线自动选人系统,由系统自动创建令牌,并选择适合促销的人群,未来每个月数据增量在3000万左右,如果运行一年就会增加3.6亿,届时单表数据量平均会达到6000万,当前的设计架构已经完全不能满足业务需求。

同时目前也存在根据令牌ID分页查询令牌下用户的功能,但仅限于给管理端运营使用,使用也不频繁。

2、解决方案的思考

1) 怎么解决这个问题

面对日益下降的数据库读写性能,以及业务增长的需求,当下面临以下几个问题:

  1. 如何解决单表数据行数过多的问题

  2. 当前分库方案存在比较严重的数据倾斜

  3. 如果应对未来数据的增长

2) 技术方案调研和对比

a.数据库分表

一般情况应对第一个问题,通常都是分库分表,而当下我们已经是8个分库,而且8个分库才占用了不足20G空间,单库资源浪费严重,所以完全不会考虑继续增加分库的方式,所以分表才是解决办法。

数据分表通常有两种方式:垂直分表和水平分表。

垂直分表指的是将数据的列进行拆分,然后应用主键或其他业务字段进行关联,从而降低单表数据占用空间,或减少冗余存储,B令牌的场景数据结构简单,数据占用空间小,所以不会使用该分表方式。

水平分表指的是将数据的行以一种路由算法拆分到多张表中,读取时候也基于这种路由算法来读取数据,这种分表策略一般用来应对数据结构不复杂,但数据行特别多的场景。这也是我们即将使用的方式。使用这种方式需要考虑的就是如何设计路由算法,这里也是使用这种方式来分表。

b.路由算法

数据分表路由算法的使用在业内也有多种,一种是利用一致性hash,选择合适的分表字段,对字段值hash后值是固定的,使用该值通过取模或者按位运算的方式得到一个固定的序号,从而确定数据存储在哪张表中。

比较常见的应用如分库大多就是使用一致性hash的方式,通过即时计算分库字段的值判断数据属于哪个分库从而决定将数据存入哪个分库或者从哪个分库读取数据。而如果查询时没有指定分库字段则需要同时向所有分库发出查询请求,最后在汇总结果。

另外像java代码的HashMap数据结构其实也是一种一致性hash算法的分表策略,通过对key进行hash后决定将数据存入数组的哪个序号,HashMap里面用的不是取模的方式获取序号,而是使用按位运算的方式,使用这种方式也决定了HashMap的扩容都是按照2的x次方的大小进行扩容,以后有机会可以介绍这个原理。

上面就是HashMap中的一个简化的数据Hash存储过程,当然我省略了一些细节,比如HashMap中每一个节点都是一个链表(冲突过多还会变成红黑树)。应用在我们的场景中就可以将每个序号当成是一张数据表即可。

以上这种路由算法的优点的路由策略简单,实时计算也不用增加额外存储空间,但也存在一个问题就是如果要扩容则需要将历史数据重新hash一遍进行迁移,比如数据库分库如果增加分库则需要将所有数据重新计算分库,HashMap扩容也会执行rehash重新计算key在数组的序号。如果数据量太大,这种计算过程耗时将会很长。同时,如果数据表太少,或者选择分片的字段离散程度低都会导致数据倾斜。

还有一种分表算法优化了这种rehash过程,这便是一致性hash环,这种方式是在实体节点之间抽象出很多虚拟节点,然后再利用一致性hash算法将数据打在这些虚拟节点上,而每个实体节点其实是负责的该实体节点逆时针方向上和另一个实体节点相邻的虚拟节点的数据。这种方式的好处是假如需要扩容增加节点,增加的节点放在环上任意位置,也只会影响到该节点顺时针方向上相邻节点的数据,只需要将该节点中的部分数据迁移到这个新节点上即可,大大降低rehash的过程。同时由于虚拟节点多,也可以增加让数据更均匀的分布在这个环上,只要将实体节点放置在合适的位置,就能最大程度保证的解决数据倾斜问题。

比如图上就是一个一致性Hash环的hash过程,在整个环上有从0到2^32-1个节点,其中实线的就是真实节点,其他都是虚拟节点,张三通过hash后落到环上的虚拟节点,然后从虚拟节点的位置顺时针寻找真实节点,最终数据就存储在真实节点上,所以疯驴子和李四就存储在节点2上,王五在节点3上,郑六在节点4上。

扩容了一个节点5号后,则需要将节点1和节点5之间的数据迁移到节点5上,其他节点数据则不用变更。但如图上看到的,只加这一个节点,也容易导致每个节点负责的数据不均匀,比如节点2和节点5,相比于其他节点负责的数据就少了很多,所以扩容时最好是成倍扩容,这样数据可以继续保持均匀。

3) 思考我的方案

再回到B令牌的业务场景上来,需要能达成以下诉求

  1. 首先必须使用水平分表来解决单表数据量过大的问题

  2. 需要能支持根据令牌分页查询用户

  3. 由于当前业务数据增量在3000万,但不排除未来业务继续增长的可能,分表数量需要能支持未来扩展

  4. 数据行数过高,未来在扩展时必须保证无需数据迁移或者数据迁移成本低

  5. 需要解决数据倾斜问题,确保不因为单表数据量过大而导致整体性能降低

基于以上诉求,首先看问题b,如果要支持根据令牌分页查询用户,就需要保证令牌下的所有用户都在同一张表上,才能简单的支持分页查询,否则用一些汇总归并算法则复杂程度过高了,而且表太多也会降低查询性能。虽然也可以通过将数据异构es提供查询功能,但仅仅是为了少量管理端的查询诉求再进行数据异构,成本有些高收益并不明显,也有些浪费资源。所以分表字段就只能确定使用令牌ID。

而上面也提到令牌ID数量并不多,而且令牌下的用户也从1万到100万不等,单纯使用一致性hash的方式用令牌ID作为分表策略则会导致数据倾斜严重,而且未来扩容时数据迁移成本也很高。

但使用一致性hash环又会导致未来在扩容时最好是按2的倍数扩容,不然就会存在有的节点负责的虚拟节点多,有的节点负责虚拟节点少,导致数据不均匀。然而在和数据库同事进行沟通,一个数据库下的数据表数量不宜太多,否则会对数据库带来较大压力,而一致性hash环这种方式可能扩两三次容就会导致分表数达到一个很高的数值。

基于以上问题,在确定使用令牌id作为分表的前提下,就需要着重思考如何支持动态扩容和解决数据倾斜的问题。

3、方案落地

1) 方案概述

a.如何支持动态扩容

分表的字段已经确定使用令牌ID,而前面也提到我们的数据结构是令牌和用户是一对多的关系数据,那么在创建令牌时hash出的分表序号存储下来,后续基于存储的分表序号进行路由,就可以保证未来扩容时也不会影响存量数据的路由,无需进行数据迁移。

b.如何解决数据倾斜

由于选用了令牌ID作为分表字段,而各令牌数据量大小不一,数据倾斜就会是一个大问题。所以这里就想办法引入了一个分表水位的概念。

在用户请求保存或删除关系用户数的时候,基于分表序号对当前分表数量进行一个增减的计数,当某个分表中的数据量处于高水位时,就将该分表从分表算法中剔除,从而让该分表不会继续产生新的数据。

比如当设置阈值1000万为高水位,由于以上5张表都没有达到高水位,则创建令牌时根据令牌ID进行Hash后取模得到3,按顺序获取表,则当前令牌的分表号为b2b_token_user_3。后续关系数据都从该表中获取。

运行一段时间后,表b2b_token_user_1数据量已经增长到了1200万,超过了1000万的水位,这时候在创建令牌则将该表移除,在此进行Hash后取模得到1,则当前分到的表就是b2b_token_user_2。而如果b2b_token_user_1的水位如果一直不能降下来,则该表后续都不会再参与分表,表中的数据量也不会再增加。

当然有一种可能就是所有表都进入了高水位,为了兜底,这时水位功能就失效,所有表都加入到分表中来。

c.定期数据归档,降低分表水位

如果表中的数据量只会不断增加,而不会减少的话,那么早晚所有的表都会达到高水位,这就不能达到动态的效果。上面背景中有提到,令牌创建后是为某一批促销服务,促销终止后,令牌也会失去作用,同时令牌上也有有效期,超过有效期的令牌也会失去作用。所以定期对数据进行归档就可以让那些处于高水位的表把水位慢慢降下来,重新加入到分表中。

而且当前令牌已经存在了一张b2b_token_user的表,里面的数据已经有1.2亿,可以将该表作为图上的0号表,这样在第一次上线时只要将历史令牌都的分表序号都记为0即可,存量数据就不需要再进行迁移,而该表数据量水位高,也不会参与分表。再搭配定期的数据归档,该表的水位也会慢慢将下来。

d.监控机制

虽然可以通过定期进行数据归档,可以让表的水位降下来,但随着业务发展,可能会存在大多数表都进入了高水位,并且都是有效数据的情况。这时候系统就会像HashMap判断容量达到75%就自动扩容一样,我们不能够自动创建表,但当75%的表都进入高水位可以告警出来,开发人员监听到告警人工介入,观察是需要调高水位,还是进行表的扩容。

3) 不足

• 水位阈值和扩容监控

目前水位的阈值还是依靠人工手动设置,应该设置多大还是比较感性的,只能设置一个,在告警以后适当调整。不过其实可以在系统中自动监控接口读写性能的波动,发现大多数表达到高水位时,接口读写性能都没有明显变化,可以系统自动调高阈值,从而形成智能阈值。

而接口性能读写出现明显变化时发现大多数表都达到了阈值,则可以告警提示应当考虑扩容。

4、总结

解决问题从来没有银弹,我们需要利用手里的技术手段和工具,进行组合、适配,使之适合我们当下的业务和场景,没有好或不好,只有适不适合。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/765869.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

写给后端开发的『vue3』请求后端接口

本文分享一下在vue3前端项目中请求后端接口获取数据。比较简单,内容如下: 1、使用axios请求后端接口 首先npm install axios,添加axios依赖,就靠它来请求后端接口了,基本等同于使用jquery发ajax。 # src/main.js i…

制作Ubuntu20.04系统盘教程

下载ios系统镜像:http://releases.ubuntu.com/20.04/ 下载启动盘制作工具:http://rufus.ie/downloads/ 安装后,在如下软件界面进行U盘系统的写入:

strcmp函数和strncmp函数【C语言】

strcmp函数和strncmp函数 strcmp函数一、strcmp函数的简介二、strcmp函数的功能三、strcmp函数的使用四、strcmp函数的模拟 strncmp函数一、strncmp的简介二、strncmp函数的功能三、strncmp函数的使用 strcmp函数 一、strcmp函数的简介 strcmp函数在库函数中的定义&#xff1…

STM32 ADC基础知识讲解

文章目录 前言一、ADC的基本介绍二、STM32 ADC讲解1.ADC分辨率2.ADC通道讲解3.ADC转换模式单次转换模式连续转换模式 4.扫描模式5.数据对齐方式左对齐右对齐 总结 前言 在正式的学习如何编写ADC代码时我们先来学习一下ADC的基础知识部分,只有掌握好了这些基础知识才…

打造i-SMART智能网联平台,亚马逊云科技助力上汽快速出海

当前在各大外资车企不断加码在华投资之际,越来越多的中国汽车品牌纷纷开始走出国门,加速推进全球化业务,将赛道转至更为广阔的海外市场。 上汽海外出行科技有限公司(简称“上汽海外出行”)成立于2018年,承…

华为OD机试真题 Java 实现【挑选字符串】【2023 B卷 100分】,附详细解题思路

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示 专栏导读 本专栏收录于《华为OD机试(JAVA)真题(A卷B卷)》。 刷的越多,抽中的概率越大,每一题都有详细的答题…

vue的生命周期和执行顺序

1,Vue 生命周期都有哪些? 序号生命周期描述1beforecreate创建前vue实例初始化阶段,不可以访问data,methods; 此时打印出的this是undefined;2created创建后vue实例初始化完成,可以访问data,meth…

postgresql部署及优化

目录 一、postgresql概念 二、PostgreSQL 特征 三、postgres安装与登录 3.1、postgres安装 3.2、postgres使用 3.3.1、postgres登录 3.3.2、修改postgres用户密码 四、PostgreSQL 命令 4.1、PostgreSQL 创建数据库 4.2、删除数据库 4.3、创建表格 4.4、删除表格 一、…

Bard:Google AI的下一代语言模型,支持多语言、语音朗读、图片解析和更多

说起时下火爆的生成式AI,并不是只有ChatGPT。Bard也是一个很优秀的产品,并且刚刚发布的很多有趣的新功能。文末告诉你如何访问Bard。 Google AI在最近的更新中发布了Bard,一个新的语言模型。Bard支持多种语言,包括中文&#xff0…

Hadoop和hive一键启动脚本

#!/bin/bashcd $HADOOP_HOME/sbinsh ./start-all.shcd $HIVE_HOME/binnohup hive --service metastore & 停止hadoop服务 #!/bin/bashcd $HADOOP_HOME/sbinsh ./stop-all.sh 停止hive服务

Stability AI 把绘画门槛打为 0!

本文由 GPT- 4 所创作,配图由 Stable Doodle 生成。 编者按 Stability AI 上新了! 其收购的 Clipdrop 发布了全新的 Stable Doodle 工具,我在使用后最为直观的感受就是 —— 把绘画门槛打下来了。 在 Stable Doodle 之前,使用各…

大型企业数字化信创论坛启幕,金蝶携手500强企业论道数字化变革!

7月14日,“数字中国第二届大型企业数字化信创论坛”在数字化新城成都盛大启幕,中国信通院、金蝶携手来自四川省企业联合会、物产中大集团、浙江省交通投资集团、华彩咨询、四川九洲集团、资阳发展投资集团的大咖、专家论道信创,探索中国企业的…

oc uitableView 展示单组数据

设置模型 #import <Foundation/Foundation.h>interface XMGWine : NSObjectproperty (nonatomic,copy) NSString *name; property (nonatomic,copy) NSString *image; property (nonatomic,copy) NSString *money;(instancetype)wineWithDict:(NSDictionary *)dict; end…

智能会议记录:利用剪映和Claude实现高效会议纪要制作

具体操作步骤可以是: 1、使用录音笔或手机录音将会议过程录制下来。 2、将录音文件上传到pc端&#xff0c;使用剪映程序,利用语音转文本和自然语言处理技术,分析音频&#xff0c;生成文字稿。 3、打开Claude网站。 4、粘贴会议文字。 5、输入提示词&#xff1a; 用中文总结该…

代理模式【静态代理和动态代理实现业务功能扩展】

静态代理 我们在不修改业务的情况下想要给它增加一些功能&#xff0c;这就需要使用代理模式。我们不会在原有业务上直接修改&#xff0c;为了避免修改导致程序不可逆转的破坏。三种角色&#xff1a;抽象角色-接口、真实角色-实现类和代理角色-代理类。真实角色和代理角色继承的…

ARM 架构是什么?

ARM&#xff08;Advanced RISC Machines&#xff09;架构是一种处理器架构&#xff0c;它是一种精简指令集计算机&#xff08;RISC&#xff09;架构。ARM架构最初由ARM Holdings&#xff08;现在是SoftBank Group的子公司&#xff09;开发&#xff0c;并在1980年代末和1990年代…

K8s核心概念 Controller

K8s核心概念 Controller Kubernetes核心概念 Controller一、pod控制器controller1.1 Controller作用及分类1.2 Deployment1.2.1 Replicaset控制器的功能1.2.2 Deployment控制器的功能1.2.3 Deployment用于部署无状态应用1.2.4 创建deployment类型应用1.2.5 访问deployment1.2.6…

SpringCloud集成Sleuth+Zipkin进行链路追踪

关于微服务链路追踪的一些概念&#xff1a;【分布式链路追踪】 文章目录 1、整合Seluth2、日志信息分析3、Zipkin介绍4、Zipkin服务端安装5、搭配Sleuth整合客户端Zipkin6、收集数据7、存储trace数据 1、整合Seluth Spring Cloud Sleuth是一个用于追踪的工具&#xff0c;它可以…

ThinkPHP 多对多关联

用多对多关联的前提 如果模型 A 多对多关联模型 C&#xff0c;必须存在一张中间表 B 记录着双方的主键&#xff0c;因为就是靠着这张中间表 B 记录着模型 A 与模型 C 的关系。 举例&#xff0c;数据表结构如下 // 商品表 goodsgoods_id - integer // 商品主键goods_name - va…

【C#】并行编程实战:使用延迟初始化提高性能

在前面的章节中讨论了 C# 中线程安全并发集合&#xff0c;有助于提高代码性能、降低同步开销。本章将讨论更多有助于提高性能的概念&#xff0c;包括使用自定义实现的内置构造。 毕竟&#xff0c;对于多线程编程来讲&#xff0c;最核心的需求就是为了性能。 延迟初始化 - .NET…