- UUID
- 数据库自增序列
- 雪花算法
- 基于 redis 、 mongodb 、 zk等中间件生成
- Leaf-segment
1、UUID
1 ,当前日期和时间 时间戳
2 ,时钟序列。 计数器
3 ,全局唯一的 IEEE 机器识别号,如果有网卡,从网卡 MAC 地址获得,没有网卡以其他方式获得。
优点:代码简单,性能好(本地生成,没有网络消耗),保证唯一(相对而言,重复概率极低可以忽略)
缺点:
每次生成的 ID 都是无序的,而且不是全数字,且无法保证趋势递增。
UUID 生成的是字符串,字符串存储性能差,查询效率慢,写的时候由于不能产生顺序的append 操作,需要进 行insert操作,导致频繁的页分裂,这种操作在记录占用空间比较大的情况下,性能下降比较大,还会增加读 取磁盘次数UUID 长度过长,不适用于存储,耗费数据库性能。
ID 无一定业务含义,可读性差。
有信息安全问题,有可能泄露 mac 地址。
2、数据库自增序列
单机模式:
优点:
实现简单,依靠数据库即可,成本小。
ID 数字化,单调自增,满足数据库存储和查询性能。
具有一定的业务可读性。(结合业务 code )
缺点:
强依赖 DB ,存在单点问题,如果数据库宕机,则业务不可用。
DB 生成 ID 性能有限,单点数据库压力大,无法扛高并发场景。
信息安全问题,比如暴露订单量, url 查询改一下 id 查到别人的订单。
数据库高可用:多主模式做负载,基于序列的起始值和步长设置,不同的初始值,相同的步长,步长大于节点数。
优点:
解决了 ID 生成的单点问题,同时平衡了负载。
缺点:
系统扩容困难:系统定义好步长之后,增加机器之后调整步长困难。
数据库压力大:每次获取一个 ID 都必须读写一次数据库。
主从同步的时候:电商下单 -> 支付 insert master db select数据 ,因为数据同步延迟导致查不到这个数 据。加 cache( 不是最好的解决方式 ) 数据要求比较严谨的话查 master 主库。
3、雪花算法
生成一个64bit 的整性数字。
第一位符号位固定为 0 , 41 位时间戳, 10 位 workId , 12位序列号,位数可以有不同实现。
雪花算法是一种生成分布式全局唯一 ID 的算法,它会得到一个 64 位长度的 long 类型数据。
其中这 64 位的数据,由 4 个部分组成。
第一个 bit 位是符号位,因为 id 不会是负数,所以它一般是 0;
接着用 41 个 bit 位来表示毫秒单位的时间戳;
再用 10 个 bit 位来表示工作机器 id;
最后 12 个 bit 位表示递增的序列号;
把这 64 个 bit 位拼接成一个 long 类型的数字,就是雪花算法的实现。
优点:
- 每个毫秒值包含的ID值很多,不够可以变动位数来增加,性能佳(依赖workId的实现)。
- 时间戳值在高位,中间是固定的机器码,自增的序列在低位,整个ID是趋势递增的。
- 能够根据业务场景数据库节点布置灵活挑战bit位划分,灵活度高。
缺点:
- 强依赖于机器时钟,如果时钟回拨,会导致重复的ID生成,所以一般基于此的算法发现时钟回拨,都会抛异常处理,阻止ID生成,这可能导致服务不可用。
4、基于redis、mongodb、zk等中间件生成
5、Leaf-segment
采用每次获取一个 ID区间段的方式来解决,区间段用完之后再去数据库获取新的号段,这样一来可以大大减轻数据库的压力。
核心字段: biz_tag , max_id , step。
biz_tag 用来区分业务, max_id 表示该 biz_tag 目前所被分配的 ID 号段的最大值, step表示每次分配的号段长度,原来每次获取 ID 都要访问数据库,现在只需要把 Step 设置的足够合理如 1000,那么现在可以在 1000 个 ID 用完之后再去访问数据库。
优点:
扩张灵活,性能强能够撑起大部分业务场景。
ID 号码是趋势递增的,满足数据库存储和查询性能要求。
可用性高,即使 ID 生成服务器不可用,也能够使得业务在短时间内可用,为排查问题争取时间。
缺点:
可能存在多个节点同时请求 ID 区间的情况,依赖 DB
双buffer :将获取一个号段的方式优化成获取两个号段,在一个号段用完之后不用立马去更新号段,还有一个缓存号段备用,这样能够有效解决这种冲突问题,而且采用双 buffer的方式,在当前号段消耗了10 % 的时候就去检查下一个号段有没有准备好,如果没有准备好就去更新下一个号段,当当前号段用完了就切换到下一个已经缓存好的号段去使用,同时在下一个号段消耗到 10 % 的时候,又去检测下一个号段有没有准备好,如此往复。
优点:
基于 JVM 存储双 buffer 的号段,减少了数据库查询,减少了网络依赖,效率更高。
缺点:
segment号段长度是固定的,业务量大时可能会频繁更新号段,因为原本分配的号段会一下用完如果号段长度设置的过长,但凡缓存中有号段没有消耗完,其他节点重新获取的号段与之前相比可能跨度会很大,动态调整 Step。
二、如何实现分库分表
将原本存储于单个数据库上的数据拆分到多个数据库,把原来存储在单张数据表的数据拆分到多张数据表中,实现数据切分,从而提升数据库操作性能。
分库分表的实现可以分为两种方式:垂直切分和水平 切分。
水平:将数据分散到多张表,涉及分区键,
- 分库:每个库结构一样,数据不一样,没有交集。库多了可以缓解io和cpu压力
- 分表:每个表结构一样,数据不一样,没有交集。表数量减少可以提高sql执行效率、减轻cpu压力
垂直:将字段拆分为多张表,需要一定的重构
- 分库:每个库结构、数据都不一样,所有库的并集为全量数据
- 分表:每个表结构、数据不一样,至少有一列交集,用于关联数据,所有表的并集为全量数据
Master slave
查多于写的场景,写在主库,查询在从库,复制Binlog日志。
代理层实现读写分离:
atlas:Db1、db2、proxy代理 配置文件 proxy配置主从关系 设置端口1234 通过代理层进行查询从库
解决主库读的压力
强制路由 主从同步走网络,极端情况下出现延迟,从库:I/O线程 进行binlog + DB线程
导致数据不能及时同步,从库查不到主库已有的数据,同步不及时,解决?强制路由
在查询语句前加:
/*master*/ select * from t1 where a =1;
垂直分库:
垂直分表:
User表
Tid | Name | Password | Sex | Tel | Wx | Hobby | ||
1 | 张三 | 123 | 1 | 2 | 3 | 4 | 5 | 6 |
2 | 李四 | 456 | 1 | 3 | 5 | 6 | 7 | 8 |
User_base
Tid | Name | Password |
1 | 张三 | 123 |
2 | 李四 | 456 |
User_info
Tid | Sex | Tel | Wx | Hobby | ||
1 | 1 | 2 | 3 | 4 | 5 | 6 |
2 | 1 | 3 | 5 | 6 | 7 | 8 |
垂直拆分:每个库表的结构不一样、每个库表的数据都至少有一列、每个库表的并集是全部数据。
优点:拆分业务清晰(专库专用)、数据维护简单,按业务不同放到不同机器上
缺点:如果单表的数据量大,读写压力大;
受某种业务场景决定、限制,一个业务影响其他数据库的瓶颈,性能问题,如双11,商品、订单库压力大,用户库压力小;
部分业务无法关联join,只能通过程序调用,开发复杂。
数据分布不均匀,有的库数据量大。
水平分库
水平分表
User表
Tid | Name | Password | Sex | Tel | Wx | Hobby | ||
1 | 张三 | 123 | 1 | 2 | 3 | 4 | 5 | 6 |
2 | 李四 | 456 | 1 | 3 | 5 | 6 | 7 | 8 |
User表1
Tid | Name | Password | Sex | Tel | Wx | Hobby | ||
1 | 张三 | 123 | 1 | 2 | 3 | 4 | 5 | 6 |
User表2
Tid | Name | Password | Sex | Tel | Wx | Hobby | ||
2 | 李四 | 456 | 1 | 3 | 5 | 6 | 7 | 8 |
水平拆分:每个库表的结构一样、每个库表的数据不一样、每个库表的并集是全量数据、数据均分。
优点:单库的数据量小,有助于性能提升;
切分的表的结构相同,程序改造较少;
提高系统稳定性和负载能力
缺点: 数据扩容困难
拆分规则很难抽象出来
分片事务的一致性 部分业务无法关联join 只能通过程序调用
需要分布式事务
跨库查询问题?怎样确认一条数据在哪个库
分布式全局唯一ID?每个库中的id一样咋办
分库分表的中间件:
Proxy代理层:mycat、atlas、mysql-proxy
jdbc增强 应用层:shardingshpere、TDDL shardingshpere阿帕奇的