分 库 分 表

1.为 什 么 要 分 库 分 表 ( 设 计 高 并 发 系 统 的 时 候 , 数 据 库 层 面 该如 何 设 计 ) ? 用 过 哪 些 分 库 分 表 中 间 件 ? 不 同 的 分 库 分 表 中 间件 都 有 什 么 优 点 和 缺 点 ? 你 们 具 体 是 如 何 对 数 据 库 如 何 进 行 垂直 拆 分 或 水 平 拆 分 的 ?

其实这块肯定是扯到高并发了,因为分库分表一定是为了支撑高并发、数据量大两个问题的。而且现在说实话,尤其是互联网类的公司面试,基本上都会来这么一下,分库分表如此普遍的技术问题,不问实在是不行,而如果你不知道那也实在是说不过去!

为 什 么 要 分 库 分 表 ? ( 设 计 高 并 发 系 统 的 时 候 , 数 据 库 层 面 该 如 何 设 计 ? )

说白了,分库分表是两回事儿,大家可别搞混了,可能是光分库不分表,也可能是光分表不分库,都有可能。我先给大家抛出来一个场景。

假如我们现在是一个小创业公司(或者是一个 BAT 公司刚兴起的一个新部门),现在注册用户就 20 万,每天活跃用户就 1 万,每天单表数据量就 1000,然后高峰期每秒钟并发请求最多就 10条,就这种系统,随便找一个有几年工作经验的,然后带几个刚培训出来的,随便干干都可以。

结果没想到我们运气居然这么好,碰上个 CEO 带着我们走上了康庄大道,业务发展迅猛,过了几个月,注册用户数达到了 2000 万!每天活跃用户数 100 万!每天单表数据量 10 万条!高峰期每秒最大请求达到1000!同时公司还顺带着融资了两轮,进账了几个亿人民币啊!公司估值达到了惊人的几亿美金!这是小独角兽的节奏!

好吧,没事,现在大家感觉压力已经有点大了,为啥呢?因为每天多 10 万条数据,一个月就多 300万条数据,现在咱们单表已经几百万数据了,马上就破千万了。但是勉强还能撑着。高峰期请求现在是 1000,咱们线上部署了几台机器,负载均衡搞了一下,数据库撑 1000QPS 也还凑合。但是大家现在开始感觉有点担心了,接下来咋整呢......

再接下来几个月,我的天,CEO 太牛逼了,公司用户数已经达到 1 亿,公司继续融资几十亿人民币啊!公司估值达到了惊人的几十亿美金,成为了国内今年最牛逼的明星创业公司!天,我们太幸运了。但是我们同时也是不幸的,因为此时每天活跃用户数上千万,每天单表新增数据多达 50 万,目前一个表总数据量都已经达到了两三千万了!扛不住啊!数据库磁盘容量不断消耗掉!高峰期并发达到惊人的5000~8000!别开玩笑了,哥。我跟你保证,你的系统支撑不到现在,已经挂掉了!

好吧,所以你看到这里差不多就理解分库分表是怎么回事儿了,实际上这是跟着你的公司业务发展走的,你公司业务发展越好,用户就越多,数据量越大,请求量越大,那你单个数据库一定扛不住。

分表

比如你单表都几千万数据了,你确定你能扛住么?绝对不行, 单表数据量太大,会极大影响你的 sql执行的性能,到了后面你的 sql 可能就跑的很慢了。一般来说,就以我的经验来看,单表到几百万的时候,性能就会相对差一些了,你就得分表了。

分表是啥意思?就是把一个表的数据放到多个表中,然后查询的时候你就查一个表。比如按照用户 id 来分表,将一个用户的数据就放在一个表中。然后操作的时候你对一个用户就操作那个表就好了。这样可以控制每个表的数据量在可控的范围内,比如每个表就固定在 200 万以内。

分库

