mysql 分表之桶池

这个是为活动系统设计的一套动态分表的策略,活动数据的特点是每个活动之间的数据彼此独立,单个活动的的数据不算很大,几乎没有活动数据可以占据单独的一个表,

首先在缓存中建立一个列表,标记还可以插入数据的表的集合,而在集合之外的其他的表已经是历史数据,至少不会有新的活动是数据插入,具体实现方式如下:

最上面的是一个一个的表,真正插入数据的地方

Actor 指活动,首先从列表中得到所有桶的信息,所谓桶,就是还可以插入数据的表,

桶信息包括两种:表的名字,和已经插入该表的活动列表。根据这些可以获得当前每个表的数据量,以及为正在添加的活动预留的空间。

因为获取的数据量是根据确实添加了的数据,之前为其他的已经结束的活动多预留的空间在这里可以重复利用。

剩余的空间不足以容纳一个新的活动的时候,就放弃这个表,

同时每个表的活动数量也可以一定程度的反应写压力,虽然不是很精确,但是两者存在一定的关联,如果严格的区分写压力,可以根据joyList中过去一定时间内插入的数据量来比较精确的反应写压力,然后将插入数据量放到桶信息里面,作为判断的依据,从而达到每个表的读写压力不至于过大的目的。从所有的桶中选择最优的,作为新的活动的存储地址。

这样有几个好处:

第一:保证了每个活动一个表的基本原则。

第二:在不出现很奇葩的情况时候,保证了每个表的数据量都在一个设置的常量上下,比如200w,不会因为每个活动数据太少造成空间浪费。

第三:动态创建表,可以保证数据的长期运行

第四:为真正分担读写压力预留了扩展空间。

 

 

 

 

