Sequence

[TOC]

背景

随着数据量和访问量的不断增长,原有的单个数据库的数据存储需要拆分为多个数据库来进行数据的存储,目前的数据拆分方式主要有两种,一种是垂直拆分,一种是水平拆分。垂直拆分就是把一个数据库中不同业务单元的数据分到不同的数据库中,水平拆分是根据一定的规则把同一业务单元的数据拆分到多个数据库中。

目前进行分库分表后,原本一个数据库上的自增id的结果,在分库分表下并不是全局唯一的. 所以,分库分表后需要有一种技术可以生成全局的唯一。

目标

  • 生成全局唯一的id;
  • 保持高性能;
  • 保持高可用

解决思路

  1. oracle sequence : 基于第三方oracle的SEQ.NEXTVAL来获取一个ID 优势:简单可用 缺点:需要依赖第三方oracle数据库
  2. mysql id区间隔离 : 不同分库设置不同的起始值和步长,比如2台mysql,就可以设置一台只生成奇数,另一台生成偶数. 或者1台用010亿,另一台用1020亿. 优势:利用mysql自增id 缺点:运维成本比较高,数据扩容时需要重新设置步长.
  3. 基于数据库更新+内存分配: 在数据库中维护一个ID,获取下一个ID时,会对数据库进行ID=ID+100 WHERE ID=XX,拿到100个ID后,在内存中进行分配 优势:简单高效 缺点:无法保证自增顺序

Sequence可用性

  1. 只要生成id的数据库不全部挂掉,均可以顺畅提供服务;
  2. 生成id的数据库数量不定,按照应用对容灾的需求指定不同机器不同机房的数据库; (比如需要考虑单元化多机房的id生成)
  3. 支持生成id的数据库hang住快速略过和恢复自动加入

原理

目前我们针对多机的id生成方案: 每个数据库只拿自己的那一段id. 如下图,sample_group_0-sample_group_3是我们生成全局唯一id的4个数据库,那么每个数据库对于同一个id有一个起始值,比如间隔是1000.

group ds value
sample_group_0 0
sample_group_1 1000
sample_group_2 2000
sample_group_3 3000

应用真正启动的时候,可能某一台机器上去取id,随机取到了sample_group_1,那么这台机器上的应用会拿到1000-1999这一千个id(批量取,这个也就保证了应用端取id性能),而这个时候4个数据库上id起始值会变成下图所示,你也许注意到了,下次从sample_group_1上取得的id就变成了5000-5999. 那么也就是这样,完全避免了多机上取id的重复.比如sample_group_1他会永远只会取到1000-1999,5000-5999,9000-9999, 13000-13999…其他数据库也一样,相互不会重叠.

group ds value
sample_group_0 0
sample_group_1 5000
sample_group_2 2000
sample_group_3 3000

TDDL sequence 工作起来是这样的:它会根据 sequence 表里的数据,取 n 个数字放到内存里(n 称为内步长,可以在定义数据源的时候配置),在 n 个数字用完以前,生成的 ID 都从内存里来,用完以后才继续去数据库里取。

举个例子,当表里只有一个条目的时候,内步长为 1000,初值为 0。第一次生成序列的时候,访问数据库,把初值改为 1000,此时可以在内存里依次生成 1 到 1000 这 1000 个数。等到内存里的 1000 个数用完了,再访问数据库,初值变成 2000,内存里开始生成 1001 到 2000 这 1000 个数。这么做应该是为了效率,步长越大,访问数据库的频率就越低。

多个序列(sequence 表里有多个条目)的情况又是怎样呢?这里又涉及到一个外步长的概念:外步长 = 内步长 * sequence 表里条目的数量。刚才说内步长为 1000 的情况下,每次访问库,初值就增加 1000,这个增加的 1000,就是外步长(因为 sequence 表里就一个条目,内外步长相等)。

源码解析

<dependency>
    <groupId>com.taobao.tddl</groupId>
    <artifactId>tddl-sequence</artifactId>
    <version>5.2.1</version>
</dependency>

使用步骤

  • 建表
CREATE TABLE `sequence` (
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
    `name` varchar(64) NOT NULL,
    `value` bigint(20) NOT NULL,
    `gmt_create` timestamp DEFAULT CURRENT_TIMESTAMP,
    `gmt_modified` timestamp NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_unique_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • 初始化表里的数据

name:序列的名称,随便取就好,比如叫 seq
value:序列的初值,比如你要从 101 开始生成,就填 100
gmt_modified:不用解释了,填当前的时间就好

  • 配置生成器的数据源
    <bean id="sequenceDao" class="com.taobao.tddl.client.sequence.impl.GroupSequenceDao" init-method="init">
        <!--<bean id="sequenceDao" class="com.taobao.tddl.client.sequence.impl.UnitGroupSequenceDao" init-method="init">单元化用这个-->

        <!--appName ,必填 -->
        <property name="appName" value="CT_APP_NAME" />
        <!--数据源的个数,对应于dbGroupKeys配置的数量 -->
        <property name="dscount" value="1" />
        <!--dbGroupKeys必填,对应于tddl中的group name -->
        <property name="dbGroupKeys">
            <list>
                <value>APP_GROUP</value>
            </list>
        </property>
        <!--是否开启自适应,一般设为true-->
        <!--内步长 -->
        <property name="innerStep" value="50" />
        <property name="adjust" value="true" />
        <property name="retryTimes" value="3" />
        <property name="tableName" value="ct_app_sequence"/>
        <!--注:在不了解sequence原理的情况下,请勿修改其他诸如innerStep等参数-->
    </bean>

    <bean id="sequence" class="com.taobao.tddl.client.sequence.impl.GroupSequence" init-method="init">
        <property name="sequenceDao" ref="sequenceDao" />
        <property name="name" value="commodityId" />
    </bean>

这个 bean 的作用是配置上一步建的那个表的来源。appName 和 dbGroupKey 就是 TDDL 的两个 key,可以确定是哪个库,这个大家很熟悉了,不多说。tableName 用来确定是哪个表。用表里的哪些列也是可以配置的,这里就不多说了,官方文档里有。

  • 配置生成器
    <bean id="sequence" class="com.taobao.tddl.client.sequence.impl.GroupSequence" init-method="init">
        <property name="sequenceDao" ref="sequenceDao" />
        <property name="name" value="commodityId" />
    </bean>
  • 使用
    拿到生成器的 bean,就可以愉快地使用了。每次调用 nextValue() 的结果每次都不一样,即使在多台机器上也是一样
@Autowired
private Sequence sequence;

...

sequence.nextValue();

注意事项

  1. 上线前在预发的sequence的值一定要尽量大于线上库
  2. 他传值的时候没有加##,导致没有把Sequence的值传到SQL里面去,
  3. 查看数据库里面有没有0-999这样的主键,正如上文所说,Sequence不会生成这样的值;
  4. 注意到DBA中找TDDL的group配置
  5. 注意要添加TABLE属性。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容