[学习笔记]分布式系统
定义
建立在网络之上的软件系统。
特性
- 透明性
- 可扩展性:动态伸缩
- 可用性与可靠性
- 可用性是指系统在各种情况对外提供服务的能力
- 可靠性而是指计算结果正确、存储的数据不丢失
- 高性能:最常见的:高并发、低延迟
- 一致性:分布式系统为了提高可用性可靠性,一般会引入冗余(复制集)。那么如何保证这些节点上的状态一致,这就是分布式系统不得不面对的一致性问题。一致性有很多等级,一致性越强,对用户越友好,但会制约系统的可用性;一致性等级越低,用户就需要兼容数据不一致的情况,但系统的可用性、并发性很高很多。
结构
- 协调中心
- 供服务节点注册或拉取地址
- 中心化:以一组节点提供类似单点的服务,使用非常广泛,比如命令服务、分布式锁。
- z.B.chubby,zookeeper
- 负载均衡
- 多个节点提供同质的服务时,具体选择哪个节点来提供服务
- 对于每个请求涉及到的多个操作,需要保证原子性
- 消息队列
- 异步处理
- 应用解耦。
分布式ID
- 数据库分库分表时,每条记录的全局唯一ID
需要满足的条件
- 全局唯一
- 高性能:高可用低延时,ID生成响应要快
- 高可用:无限接近于100%的可用性
- 好接入
- 趋势递增
1. UUID
- 缺点
- 缺点:
- 无序,不具备趋势自增特性
- 没有具体的业务含义
- 长度过长的字符串,存储以及查询对MySQL的性能消耗较大
- MySQL官方明确建议主键要尽量越短越好
- 不可读
- 缺点:
2. 基于数据库主键自增
- 缺点
- 存在数据库宕机风险,无法扛高并发场景。
- 适用场景
- 小规模的,数据访问量小的业务场景。
- 无高并发场景,插入记录可控的场景。
3. 基于数据库集群模式
- 设置
初始值
和自增步长
- 缺点:
- 扩容不便。
- 单个数据库自身压力还是大,依旧无法满足高并发场景。
- 适用场景
- 数据量不大,数据库不需要扩容的场景。
4. 基于数据库的号段模式
- 当下分布式ID生成器的主流实现方式之一。
每次从数据库取出一个号段范围,具体的业务服务将本号段生成自增ID并加载到内存。
CREATE TABLE id_generator (
id int(10) NOT NULL,
max_id bigint(20) NOT NULL COMMENT '当前最大id',
step int(20) NOT NULL COMMENT '号段长度',
biz_type int(20) NOT NULL COMMENT '业务类型',
version int(20) NOT NULL COMMENT '版本号',
PRIMARY KEY (`id`)
)
等这批号段ID用完,再次向数据库申请新号段,对max_id字段做一次update操作:max_id=max_id+step,新的号段范围是(max_id,max_id+step]。
update id_generator set max_id = #{max_id+step}, version = version + 1 where version = #{version} and biz_type = XXX
- 缺点
- ID号码不够随机,完整的顺序递增可能带来安全问题。
- 仍存在数据库宕机风险。
- 可能存在分布式环境各节点同一时间争抢分配ID号段的情况,这可能导致并发问题而出现ID重复生成。
美团Leaf
双Buffer
将获取一个号段的方式优化成获取两个号段,在一个号段用完之后不用立马去更新号段,还有一个缓存号段备用,这样能够有效解决这种并发冲突问题。
而且采用双buffer的方式时,在当前号段消耗了10%的时候就去检查下一个号段有没有准备好,如果没有准备好就去更新下一个号段,当当前号段用完了就切换到下一个已经缓存好的号段去使用,同时在下一个号段消耗到10%的时候,又去检测下一个号段有没有准备好,如此往复。
建议号段长度为业务高峰期QPS的100倍,这样即使数据库宕机了,业务ID的生成也能够维持相当长的时间,而且可以有效的兼容偶尔的网络抖动等问题。
- 缺点
- 号段长度是固定的,业务量大时可能会频繁更新号段。
- 如果号段长度设置的过长,但凡缓存中有号段没有消耗完,其他节点重新获取的号段与之前相比可能跨度会很大。
动态调整号段长度
假设服务QPS(每秒查询率)为Q,号段长度为L,号段更新周期为T,那么Q\(\times\)T=L。
最开始L长度是固定的,随着Q的增长,T会越来越小。
但是本方案是希望T是固定的。如果L可以和Q正相关的话,T就可以趋近一个定值了。
所以本方案每次更新号段的时候,会根据上一次更新号段的周期T和长度step,来决定下一次的号段长度nextStep。
比如,
T < 15min,nextStep = step * 2
15min < T < 30min,nextStep = step
T > 30min,nextStep = step / 2
5. 基于Redis模式
- Redis的INCR命令能够将key中存储的数字值增一。
- 两种持久化方式
- RDB:会定时打一个快照进行持久化,假如连续自增但redis没及时持久化,而这会Redis挂掉了,重启Redis后会出现ID重复的情况。
- AOF:会对每条写命令进行持久化,即使Redis挂掉了也不会出现ID重复的情况,但由于incr命令的特殊性,会导致Redis重启恢复的数据时间过长。
- 缺点
- 强依赖于Redis,可能存在单点问题。
- 占用宽带,而且需要考虑网络延时等问题带来地性能冲击。
- 适用场景
- 对性能要求不是太高,而且规模较小业务较轻的场景,注意网络问题和单点压力问题。
6. 基于雪花算法(Snowflake)模式
- 生成的是64-bit的Long类型的ID。
- ID组成结构:正数位(1-bit)+ 时间戳(41-bit)+ 机器ID(5-bit)+ 数据中心(5-bit)+ 自增值(12-bit)
- 正数位:Java中long的最高位是符号位代表正负,正数是0,负数是1。一般生成ID都为正数,所以默认为0。
- 时间戳:毫秒级的时间,通常为当前时间戳 - 固定开始时间戳。(41-bit的时间戳可以\(\frac{2^{41}}{1000\times60\times60\times24\times365}=69\;years\))
- 工作机器id(workId):可以灵活配置,机房或者机器号组合都可以。
- 序列号:自增值,支持同一毫秒内同一个节点可以生成\(2^{12}=4096\)个ID