class DataModel
{
//存储桶池状态的set key
const KEY = 'jiaminJoyTest';
//每个桶里面放的最多的活动数,考虑写压力,-1 表示不理会
const MAXEACHBUCKET = 3;
//每个活动预估的数字,20w
const EACHJOYNUM = 200000;
//每个表里面存储数据最多的基准值,200w
const MAXTABNUM = 2000000;

/**
* 添加新的桶,或者把活动添加到一个指定桶里面
*
* 之所以把修改和添加放在一起,是为了方便维护bucket里面的结构一直
* 只能有select 触发
* @param array $bucket
**/
protected static function addBucket($jid , $state = array())
{
$redis = new myRedis();
if(empty($state)){
//这里加入日志,提示创建了表
$state = array(
'name' => UdataModel::createTab() ,
'joyNum' => 1,
'joyList' => array($jid)
);
} else {
if($redis->sRem(self::KEY , json_encode($state))){
$state['joyNum'] += 1;
array_push($state['joyList'] , $jid);
} else {
Common::debug('删除桶池失败');
Message::showError('删除更新桶失败');
}

}
if($redis->sAdd(self::KEY , json_encode($state))){
return $state['name'];
}
//这里报警,添加失败,丢失了桶
Common::debug('添加桶信息进入set 失败');
return false;
}

/**
* 从桶池中选择当前可以插入的表
* 就是从目前的bucket 中找一个最优的,
* 然后插入,如果都不满足条件,创建一个新的,如果超出了最大的桶池数就报警,
* 同时,创建成功
*
**/
public static function select($jid)
{
//查找所有符合条件的,然后得到一个负载最小的
$redis = new myRedis();
$data = $redis->sMembers(self::KEY);
$target = array();
if(count($data) === 0){
self::testAdd();
}
foreach($data as $bucket){
Common::Debug('bucket : ' . $bucket);
$state = json_decode($bucket , true) ;
// 桶里面已经存储的总量
$storedInTab = UdataModel::getNumByName($state['name']);
if($storedInTab === 0){
BaseModelCommon::debug("no data in ". $state['name']);
}

if($state['joyNum'] !== count( $state['joyList']) ){
//发送报警邮件,严重错误
}
//目前在桶里面的活动已经填充了的数据
$activeData = UdataModel::getDataNum($state['name'], $state['joyList']);
BaseModelCommon::Debug('activeData : ' . $activeData) ;
//需要为桶里面的活动预留的空间,
//预估的每个活动的记录数 * 活动的数目 - 已经存储了的数据 = 接来下还要占据的记录数
$activeNeedSpace = self::EACHJOYNUM * $state['joyNum'] - $activeData;
BaseModelCommon::Debug('activeNeedSpace : ' . $activeNeedSpace) ;
//发送报警邮件,告知管理员,同时这个桶池不再添加活动,避免进一步增加上限
if($activeNeedSpace < 0){
continue;
}
// 剩余的空间为每个表的最大数据数 - (已经存储的数据 + 还将要存储的数据数)
$leave = self::MAXTABNUM - ($storedInTab + $activeNeedSpace);

BaseModelCommon::Debug('leave : ' . $leave) ;
// 如果剩下的空间还有就插入,进入备用列表,200w已经很保守了,没有必要降到200w以下
if($leave > 0){
if(empty($target)){
$target = $state;
} else {
//就根据插入的活动数量来判断负载, 不是最科学的,但是也是有相当道理
if($target['joyNum'] > $state['joyNum']){
$target = $state;
}
}
}
Common::Debug('target : ' . json_encode($target));
}
//return self::addBucket($jid , $target);
}

/**
* 测试添加addBucket
* @return array () 添加的桶的信息,名字等等
**/
public static function testAdd()
{
$states = array(
//name : 表名
//joyNum : 已经插入的活动数目
//joyList : 该表里面 正在插入的joy id列表
array('name' => 'udata_1', 'joyNum' => 3, 'joyList' => array( 3,2,4 )),
array('name' => 'udata_2', 'joyNum' => 0, 'joyList' => array()),
array('name' => 'udata_3', 'joyNum' => 1, 'joyList' => array(7)),
);
$redis = new myRedis;
if($redis->sCard(self::KEY) === 0){
//灌数据
foreach($states as $state){
$redis->sAdd(self::KEY , json_encode($state));
}
}
//echo self::addBucket();
}

/**
* 监测程序的运行
*
* 程序员应该实时知道自己程序的运行状况
* @return array
**/
public static function inspect()
{
//array('name' => 'udata_1', 'joyNum' => 2, 'joyList' => array( 3,2,4 )),
$redis = new myRedis;
$data = $redis->sMembers(self::KEY);
header("Cache-Control:no-cache");
echo "

";
foreach($data as $state){
$bucket = json_decode($state , true);
echo "

";
echo "

";
echo "

";
}
echo "

当前正在插入表 表内项目数 表内数据量 详情
{$bucket['name']} {$bucket['joyNum']} " . UdataModel::getNumByName($bucket['name']) . " ";
foreach($bucket['joyList'] as $joy){
echo "
joy id 为{$joy}数量" . UdataModel::getDataNum($bucket['name'], $joy) . "

";
}
echo "

";
}

/**
* 清除数据
* 开发的时候用
**/
public static function clear()
{
$redis = new myRedis();
$data = $redis->sMembers(self::KEY);
foreach($data as $state){
$redis->sRem(self::KEY, $state);
}
$data = $redis->sMembers(self::KEY);
if(!count($data)) {
echo "it is clear
";
} else {
echo "no clear
";
}
}

/**
* 清除bucket
*
* 当表里的数据已经达到200w的时候,就把这个表对应的桶删除
* 定时脚本,定期清理过期的数据
**/
public static function delBucket()
{
$data = $redis->sMembers(self::KEY);
foreach($data as $bucket){
$state = json_decode($bucket , true) ;
//数据已经满了的话,就从桶池里面删除,不再插入数据
if(UdataModel::getNumByName($state['name']) >= self::MAXTABNUM){
$redis->sRem(self::KEY , $bucket) ;
}
}
}

function __construct(){}
}

Leave a comment

Your email address will not be published.

*