• 第2-2-4章 常见组件与中台化-常用组件服务介绍-分布式ID-附Snowflake雪花算法的代码实现


    2.3 分布式ID

    全套代码及资料全部完整提供,点此处下载
    https://download.csdn.net/download/weixin_42208775/86977792

    2.3.1 功能概述

    ID,全称Identifier,中文翻译为标识符,是用来唯一标识对象或记录的符号。比如我们每个人都有自己的身份证号,这个就是我们的标识符,有了这个唯一标识,就能快速识别出每一个人。

    **在计算机世界里,复杂的分布式系统中,经常需要对大量的数据、消息、HTTP 请求等进行唯一标识。**比如对于分微服务架构的系统中,服务间相互调用需要唯一标识,幂等处理,调用链路分析,日志追踪的时候都需要使用这个唯一标识,此时我们的系统就迫切的需要一个全局唯一的ID。

    另外随着社会的发展,各种金融、电商、支付、等系统中产生的数据越来越多,对数据库进行分库分表是比较常见的,而分库后则需要有一个唯一ID来标识一条数据或消息,单个数据库的自增ID显然不能满足需求,此时也会需要一个能够生成全局唯一ID的系统。

    工程结构:

    在这里插入图片描述

    2.3.2 应用场景

    1、全局唯一

    这个最简单,就是说不能出现重复的ID,既然是唯一标识,这是最基本的要求。比如采用UUID.randomUUID()的方式产生唯一且不重复的分布式主键。最终生成一个字符串类型的主键。缺点是生成的主键无序。

    2、趋势递增

    先来了解下什么是趋势递增?

    简单说就是在一段时间内,生成的ID是递增的趋势,而不强求下一个ID必须大于前一ID。例如在一段时间内生成的ID在【0,1000】之间,过段时间生成的ID在【1000,2000】之间。

    为什么要趋势递增?

    目前大部分的互联网公司使用了开源的MySQL数据库,存储引擎选择InnoDB。MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS数据库使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键,这样在插入新的数据时B-tree的结构不会时常被打乱重塑,能有效的提高存取效率。

    3、单调递增

    通俗的说就是下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。

    4、信息安全

    如果ID是连续递增的,那么恶意用户可以根据当前ID推测出下一个ID,爬取系统中数据的工作就非常容易实现,直接按照顺序访问指定URL即可;如果是订单号就更加危险,竞争对手可以直接知道系统一天的总订单量。所以在一些应用场景下,会需要ID无规则、不规则,切不易被破解。

    5、雪花算法SNOWFLAKE

    雪花算法,能够保证不同进程主键的不重复性,相同进程主键的有序性。二进制形式包含4部分,从高位到低位分表为:1bit符号位、41bit时间戳位、10bit工作进程位以及12bit序列号位。

    • 符号位(1bit)

    预留的符号位,恒为零。

    • 时间戳位(41bit)

    41位的时间戳可以容纳的毫秒数是2的41次幂,一年所使用的毫秒数是:365 * 24 * 60 * 60 * 1000 Math.pow(2, 41) / (365 * 24 * 60 * 60 * 1000L) = 69.73年不重复;

    • 工作进程位(10bit)

    该标志在Java进程内是唯一的,如果是分布式应用部署应保证每个工作进程的id是不同的。该值默认为0,可通过属性设置。

    • 序列号位(12bit)

    该序列是用来在同一个毫秒内生成不同的ID。如果在这个毫秒内生成的数量超过4096(2的12次幂),那么生成器会等待到下个毫秒继续生成。

    在这里插入图片描述

    优点:

    • 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。

    • 不依赖第三方组件,稳定性高,生成ID的性能也非常高。

    • 可以根据自身业务特性分配bit位,非常灵活

      缺点:

      强依赖机器时钟,如果机器上时钟回拨,会导致发号重复。

    2.3.3 使用说明

    分布式ID生成系统部署完成后,第三方系统接入即可直接获取ID。

    1. 引入distributedid-client依赖:在项目pom.xml添加坐标

      <dependencies>
           <dependency>
                <groupId>com.itheima.distributedidgroupId>
                <artifactId>distributedid-clientartifactId>
                <version>1.0-SNAPSHOTversion>
           dependency>
      dependencies>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    2. 分布式ID生成系统客户端配置,在项目resources目录下编辑distributedid_client.properties

      #服务器地址
      distributedid.server=211.103.136.244:7315
      #部署多个的话,可自行添加
      #distributedid.server=211.103.136.244:7315,ip2:port,...
      #超时时间
      distributedid.readTimeout=5000
      distributedid.connectTimeout=5000
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    3. 获取ID时,直接调用即可

      Long id = 0L;
      //从服务端获取自增型ID
      id = DistributedId.autoincrementId("your service name");
                      
      //本地生成雪花算法ID
      id = DistributedId.snowflake();
      
      //从服务端获取雪花算法ID
      id = DistributedId.snowflakeFromServer();
      
      //使用号段模式获取单个ID
      id = DistributedId.segment();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    4. 数据库脚本

      /*
       Navicat Premium Data Transfer
      
       Source Server         : 本地MySQL数据库
       Source Server Type    : MySQL
       Source Server Version : 50728
       Source Host           : localhost:3306
       Source Schema         : distributedid
      
       Target Server Type    : MySQL
       Target Server Version : 50728
       File Encoding         : 65001
       
      */
      
      SET NAMES utf8mb4;
      SET FOREIGN_KEY_CHECKS = 0;
      
      -- ----------------------------
      -- Table structure for segment_id_info
      -- ----------------------------
      DROP TABLE IF EXISTS `segment_id_info`;
      CREATE TABLE `segment_id_info`  (
        `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键',
        `biz_type` varchar(63) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '业务类型,唯一',
        `begin_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '开始id,仅记录初始值,无其他含义。初始化时begin_id和max_id应相同',
        `max_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '当前最大id',
        `step` int(11) NULL DEFAULT 0 COMMENT '步长',
        `delta` int(11) NOT NULL DEFAULT 1 COMMENT '每次id增量',
        `remainder` int(11) NOT NULL DEFAULT 0 COMMENT '余数',
        `create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
        `update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '更新时间',
        `version` bigint(20) NOT NULL DEFAULT 0 COMMENT '版本号',
        PRIMARY KEY (`id`) USING BTREE,
        UNIQUE INDEX `uniq_biz_type`(`biz_type`) USING BTREE
      ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '号段ID信息表' ROW_FORMAT = Dynamic;
      
      -- ----------------------------
      -- Table structure for sequence_id
      -- ----------------------------
      DROP TABLE IF EXISTS `sequence_id`;
      CREATE TABLE `sequence_id`  (
        `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键',
        `biz_type` char(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '业务类型,唯一',
        PRIMARY KEY (`id`) USING BTREE,
        UNIQUE INDEX `biz_type`(`biz_type`) USING BTREE
      ) ENGINE = InnoDB AUTO_INCREMENT = 62 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
      
      SET FOREIGN_KEY_CHECKS = 1;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49

    2.3.4 项目截图

    • 后端代码

    在这里插入图片描述

    • swagger页面

    在这里插入图片描述

    2.3.5 Snowflake雪花算法的代码实现

    package com.itheima.distributedid.core;
    
    import com.itheima.distributedid.core.domain.DistributedIdException;
    
    import java.lang.management.ManagementFactory;
    import java.net.InetAddress;
    import java.net.NetworkInterface;
    
    /**
     * Twitter的Snowflake算法
     * 

    * 协议格式 1: 41位时间戳 2:5位数据中心标识 3:5位机器标识 4:12位序列号 *

    * 1111111111111111111111111111111 11111 11111 111111111111 */ public class Snowflake { //起始时间戳,可以修改为服务器第一次启动的时间 //一旦服务已经开始使用,起始时间戳就不能改变了,理论上可以使用69年 private final static long START_TIME = 1484754361114L; /** * 每一个部分占用的位数 */ private final static long SEQUENCE_BIT = 12;//序列号占用的位数 private final static long MACHINE_BIT = 5;//序机器标识 占用的位数 private final static long DATA_CENTER_BIT = 5;//数据中心标识占用的位数 /** * 每一个部分的最大值 11111111111111111 1111111100000 000000000011111 */ private final static long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_BIT); private final static long MAX_MACHINE_ID = ~(-1L << MACHINE_BIT); private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT); /** * 每一部分向左位移数 1111111111111111111111111111111 11111 11111 111111111111 */ private final static long MACHINE_LEFT = SEQUENCE_BIT; private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT; private long dataCenterId;//数据中心ID private long machineId;//数据中心ID private long sequence = 0L;//数据中心ID private long lastTimestamp = -1L;//数据中心ID /** * 分布式部署的时候,数据节点标识和机器标识作为联合键,必须唯一的 * * @param dataCenterId 数据中心标识ID * @param machineId 机器标识ID */ public Snowflake(long dataCenterId, long machineId) { if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) { throw new DistributedIdException("数据中心ID不合法"); } if (machineId > MAX_MACHINE_ID || machineId < 0) { throw new DistributedIdException("机器标识ID不合法"); } this.dataCenterId = dataCenterId; this.machineId = machineId; } /** * 获取下一个ID * * @return */ public synchronized long nextId() { long currentTmestamp = getNowTimestamp(); if (currentTmestamp < lastTimestamp) { throw new RuntimeException("时钟错误,拒绝生成ID"); } if (currentTmestamp == lastTimestamp) { //相同毫秒内,序列号自增 sequence = (sequence + 1) & MAX_SEQUENCE; //同一毫秒的序列号已经达到最大 if (sequence == 0L) { currentTmestamp = getNexMill(); } } else { //不同毫秒内,序列号置为0 sequence = 0L; } lastTimestamp = currentTmestamp; return (currentTmestamp - START_TIME) << TIMESTAMP_LEFT //时间戳的部分 | dataCenterId << DATA_CENTER_LEFT //数据中心的部分 | machineId << MACHINE_LEFT //机器标识的部分 | sequence; //序列号的部分 } /** * 保证获取到的毫秒值是在最后一次分发ID的毫秒值之后lastTimestamp * 当某一个毫秒,序列号用完了之后,等待到下一个毫秒,在进行序列号的使用 * * @return */ private long getNexMill() { long timestamp = this.getNowTimestamp(); //不断的遍历,直到获取到lastTimestamp下一个毫秒值 while (timestamp <= lastTimestamp) { //进行时间回拨 timestamp = this.getNowTimestamp(); } return timestamp; } //获取当前毫秒值 private long getNowTimestamp() { return System.currentTimeMillis(); } /** * 使用当前计算机的MAC生成数据中心标识ID * * @param maxDataCenterId * @return */ private static long getDataCenterId(long maxDataCenterId) { long id = 0L; try { InetAddress ip = InetAddress.getLocalHost(); NetworkInterface network = NetworkInterface.getByInetAddress(ip); if (network == null) { id = 1L; } else { byte[] mac = network.getHardwareAddress(); if (mac != null) { id = ((0x000000FF & (long) mac[mac.length - 1]) | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6; id = id % (maxDataCenterId + 1); } } } catch (Exception e) { e.printStackTrace(); } return id; } //根据当前计算机的进程PID生成机器识别ID private static long getMachineId(long dataCenterId, long maxMachineId) { StringBuilder sb = new StringBuilder(); sb.append(dataCenterId); //获取JVM进程的PID String name = ManagementFactory.getRuntimeMXBean().getName(); if (name != null) { sb.append(name.split("@")[0]); } /** * MAC+PID 的hashcode 获取16个低位 */ int id = sb.toString().hashCode() & 0xffff; return id % (maxMachineId + 1); } public Snowflake() { dataCenterId = getDataCenterId(MAX_DATA_CENTER_ID); machineId = getMachineId(dataCenterId, MAX_MACHINE_ID); } //public static void main(String[] args) { // //指定数据中心和机器识别id // Snowflake snowflake = new Snowflake(2, 3); // System.out.println("指定数据中心和机器识别ID来生成ID"); // for (int i = 0; i < 10; i++) { // System.out.println(snowflake.nextId()); // } // // //默认快速使用方式 // snowflake = new Snowflake(); // System.out.println("快速使用方式来生成ID"); // for (int i = 0; i < 10; i++) { // System.out.println(snowflake.nextId()); // } //} }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
  • 相关阅读:
    大型医院容灾备份平台建设与应用实践
    小程序毕设作品之微信美食菜谱小程序毕业设计成品(4)开题报告
    智能门锁迈入“长尾”时代
    uniapp 对video视频组件嵌套倍速按钮
    【word日常操作】word里面表格已经设置了重复标题行,但是显示无效怎么办
    嵌入式Linux--进程间通讯--消息队列
    【PCIE703】基于华为海思ARM的8路SDI高清视频图像处理平台(KU060+HI3531D)
    【Linux操作系统】——网络配置与SSH远程
    Hdfs梳理
    torch_vision(一):数据增强和转换模块torchvision.transforms
  • 原文地址:https://blog.csdn.net/weixin_42208775/article/details/127833687