原文: https://github.com/lvCmx/study
(1) 事务的特性java
(2) 并发操做问题mysql
(3) 事务的隔离级别git
在 MySQL 数据库中,支持上面四种隔离级别,默认的为 Repeatable read (可重复读);而在 Oracle 数据库中,只支持 Serializable (串行化)级别和 Read committed (读已提交)这两种级别,其中默认的为 Read committed 级别。github
索引是表的目录,在查找内容以前能够先在目录中查找索引位置,以此快速定位查询数据。对于索引,会保存在额外的文件中。
索引是数据库中专门用于帮助用户快速查询数据的一种数据结构,相似于字典中的目录,查找字典内容时能够根据目录查找到数据的存放位置,而后直接获取便可。面试
索引自己也很大,不可能所有存储在内存中,所以索引每每索引文件的形式存储在磁盘上。索引查找过程当中就要产生磁盘 IO 消耗,相对于内存存取,IO 存取的消耗要高几个数量级。因此评价一个数据结构做为索引的优劣最重要的指标就是在查找过程当中磁盘 I/O 操做次数的渐进复杂度。(换句话说,索引的结构组织要尽可能减小查找过程当中磁盘 I/O 的存取次数。)redis
B-tree、Hash、R-Tree、全文索引、主键索引、普通索引等。在 mysql 中索引是在存储引擎层而不是服务器层实现的,因此没有统一的索引标准。sql
B-Tree 索引:数据库
B-Tree 无论叶子节点仍是非叶子节点,都会保存数据,这样致使在非叶子节点中能保存的指针数量变少,指针少的状况下要保存大量数据,只能增长树的高度,致使 IO 操做变多,查询性能变低。而每个页的存储空间是有限的,若是 data 数据较大时将会致使每一个节点(一个页)能存储的 key 的数量很小,当存储的数据量很大时一样会致使 B-Tree 的深度较大,增大查询时的磁盘 IO 次数,进而影响查询效率。数组
在 B+Tree 中,全部数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只能存储 key 值信息,这样能够大大加大每一个节点存储的 key 值数量,下降 B+Tree 的高度。
数据库中的 B+Tree 索引能够分为汇集索引(clustered index)和辅助索引(secondary index)。上面的 B+Tree 示例图在数据库中的实现即为汇集索引,汇集索引的 B+Tree 中的叶子节点存放的是整张表的行记录数据。辅助索引与汇集索引的区别在于辅助索引的叶子节点并不包含行记录的所有数据,而是存储相应行数据的汇集索引键,即主键。当经过辅助索引来查询数据时,InnoDB 存储引擎会遍历辅助索引找到主键,而后再经过主键在汇集索引中找到完整的行记录数据。
hash 索引
在进行查询操做时,使用 hash 索引效率很高。所以,当使用一个语句去比较字符中,而后返回结果集。这样的操做使用 hash 索引是很快的。
在字符串上建立 Hash 索引很是好,列值将插入到 Hash 表中和一个键对应,并和实际的数据行有一个映射关系,也就是该键是一个指向表中数据行的指针。Hash 表实际是基于关联数组,假若有一个这样的语句:“boyce = 0×28936”其中 0×28936 是关联到存储在内存中的 boyce。在 hash 表索引中查找 boyce 的值并返回内存中的数据,要比检索整个表的列值要快得多。
Hash 表不能进行排序的数据结构,Hash 表擅长的是键值对,也就是说,检索语句检查相等性。在 Hash 表中键值是没有排序的,在存储的时候也没有任何的排序规则。由于 hash 索引不够灵活,因此,hash 索引不是默认的索引的数据结构。
全文索引
全文索引是一种特殊类型的索引,它查找的是文本中的关键词,而不是直接比较索引中的值。全文索引和其余几种索引的匹配方式彻底不同,它更相似于搜索引擎作的事情,而不是简单的 where 条件匹配。能够在相同的列上,同时建立全文索引和 B-Tree 索引,全文索引适用于 Match Against 操做,而不是普通的 where 条件操做。
索引能够包含一个列(即字段)或多个列的值。若是索引包含多个列,通常会将其称做复合索引,此时,列的顺序就十分重要,由于 MySQL 只能高效的使用索引的最左前缀列。建立一个包含两个列的索引,和建立两个只包含一列的索引是大不相同的。
在 B+Tree 的每一个叶子节点增长一个指向相邻叶子节点的指针,就造成了带有顺序访问指针的 B+Tree。作这个优化的目的是为了提升区间访问的性能,例如图中若是要查询 key 为从 18 到 49 的全部数据记录,当找到 18 后,只需顺着节点和指针顺序遍历就能够一次性访问到全部数据节点,极大提到了区间查询效率。
在 InnoDB 存储引擎中,数据存放的方式是以页的方式进行存放,计算机在存储数据的时候,有最小存储单元,就是最小数据扇区,一个扇区的大小是 512 字节,而文件系统(ext4)他的最小单元是块,一个块的大小是 4k,而对于咱们的 InnoDB 存储引擎也有本身的最小存储单元--页。一个页的大小是 16K。在 InnoDB 中默认的数据页的大小是 16K。
数据表中的数据都是存储在页中的,因此一个页中能存储多少行数据呢?假设一个数据的大小是 1K,那么一个页能够存放 16 行这样的数据。
例如:
假设 B+树高为 2,即存在一个根节点和若干个叶子节点,那么这棵 B+树的存放总记录数为:根节点指针数*单个叶子节点记录行数。
假设一行数据大小为:1KB,那么一页(16KB)中能够存放 16 行数据。
假设主键 ID 为 bigint 类型,长度为 8 字节,而指针大小在 InnoDB 源码中设置为 6 字节,这样一共 14 个字节。(8+6 的意思是 B+Tree 有 key 和指针)
16KB * 1024(化成字节) / 14 =1170(一个节点能够存放页的指针数据)
那么能够算出一棵高度为 2 的 B+树,能存放 1170*16=18720 行 解释:1170 表示一个节点可以建立的指针数,而 16 表示,一个存放数据的页能够存放 16 行数据。*
3 阶 B+树能够存放 1170117016=2 千万(左右)
所在在 InnoDb 中 B+树高度通常为 1-3 层,它就能知足千万级的数据存储,在查找数据时一次页的查找表明一次 IO,因此经过主键索引查询一般只须要 1-3 次 IO 操做便可查找到数据。
在没有加数据记录大小的状况下:
InnoDB 存储引擎中页的大小为 16KB,通常表的主键类型为 INT(占用 4 个字节)或 BIGINT(占用 8 个字节),指针类型也通常为 4 或 8 个字节,也就是说一个页(B+Tree 中的一个节点)中大概存储 16KB/(8B+8B)=1K 个键值(由于是估值,为方便计算,这里的 K 取值为〖10〗^3)。也就是说一个深度为 3 的 B+Tree 索引能够维护 10^3 10^3 10^3 = 10 亿 条记录。
InnoDB 的数据文件自己就是索引文件,InnoDB 中,表数据文件自己就是按 B+Tree 组织的一个索引结构,这棵树的叶节点 data 域保存了完整的数据记录,这个索引的 key 是数据表的主键。InnoDB 表都必须有主键,若是没有定义主键,会默认添加一个主键。
一级索引(主键):
二级索引:
MyISAM 索引文件和数据文件是分离的,索引文件仅保存记录所在页的指针,经过这些地址来读取页,进而读取被索引的行。
(5) InnoDB 与 MyISAM 在 B+Tree 索引的区别
第一重大的区别是 InnoDB 的数据文件自己就是索引文件,MyISAM 索引文件和数据文件是分离的,索引文件仅仅保存数据记录的地址,而在 InnoDB 中,表数据文件自己就是按 B+Tree 组织的一个索引结构。这棵树的叶节点 data 域保存了完整的数据记录,这个索引的 key 是数据表的主键,所以 InnoDB 表数据文件自己就是主索引。由于 InnoDB 的数据文件自己要按主键汇集,因此 InnoDB 要求表必须有主键(MyISAM 能够没有)若是没有显示指定,则 MySQL 系统会自动选择一个能够惟一标识数据的列做为主键,若是不存在这种列,则 MySQL 自动为 InnoDB 表生成一个隐含字段做为主键,这个字段长度为 6 个字节,类型为长整型。
第二个与 MyISAM 索引的不一样是 InnoDB 的辅助索引 DATA 域存储相应记录主键的值而不是地址,InnoDB 的全部辅助索引都引用主键做为 data 域。
汇集索引这种实现方式使得按主键的搜索十分高效,可是辅助索引搜索须要检索两遍索引:首先检索辅助索引得到主键,而后用主键到主索引中检索得到记录。
(6) 有一道 MySQL 的面试题,为何 MySQL 的索引要使用 B+树而不是其它树形结构?好比 B 树?
由于 B 树无论叶子节点仍是非叶子节点,都会保存数据,这样致使在非叶子节点中能保存的指针数量变少(有些资料也称为扇出),指针少的状况下要保存大量数据,只能增长树的高度,致使 IO 操做变多,查询性能变低;红黑树 BST 的时间复杂度是依赖于树的高度,可是,红黑树的高度与 Btree 相比,高度更大。
汇集索引:也叫聚簇索引,汇集索引表记录的排列顺序和索引的排列顺序一致,因此查询效率快,只要找到第一个索引值索引,其他就连续性记录在物理也同样连续存放,汇集索引对应的缺点就是修改慢,由于为了保证表中记录的物理和索引顺序一致,在记录插入的时候会对数据页从新排序(InnoDB 的 B+树)。
非汇集索引制定了表中记录的逻辑顺序,可是记录的物理和索引不必定一致,两种索引都采用 B+Tree 结构,非汇集索引的叶子层并不和实际数据页相重叠,而采用叶子层包含一个指向表中的记录在数据页中的指针方式,非汇集索引层次多,不会形成数据重排。
在 Mysql 将每一个数据库(Schema)保存为数据目录下的一个子目录。建立表时,Mysql 会在数据库子目录下建立一个和表同名的.frm 文件保存表的定义。
有关:show table status like 'user'查看 user 表的相关信息:
(1) InnoDB 存储引擎:Mysql 的默认事务型引擎
InnoDB 使用的是行级锁,但实际是有限制的,只有在你增删改查时匹配的条件字段带有索引时,InnoDB 才会使用行级锁,在你增删改查时匹配的条件字段不带有索引时。InnoDB 使用的将是表级锁。
InnoDB 是新版本 mysql 的默认引擎,支持事务处理和外键,可是其缺点就是慢了些,存储方式分为两种
一、共享表空间存储。这种方式建立的表的表结构保存在.frm 文件中,数据和索引保存在 innodb_data_home_dir 和 innodb_data_file_path 定义的表空间中,能够是多个文件。
二、多表空间存储。(.frm 表结构和 idb 数据。) 这种方式建立的表的表结构仍然保存在.frm 文件中,可是每一个表的数据和索引单独保存在.ibd 中。若是是个分区表,则每一个分区对应单独的.ibd 文件,文件名是“表名+分区名”,能够在建立分区的时候指定每一个分区的数据文件的位置,以此来将表的 IO 均匀分布在多个磁盘上。
使用 engine=innodb default charset=utf-8;
MyISAM 存储引擎是旧版本 mysql 的默认引擎,如今默认引擎是 InnoDB,MyISAM 引擎的主要特色就是快,没有事务处理操做,也不支持外键操做。适合 insert 与 select 的操做表。MyISAM 存储引擎的表在数据库中,每个表都被存放为三个以表名命名的物理文件。定义表结构.frm,存放表数据.myd 和索引数据.myi。使用方法:engine=myisam default charset=utf-8;
其中 MyISAM 支持如下三种类型的索引;
存储引擎使用存在内存中的内容来建立表,每一个 Memory 表只实现对应一个磁盘文件,格式是.frm(只保存表结构,不保存内容)。Memory 类型的表访问很是快,由于它的数据是放在内存中的,而且默认使用 Hash 索引,可是一旦服务关闭,表中的数据就会丢失掉。
CREATE TABLE tab_memory ENGINE=MEMORY
给 Memory 表建立索引的时候,能够指定使用 Hash 索引仍是 BTree 索引。
Create index mem_hash using HASH on table_memory(city_id);
InnoDB 和 MyISAM 是许多人在使用 MySQL 时最经常使用的两个表类型,MyISAM 不支持事务处理等高级处理,而 InnoDB 类型支持。MyISAM 类型的强调的是性能。其执行速度比 InnoDB 类型更快,可是不提供事务类型支持。而 InnoDB 提供事务支持和外键。
把联合索引单独拿出来是由于去滴滴面试的时候被问到过。
联合索引能够将多个字段组合建立一个索引,在查询 where 条件中使用组合索引时,它符合最左匹配规则。例如为 A,B,C 三列建立索引,则它支持 A/A,B/A,B,C 而 B,C 则没法使用组合索引。
当一个列存在联合索引和单列索引时,mysql 会根据查询语句的成原本选择走哪条索引。
须要两次:在前面的索引一节咱们详细的介绍过辅助索引的结构,辅助索引的 B+Tree 结点的叶子结点存放的是一级索引的值,因此查到一级索引时,它会再查询一次一级索引。
读写分离主要解决的是数据库的写操做是比较耗时的,而数据库的读取则是很是快的,因此读写分离来解决数据库的写入,影响了查询的效率。
读写分离的好处
主库将插入、更新或删除的记录写入到了 binlog 日志,而后从库链接到主库以后,从库有一个 IO 线程,将主库的 binlog 日志拷贝到本身本地,写入一个中继日志中。接着从库中有一个 SQL 线程会从中继日志读取 binlog,而后执行 binlog 日志中的内容,也就是在本身本地再次执行一遍 SQL,这样就能够保证本身跟主库的数据是同样的。
这里有一个很是重要的一点,就是从库同步主库数据的过程是串行化的,也就是说主库上并行的操做,在从库上会串行执行。因此这就是一个很是重要的点了,因为从库从主库拷贝日志以及串行执行 SQL 的特色,在高并发场景下,从库的数据必定会比主库慢一些,是有延时的。因此常常出现,刚写入主库的数据多是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。
并且这里还有另一个问题,就是若是主库忽然宕机,而后刚好数据还没同步到从库,那么有些数据可能在从库上是没有的,有些数据可能就丢失了。
mysql 半同步复制:
这个所谓半同步复制,semi-sync 复制,指的就是主库写入 binlog 日志以后,就会将强制此时当即将数据同步到从库,从库将日志写入本身本地的 relay log 以后,接着会返回回一个 ack 给主库,主库接收到至少一个从库的 ack 以后才会认为写操做完成了。
并行复制
所谓并行复制,指的是从库开启多个线程,并行读取 relay log 中不一样库的日志,而后并行重放不一样库的日志,这是库级别的并行。
在 Master 上增长一个自增表,这个表仅含有 1 个字段,当 master 接收到任何数据更新的请求时,均会触发这个触发器,该触发器更新自增表中的记录。
MySQL_Poxy 解决方案:
写数据时:因为 Count_table 也参与 Mysq 的主从同步,所以在 Master 上做的 Update 更新也会同步到 Slave 上。当 Client 经过 Proxy 进行数据读取时,Proxy 能够先向 Master 和 Slave 的 Count_table 表发送查询请求,当两者的数据相同时,Proxy 能够认定 Master 和 Slave 的数据状态是一致的,而后把 select 请求发送到 Slave 服务器上,不然就发送到 Master 上。以下图所示:
读数据时:经过这种方式,就能够比较完美的结果 MySQL 的同步延迟不可控问题。之因此所“比较完美”,是由于这种方案 double 了查询请求,对 Master 和 Slave 构成了额外的压力。不过因为 Proxy 与真实的 Mysql Server 采用链接池的方式链接,所以额外的压力仍是能够接受的
好比一个项目单表都几千万数据了,mysql 数据库已经抗不住了,单表数据量太大,会极大影响你的 sql 执行的性能,会发遭受 sql 可能就跑的愈来愈慢。
分表就是把一个表的数据放到多个表中,而后项目查询数据的时候只查询一个表,好比按照用户 id 来分表,将一个用户的数据就放在一个用中。这样能够控制每一个表的数据量在可控的范围内,好比每一个表固定在 200 万之内。
分库就是你一个库最多支撑并发 2000,并且一个健康的单库并发值你最好保持在每秒 1000 左右,不要太大,那么你能够将一个库的数据拆分到多个库中,访问的时候访问一个库就行了。
数据库分库/分表中间件有两类:一类是 client 层,也就是直接在系统中使用的,另外一类是 proxy 层,须要单独部署这种中间件。
水平拆分:就是把一个表的数据给拆分到多个库的多个表里面去,可是每一个库的表结构都同样,只不过每一个库下表放的数据是不一样的,全部的不一样库的表数据加起来就是所有数据,水平拆分的意义,就是将数据均匀放更多的库里,而后用多个库来抗更高的并发,还有就是用多个库的存储容量来进行扩容。
垂直拆分:就是把一个有不少字段的表给拆分红多个表,或者是多个库上去,每一个库表的结构都不同,每一个库都都包含部分字段。通常来讲会将较少的访问频率很高的字段放到一个表里去,而后将较多的访问频率很低的字段放到另一个表里去。由于数据库是有缓存的,你访问频率高的行字段越少,就能够在缓存里缓存更多的行,性能就越好。这个通常在表层面作的较多一些。
你就得考虑一下,你的项目里该如何分库分表?通常来讲,垂直拆分,你能够在表层面来作,对一些字段特别多的表作一下拆分;水平拆分,你能够说是并发承载不了,或者是数据量太大,容量承载不了,你给拆了,按什么字段来拆,你本身想好;分表,你考虑一下,你若是哪怕是拆到每一个库里去,并发和容量都 ok 了,可是每一个库的表仍是太大了,那么你就分表,将这个表分开,保证每一个表的数据量并非很大。
并且这儿还有两种分库分表的方式,一种是按照 range 来分,就是每一个库一段连续的数据,这个通常是按好比时间范围来的,可是这种通常较少用,由于很容易产生热点问题,大量的流量都打在最新的数据上了;或者是按照某个字段 hash 一下均匀分散,这个较为经常使用。
range 来分,好处在于说,后面扩容的时候,就很容易,由于你只要预备好,给每月都准备一个库就能够了,到了一个新的月份的时候,天然而然,就会写新的库了;缺点,可是大部分的请求,都是访问最新的数据。实际生产用 range,要看场景,你的用户不是仅仅访问最新的数据,而是均匀的访问如今的数据以及历史的数据
hash 分法,好处在于说,能够平均分配没给库的数据量和请求压力;坏处在于说扩容起来比较麻烦,会有一个数据迁移的这么一个过程。
停机迁移方案中,就是把系统在凌晨 12 点开始运维,系统停掉,而后提早写好一个导数据的一次性工具,此时直接跑起来,而后将单库单表的数据写到分库分表里面去。 导入数据完成了以后,修改系统的数据库链接配置,包括可能代码和 SQL 也许有修改,那你就用最新的代码,而后直接启动连到新的分库分表上去。
此方案不用停机,比较经常使用。 简单来讲,就是在线上系统里面,以前全部写库的地方,增删改操做,都除了对老库增删改,都加上对新库的增删改,这就是所谓的双写。同时写两个库,老库和新库。而后系统部署以后,新库数据差太远,用以前说的导数工具,路起来读老库数据写新库,写的时候要根据 gmt_modified 这类判断这条数据最后修改时间,除非是读出来的数据在新库里没有,或者是比新库的数据新才会写。
数据库通过分库分表以后,系统在向数据库中插入数据的时候,须要生成一个惟一的数据库 id,它在分库分表中必须是全局惟一的。经常使用的生成方法以下:
在数据库中单首创建一张表,这张表只包含一个字段 id,每次要向分库分表中插入数据时,先从这张表中获取一个 id。
缺点:这张生成 id 的表是单库单表的,要是高并发的话,就会产生瓶颈。
适合的场景:你分库分表就俩缘由,要不就是单库并发过高,要不就是单库数据量太大;除非是你并发不高,可是数据量太大致使的分库分表扩容,你能够用这个方案,由于可能每秒最高并发最多就几百,那么就走单独的一个库和表生成自增主键便可。
好处就是本地生成,不须要基于数据库,很差之处就是,UUID 太长了,而且是字符串,做为主键性能太差了,不适合用于主键。
适合的场景:若是你是要随机生成个什么文件名了,编号之类的,你能够用 uuid,可是做为主键是不能用 uuid 的。
这个就是获取当前时间便可,可是问题是,并发很高的时候,好比一秒并发几千,会有重复的状况,这个是确定不合适的,基本就不用考虑了。
适合的场景:通常若是用这个方案,是将当前时间跟不少其余的业务字段拼接起来,做为一 id,若是业务上你以为能够接受,那么是能够的,你能够将别的业务字段值跟当前时间拼接起来,组成一个全局惟一的编号,订单编号啊:时间戳 + 用户 id + 业务含义编码。
redis 是部署在数据库集群以外,它不依赖于数据库,使用 redis 可以生成惟一的 id 这主要依赖于 Redis 是单线程的,因此也能够用生成全局惟一的 ID,能够用 redis 的原子操做 incr 和 incrby 来实现。
也可使用 redis 集群来获取更高的吞吐量,假如一个集群中有 5 台 Redis,能够初始化每台 Redis 的值分别是 1,2,3,4,5。而后步长都是 5,各个 redis 生成的 ID 为:
A:1,6,11,16,21
B:2,7,12,17,22
C:3,8,13,18,23
D:4,9,14,19,24
E:5,10,15,20,25
twitter 开源的分布式 id 生成算法,就是把一个 64 位的 long 型的 id,1 个 bit 是不用的,用其中的 41 bit 做为毫秒数,用 10 bit 做为工做机器 id,12 bit 做为序列号
例如:0 1011100111101101000110100010111 10001 00001 000000000001
第 1 位 0:表示正数
接着 41 位表示:1559661847--> 2019-6-4 23:24:7
接着 5 位表示:17 机房
接着 5 位表示:17 机房下的第 1 台机器
最后 12 位表示:当前时间戳下并发的自增编号。
参考代码:
public class IdWorker{ private long workerId; private long datacenterId; private long sequence; private long twepoch = 1288834974657L; private long workerIdBits = 5L; private long datacenterIdBits = 5L; private long maxWorkerId = -1L ^ (-1L << workerIdBits); // 这个是二进制运算,就是5 bit最多只能有31个数字,也就是说机器id最多只能是32之内 private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); // 这个是一个意思,就是5 bit最多只能有31个数字,机房id最多只能是32之内 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 IdWorker(long workerId, long datacenterId, long sequence){ // 这儿不就检查了一下,要求就是你传递进来的机房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; } 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)); } // 在同一个毫秒内,又发送了一个请求生成一个id,0 -> 1 if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; // 这个意思是说一个毫秒内最多只能有4096个数字,不管你传递多少进来,这个位运算保证始终就是在4096这个范围内,避免你本身传递个sequence超过了4096这个范围 if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0; } // 这儿记录一下最近一次生成id的时间戳,单位是毫秒 lastTimestamp = timestamp; // 这儿就是将时间戳左移,放到41 bit那儿;将机房id左移放到5 bit那儿;将机器id左移放到5 bit那儿;将序号放最后10 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 long getWorkerId(){ return workerId; } public long getDatacenterId(){ return datacenterId; } public long getTimestamp(){ 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()); } } }