• 分布式ID生成


    要求
        1. 全局唯一
        2. 趋势递增
        3. 单调递增:保证上一个 ID 的大小一定小于下一个 ID
        4. 信息安全
    
    # 方案1: 数据库自增ID
    
    # 方案2: 美团leaf (依赖MySql)
    
    思想:预分配、批量发号
    
    - TxID段取值经验值: 服务高峰期发号QPS的600倍(10分钟)
    
    1. leaf服务节点: 用于生成TxID
    2. 每个leaf服务节点,在对外提供生成TxID之前: 先向Mysql申请能够分配的TxID段,将SELECT的结果(tag,max_id,step)保存在内存中
    3. 分配TxID: leaf服务节点,在内存中,对cur_txid++,直到到达cur_txid==max_id时,才重新向Mysql申请下一个TxID段
    
    缺点: 该方案,在向Mysql申请TxID段时,会存在P99波动,采用"双buffer"进行优化(即: 当TxID使用达到10%时,提前去Mysql申请TxID段)
    
    # 方案3: 雪花算法
    
    64个二进制位 = 符号位0 + 机器码位10 + 时间戳位41(ms) + 序列号位12
    QPS: 1ms单节点生成序列号数2^12=4096,1s单节点生成4096*1000=400万
    - 说明: 各个厂商都对雪花算法进行了改进,不同点,主要是在"时钟回拨"的解决方案,常见的解决方案
    1. 索尼
       1. 当发生时钟回拨时,采用sleep的方式,同步等待(Sonyflake),该方案存在明显的弊端,若时钟回拨的时间很长,会超时,无法对外提供服务
    2. 较完美的解决方案: 根据时钟回拨的时间长短,设置不同的
       1. <= 1s: 回拨时间较短
          1. 在内存中,保存最近1s的最大序列号,即 map[elapsedTime]max_sequence: { elapsedTime=生成TxID的时间戳,max_sequence=在这个elapsedTime时间戳,序列号的最大值}
          2. 查找到该elapsedTime对应的max_sequence,在max_sequence基础上+1
       2. 1s < 回拨时间 <= 5s
          1. 将请求转发到其他机器 (等该机器时钟回拨问题解决后,可以继续对外提供服务)
          2. 上报监控告警
       3. 时钟回拨 > 5s
          1. 该服务不对外提供服务,执行服务下线操作
    
    时钟回拨
    1.定义:所谓时间回拨,即当前得到的本地时间小于了之前获取的本地时间,感觉时针被“回拨”了。
    2.那么什么情况下可能出现时间回拨问题呢?人工设置、NTP网络时间同步
    
    索尼Sonyflake
    
    1. type Sonyflake struct {
    2. mutex *sync.Mutex // 线程安全,主要是在多协程的时候通过锁去保证id唯一性
    3. machineID uint16 // 实例机器id号
    4. startTime int64 // 算法起始时间的时间戳,以1ms为单位时间,是一个固定值,不会更新
    5. /* 关键变量 */
    6. elapsedTime int64 // 当前时间 - startTime
    7. sequence uint16 // 序列号
    8. }
    9. func (sf *Sonyflake) NextID() (string, error) {
    10. const maskSequence = uint16(1<1) // 掩码号
    11. sf.mutex.Lock() // 上锁
    12. defer sf.mutex.Unlock()
    13. current := time.Now() - sf.startTime // 当前时间 > startTime
    14. if sf.elapsedTime < current {
    15. sf.elapsedTime = current // 更新elapsedTime
    16. sf.sequence = 0 // 重置seq
    17. } else {
    18. // case1: elapsedTime=current,就意味着还在同一个单位时间内,生成多个TxID
    19. // case2: elapsedTime>current,就说明发生了时间回拨的问题
    20. sf.sequence = (sf.sequence + 1) & maskSequence // 序列号+1
    21. if sf.sequence == 0 { // 若在单位时间内达到了最大可生成序列号的限制
    22. sf.elapsedTime++ // 将elapsedTime设为下一个周期
    23. overtime := sf.elapsedTime - current // 计算时长,睡眠等待(如果回拨的时间过长,会sleep很久)
    24. time.Sleep(overtime)
    25. }
    26. }
    27. return sf.toID()
    28. }
    29. func (sf *Sonyflake) toID() (uint64, error) {
    30. if sf.elapsedTime >= 1<
    31. return 0, errors.New("over the time limit")
    32. }
    33. return uint64(sf.elapsedTime)<<(BitLenSequence+BitLenMachineID) | uint64(sf.sequence)<uint64(sf.machineID), nil
    34. }

    采用“历史时间”:

    这里是我们方案的核心,我们这里采用的不是实际时间,而是历史时间,在进程启动后,我们会将当前时间(实际处理采用了延迟10ms启动)作为该业务这台机器进程的时间戳中的起始时间字段。后续的自增是在序列号自增到最大值时候时间戳增1,而序列号重新归为0,算是将时间戳和序列号作为一个大值进行自增,只是初始化不同。

    序列号自增:

    每次有数据请求,直接对序列号增加即可,序列号从0增加到最大,到达最大时,时间戳字段增加1,其实是时间增加1毫秒,序列号重0计算。


    参考:彻底解决雪花算法时间回拨问题新方案

  • 相关阅读:
    redis安装及主从、哨兵配置
    Java_SpringBoot_MybatisPlus入门
    java基于ssm医院远程诊断预约管理系统
    几种在ARM MCU上控制流水灯的方法
    SpringBoot整合Groovy示例
    一文速学-Base64算法及编解码方法+Python代码
    小熊听书项目的测试
    PackagesNotFoundError:学习利用报错信息找到解决方法
    SpringBoot整合XXL-JOB详解
    jsp装修建材管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目
  • 原文地址:https://blog.csdn.net/weixin_36750623/article/details/126783055