分库是啥意思?就是你一个库一般我们经验而言,最多支撑到并发 2000,一定要扩容了,而且一个健康的单库并发值你最好保持在每秒 1000 左右,不要太大。那么你可以将一个库的数据拆分到多个库中,访问的时候就访问一个库好了。

这就是所谓的 分库分表,为啥要分库分表?你明白了吧。

用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点 ?

这个其实就是看看你了解哪些分库分表的中间件,各个中间件的优缺点是啥?然后你用过哪些

分库分表的中间件。

比较常见的包括:

 cobar

 TDDL

 atlas

 sharding-jdbc

 mycat

cobar

阿里 b2b 团队开发和开源的,属于 proxy 层方案,就是介于应用服务器和数据库服务器之间。

应用程序通过 JDBC 驱动访问 cobar 集群,cobar 根据 SQL 和分库规则对 SQL 做分解,然后

分发到 MySQL 集群不同的数据库实例上执行。早些年还可以用,但是最近几年都没更新了,

基本没啥人用,差不多算是被抛弃的状态吧。而且不支持读写分离、存储过程、跨库 join 和

分页等操作。

TDDL

淘宝团队开发的,属于 client 层方案。支持基本的 crud 语法和读写分离,但不支持 join、多

表查询等语法。目前使用的也不多,因为还依赖淘宝的 diamond 配置管理系统。

atlas

360 开源的,属于 proxy 层方案,以前是有一些公司在用的,但是确实有一个很大的问题就是

社区最新的维护都在 5 年前了。所以,现在用的公司基本也很少了。

sharding-jdbc

当当开源的,属于 client 层方案。确实之前用的还比较多一些,因为 SQL 语法支持也比较多,

没有太多限制,而且目前推出到了 2.0 版本,支持分库分表、读写分离、分布式 id 生成、柔

性事务(最大努力送达型事务、TCC 事务)。而且确实之前使用的公司会比较多一些(这个在

官网有登记使用的公司,可以看到从 2017 年一直到现在,是有不少公司在用的),目前社区

也还一直在开发和维护,还算是比较活跃,个人认为算是一个现在也 可 以 选 择 的 方 案。

mycat

基于 cobar 改造的,属于 proxy 层方案,支持的功能非常完善,而且目前应该是非常火的而

且不断流行的数据库中间件,社区很活跃,也有一些公司开始在用了。但是确实相比于 sharding

jdbc 来说,年轻一些,经历的锤炼少一些。

总 结

综上,现在其实建议考量的,就是 sharding-jdbc 和 mycat,这两个都可以去考虑使用。

sharding-jdbc 这种 client 层方案的优点在于 不用部署 ,运维成本低,不需要代理层的二次转发

请求,性能很高,但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要耦合sharding-jdbc 的依赖;

mycat 这种 proxy 层方案的 缺 点 在 于 需 要 部 署,自己运维一套中间件,运维成本高,但是好处

在于对于各个项目是透明的,如果遇到升级之类的都是自己中间件那里搞就行了。

通常来说,这两个方案其实都可以选用,但是我个人建议中小型公司选用 sharding-jdbc,client

层方案轻便,而且维护成本低,不需要额外增派人手,而且中小型公司系统复杂度会低一些,

项目也没那么多;但是中大型公司最好还是选用 mycat 这类 proxy 层方案,因为可能大公司

系统和项目非常多,团队很大,人员充足,那么最好是专门弄个人来研究和维护 mycat,然后

大量项目直接透明使用即可。

你 们 具 体 是 如 何 对 数 据 库 如 何 进 行 垂 直 拆 分 或 水 平 拆 分 的 ?

水 平 拆 分的意思,就是把一个表的数据给弄到多个库的多个表里去,但是每个库的表结构都一

样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据。水平拆分的意

义,就是将数据均匀放更多的库里,然后用多个库来扛更高的并发,还有就是用多个库的存储

容量来进行扩容。

