第2-2-4章 常见组件与中台化-常用组件服务介绍-分布式ID-附Snowflake雪花算法的代码实现
2.3 分布式ID
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。
引入distributedid-client依赖:在项目pom.xml添加坐标
<dependencies>
<dependency>
<groupId>com.itheima.distributedid</groupId>
<artifactId>distributedid-client</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
分布式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
获取ID时,直接调用即可
Long id = 0L;
//从服务端获取自增型ID
id = DistributedId.autoincrementId("your service name"); //本地生成雪花算法ID
id = DistributedId.snowflake(); //从服务端获取雪花算法ID
id = DistributedId.snowflakeFromServer(); //使用号段模式获取单个ID
id = DistributedId.segment();
数据库脚本
/*
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;
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算法
* <p>
* 协议格式 1: 41位时间戳 2:5位数据中心标识 3:5位机器标识 4:12位序列号
* <p>
* 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());
// }
//}
}
第2-2-4章 常见组件与中台化-常用组件服务介绍-分布式ID-附Snowflake雪花算法的代码实现的更多相关文章
- C#使用Xamarin开发可移植移动应用终章(11.获取设备信息与常用组件,开源一个可开发模版.)
前言 系列目录 C#使用Xamarin开发可移植移动应用目录 源码地址:https://github.com/l2999019/DemoApp 可以Star一下,随意 - - 说点什么.. 本系列,终 ...
- 蘑菇街Android组件与插件化
插件化的基石 -- apk动态加载 随着我街业务的蓬勃发展,产品和运营随时上新功能新活动的需求越来越强烈,经常可以听到"有个功能我想周x上,行不行".行么?当然是不行啦,上新功能得 ...
- ASP.NET Core 中文文档 第四章 MVC(3.9)视图组件
作者: Rick Anderson 翻译: 娄宇(Lyrics) 校对: 高嵩 章节: 介绍视图组件 创建视图组件 调用视图组件 演练:创建一个简单的视图组件 附加的资源 查看或下载示例代码 介绍视图 ...
- 第 11 章 进度条媒体对象和 Well 组件
学习要点: 1.Well 组件 2.进度条组件 3.媒体对象组件 主讲教师:李炎恢 本节课我们主要学习一下 Bootstrap 的三个组件功能:Well 组件.进度条组件.媒体对象组件. 一.Well ...
- 谈谈我对前端组件化中“组件”的理解,顺带写个Vue与React的demo
前言 前端已经过了单兵作战的时代了,现在一个稍微复杂一点的项目都需要几个人协同开发,一个战略级别的APP的话分工会更细,比如携程: 携程app = 机票频道 + 酒店频道 + 旅游频道 + ..... ...
- React Native常用组件Image使用
前言 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所 ...
- web 前端常用组件【06】Upload 控件
因为有万恶的IE存在,所以当Web项目初始化并进入开发阶段时. 如果是项目经理,需要知道客户将会用什么浏览器来访问系统. 明确知道限定浏览器的情况下,你才能从容的让手下的封装必要的前端组件. 本篇文章 ...
- SWT常用组件(转)
转载自:http://www.cnblogs.com/happyPawpaw/archive/2012/10/19/2730478.html 1按钮组件(Button) (1)Button组件常用样式 ...
- Android编程: fragment组件、菜单和Intent组件
学习内容:fragment组件.菜单和Intent组件 ====fragment组件====1.fragment是一种自我容纳,模块化的,嵌入在一个Activity里面的视图组件 可以在运行时动 ...
随机推荐
- springboot配置(yami配置文件,JSR303数据校验,多环境配置)
yami配置文件 YAML是 "YAML Ain't a Markup Language" (YAML不是一种标记语言)的递归缩写.在开发的这种语言时,YAML 的意思其实是:&q ...
- kubeadm部署k8s v1.19.4版本集群
1. 准备2台2c4g虚机 配置地址192.168.198.144,192.168.198.146,一台作为master,一台作为node 2. 部署环境准备,每一台虚机都需要操作 # 关闭防火墙sy ...
- 第四十四篇:Git分支(关键知识点)
好家伙, GIT分支 分支就像是平行宇宙,两个平行宇宙自己平行,不相干扰,平安无事, 某一天它想不开,合并了.然后就变成了我写这篇博客的动机了. 1.关于Git分支中常用的指令 列出所有分支 git ...
- Currtid 函数与性能问题
对于Oracle ,一条tuple 的 rowid正常是不会变化的(引发row movement的操作除外,如:跨分区迁移update,表收缩),因此,应用设计上可以方便的使用rowid,加快访问速度 ...
- 华南理工大学 Python第3章课后小测-2
1.(单选)给出如下代码 s = 'Hello scut' print(s[4::-1]) 上述代码的输出结果是(本题分数:2)A) HelloB) olleHC) scutD) tucs您的答案:B ...
- Python 第五次实验
[1] (程序设计)编写程序,将二维列表数据写入 CSV文件(命名为"out.csv"),用逗号隔开.二维列表如下:[['Name','Age','Gender'], ['Bob' ...
- 输入法词库解析(五)极点码表.mb
详细代码:https://github.com/cxcn/dtool 前言 mb 是极点五笔的码表格式. 解析 偏移量 描述 0x00 版本信息 0x1B 码表介绍 0x11F 所用到的按键数 0x1 ...
- day01-项目开发流程
多用户即时通讯系统01 1.项目开发流程 2.需求分析 用户登录 拉取在线用户列表 无异常退出(包括客户端和服务端) 私聊 群聊 发文件 服务器推送新闻/广播 3.设计阶段 3.1界面设计 用户登录: ...
- 基于electron+vue+element构建项目模板之【自定义标题栏&右键菜单项篇】
1.概述 开发平台OS:windows 开发平台IDE:vs code 本篇章将介绍自定义标题栏和右键菜单项,基于electron现有版本安全性的建议,此次的改造中主进程和渲染进程彼此语境隔离,通过预 ...
- Docker 部署 RocketMQ Dledger 集群模式( 版本v4.7.0)
文章转载自:http://www.mydlq.club/article/97/ 系统环境: 系统版本:CentOS 7.8 RocketMQ 版本:4.7.0 Docker 版本:19.03.13 一 ...