[学习笔记]分布式系统

定义

建立在网络之上的软件系统。

特性

  • 透明性
  • 可扩展性:动态伸缩
  • 可用性与可靠性
    • 可用性是指系统在各种情况对外提供服务的能力
    • 可靠性而是指计算结果正确、存储的数据不丢失
  • 高性能:最常见的:高并发、低延迟
  • 一致性:分布式系统为了提高可用性可靠性,一般会引入冗余(复制集)。那么如何保证这些节点上的状态一致,这就是分布式系统不得不面对的一致性问题。一致性有很多等级,一致性越强,对用户越友好,但会制约系统的可用性;一致性等级越低,用户就需要兼容数据不一致的情况,但系统的可用性、并发性很高很多。

结构

  • 协调中心
    • 供服务节点注册或拉取地址
    • 中心化:以一组节点提供类似单点的服务,使用非常广泛,比如命令服务、分布式锁。
    • 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)模式

image

  • 生成的是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

参考

posted @ 2022-01-30 01:43  Aireen_Ye  阅读(49)  评论(0编辑  收藏  举报
底部 顶部 留言板 归档 标签
Der Erfolg kommt nicht zu dir, du musst auf den Erfolg zugehen.