垂 直 拆 分的意思,就是 把 一 个 有 很 多 字 段 的 表 给 拆 分 成 多 个 表, 或 者 是 多 个 库 上 去。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,会 将 较 少 的 访 问 频 率 很 高 的 字 段 放 到一 个 表 里 去,然后 将 较 多 的 访 问 频 率 很 低 的 字 段 放 到 另 外 一 个 表 里 去。因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。这个一般在表层面做的较多一些。

这个其实挺常见的,不一定我说,大家很多同学可能自己都做过,把一个大表拆开,订单表、订单支付表、订单商品表。

还有 表 层 面 的 拆 分,就是分表,将一个表变成 N 个表,就是 让 每 个 表 的 数 据 量 控 制 在 一 定范围内,保证 SQL 的性能。否则单表数据量越大,SQL 性能就越差。一般是 200 万行左右,不要太多,但是也得看具体你怎么操作,也可能是 500 万,或者是 100 万。你的 SQL 越复杂,就最好让单表行数越少。

好了,无论分库还是分表,上面说的那些数据库中间件都是可以支持的。就是基本上那些中间件可以做到你分库分表之后, 中 间 件 可 以 根 据 你 指 定 的 某 个 字 段 值,比如说 userid, 自 动 路 由到 对 应 的 库 上 去 , 然 后 再 自 动 路 由 到 对 应 的 表 里 去。

你就得考虑一下,你的项目里该如何分库分表?一般来说,垂直拆分,你可以在表层面来做,对一些字段特别多的表做一下拆分;水平拆分,你可以说是并发承载不了,或者是数据量太大,容量承载不了,你给拆了,按什么字段来拆,你自己想好;分表,你考虑一下,你如果哪怕是拆到每个库里去,并发和容量都 ok 了,但是每个库的表还是太大了,那么你就分表,将这个表分开,保证每个表的数据量并不是很大。而且这儿还有两种 分 库 分 表 的 方 式:

 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如 时 间 范 围来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。

 或者是按照某个字段 hash 一下均匀分散,这个较为常用。

range 来分,好处在于说,扩容的时候很简单,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是大部分的请求,都是访问最新的数据。实际生产用 range,要看场景。

hash 分发,好处在于说,可以平均分配每个库的数据量和请求压力;坏处在于说扩容起来比较麻烦,会有一个数据迁移的过程,之前的数据需要重新计算 hash 值重新分配到不同的库或表。

2.现 在 有 一 个 未 分 库 分 表 的 系 统 , 未 来 要 分 库 分 表 , 如 何 设 计才 可 以 让 系 统 从 未 分 库 分 表 动 态 切 换 到 分 库 分 表 上 ?

停 机 迁 移 方 案

我先给你说一个最 low 的方案,就是很简单,大家伙儿凌晨 12 点开始运维,网站或者 app 挂个公告,说 0 点到早上 6 点进行运维,无法访问。

接着到 0 点停机,系统停掉,没有流量写入了,此时老的单库单表数据库静止了。然后你之前得写好一个导 数 的 一 次 性 工 具,此时直接跑起来,然后将单库单表的数据哗哗哗读出来,写到分库分表里面去。

导数完了之后,就 ok 了,修改系统的数据库连接配置啥的,包括可能代码和 SQL 也许有修改,那你就用最新的代码,然后直接启动连到新的分库分表上去。

验证一下,ok 了,完美,大家伸个懒腰,看看看凌晨 4 点钟的北京夜景,打个滴滴回家吧。

但是这个方案比较 low,谁都能干,我们来看看高大上一点的方案。

双写迁移方案

这个是我们常用的一种迁移方案,比较靠谱一些,不用停机,不用看北京凌晨 4 点的风景。

简单来说,就是在线上系统里面,之前所有写库的地方,增删改操作, 除 了 对 老 库 增 删 改 , 都 加 上 对 新 库 的增 删 改,这就是所谓的 双 写,同时写俩库,老库和新库。

然后系统部署之后,新库数据差太远,用之前说的导数工具,跑起来读老库数据写新库,写的时候要根据

