欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  数据库

数据库分表后,并发环境下,生成全局id生成的几种方式

程序员文章站 2022-05-15 17:18:38
...

最近一个项目由于数据量变大,需要进行数据分表。数据存储在淘宝的tddl上。分表后,原先的自增id就不能使用了。tddl对java支持很好,分表后无需考虑全局id的问题。但是这个项目使用的是php进行开发,必须自己生成全局id。以下列出几种分表方案,仅当抛砖引玉

最近一个项目由于数据量变大,需要进行数据分表。数据存储在淘宝的tddl上。分表后,原先的自增id就不能使用了。tddl对java支持很好,分表后无需考虑全局id的问题。但是这个项目使用的是php进行开发,必须自己生成全局id。以下列出几种分表方案,仅当抛砖引玉。

方法1:使用CAS(compare and swap)

其实这里并不是严格的CAS,而是使用了比较交换原子操作的思想。

生成思路如下:

每次生成全局id时,先从sequence表中获取当前的全局最大id。然后在获取的全局id上做加1操作。把加1后的值更新到数据库。更新时是关键。

如加1后的值为203,表名是users,数据表结构如下:

CREATE TABLE `SEQUENCE` (
    `name` varchar(30) NOT NULL COMMENT '分表的表名',
    `gid` bigint(20) NOT NULL COMMENT '最大全局id',
    PRIMARY KEY (`name`)
) ENGINE=InnoDB

那么更新语句是。

update sequence set gid = 203 where name = ‘users’ and gid ;

sql语句的 and gid

如果update语句的影响记录条数为0说明,已经有其他进程提前生成了203这个值,并写入了数据库。需要重复以上步骤从新生成。

代码实现如下:

//$name 表名
function next_id_db($name){
  //获取数据库全局sequence对象
  $seq_dao = Wk_Sequence_Dao_Sequence::getInstance();
  $threshold = 100; //最大尝试次数
  for($i = 0; $i get_seq_id($name);//从数据库获取全局id
    $id = $last_id +1;
    $ret = $seq_dao->set_seq_id($name, $id);
    if($ret){
      return $id;
      break;
    }
  }
  return false;
}

方案2:使用全局锁。

在进行并发编程时,一般都会使用锁机制。其实,全局id的生成也是解决并发问题。

生成思路如下:

在使用redis的setnx方法和memcace的add方法时,如果指定的key已经存在,则返回false。利用这个特性,实现全局锁。

每次生成全局id前,先检测指定的key是否存在。

如果不存在则使用redis的incr方法或者memcache的increment进行加1操作。这两个方法的返回值是加1后的值。

如果存在,则程序进入循环等待状态。循环过程中不断检测key是否还存在,如果key不存在就执行上面的操作。

代码如下:

//使用redis实现
//$name 为 逻辑表名
function next_id_redis($name){
  $redis = Wk_Redis_Util::getRedis();//获取redis对象
  $seq_dao = Wk_Sequence_Dao_Sequence::getInstance();//获取存储全局id数据表对象
  if(!is_object($redis)){
    throw new Exception("fail to create redis object");
  }
  while(1){
    //检测key是否存在,相当于检测锁是否存在
    $ret = $redis->setnx("sequence_{$name}_flag",time());
    if($ret){
      break;
    }
    $time = $redis->get("sequence_{$name}_flag");
    if(time() - $time > 1){//如果循环等待时间大于1秒,则不再等待。
      break;
    }
  }
  $id = $redis->incr("sequence_{$name}");
  //如果操作失败,则从sequence表中获取全局id并加载到redis
  if (intval($id) === 1 or $id === false) {
    $last_id = $seq_dao->get_seq_id($name);//从数据库获取全局id
    if(!is_numeric($last_id)){
      throw new Exception("fail to get id from db");
    }
    $ret = $redis->set("sequence_{$name}",$last_id);
    if($ret == false){
      throw new Exception("fail to set redis key [ sequence_{$name} ]");
    }
    $id = $redis->incr("sequence_{$name}");
    if(!is_numeric($id)){
      throw new Exception("fail to incr redis key [ sequence_{$name} ]");
    }
  }
  $seq_dao->set_seq_id($name, $id);//把生成的全局id写入数据表sequence
  $redis->delete("sequence_{$name}_flag");//删除key,相当于释放锁
  $db = null;
  return $id;
}

方案3:redis和db结合。

使用redis直接操作内存,可能性能会好些。但是如果redis死掉后,如何处理呢?把以上两种方案结合,提供更好的稳定性。

代码如下:

function next_id($name){
    try{
        return $this->next_id_redis($name);
    }
    catch(Exception $e){
        return $this->next_id_db($name);
    }
}

另外对于全局id的生成,Flicker和Twitter也都公布了自己的方案。感兴趣的人,可以了解下。

http://my.oschina.net/u/142836/blog/174465