Sharding(切片)技术(解决数据库分库一致性问题)

2024-06-24 08:32

本文主要是介绍Sharding(切片)技术(解决数据库分库一致性问题),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Sharding(切片) 不是一门新技术,而是一个相对简朴的软件理念,就是当我们的数据库单机无法承受高强度的i/o时,我们就考虑利用 sharding 来把这种读写压力分散到各个主机上去。

所以Sharding 不是一个某个特定数据库软件附属的功能,而是在具体技术细节之上的抽象处理,是Horizontal Partitioning 水平扩展(或横向扩展)的解决方案,其主要目的是为突破单节点数据库服务器的 I/O 能力限制,注意这里是突破单点数据库服务器的“I/O”能力。

在MySql 5.1 中增加了对单表的 PARTITION(分区)支持,可以把一张很大的单表通过 partition 分区成很多物理文件,避免每次操作一个大文件,可以对读写新能有所提升,下面是一个 partition 分区的例子。

一张游戏的日志表,有几千万行的数据,记录了接近一年的游戏物品获取日志,如果不对它进行 partition 分区存储,每次统计和分析日志都会消耗大量的时间。然后我们新建一张分区表,把老的日志数据导入到新的数据,统计分析的时间就会节约很多。

	CREATE TABLE `xxxxxxxx` (     `crttm` int(11) NOT NULL,     `srvid` int(11) NOT NULL,     `evtid` int(11) NOT NULL,     `aid` int(11) NOT NULL,     `rid` int(11) NOT NULL,     `itmid` int(11) NOT NULL,     `itmnum` int(11) NOT NULL,     `gdtype` int(11) NOT NULL,     `gdnum` int(11) NOT NULL,     `islmt` int(11) NOT NULL,  KEY `crttm` (`crttm`),  KEY `itemid` (`itmid`),  KEY `srvid` (`srvid`),  KEY `gdtype` (`gdtype`)  ) ENGINE=myisam DEFAULT CHARSET=utf8  PARTITION BY RANGE (crttm)   (  PARTITION p201303 VALUES LESS THAN (unix_timestamp('2014-04-01')),  PARTITION p201304 VALUES LESS THAN (unix_timestamp('2014-05-01')),  PARTITION p201305 VALUES LESS THAN (unix_timestamp('2014-06-01')),  PARTITION p201306 VALUES LESS THAN (unix_timestamp('2014-07-01')),  PARTITION p201307 VALUES LESS THAN (unix_timestamp('2014-08-01')),  PARTITION p201308 VALUES LESS THAN (unix_timestamp('2014-09-01')),  PARTITION p201309 VALUES LESS THAN (unix_timestamp('2014-10-01')),  PARTITION p201310 VALUES LESS THAN (unix_timestamp('2014-11-01')),  PARTITION p201311 VALUES LESS THAN (unix_timestamp('2014-12-01')),  PARTITION p201312 VALUES LESS THAN (unix_timestamp('2015-01-01')),  PARTITION p201401 VALUES LESS THAN (unix_timestamp('2015-02-01'))  ); 

对于这种业务场景,使用 mysql 的 partition 就已经足够了,但是对于 i/o 非常频繁的大表,单机垂直升级也已经支撑不了,存储已经不是影响其性能的主要原因,这时候就要用到sharding了。

我们一般会将一张大表的唯一键作为 hash 的 key,比如我们想要水平拆分的是一张拥有3千万行数据的用户表,我们可以利用唯一的字段用户id作为拆分的依据,这样就可以依据如下的方式,将用户表水平拆分成3张,下面是伪代码,将老的用户数据导入到新的3个被水平拆分的数据库中。

if userId % 3 == 0:#insert data in user_table (user_table_0 databaseip: 127.0.0.1)
elif userId % 3 == 1:#insert data in user_table (user_table_1 databaseip: 127.0.0.2)
else:#insert data in user_table (user_table_2 databaseip: 127.0.0.3)