gmt_modified 这类字段判断这条数据最后修改的时间,除非是读出来的数据在新库里没有,或者是比新库的数据新才会写。简单来说,就是不允许用老数据覆盖新数据。

导完一轮之后,有可能数据还是存在不一致,那么就程序自动做一轮校验,比对新老库每个表的每条数据,接着如果有不一样的,就针对那些不一样的,从老库读数据再次写。反复循环,直到两个库每个表的数据都完全一致为止。

接着当数据完全一致了,就 ok 了,基于仅仅使用分库分表的最新代码,重新部署一次,不就仅仅基于分库分表在操作了么,还没有几个小时的停机时间,很稳。所以现在基本玩儿数据迁移之类的,都是这么干的。

3.如何设计可以动态扩容缩容的分库分表方案?

对于分库分表来说,主要是面对以下问题:

  1. 选择一个数据库中间件,调研、学习、测试;
  2. 设计你的分库分表的一个方案,你要分成多少个库,每个库分成多少个表,比如 3 个库,每个库 4个表;
  3. 基于选择好的数据库中间件,以及在测试环境建立好的分库分表的环境,然后测试一下能否正常进行分库分表的读写;
  4. 完成单库单表到分库分表的 迁 移,双写方案;
  5. 线上系统开始基于分库分表对外提供服务;
  6. 扩容了,扩容成 6 个库,每个库需要 12 个表,你怎么来增加更多库和表呢?

这个是你必须面对的一个事儿,就是你已经弄好分库分表方案了,然后一堆库和表都建好了,基于分库分表中间件的代码开发啥的都好了,测试都 ok 了,数据能均匀分布到各个库和各个表里去,而且接着你还通过双写的方案咔嚓一下上了系统,已经直接基于分库分表方案在搞了。

那么现在问题来了,你现在这些库和表又支撑不住了,要继续扩容咋办?这个可能就是说你的每个库的容量又快满了,或者是你的表数据量又太大了,也可能是你每个库的写并发太高了,你得继续扩容。

这都是玩儿分库分表线上必须经历的事儿。

停 机 扩 容 ( 不 推 荐 )

这个方案就跟停机迁移一样,步骤几乎一致,唯一的一点就是那个导数的工具,是把现有库表的数据抽出来慢慢倒入到新的库和表里去。但是最好别这么玩儿,有点不太靠谱,因为既然 分 库 分 表就说明数据量实在是太大了,可能多达几亿条,甚至几十亿,你这么玩儿,可能会出问题。

从单库单表迁移到分库分表的时候,数据量并不是很大,单表最大也就两三千万。那么你写个工具,多弄几台机器并行跑,1 小时数据就导完了。这没有问题。

如果 3 个库 + 12 个表,跑了一段时间了,数据量都 1~2 亿了。光是导 2 亿数据,都要导个几个小时,

6 点,刚刚导完数据,还要搞后续的修改配置,重启系统,测试验证,10 点才可以搞完。所以不能这么搞。

优 化 后 的 方 案

一开始上来就是 32 个库,每个库 32 个表,那么总共是 1024 张表。

我可以告诉各位同学,这个分法,第一,基本上国内的互联网肯定都是够用了,第二,无论是并发支撑还是数据量支撑都没问题。

每个库正常承载的写入并发量是 1000,那么 32 个库就可以承载 32 * 1000 = 32000 的写并发,如果每个库承载 1500 的写并发,32 * 1500 = 48000 的写并发,接近 5 万每秒的写入并发,前面再加一个 MQ,削峰,每秒写入 MQ 8 万条数据,每秒消费 5 万条数据。

有些除非是国内排名非常靠前的这些公司,他们的最核心的系统的数据库,可能会出现几百台数据库的这么一个规模,128 个库,256 个库,512 个库。

1024 张表,假设每个表放 500 万数据,在 MySQL 里可以放 50 亿条数据。

