`
alricren
  • 浏览: 28598 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

关于id generator

阅读更多

前几日,徐XX同学翻出了一篇mixi engineer blog上的老文,地址是
http://alpha.mixi.co.jp/blog/?p=272。其中一部分提到了如下的情况。

mixi的日记服务,在表设计上是采用了sharding的方式,即日记并不是全部存放
在一张表内的,而是根据member_id放入不同库的相同表结构中。因为不同的库
可以放在不同的物理机器上,所以这样做不仅减少了每张表的大小,也能够同时
利用起多台机器的资源,加速对表的读写。

Sharding带来了好处,也会带来负面影响。因为日记存放在不同的数据表中,又
要保持日记ID的全局唯一性,那么我们就无法再利用每张表各自的
auto_increment来生成主键了。

既然要生成全局唯一的ID,一个容易想到的方法是虽然日记被写入了不同的表,
但是我们仍然可以在一张表内来生成ID。Mixi也是这么做的。

表结构如下:
create table id_pot (
    id bigint unsigned not null
) engine=innodb;
//注意,这张表没有申明主建。Mysql会隐含地帮你生成主键。

获取唯一ID的方法:
UPDATE id_pot SET id = LAST_INSERT_ID(id+1);
SELECT LAST_INSERT_ID();
//关于LAST_INSERT_ID可以查阅MySQL的官方文档

然后,就如blog上所说的,在过年的时候,大量的用户在写日记,而瓶颈就出现
在了这获取唯一主键上。经过调查发现,是因为有大量的数据库连接在并行着获
取ID而产生了死锁。

其实我觉得这里并不是真正意义上的死锁,死锁是指线程A需要获得X,Y两个资源
的锁来完成一个事务,而线程B也是同样,只是A和B在获取锁的顺序上不一致,如
果A先获得了X的锁,而B先获得了Y的锁,在这种情况下就会产生死锁。(补充:
在Innodb的事务中,锁是在真正需要的时候才会去获得,并在整个事务结束后才
会释放。)而在这里,所有的线程都只是想获得一行数据的锁,这个时候就需要
排队,同时Mysql也会尝试判断是否产生了死锁。而这里发生的“死锁”就源于尝
试去解决死锁。简单看了下源代码,大致的调用关系如下:

lock_rec_lock -> lock_rec_lock_slow -> lock_rec_enqueue_waiting ->
lock_deadlock_occurs -> lock_deadlock_recursiv -> return
LOCK_EXCEED_MAX_DEPTH

在源代码中有这样一个常值LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK, 值为200死锁检
查是一个递归的过程,当深度大于这个常值时就会放弃检查,返回
LOCK_EXCEED_MAX_DEPTH。这样做的原因应该是处于对cpu资源的考量。

存疑:按理说一句update只要加一个排它锁,对于没有持有锁的transaction是
不是没有必要检查死锁

原因发现了,那么解决方法就是减少对一行数据的并发执行数。blog的作者采取
了将原本多台application server(比如有200台,每台apache的max_client为
100, 每个apache进程持有一个数据库链接)直接连mysql,改为连接N台ID
generate API Server,然后由API Server去连数据库。API Server的
max_client设为10,保证这台server对mysql的并发更新数最大为10,另外因为
tcp协议中连接是可以排队的并且获取一个id所花费的时间是很短的,所以不会
有什么大的影响,实际测试下来一台机器可以达到2000qps (之前mybench的测试
结果挺奇怪的,qps才200?)

在这之后呢,kzys又写了一篇相关的文章
http://alpha.mixi.co.jp/blog/?p=2453
在这篇文章中提到,上述的做法后来遇到了too many connections的情况(mysql
有一个变量叫max_connections, 可以通过show global variables like
'max_connections';来查看。当连接数超过这个数值时,就会报too many
connections)。原因文章里没有细说,比较奇怪,照我的理解如果1台api
server保持着10个数据库连接,而mysql的max_connections是知道的,所以最多
放几台api server是完全可以计算的。当application server向api server的请
求非常大时,整体的响应时间会增大。但是应该也不至于在mysql上出现too
many connections阿...

其实改善id generator的一个比较简单的方法就是把engine从Innodb改成MyIsam。
MyIsam相比Innodb而言,不支持事务,也就是不会存在死锁,不需要去检查是否
有死锁。只能表锁不能行锁。这两点其实很适合于高并发的id generator。不会
死锁,也就没有了Innodb200个并发的限制,而表锁的实现逻辑必然大大简单于多
版本控制的行锁,速度也会更快。

我做了一些简单的测试:

方案1:
create table id_pot(id int unsigned not null, counter bigint unsigned
not null, primary key(id));
insert into id_pot values(1, 0);

update id_pot set counter = last_insert_id(counter+1) where id = 1;
select last_insert_id();

进程数     100        200       300       400       每个进程执行200次
Innodb   2213       2157    deadlock    deadlock     单位QPS
MyIsam   2121       2180      2098      2090

Innodb在200以下和MyIsam不相上下,超过以后就如预期的开始产生死锁。而
MyIsam因为是表锁,多大的并发其实还是在顺序的一个个处理的,在各个并发数下都很稳定。

方案2:
create table id_pot(id bigint unsignet not null auto_increment, stub
char(1) not null, primary key(id), unique key(stub));
insert into id_pot(0, 'a');

replace into id_pot(stub) values('a');
select last_insert_id();

进程数     100        200       300       400       每个进程执行200次
Innodb    415        353    deadlock    deadlock     单位QPS
MyIsam   2248       2185      2131      2136

MyIsam依然稳定,而Innodb就较之前的方法慢了很多。这次采用的是replace并
利用auto_increment来获得id。之前还试过一个直接在主键上id =
last_insert_id(id+1),速度更慢,惨不忍睹。所以也许是和频繁改变主键导致
簇索引(B+tree一个)频繁结构变化有关。
       
如果2200左右的QPS无法满足需求了呢,除了提高硬件以外,一种简单的方式就
是使用两台mysql,并初始化设定一台从1开始,一台从2开始,并且步长都是2。
这样保证了两台生成的ID不会重复。但是这样做的一个缺点时,id对应的记录不
再是按时间顺序排列的了。

Mixi的日记服务到目前为止还是在使用一张MyIsam表来提供主键生成。

Mixi blog里还有很多料,有些我们现在看起来会觉得有点奇怪的地方都还是有其
历史原因的。我们会发现echo这个功能的主键是member_id + timestamp, 这个功
能发布于日记id生成的瓶颈还没有解决的情况下。这样的主键有2个缺点,一个是
功能上的限制了一个member_id在同一timestamp下不能有两条echo,一个是因为所
有的secondary索引(就是我们平时定义的索引)中必然包含着簇索引(主键索
引),所以主键越大,所有的secondary索引也都变大。考虑到echo的发布频率一
定比日记还要高很多,所以采取了member_id + timestamp这个没有主键生成瓶颈
的策略。member_id + timestamp有两种形式,一种就是member_id +
timestamp,还有一种是timestamp + member_id,这两种主键是有比较大的区别
的。因为Innodb的簇索引是一颗B+tree,按照主键的的大小顺序排序。前者是
member_id在前,所以插入的数据是非有序的,B+tree的结构调整会比较多,而如
果是timestamp在前,那么插入的记录是有序的概率会大很多。光论插入的性
能,timestamp放前会比较好。但是如果是读取,通常是select * from xxx
where member_id = x order by timestamp desc。这时候如果是member_id +
timestamp做主键,就会直接用到主键索引。再加上由于Mysql之前放了一个Q4M队
列来缓解插入压力,所以最终的这个选择在当时还是非常正确的。

那么如果需求继续增长,2台MyIsam的id generator满足不了需求了,该怎么办?
使用UUID?使用UUID会直接消除id generator的瓶颈,但是之前提到无序的主键
插入会对mysql的插入有很大的影响,而UUID正是无序的。最终瓶颈可能就真的
出在真正写记录的时候了。(具体的以后做个试验模拟下看看)

twitter给出了一个解决方案: https://github.com/twitter/snowflake

分享到:
评论

相关推荐

    idgenerator:idgenerator是基于redis的id生成器

    idgenerator是基于redis的id生成器 dgenerator是基于redis的id生成器 安装 取得 go get github.com/lbfatcgf/idgenerator 快速开始 package main import ( "fmt" "net/http" "os" "os/signal" "syscall" ...

    一个简单的自定义ID 生成器IDGenerator

    一个用Java 编写简单的自定义ID 生成器IDGenerator

    idgenerator分布式主键ID生成器

    迄今为止最全面的分布式主键ID生成器。优化的雪花算法(SnowFlake)——雪花漂移算法,在缩短ID长度的同时,具备极高瞬时并发处理能力...支持容器环境自动扩容(自动注册 WorkerId ),单机或分布式唯一IdGenerator。

    Hibernate的generator属性

    这个属性可以配置在 `hibernate.hbm.xml` 文件中的 `<id>` 标签内,它允许你选择不同的策略来生成 ID。 1. **identity**:这个生成器适用于像 MySQL 这样的数据库,它依赖于数据库自身的自动递增功能。例如,在 ...

    迄今为止最全面的分布式主键ID生成器,多语言新雪花算法(SnowFlake IdGenerator).zip

    支持容器环境自动扩容(自动注册 WorkerId ),单机或分布式唯一IdGenerator。 迄今为止最全面的分布式主键ID生成器。 优化的雪花算法(SnowFlake)——雪花漂移算法,在缩短ID长度的同时,具备极高瞬时并发处理...

    idGenerator

    "IdGenerator"是一个在JavaScript环境中用于生成唯一标识符(ID)的工具或库。在Web开发中,尤其是在处理大量动态数据或需要跟踪和管理多个对象时,生成唯一ID至关重要。JavaScript作为前端的主要编程语言,其内建...

    hibernate中的generator的生成方式hibernate中的generator的生成方式

    在Hibernate中,`Generator`是负责生成主键值的策略,通常在`<id>`元素中通过`class`属性指定。不同的数据库和不同的应用场景可能需要不同的生成策略。接下来,我们将逐一介绍各种常见的生成策略及其适用场景。 ###...

    百度分布式id 代码uid-generator

    说的简单一点就是:应用在启动时会往数据库表(uid-generator需要新增一个WORKER_NODE表)中去插入一条数据,数据插入成功后返回的该数据对应的自增唯一id就是该机器的workId,而数据由host,port组成。

    id-generator:生成带有前缀的随机ID(la Stripe)

    var IdGenerator = require ( 'auth0-id-generator' ) ; var generator = new IdGenerator ( ) ; var id = generator . new ( 'cus' ) ; console . log ( id ) ; // cus_lO1DEQWBbQAACfHO 预定义的一组允许的前缀...

    id-generator:自用id生成器

    【id-generator:自用id生成器】 在软件开发中,唯一标识符(ID)的生成是至关重要的,尤其是在分布式系统中。`id-generator` 是一个专门为个人或团队自定义设计的ID生成器,它旨在为应用程序提供高效、唯一且可...

    多语言新雪花算法(SnowFlake IdGenerator)-算法实现资源

    ID SnowFlake——ID50W/0.1s C#/Java/Go/Rust/C/SQL PHP PythonNode.jsRuby FFI WorkerId IdGenerator

    mybatis-generator自动生成实体没有注释问题

    直接运行 generator.sh 命令就可以, 如果是window系统,把后缀改为bat就可以了。工具来源,http://www.cnblogs.com/NieXiaoHui/p/6100895.html#undefined,我只是一个搬运工。 显示效果如下: public class ...

    mybatis_generator使用手册

    <context id="DB2Tables" targetRuntime="MyBatis3Simple"> connectionURL="jdbc:mysql://localhost:3306/mydb" userId="root" password="password"> targetProject=".\src\main\java"> target...

    MyBatis Generator配置方法

    <context id="MySQL" targetRuntime="MyBatis3"> <!-- 指定数据库方言 --> <plugin type="org.mybatis.generator.plugins.SerializablePlugin"/> ${jdbcDriver}" connectionURL="${jdbcUrl}" userId="${jdbc...

    Python Generator

    标题中提到了Python Generator,这是指Python中的生成器,一种特殊的迭代器,能够让用户在循环中逐步产生一个序列中的值,而不是一次性返回整个序列。在描述中提到的是David Beazley所作的关于Python生成器的系统...

    代码生成工具generator.xml文件

    generator.xml文件是generator工具的配置中心,它包含了关于生成代码的所有设置。例如,数据库连接信息(包括URL、用户名、密码等)、需要生成代码的数据库表名、生成的代码模板、目标文件路径等。以下是一份简单的...

    mybatis的generator工具

    <id>generate-mybatis-entities</id> <goal>generate ``` 然后运行`mvn mybatis-generator:generate`,Generator就会根据配置文件中的设置自动生成相应的Java和XML文件。 生成的Dao接口会包含CRUD...

    id-generator-java:Java实现的ID生成器

    id-生成器-java Java实现的ID生成器 格式: T |64| * L |6| R|4| 否 |12| * S |10| * ID = TLRNS,96 位 T 时间戳,以毫秒为单位,64 位 l 逻辑区域,如分区或ISP,6bits,容量为64个区域 R 保留位,4 位,容量 ...

    idGenerator:使用golanggo的idGenerator

    #ID产生器 用go实现的id生成器,支持每秒qps:131072,超过需要等待下一秒 依赖 mysql(或zk,redis等) 需要使用mysql来保证多台机器获取到的workId不同当然,如果是单点,那随意设置workId 使用介绍 初始化程序 ...

    mybatis generator eclipse插件的安装

    <context id="MySQL" targetRuntime="MyBatis3"> connectionURL="jdbc:mysql://localhost:3306/mydb" userId="root" password="password"/> ``` 5. **运行MBG**:在Eclipse中,右键点击项目,...

Global site tag (gtag.js) - Google Analytics