我们还会对每一个被拆分的数据库,做一个双主 master 的副本集备份,至于backup,我们则可以使用 percona的cluster来解决。它是比 mysql m/s 或者 m/m 更靠谱的方案。http://www.percona.com/software/percona-xtradb-cluster

所以最后拆分的拓扑图大致如下:connhash.jpg

随着我们的业务增长,数据涨到5千万了,慢慢的发现3个sharding不能满足我们的需求了,因为服务器紧张,所以这时候BOSS打算再加2个sharding,以后会慢慢加到10个sharding。

所以我们得在之前的3台sharding服务器上分别执行导入数据代码,将数据根据新的hash规则导入到每台sharding服务器上。几乎5千万行数据每行都移动了一遍,如果服务器够牛逼,Mysql每秒的插入性能能高达 2000/s,即使这样整个操作,都要让服务暂停8个小时左右。这时候DBA的脸色已经不好看了,他应该是已经通宵在导数据了。

那有没有一种更好的办法,让添加或者删除 sharding 节点对整个分片系统的数据迁移量降低呢?

我们可以利用一致性哈希算法,把用户id散列到各个 sharding 节点,这样就可以保证添加和删除节点数据迁移影响较小。关于什么是一致性哈性算法,参考我的另一篇博客:http://snoopyxdy.blog.163.com/blog/static/601174402012722102446720/

这里介绍一个Node.js模块,hashring,github主页地址如下,上面有demo和api文档:https://github.com/3rd-Eden/node-hashring这是一个使用的demo代码,我翻译了注释,供大家参考:

// 加载模块,返回HashRing的构造函数
var HashRing = require('hashring');//实例化HashRing,这个例子中,我们把各个服务器均匀的添加了,没有设置权重
// 设置了最大的缓冲区 10000
var ring = new HashRing(['127.0.0.1','127.0.0.2','127.0.0.3', '127.0.0.4'], 'md5', {'max cache size': 10000});//我们获取这个字符串的服务器ip
var server = ring.get('foo bar banana'); // returns 127.0.0.x
console.log(server)// 如果你想把数据冗余的存储在多个服务器上
ring.range('foo bar banana', 2).forEach(function forEach(server) {console.log(server); // do stuff with your server
});// 对环上移除或新增加一台服务器
ring.add('127.0.0.7').remove('127.0.0.1');var server = ring.get('foo bar banana'); // returns 127.0.0.x
console.log(server)

接下来我们就要验证这种方式的可行性。第一,假如我们有3万条数据,根据一致性哈希算法存储好了之后,这个算法是否能够较平均的将3万条数据分散到3台sharding服务器上。第二,当数据量增加到5万,然后我们增加2台sharding服务器后,这个算法移动的数据量和最终每台服务器上的数据分布是如何的。

connHashStep1.js将3万用户数据通过一致性哈希算法存储在3台服务器上

var HashRing = require('hashring');
var ring = new HashRing(['127.0.0.1','127.0.0.2','127.0.0.3', ], 'md5', {'max cache size': 10000});var record = {'127.0.0.1':0,'127.0.0.2':0,'127.0.0.3':0
};
var userMap = {}for(var i=1; i<=30000; i++){var userIdStr = i.toString();var server = ring.get(userIdStr);userMap[userIdStr] = server;record[server]++;
}console.log(record);

第一次利用一致性hash之后,每台服务器存储的用户数据。

{ '127.0.0.1': 9162, '127.0.0.2': 9824, '127.0.0.3': 11014 }

connHashStep2.js将5万用户数据通过一致性哈希算法存储在3台服务器上,然后用户数据5万不改变,新增加2台sharding,查看新的5台sharding的用户数据存储情况以及计算移动的数据条数。