每秒 5 万的写并发,总共 50 亿条数据,对于国内大部分的互联网公司来说,其实一般来说都够了。

谈分库分表的扩容, 第 一 次 分 库 分 表 , 就 一 次 性 给 他 分 个 够,32 个库,1024 张表,可能对大部分的中小型互联网公司来说,已经可以支撑好几年了。

一个实践是利用 32 * 32 来分库分表,即分为 32 个库,每个库里一个表分为 32 张表。一共就是 1024 张表。根据某个 id 先根据 32 取模路由到库,再根据 32 取模路由到库里的表。

刚开始的时候,这个库可能就是逻辑库,建在一个数据库上的,就是一个 mysql 服务器可能建了 n 个库,比如 32 个库。后面如果要拆分,就是不断在库和 mysql 服务器之间做迁移就可以了。然后系统配合改一下配置即可。

比如说最多可以扩展到 32 个数据库服务器,每个数据库服务器是一个库。如果还是不够?最多可以扩展到 1024 个数据库服务器,每个数据库服务器上面一个库一个表。因为最多是 1024个表。

这么搞,是不用自己写代码做数据迁移的,都交给 dba 来搞好了,但是 dba 确实是需要做一些库表迁移的工作,但是总比你自己写代码,然后抽数据导数据来的效率高得多吧。

哪怕是要减少库的数量,也很简单,其实说白了就是按倍数缩容就可以了,然后修改一下路由规则。

这里对步骤做一个总结:

. 设定好几台数据库服务器,每台服务器上几个库,每个库多少个表,推荐是 32 库 * 32 表,对于大部分公司来说,可能几年都够了。

. 路由的规则,orderId 模 32 = 库,orderId / 32 模 32 = 表

. 扩容的时候,申请增加更多的数据库服务器,装好 mysql,呈倍数扩容,4 台服务器,扩到 8 台服

务器,再到 16 台服务器。

. 由 dba 负责将原先数据库服务器的库,迁移到新的数据库服务器上去,库迁移是有一些便捷的工具的。

. 我们这边就是修改一下配置,调整迁移的库所在数据库服务器的地址。

. 重新发布系统,上线,原先的路由规则变都不用变,直接可以基于 n 倍的数据库服务器的资源,继续进行线上系统的提供服务。

4. 分库分表之后 ,id主键如何处理

其实这是分库分表之后你必然要面对的一个问题,就是 id 咋生成?因为要是分成多个表之后,每个表都是从 1 开始累加,那肯定不对啊,需要一个 全 局 唯 一的 id 来支持。所以这都是你实际生产环境中必须考虑的问题。

基 于 数 据 库 的 实 现 方 案

数 据 库 自增 增 id

这个就是说你的系统里每次得到一个 id,都是往一个库的一个表里插入一条没什么业务含义的数据,然后获取一个数据库自增的一个 id。拿到这个 id 之后再往对应的分库分表里去写入。

这个方案的好处就是方便简单,谁都会用; 缺 点 就 是 单 库 生 成自增 id,要是高并发的话,就会有瓶颈的;如果你硬是要改进一下,那么就专门开一个服务出来,这个服务每次就拿到当前 id 最大值,然后自己递增几个 id,一次性返回一批 id,然后再把当前最大 id 值修改成递增几个 id 之后的一个值;但是 无 论 如何 都 是 基 于 单 个 数 据 库。

适 合 的 场 景:你分库分表就俩原因,要不就是单库并发太高,要不就是单库数据量太大;除非是你 并 发 不 高 ,但 是 数 据 量 太 大导致的分库分表扩容,你可以用这个方案,因为可能每秒最高并发最多就几百,那么就走单独的一个库和表生成自增主键即可。

设 置 数 据库 库 sequence  或 者 表 自 增 字 段 步 长

可以通过设置数据库 sequence 或者表的自增字段步长来进行水平伸缩。

比如说,现在有 8 个服务节点,每个服务节点使用一个 sequence 功能来产生 ID,每个 sequence 的起始 ID 不同,并且依次递增,步长都是 8。

