当前位置:网站首页>分库分表的 9种分布式主键ID 生成方案,挺全乎的

分库分表的 9种分布式主键ID 生成方案,挺全乎的

2020-11-09 18:09:00 InfoQ

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/AUvcsschhqrhKopM5-XeMA","title":""},"content":[{"type":"text","text":"《sharding-jdbc 分库分表的 4种分片策略》"}]},{"type":"text","text":" 中我们介绍了 "},{"type":"codeinline","content":[{"type":"text","text":"sharding-jdbc"}]},{"type":"text","text":" 4种分片策略的使用场景,可以满足基础的分片功能开发,这篇我们来看看分库分表后,应该如何为分片表生成全局唯一的主键 "},{"type":"codeinline","content":[{"type":"text","text":"ID"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"引入任何一种技术都是存在风险的,分库分表当然也不例外,除非库、表数据量持续增加,大到一定程度,以至于现有高可用架构已无法支撑,否则不建议大家做分库分表,因为做了数据分片后,你会发现自己踏上了一段踩坑之路,而分布式主键 "},{"type":"codeinline","content":[{"type":"text","text":"ID"}]},{"type":"text","text":" 就是遇到的第一个坑。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不同数据节点间生成全局唯一主键是个棘手的问题,一张逻辑表 "},{"type":"codeinline","content":[{"type":"text","text":"t_order"}]},{"type":"text","text":" 拆分成多个真实表 "},{"type":"codeinline","content":[{"type":"text","text":"t_order_n"}]},{"type":"text","text":",然后被分散到不同分片库 "},{"type":"codeinline","content":[{"type":"text","text":"db_0"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"db_1"}]},{"type":"text","text":"... ,各真实表的自增键由于无法互相感知从而会产生重复主键,此时数据库本身的自增主键,就无法满足分库分表对主键全局唯一的要求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" db_0--\n |-- t_order_0\n |-- t_order_1\n |-- t_order_2\n db_1--\n |-- t_order_0\n |-- t_order_1\n |-- t_order_2"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"尽管我们可以通过严格约束,各个分片表自增主键的 "},{"type":"codeinline","content":[{"type":"text","text":"初始值"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"步长"}]},{"type":"text","text":" 的方式来解决 "},{"type":"codeinline","content":[{"type":"text","text":"ID"}]},{"type":"text","text":" 重复的问题,但这样会让运维成本陡增,而且可扩展性极差,一旦要扩容分片表数量,原表数据变动比较大,所以这种方式不太可取。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" 步长 step = 分表张数\n\n db_0--\n |-- t_order_0 ID: 0、6、12、18...\n |-- t_order_1 ID: 1、7、13、19...\n |-- t_order_2 ID: 2、8、14、20...\n db_1--\n |-- t_order_0 ID: 3、9、15、21...\n |-- t_order_1 ID: 4、10、16、22...\n |-- t_order_2 ID: 5、11、17、23..."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前已经有了许多第三放解决方案可以完美解决这个问题,比如基于 "},{"type":"codeinline","content":[{"type":"text","text":"UUID"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"SNOWFLAKE"}]},{"type":"text","text":"算法 、"},{"type":"codeinline","content":[{"type":"text","text":"segment"}]},{"type":"text","text":"号段,使用特定算法生成不重复键,或者直接引用主键生成服务,像美团("},{"type":"codeinline","content":[{"type":"text","text":"Leaf"}]},{"type":"text","text":")和 滴滴("},{"type":"codeinline","content":[{"type":"text","text":"TinyId"}]},{"type":"text","text":")等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而"},{"type":"codeinline","content":[{"type":"text","text":"sharding-jdbc"}]},{"type":"text","text":" 内置了两种分布式主键生成方案,"},{"type":"codeinline","content":[{"type":"text","text":"UUID"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"SNOWFLAKE"}]},{"type":"text","text":",不仅如此它还抽离出分布式主键生成器的接口,以便于开发者实现自定义的主键生成器,后续我们会在自定义的生成器中接入 滴滴("},{"type":"codeinline","content":[{"type":"text","text":"TinyId"}]},{"type":"text","text":")的主键生成服务。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前边介绍过在 sharding-jdbc 中要想为某个字段自动生成主键 ID,只需要在 "},{"type":"codeinline","content":[{"type":"text","text":"application.properties"}]},{"type":"text","text":" 文件中做如下配置:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"# 主键字段\nspring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id\n# 主键ID 生成方案\nspring.shardingsphere.sharding.tables.t_order.key-generator.type=UUID\n# 工作机器 id\nspring.shardingsphere.sharding.tables.t_order.key-generator.props.worker.id=123"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"key-generator.column"}]},{"type":"text","text":" 表示主键字段,"},{"type":"codeinline","content":[{"type":"text","text":"key-generator.type"}]},{"type":"text","text":" 为主键 ID 生成方案(内置或自定义的),"},{"type":"codeinline","content":[{"type":"text","text":"key-generator.props.worker.id"}]},{"type":"text","text":" 为机器ID,在主键生成方案设为 "},{"type":"codeinline","content":[{"type":"text","text":"SNOWFLAKE"}]},{"type":"text","text":" 时机器ID 会参与位运算。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">在使用 sharding-jdbc 分布式主键时需要注意两点:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一旦 "},{"type":"codeinline","content":[{"type":"text","text":"insert"}]},{"type":"text","text":" 插入操作的实体对象中主键字段已经赋值,那么即使配置了主键生成方案也会失效,最后SQL 执行的数据会以赋的值为准。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不要给主键字段设置自增属性,否则主键ID 会以默认的 "},{"type":"codeinline","content":[{"type":"text","text":"SNOWFLAKE"}]},{"type":"text","text":" 方式生成。比如:用 "},{"type":"codeinline","content":[{"type":"text","text":"mybatis plus"}]},{"type":"text","text":" 的 "},{"type":"codeinline","content":[{"type":"text","text":"@TableId"}]},{"type":"text","text":" 注解给字段 "},{"type":"codeinline","content":[{"type":"text","text":"order_id"}]},{"type":"text","text":" 设置了自增主键,那么此时配置哪种方案,总是按雪花算法生成。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面我们从源码上分析下 sharding-jdbc 内置主键生成方案 "},{"type":"codeinline","content":[{"type":"text","text":"UUID"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"SNOWFLAKE"}]},{"type":"text","text":" 是怎么实现的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"UUID"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"打开 "},{"type":"codeinline","content":[{"type":"text","text":"UUID"}]},{"type":"text","text":" 类型的主键生成实现类 "},{"type":"codeinline","content":[{"type":"text","text":"UUIDShardingKeyGenerator"}]},{"type":"text","text":" 的源码发现,它的生成规则只有 "},{"type":"codeinline","content":[{"type":"text","text":"UUID.randomUUID()"}]},{"type":"text","text":" 这么一行代码,额~ 心中默默来了一句"},{"type":"text","marks":[{"type":"strong"}],"text":"卧槽"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"UUID 虽然可以做到全局唯一性,但还是不推荐使用它作为主键,因为我们的实际业务中不管是 "},{"type":"codeinline","content":[{"type":"text","text":"user_id"}]},{"type":"text","text":" 还是 "},{"type":"codeinline","content":[{"type":"text","text":"order_id"}]},{"type":"text","text":" 主键多为整型,而 UUID 生成的是个 32 位的字符串。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它的存储以及查询对 "},{"type":"codeinline","content":[{"type":"text","text":"MySQL"}]},{"type":"text","text":" 的性能消耗较大,而且 "},{"type":"codeinline","content":[{"type":"text","text":"MySQL"}]},{"type":"text","text":" 官方也明确建议,主键要尽量越短越好,作为数据库主键 UUID 的无序性还会导致数据位置频繁变动,严重影响性能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"public final class UUIDShardingKeyGenerator implements ShardingKeyGenerator {\n private Properties properties = new Properties();\n\n public UUIDShardingKeyGenerator() {\n }\n\n public String getType() {\n return \"UUID\";\n }\n\n public synchronized Comparable generateKey() {\n return UUID.randomUUID().toString().replaceAll(\"-\", \"\");\n }\n\n public Properties getProperties() {\n return this.properties;\n }\n\n public void setProperties(Properties properties) {\n this.properties = properties;\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"SNOWFLAKE"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"SNOWFLAKE"}]},{"type":"text","text":"(雪花算法)是默认使用的主键生成方案,生成一个 64bit的长整型("},{"type":"codeinline","content":[{"type":"text","text":"Long"}]},{"type":"text","text":")数据。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"sharding-jdbc"}]},{"type":"text","text":" 中雪花算法生成的主键主要由 4部分组成,"},{"type":"codeinline","content":[{"type":"text","text":"1bit"}]},{"type":"text","text":"符号位、"},{"type":"codeinline","content":[{"type":"text","text":"41bit"}]},{"type":"text","text":"时间戳位、"},{"type":"codeinline","content":[{"type":"text","text":"10bit"}]},{"type":"text","text":"工作进程位以及 "},{"type":"codeinline","content":[{"type":"text","text":"12bit"}]},{"type":"text","text":" 序列号位。 "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/84/84b9668edec202a384f320e51b1939fd.png","alt":"雪花算法ID组成","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":" 符号位(1bit位)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java 中 Long 型的最高位是符号位,正数是0,负数是1,一般生成ID都为正数,所以默认为0"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":" 时间戳位(41bit) "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"41位的时间戳可以容纳的毫秒数是 2 的 41次幂,而一年的总毫秒数为 "},{"type":"codeinline","content":[{"type":"text","text":"1000L * 60 * 60 * 24 * 365"}]},{"type":"text","text":",计算使用时间大概是69年,额~,我有生之间算是够用了。"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"Math.pow(2, 41) / (365 * 24 * 60 * 60 * 1000L) = = 69年 "}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"工作进程位(10bit) "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"表示一个唯一的工作进程id,默认值为 0,可通过 "},{"type":"codeinline","content":[{"type":"text","text":"key-generator.props.worker.id"}]},{"type":"text","text":" 属性设置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"spring.shardingsphere.sharding.tables.t_order.key-generator.props.worker.id=0000"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"序列号位(12bit) "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同一毫秒内生成不同的ID。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"时钟回拨 "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"了解了雪花算法的主键 ID 组成后不难发现,这是一种严重依赖于服务器时间的算法,而依赖服务器时间的就会遇到一个棘手的问题:"},{"type":"codeinline","content":[{"type":"text","text":"时钟回拨"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"为什么会出现时钟回拨呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"互联网中有一种网络时间协议 "},{"type":"codeinline","content":[{"type":"text","text":"ntp"}]},{"type":"text","text":" 全称 ("},{"type":"codeinline","content":[{"type":"text","text":"Network Time Protocol"}]},{"type":"text","text":") ,专门用来同步、校准网络中各个计算机的时间。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这就是为什么,我们的手机现在不用手动校对时间,可每个人的手机时间还都是一样的。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们的硬件时钟可能会因为各种原因变得不准( "},{"type":"codeinline","content":[{"type":"text","text":"快了"}]},{"type":"text","text":" 或 "},{"type":"codeinline","content":[{"type":"text","text":"慢了"}]},{"type":"text","text":" ),此时就需要 "},{"type":"codeinline","content":[{"type":"text","text":"ntp"}]},{"type":"text","text":" 服务来做时间校准,做校准的时候就会发生服务器时钟的 "},{"type":"codeinline","content":[{"type":"text","text":"跳跃"}]},{"type":"text","text":" 或者 "},{"type":"codeinline","content":[{"type":"text","text":"回拨"}]},{"type":"text","text":" 的问题。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"雪花算法如何解决时钟回拨"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服务器时钟回拨会导致产生重复的 ID,"},{"type":"codeinline","content":[{"type":"text","text":"SNOWFLAKE"}]},{"type":"text","text":" 方案中对原有雪花算法做了改进,增加了一个最大容忍的时钟回拨毫秒数。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果时钟回拨的时间超过最大容忍的毫秒数阈值,则程序直接报错;如果在可容忍的范围内,默认分布式主键生成器,会等待时钟同步到最后一次主键生成的时间后再继续工作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最大容忍的时钟回拨毫秒数,默认值为 0,可通过属性 "},{"type":"codeinline","content":[{"type":"text","text":"max.tolerate.time.difference.milliseconds"}]},{"type":"text","text":" 设置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"# 最大容忍的时钟回拨毫秒数\nspring.shardingsphere.sharding.tables.t_order.key-generator.max.tolerate.time.difference.milliseconds=5"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面是看下它的源码实现类 "},{"type":"codeinline","content":[{"type":"text","text":"SnowflakeShardingKeyGenerator"}]},{"type":"text","text":",核心流程大概如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后一次生成主键的时间 "},{"type":"codeinline","content":[{"type":"text","text":"lastMilliseconds"}]},{"type":"text","text":" 与 当前时间"},{"type":"codeinline","content":[{"type":"text","text":"currentMilliseconds"}]},{"type":"text","text":" 做比较,如果 "},{"type":"codeinline","content":[{"type":"text","text":"lastMilliseconds"}]},{"type":"text","text":" > "},{"type":"codeinline","content":[{"type":"text","text":"currentMilliseconds"}]},{"type":"text","text":"则意味着时钟回调了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那么接着判断两个时间的差值("},{"type":"codeinline","content":[{"type":"text","text":"timeDifferenceMilliseconds"}]},{"type":"text","text":")是否在设置的最大容忍时间阈值 "},{"type":"codeinline","content":[{"type":"text","text":"max.tolerate.time.difference.milliseconds"}]},{"type":"text","text":"内,在阈值内则线程休眠差值时间 "},{"type":"codeinline","content":[{"type":"text","text":"Thread.sleep(timeDifferenceMilliseconds)"}]},{"type":"text","text":",否则大于差值直接报异常。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" \n/**\n * @author xiaofu\n */\npublic final class SnowflakeShardingKeyGenerator implements ShardingKeyGenerator{\n @Getter\n @Setter\n private Properties properties = new Properties();\n \n public String getType() {\n return \"SNOWFLAKE\";\n }\n \n public synchronized Comparable generateKey() {\n \t/**\n \t * 当前系统时间毫秒数 \n \t */ \n long currentMilliseconds = timeService.getCurrentMillis();\n /**\n * 判断是否需要等待容忍时间差,如果需要,则等待时间差过去,然后再获取当前系统时间 \n */ \n if (waitTolerateTimeDifferenceIfNeed(currentMilliseconds)) {\n currentMilliseconds = timeService.getCurrentMillis();\n }\n /**\n * 如果最后一次毫秒与 当前系统时间毫秒相同,即还在同一毫秒内 \n */\n if (lastMilliseconds == currentMilliseconds) {\n \t/**\n \t * &位与运算符:两个数都转为二进制,如果相对应位都是1,则结果为1,否则为0\n \t * 当序列为4095时,4095+1后的新序列与掩码进行位与运算结果是0\n \t * 当序列为其他值时,位与运算结果都不会是0\n \t * 即本毫秒的序列已经用到最大值4096,此时要取下一个毫秒时间值\n \t */\n if (0L == (sequence = (sequence + 1) & SEQUENCE_MASK)) {\n currentMilliseconds = waitUntilNextTime(currentMilliseconds);\n }\n } else {\n \t/**\n \t * 上一毫秒已经过去,把序列值重置为1 \n \t */\n vibrateSequenceOffset();\n sequence = sequenceOffset;\n }\n lastMilliseconds = currentMilliseconds;\n \n /**\n * XX......XX XX000000 00000000 00000000\t时间差 XX\n * \t\tXXXXXX XXXX0000 00000000\t机器ID XX\n * \t\t XXXX XXXXXXXX\t序列号 XX\n * 三部分进行|位或运算:如果相对应位都是0,则结果为0,否则为1\n */\n return ((currentMilliseconds - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (getWorkerId() << WORKER_ID_LEFT_SHIFT_BITS) | sequence;\n }\n \n /**\n * 判断是否需要等待容忍时间差\n */\n @SneakyThrows\n private boolean waitTolerateTimeDifferenceIfNeed(final long currentMilliseconds) {\n \t/**\n \t * 如果获取ID时的最后一次时间毫秒数小于等于当前系统时间毫秒数,属于正常情况,则不需要等待 \n \t */\n if (lastMilliseconds <= currentMilliseconds) {\n return false;\n }\n /**\n * ===>时钟回拨的情况(生成序列的时间大于当前系统的时间),需要等待时间差 \n */\n /**\n * 获取ID时的最后一次毫秒数减去当前系统时间毫秒数的时间差 \n */\n long timeDifferenceMilliseconds = lastMilliseconds - currentMilliseconds;\n /**\n * 时间差小于最大容忍时间差,即当前还在时钟回拨的时间差之内 \n */\n Preconditions.checkState(timeDifferenceMilliseconds < getMaxTolerateTimeDifferenceMilliseconds(), \n \"Clock is moving backwards, last time is %d milliseconds, current time is %d milliseconds\", lastMilliseconds, currentMilliseconds);\n /**\n * 线程休眠时间差 \n */\n Thread.sleep(timeDifferenceMilliseconds);\n return true;\n }\n \n // 配置的机器ID\n private long getWorkerId() {\n long result = Long.valueOf(properties.getProperty(\"worker.id\", String.valueOf(WORKER_ID)));\n Preconditions.checkArgument(result >= 0L && result < WORKER_ID_MAX_VALUE);\n return result;\n }\n \n private int getMaxTolerateTimeDifferenceMilliseconds() {\n return Integer.valueOf(properties.getProperty(\"max.tolerate.time.difference.milliseconds\", String.valueOf(MAX_TOLERATE_TIME_DIFFERENCE_MILLISECONDS)));\n }\n \n private long waitUntilNextTime(final long lastTime) {\n long result = timeService.getCurrentMillis();\n while (result <= lastTime) {\n result = timeService.getCurrentMillis();\n }\n return result;\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但从 "},{"type":"codeinline","content":[{"type":"text","text":"SNOWFLAKE"}]},{"type":"text","text":" 方案生成的主键ID 来看,"},{"type":"codeinline","content":[{"type":"text","text":"order_id"}]},{"type":"text","text":" 它是一个18位的长整型数字,是不是发现它太长了,想要 "},{"type":"codeinline","content":[{"type":"text","text":"MySQL"}]},{"type":"text","text":" 那种从 0 递增的自增主键该怎么实现呢?别急,后边已经会给出了解决办法!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/06/06077bd1fd0672c46b8917c5b0b711b0.png","alt":"SNOWFLAKE 主键ID","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"自定义"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"sharding-jdbc"}]},{"type":"text","text":" 利用 "},{"type":"codeinline","content":[{"type":"text","text":"SPI"}]},{"type":"text","text":" 全称( "},{"type":"codeinline","content":[{"type":"text","text":"Service Provider Interface"}]},{"type":"text","text":") 机制拓展主键生成规则,这是一种服务发现机制,通过扫描项目路径 "},{"type":"codeinline","content":[{"type":"text","text":"META-INF/services"}]},{"type":"text","text":" 下的文件,并自动加载文件里所定义的类。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"实现自定义主键生成器其实比较简单,只有两步。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一步,实现 "},{"type":"codeinline","content":[{"type":"text","text":"ShardingKeyGenerator"}]},{"type":"text","text":" 接口,并重写其内部方法,其中 "},{"type":"codeinline","content":[{"type":"text","text":"getType()"}]},{"type":"text","text":" 方法为自定义的主键生产方案类型、"},{"type":"codeinline","content":[{"type":"text","text":"generateKey()"}]},{"type":"text","text":" 方法则是具体生成主键的规则。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面代码中用 "},{"type":"codeinline","content":[{"type":"text","text":"AtomicInteger"}]},{"type":"text","text":" 来模拟实现一个有序自增的 ID 生成。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"/**\n * @Author: xiaofu\n * @Description: 自定义主键生成器\n */\n@Component\npublic class MyShardingKeyGenerator implements ShardingKeyGenerator {\n\n\n private final AtomicInteger count = new AtomicInteger();\n\n /**\n * 自定义的生成方案类型\n */\n @Override\n public String getType() {\n return \"XXX\";\n }\n\n /**\n * 核心方法-生成主键ID\n */\n @Override\n public Comparable generateKey() {\n return count.incrementAndGet();\n }\n\n @Override\n public Properties getProperties() {\n return null;\n }\n\n @Override\n public void setProperties(Properties properties) {\n\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二步,由于是利用 "},{"type":"codeinline","content":[{"type":"text","text":"SPI"}]},{"type":"text","text":" 机制实现功能拓展,我们要在 "},{"type":"codeinline","content":[{"type":"text","text":"META-INF/services"}]},{"type":"text","text":" 文件中配置自定义的主键生成器类路劲。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"com.xiaofu.sharding.key.MyShardingKeyGenerator"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6a/6a56178a8f7fbd4d35813d74dbc1526c.png","alt":"自定义主键 SPI 配置","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面这些弄完我们测试一下,配置定义好的主键生成类型 "},{"type":"codeinline","content":[{"type":"text","text":"XXX"}]},{"type":"text","text":",并插入几条数据看看效果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"spring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id\nspring.shardingsphere.sharding.tables.t_order.key-generator.type=XXX"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通过控制台的SQL 解析日志发现,"},{"type":"codeinline","content":[{"type":"text","text":"order_id"}]},{"type":"text","text":" 字段已按照有序自增的方式插入记录,说明配置的没问题。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fa/fa85d1e74617da14b5e7fea736e14ab4.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"举一反九"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"既然可以自定义生成方案,那么实现分布式主键的思路就很多了,又想到之前我写的这篇 "},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s?_biz=MzAxNTM4NzAyNg==&mid=2247483785&idx=1&sn=8b828a8ae1701b810fe3969be536cb14&chksm=9b859174acf21862f0b95e0502a1a441c496a5488f5466b2e147d7bb9de072bde37c4db25d7a&token=113284294&lang=zhCN#rd","title":""},"content":[{"type":"text","text":"《9种 分布式ID生成方案》"}]},{"type":"text","text":",发现可以完美兼容,这里挑选其中的 滴滴("},{"type":"codeinline","content":[{"type":"text","text":"Tinyid"}]},{"type":"text","text":")来实践一下,由于它是个单独的分布式ID生成服务,所以要先搭建环境了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Tinyid"}]},{"type":"text","text":" 的服务提供"},{"type":"codeinline","content":[{"type":"text","text":"Http"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"Tinyid-client"}]},{"type":"text","text":" 两种接入方式,下边使用 "},{"type":"codeinline","content":[{"type":"text","text":"Tinyid-client "}]},{"type":"text","text":" 方式快速使用,更多的细节到这篇文章里看吧,实在是介绍过太多次了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Tinyid 服务搭建"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先拉源代码 "},{"type":"codeinline","content":[{"type":"text","text":"https://github.com/didi/tinyid.git"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由于是基于号段模式实现的分布式ID,所以依赖于数据库,要创建相应的表 "},{"type":"codeinline","content":[{"type":"text","text":"tiny_id_info"}]},{"type":"text","text":" 、"},{"type":"codeinline","content":[{"type":"text","text":"tiny_id_token"}]},{"type":"text","text":" 并插入默认数据。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"sql"},"content":[{"type":"text","text":"\nCREATE TABLE `tiny_id_info` (\n\t`id` BIGINT (20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键',\n\t`biz_type` VARCHAR (63) NOT NULL DEFAULT '' COMMENT '业务类型,唯一',\n\t`begin_id` BIGINT (20) NOT NULL DEFAULT '0' COMMENT '开始id,仅记录初始值,无其他含义。初始化时begin_id和max_id应相同',\n\t`max_id` BIGINT (20) NOT NULL DEFAULT '0' COMMENT '当前最大id',\n\t`step` INT (11) DEFAULT '0' COMMENT '步长',\n\t`delta` INT (11) NOT NULL DEFAULT '1' COMMENT '每次id增量',\n\t`remainder` INT (11) NOT NULL DEFAULT '0' COMMENT '余数',\n\t`create_time` TIMESTAMP NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '创建时间',\n\t`update_time` TIMESTAMP NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '更新时间',\n\t`version` BIGINT (20) NOT NULL DEFAULT '0' COMMENT '版本号',\n\tPRIMARY KEY (`id`),\n\tUNIQUE KEY `uniq_biz_type` (`biz_type`)\n) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8 COMMENT 'id信息表';\n\nCREATE TABLE `tiny_id_token` (\n\t`id` INT (11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增id',\n\t`token` VARCHAR (255) NOT NULL DEFAULT '' COMMENT 'token',\n\t`biz_type` VARCHAR (63) NOT NULL DEFAULT '' COMMENT '此token可访问的业务类型标识',\n\t`remark` VARCHAR (255) NOT NULL DEFAULT '' COMMENT '备注',\n\t`create_time` TIMESTAMP NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '创建时间',\n\t`update_time` TIMESTAMP NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '更新时间',\n\tPRIMARY KEY (`id`)\n) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8 COMMENT 'token信息表';\n\nINSERT INTO `tiny_id_token` (`id`, `token`, `biz_type`, `remark`, `create_time`, `update_time`) VALUES ('1', '0f673adf80504e2eaa552f5d791b644c', 'order', '1', '2017-12-14 16:36:46', '2017-12-14 16:36:48');\n\nINSERT INTO `tiny_id_info` (`id`, `biz_type`, `begin_id`, `max_id`, `step`, `delta`, `remainder`, `create_time`, `update_time`, `version`) VALUES ('1', 'order', '1', '1', '100000', '1', '0', '2018-07-21 23:52:58', '2018-07-22 23:19:27', '1');\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"并在 "},{"type":"codeinline","content":[{"type":"text","text":"Tinyid"}]},{"type":"text","text":" 服务中配置上边表所在数据源信息"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"sql"},"content":[{"type":"text","text":"datasource.tinyid.primary.url=jdbc:mysql://47.93.6.e:3306/ds-0?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8\ndatasource.tinyid.primary.username=root\ndatasource.tinyid.primary.password=root"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后项目 "},{"type":"codeinline","content":[{"type":"text","text":"maven install"}]},{"type":"text","text":" ,右键 "},{"type":"codeinline","content":[{"type":"text","text":"TinyIdServerApplication"}]},{"type":"text","text":" 启动服务, "},{"type":"codeinline","content":[{"type":"text","text":"Tinyid"}]},{"type":"text","text":" 分布式ID生成服务就搭建完毕了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"自定义 Tinyid 主键类型"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Tinyid"}]},{"type":"text","text":" 服务搭建完下边在项目中引入它,新建个 "},{"type":"codeinline","content":[{"type":"text","text":"tinyid_client.properties"}]},{"type":"text","text":" 文件其中添加 "},{"type":"codeinline","content":[{"type":"text","text":"tinyid.server"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"tinyid.token"}]},{"type":"text","text":" 属性,"},{"type":"codeinline","content":[{"type":"text","text":"token"}]},{"type":"text","text":" 为之前 SQL 预先插入的用户数据。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"# tinyid 分布式ID\n# 服务地址\ntinyid.server=127.0.0.1:9999\n# 业务token\ntinyid.token=0f673adf80504e2eaa552f5d791b644c"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代码中获取 ID更简单,只需一行代码,业务类型 "},{"type":"codeinline","content":[{"type":"text","text":"order"}]},{"type":"text","text":" 是之前 SQ L 预先插入的数据。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"Long id = TinyId.nextId(\"order\");"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们开始自定义 "},{"type":"codeinline","content":[{"type":"text","text":" Tinyid"}]},{"type":"text","text":" 主键生成类型的实现类 "},{"type":"codeinline","content":[{"type":"text","text":"TinyIdShardingKeyGenerator "}]},{"type":"text","text":" 。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n * @Author: xiaofu\n * @Description: 自定义主键生成器\n */\n@Component\npublic class TinyIdShardingKeyGenerator implements ShardingKeyGenerator {\n \n /**\n * 自定义的生成方案类型\n */\n @Override\n public String getType() {\n return \"tinyid\";\n }\n\n /**\n * 核心方法-生成主键ID\n */\n @Override\n public Comparable generateKey() {\n \n Long id = TinyId.nextId(\"order\");\n \n return id;\n }\n\n @Override\n public Properties getProperties() {\n return null;\n }\n\n @Override\n public void setProperties(Properties properties) {\n\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"并在配置文件中启用 "},{"type":"codeinline","content":[{"type":"text","text":" Tinyid"}]},{"type":"text","text":" 主键生成类型,到此配置完毕,赶紧测试一下。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"# 主键字段\nspring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id\n# 主键ID 生成方案\nspring.shardingsphere.sharding.tables.t_order.key-generator.type=tinyid"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"测试 Tinyid 主键"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"向数据库插入订单记录测试发现,主键ID字段 "},{"type":"codeinline","content":[{"type":"text","text":"order_id"}]},{"type":"text","text":" 已经为趋势递增的了, "},{"type":"codeinline","content":[{"type":"text","text":" Tinyid"}]},{"type":"text","text":" 服务成功接入,完美!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/26/26a5e37467173714105788c558b4cb36.png","alt":"在这里插入图片描述","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"总结"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"后续的八种生成方式大家参考 "},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s?_biz=MzAxNTM4NzAyNg==&mid=2247483785&idx=1&sn=8b828a8ae1701b810fe3969be536cb14&chksm=9b859174acf21862f0b95e0502a1a441c496a5488f5466b2e147d7bb9de072bde37c4db25d7a&token=113284294&lang=zhCN#rd","title":""},"content":[{"type":"text","text":"《9种 分布式ID生成方案》"}]},{"type":"text","text":" 按需接入吧,整体比较简单这里就不依次实现了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"案例 GitHub 地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/springboot-sharding-jdbc"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果对你有用,欢迎 在看、点赞、转发 ,您的认可是我最大的动力。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"整理了几百本各类技术电子书,送给小伙伴们。关注公号回复 "},{"type":"text","marks":[{"type":"strong"}],"text":"666"},{"type":"text","text":" 自行领取。和一些小伙伴们建了一个技术交流群,一起探讨技术、分享技术资料,旨在共同学习进步,如果感兴趣就加入我们吧!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}

版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://xie.infoq.cn/article/ba7af5c25c5dab951f4633212?utm_source=rss&utm_medium=article