var HashRing = require('hashring');
var ring = new HashRing(['127.0.0.1','127.0.0.2','127.0.0.3', ], 'md5', {'max cache size': 10000});var record = {'127.0.0.1':0,'127.0.0.2':0,'127.0.0.3':0
};
var userMap = {}for(var i=1; i<=50000; i++){var userIdStr = i.toString();var server = ring.get(userIdStr);userMap[userIdStr] = server;record[server]++;
}console.log(record);//新增加2个sharding节点
var record2 = {'127.0.0.1':0,'127.0.0.2':0,'127.0.0.3':0,'127.0.0.4':0,'127.0.0.5':0,
};
ring.add('127.0.0.4').add('127.0.0.5')var moveStep = 0;
for(var i=1; i<=50000; i++){var userIdStr = i.toString();var server = ring.get(userIdStr);//当用户的存储server改变,则计算移动if(userMap[userIdStr] && userMap[userIdStr] != server){userMap[userIdStr] = server;moveStep++;}record2[server]++;
}
console.log(record2);
console.log('move step:'+moveStep);

5万用户数据,存储在3台服务器上的数目:

{ '127.0.0.1': 15238, '127.0.0.2': 16448, '127.0.0.3': 18314 }

当我们sharding增加到5台,存储在5台服务器上的数目:

{ '127.0.0.1': 8869,'127.0.0.2': 9972,'127.0.0.3': 10326,'127.0.0.4': 10064,'127.0.0.5': 10769 }

最终我们移动的用户数量:

move step:20833

其实你会发现

20833 = 10064 + 10769 

也就是说,我们只是将1-3节点的部分数据移动到了4,5节点,并没有多余的移动一行数据。根据上面的示例,如果是5千万数据,利用一致性哈希的算法,添加2个节点,仅需2-3小时就可以完成。

那么什么时候我们需要利用一致性哈希水平拆分数据库单表呢?1、当我们拥有一个数据量非常大的单表,比如上亿条数据。2、不仅数据量巨大,这个单表的访问读写也非常频繁,单机已经无法抗住 I/O 操作。3、此表无事务性操作,如果涉及分布式事务是相当复杂的事情,在拆分此类表需要异常小心。4、查询条件单一,对此表的查询更新条件常用的仅有1-2个字段,比如用户表中的用户id或用户名。最后,这样的拆分也是会带来负面性的,当水平拆分了一个大表,不得不去修改应用程序或者开发db代理层中间件,这样会加大开发周期、难度和系统复杂性。

  • 求大牛指点不足,指出错误~

这篇关于Sharding(切片)技术(解决数据库分库一致性问题)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1089632

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

MySQL数据库宕机,启动不起来,教你一招搞定!

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG、Mongodb数据库运维(如安装迁移,性能优化、故障应急处理等)公众号:老苏畅谈运维欢迎关注本人公众号,更多精彩与您分享。 MySQL数据库宕机,数据页损坏问题,启动不起来,该如何排查和解决,本文将为你说明具体的排查过程。 查看MySQL error日志 查看 MySQL error日志,排查哪个表(表空间

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

如何解决线上平台抽佣高 线下门店客流少的痛点!

目前,许多传统零售店铺正遭遇客源下降的难题。尽管广告推广能带来一定的客流,但其费用昂贵。鉴于此,众多零售商纷纷选择加入像美团、饿了么和抖音这样的大型在线平台,但这些平台的高佣金率导致了利润的大幅缩水。在这样的市场环境下,商家之间的合作网络逐渐成为一种有效的解决方案,通过资源和客户基础的共享,实现共同的利益增长。 以最近在上海兴起的一个跨行业合作平台为例,该平台融合了环保消费积分系统,在短

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监

缓存雪崩问题

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。 解决方案: 1、使用锁进行控制 2、对同一类型信息的key设置不同的过期时间 3、缓存预热 1. 什么是缓存雪崩 缓存雪崩是指在短时间内,大量缓存数据同时失效,导致所有请求直接涌向数据库,瞬间增加数据库的负载压力,可能导致数据库性能下降甚至崩溃。这种情况往往发生在缓存中大量 k

金融业开源技术 术语

金融业开源技术  术语 1  范围 本文件界定了金融业开源技术的常用术语。 本文件适用于金融业中涉及开源技术的相关标准及规范性文件制定和信息沟通等活动。

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)