适 合 的 场 景:在用户防止产生的 ID 重复时,这种方案实现起来比较简单,也能达到性能目标。但是服务节点固定,步长也固定,将来如果还要增加服务节点,就不好搞了。

UUID

好处就是本地生成,不要基于数据库来了;不好之处就是,UUID 太长了、占用空间大, 作 为 主 键 性 能 太 差了;更重要的是,UUID 不具有有序性,会导致 B+ 树索引在写的时候有过多的随机写操作(连续的 ID 可以产生部分顺序写),还有,由于在写的时候不能产生有顺序的 append 操作,而需要进行 insert 操作,将会读取整个 B+ 树节点到内存,在插入这条记录后会将整个节点写回磁盘,这种操作在记录占用空间比大的情况下,性能下降明显。

适合的场景:如果你是要随机生成个什么文件名、编号之类的,你可以用 UUID,但是作为主键是不能用 UUID的。

UUID.randomUUID().toString().replace(“-”, “”) -> sfsdf23423rr234sfdaf

获 取 系 统 当 前 时 间

这个就是获取当前时间即可,但是问题是, 并 发 很 高 的 时 候,比如一秒并发几千, 会 有 重 复 的 情 况,这个是肯定不合适的。基本就不用考虑了。

适合的场景:一般如果用这个方案,是将当前时间跟很多其他的业务字段拼接起来,作为一个 id,如果业务上你觉得可以接受,那么也是可以的。你可以将别的业务字段值跟当前时间拼接起来,组成一个全局唯一的编号。

snowflake算 法

snowflake 算法是 twitter 开源的分布式 id 生成算法,采用 Scala 语言实现,是把一个 64 位的long 型的 id,1 个 bit 是不用的,用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit作为序列号。

1 bit:不用,为啥呢?因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id

都是正数,所以第一个 bit 统一都是 0。

41 bit:表示的是时间戳,单位是毫秒。41 bit 可以表示的数字多达 2^41 - 1,也就是可以标

识 2^41 - 1 个毫秒值,换算成年就是表示 69 年的时间。

10 bit:记录工作机器 id,代表的是这个服务最多可以部署在 2^10 台机器上哪,也就是 1024 台

机器。但是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 id。意思就是最多代表 2^5 个机房

(32 个机房),每个机房里可以代表 2^5 个机器(32 台机器)。

12 bit:这个是用来记录同一个毫秒内产生的不同 id,12 bit 可以代表的最大正整数是 2^12 -

1 = 4096,也就是说可以用这个 12 bit 代表的数字来区分 同 一 个 毫 秒 内的 4096 个不同的 id。

public class IdWorker {
private long workerId;
private long datacenterId;
private long sequence;
public IdWorker(long workerId, long datacenterId, long sequence) {
// sanity check for workerId
// 这儿不就检查了一下,要求就是你传递进来的机房 id 和机器 id 不能超过 32,不能小于 0
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(
String.format("worker Id can't be greater than %d or less than 0",
maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(
String.format("datacenter Id can't be greater than %d or less than 0",
maxDatacenterId));
}
System.out.printf(
"worker starting. timestamp left shift %d, datacenter id bits %d, worker
id bits %d, sequence bits %d, workerid %d",
timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits,
workerId);
this.workerId = workerId;
this.datacenterId = datacenterId;
this.sequence = sequence;
}
private long twepoch = 1288834974657L;
private long workerIdBits = 5L;
private long datacenterIdBits = 5L;
// 这个是二进制运算,就是 5 bit 最多只能有 31 个数字,也就是说机器 id 最多只能是 32 以内
private long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 这个是一个意思,就是 5 bit 最多只能有 31 个数字,机房 id 最多只能是 32 以内
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private long sequenceBits = 12L;
private long workerIdShift = sequenceBits;
private long datacenterIdShift = sequenceBits + workerIdBits;
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private long sequenceMask = -1L ^ (-1L << sequenceBits);
private long lastTimestamp = -1L;
public long getWorkerId() {
return workerId;
}
public long getDatacenterId() {
return datacenterId;
}
public long getTimestamp() {
return System.currentTimeMillis();
}
public synchronized long nextId() {
// 这儿就是获取当前时间戳,单位是毫秒
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
System.err.printf("clock is moving backwards. Rejecting requests until %d.",
lastTimestamp);
throw new RuntimeException(String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds",
lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
// 这个意思是说一个毫秒内最多只能有 4096 个数字
// 无论你传递多少进来,这个位运算保证始终就是在 4096 这个范围内,避免你自己传递个
sequence 超过了 4096 这个范围
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}
// 这儿记录一下最近一次生成 id 的时间戳,单位是毫秒
lastTimestamp = timestamp;
// 这儿就是将时间戳左移,放到 41 bit 那儿;
// 将机房 id 左移放到 5 bit 那儿;
// 将机器 id 左移放到 5 bit 那儿;将序号放最后 12 bit;
// 最后拼接起来成一个 64 bit 的二进制数字,转换成 10 进制就是个 long 型
return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId <<
datacenterIdShift)
| (workerId << workerIdShift) | sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
// ---------------测试---------------
public static void main(String[] args) {
IdWorker worker = new IdWorker(1, 1, 1);
for (int i = 0; i < 30; i++) {
System.out.println(worker.nextId());
}
}
}

怎么说呢,大概这个意思吧,就是说 41 bit 是当前毫秒单位的一个时间戳,就这意思;然后 5 bit 是你传递进来的一个 机房 房 id(但是最大只能是 32 以内),另外 5 bit 是你传递进来的 机器 器 id(但是最大只能是 32 以内),剩下的那个 12 bit 序列号,就是如果跟你上次生成 id 的时间还在一个毫秒内,那么会把顺序给你累加,最多在 4096 个序号以内。

所以你自己利用这个工具类,自己搞一个服务,然后对每个机房的每个机器都初始化这么一个东西,刚开始这个机房的这个机器的序号就是 0。然后每次接收到一个请求,说这个机房的这个机器要生成一个 id,你就找到对应的 Worker 生成。

利用这个 snowflake 算法,你可以开发自己公司的服务,甚至对于机房 id 和机器 id,反正给你预留了 5bit + 5 bit,你换成别的有业务含义的东西也可以的。

这个 snowflake 算法相对来说还是比较靠谱的,所以你要真是搞分布式 id 生成,如果是高并发啥的,那么用这个应该性能比较好,一般每秒几万并发的场景,也足够你用了。

经典面试题 之 分库分表相关推荐

  1. 阿里面试题:分库分表无限扩容后的瓶颈以及解决方案

    以下文章来源方志朋的博客,回复"666"获面试宝典 像我这样的菜鸟,总会有各种疑问,刚开始是对 JDK API 的疑问,对 NIO 的疑问,对 JVM 的疑问,当工作几年后,对服务 ...

  2. 高薪程序员面试题精讲系列96之分库分表了解吗?你项目中用到过吗?怎么用的?

    一. 面试题及剖析 1. 今日面试题 分库分表了解吗? 有哪些分库分表的实现方案? 你项目中用到过分库分表吗?怎么用的? 数据库有没有进行过分区? 2. 题目剖析 现在我们出去面试时的现状,真的很贴合 ...

  3. 分库分表学习总结(5)——有关分库分表相关面试题总结

    一.为什么要分库分表? 分表 比如你单表都几千万数据了,你确定你能扛住么?绝对不行,单表数据量太大,会极大影响你的 sql 执行的性能,到了后面你的 sql 可能就跑的很慢了.一般来说,就以我的经验来 ...

  4. 3天,我把MySQL索引、锁、事务、分库分表撸干净了!

    最近项目增加,缺人手,面试不少,但匹配的人少的可怜.跟其他组的面试官聊,他也抱怨了一番,说候选人有点儿花拳绣腿,回答问题不落地,拿面试最常问的MySQL来说,并不只是懂"增删改查" ...

  5. SpringBoot + Sharding JDBC,一文搞定分库分表、读写分离

    程序员的成长之路 互联网/程序员/技术/资料共享 关注 阅读本文大概需要 30 分钟. 来自:blog.csdn.net/qq_40378034/article/details/115264837 S ...

  6. SpringBoot + Sharding JDBC 读写分离、分库分表

    Sharding-JDBC 最早是当当网内部使用的一款分库分表框架,到2017年的时候才开始对外开源,这几年在大量社区贡献者的不断迭代下,功能也逐渐完善,现已更名为 ShardingSphere,20 ...

  7. mysql分表插件_分库分表简单?那我想问如何实现“分库分表插件”?

    随着系统数据量的日益增长,在说起数据库架构和数据库优化的时候,我们难免会常常听到分库分表这样的名词. 当然,分库分表有很多的方法论,比如垂直拆分.水平拆分:也有很多的中间件产品,比如MyCat.Sha ...

  8. 如何完成一次快速的查询 - 从MySQL到分库分表到ES和HBASE

    哪个男孩不想完成一次快速的查询? 1. MySQL 查询慢是什么体验? 谢邀,利益相关. 大多数互联网应用场景都是读多写少,业务逻辑更多分布在写上.对读的要求大概就是要快.那么都有什么原因会导致我们完 ...

  9. 分库分表之后,主键的处理方法

    面试题 分库分表之后,id 主键如何处理? 面试官心理分析 其实这是分库分表之后你必然要面对的一个问题,就是 id 咋生成?因为要是分成多个表之后,每个表都是从 1 开始累加,那肯定不对啊,需要一个全 ...

最新文章

  1. C语言---写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串
  2. twsited快速基础
  3. linux下camera驱动分析_《Linux设备驱动程序》(五)——字符设备驱动(下)
  4. Kafka单机、集群模式安装详解(二)
  5. 11G新特性 -- 分区表和增量统计信息
  6. 通俗易懂!《图机器学习导论》附69页PPT
  7. 软件学报 流程 期刊投稿记录 状态变更 时间
  8. python之类介绍
  9. Photoshop中预设资源安装方式-笔刷、动作、渐变、形状、样式等
  10. 【学习记录】阿里天池SQL练习题1-python与SQL方式比较
  11. 《白帽子讲Web安全》| 学习笔记之Web框架安全
  12. 【IEEE/ACM专区】一篇高质量的IEEE/ACM Transaction论文是如何顺利发表的?
  13. “核高基”重大专项取得阶段性成果
  14. 适合小白入门的随机森林介绍
  15. 基于单片机的电子琴系统设计(#0424)
  16. 华为nova7se能云闪付吗_华为nova7se支持NFC功能吗?可以用NFC来刷公交卡吗
  17. 计算机网络实验二cdma编码,CDMA编码实验_长春理工大学
  18. html整理,常用标签
  19. 分享EasyRecovery数据恢复软件恢复丢失的数据免费版
  20. 北京内推 | 中国电信总部大数据AI中心招聘计算机视觉算法实习生

热门文章

  1. Mysql常用的sql语句大全
  2. (十一)jmeter接口自动化难点系列---设置响应超时时间
  3. 在多个 PDF 中查找文本
  4. mysql在test库中创建表stu_数据库mysql练习
  5. win10轻松自动“备份和恢复”只要5步
  6. 单片机的上位机简单开发(1)
  7. m277打印机 重置_惠普M277n打印机使用说明书(惠普M277n打印机使用指南PDF资料)V1.0 最新版...
  8. 卷积神经网络(CNN)图像识别知识总结
  9. base64图片压缩到指定大小
  10. js-多个果冻按钮之当前果冻按钮弹性特效