【从零开始学习Redis | 第七篇】利用Redis构造全局唯一ID(含其他构造方法)

目录

前言:

什么是全局唯一ID? 

尝试构造全局唯一ID: 

其他构造全局唯一ID的方法

1.基于数据库自增构造全局唯一ID:

2.基于UUID构造全局唯一ID:

3.基于雪花算法构造全局唯一ID:

总结:


 

前言:

        在各种实际业务中,全局唯一ID是一个重要的存在,它用来标识用户的特定服务,方便用户在后续基于这个ID来进行各种服务。而如何构造全局唯一ID也是一个比较重要的知识点。因此今天来介绍一下如何基于Redis构造全局唯一ID。

什么是全局唯一ID? 

        全局唯一ID(Global Unique Identifier,简称GUID)是在计算机系统中用于唯一标识实体或对象的标识符。它通常由一个128位的数字字符串组成,采用特定的算法生成,以确保在相同的算法和生成器设置下几乎不会重复。


让我们回到业务中:

每个店铺都可下发自己店铺的优惠卷,当用户抢购的时候,就会生成订单到订单表中,并且返回订单号给用户。但是如果只是使用简单的数据库自增就会出现问题:

        1.少量数据下,采用数据库自增的方式,会泄漏信息给用户。用户可以根据订单ID推测出优惠卷的订单数,可能会引发恶意行为。

        2.大量数据下,如果所有的订单都在一个张表中,在进行SQL查询的时候效率会大大降低,并且如果我们进行了分表,那么多个订单表中间的数据库自增是隔离的,并不能保证多表下的ID唯一


        那么我们目前可以知道:如果要追求ID的唯一性,那么就应该避免在数据库中进行ID的构造。基于这种情况,那么我们就把ID的构造放到Redis中进行。

尝试构造全局唯一ID: 

而为了增加ID的安全性,我们也不直接使用Redis的自增数值,而是再拼接一些其他的信息:

由图可看得,我们设计的ID一共分为三部分:

1.符号位:永远为0,标识我们的ID是一个整数。

2.时间戳:从自定义时间开始,按秒计算。那么32位我们大约可以使用60多年。

3.序列号:同一时间内下单进行自增。在时间戳相等的形况下的区分不同的订单。

代码实现:

    public long nextId(String keyPrefix) {
        //1.生成时间戳
        LocalDateTime now = LocalDateTime.now();
        long nowhSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowhSecond - BEGIN_TIMESTAMP;
        //2.生成序列号
        //1.获取日期,精确到天
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        Long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
      
        return (timestamp << COUNT_BITS) | count;
    }

 在这里我们唯一需要讲的就是返回结果中的(timestamp << COUNT_BITS) | count

                其实是他就是一个拼接序列号的过程,只不过相比较于符号运算来讲,使用位运算的效率更高。

其他构造全局唯一ID的方法

1.基于数据库自增构造全局唯一ID:

 基于数据库自增构造ID,之前我们讲了主要的难点是:分表之后无法统一构造自增唯一ID,多个订单表在构造ID的时候可能会出现重复。

那么其实解决方案很简单:既然多个表在构造ID的时候会出现重复问题,那么我们就不要在订单表中构造ID了,创建一个订单ID表去专门维护ID

比如我们可以这样设计一个订单ID表:

create table order_test.order_id
(
    id   int auto_increment
        primary key,
    name varchar(20) null,
    constraint name
        unique (name)
);

那么我们就得到了这样一张表:

在查询的时候,我们使用这样一条语句:

begin ;
replace into order_id (name) values ("order_id");
SELECT last_insert_id();
COMMIT ;

 这样我们基于name的唯一性,就做到了对id的自增:

 但是这种创建一张表去维护订单ID的方式仍然是有问题的:高并发场景下,万一这张表挂了怎么办

所以为了优化,我们还可以采取多表的思想:

在新思想中,我们让一个表维护订单id为偶数,一个表维护订单id为奇数。这样的话,我们就是实现了减轻单表压力。而且这种思想是可以不断的改进的,我们可以通过让表维护不同类型的数字来不断的拆表,减轻单表压力

但是这种方法得到的订单id,他不一定是逐个递增的,只能说是整体呈现递增趋势

而且这种方式如果要抵抗高并发的话,就要不断的去加数据库,对维护数字进行分类。因此这种方式其实缺点还是比较明显的。

但其实基于数据库构造全局唯一ID是有成熟的方案的:美团的LEAF数据库方案,原文链接我也留在这里:

Leaf——美团点评分布式ID生成系统 - 美团技术团队 (meituan.com)icon-default.png?t=N7T8https://tech.meituan.com/2017/04/21/mt-leaf.html        LEAF数据库方案简而言之就一句话:批量获取ID进行处理。在上文我们简单的对数据库进行优化的时候,优化问题基本都来源于高并发下数据库高频的读写操作。而LEAF数据库方案也是针对这个方面进行优化的

我们可以把图中的leaf简单的理解为是一个生成全局唯一ID的服务。那么整个LEAF数据库的思想就是:leaf服务提前就拿好一批号端,例如从0-1000。那么我在生成唯一ID的时候,压力就从数据库转到了Leaf这个服务里面。

优点:

        1.leaf只是一个简单的web服务,方便进行扩展

        2.ID号也满足趋势递增的要求

        3.容灾性高,由于生成唯一ID的是leaf服务,而且内部有号段缓存,因此即使数据库挂了,短时间内也可以正常对外提供服务

缺点:

        当所有的leaf用完自己的号段之后,就会向数据库再次请求号段,此时leaf服务是不可用的。而如果此时有大量的请求leaf服务,就会引发一段尖刺。

解决方案:

        我们并不会等到号段全部用完之后再去请求新的号段。美团给出的技术方案是当号段消费到某个点时就异步的把下一个号段加载到内存中。而不需要等到号段用尽的时候才去更新号段。

        一开始先用A号段,等 A号段消耗10%的时候,就向数据库请求新号段。之后当前号段消耗完之后就可以进行快速的切换。如此循环往复。

2.基于UUID构造全局唯一ID

虽然基于UUID可以保证构造ID的唯一性,但是UUID是随机生成的数字+字母。这使得UUID构造出的ID不具备递增性,在做数据库索引的时候效率比较慢。

而且UUID也存在安全性问题:UUID有一版是基于MAC地址来构造唯一标识的,可能会有泄漏MAC地址的风险

import java.util.UUID;

public class UUIDExample {
    public static void main(String[] args) {
        // 生成随机的UUID
        UUID uuid = UUID.randomUUID();
        System.out.println("随机生成的UUID: " + uuid.toString());

        // 根据字符串生成UUID
        String uuidString = "38400000-8cf0-11bd-b23e-10b96e4ef00d";
        UUID fromString = UUID.fromString(uuidString);
        System.out.println("从字符串生成的UUID: " + fromString.toString());
    }
}

3.基于雪花算法构造全局唯一ID

六十四位雪花算法构成:

其实可以看出,雪花算法跟我们上文提到的基于Redis生成全局唯一ID的思路是一样的。因此这里不做赘述。

而基于雪花算法,最常问的一个面试问题就是:如何解决时间回拨问题

其实就是说:如果时间戳回拨了,我们要如何处理?因为时间戳回拨之后,生成的id就有可能和以前的重合,那么我们要如何进行处理呢?

大部分的开源版本的雪花算法面对时间回拨问题采用的是抛异常的处理方法,其实我感觉这属于是没处理。

比较好的处理方法是根据回拨时间的长短来个性化处理方案:

        1.回拨时间很短(<=100ms):直接睡眠当前线程,等到时间戳加载正常之后再生成ID

        2.回拨时间适中(>100ms<=1s):寻找当前回拨时间毫秒内的最大id,直接沿着那个id++就可以。

        3.回拨时间较长(>1s<=5s):创建多个雪花算法服务,当前使用的雪花算法服务时间戳发生回拨就换一个。

        4.回拨时间很长(>5s):直接下线服务,人工介入手动调试时间戳。

除此之外,我们还可以提前就预留好一部分的分布式ID,在时间戳回拨之后,我们使用这部分预留的分布式ID,直至时间戳恢复正常。

总结:

当我们在设计分布式系统时,唯一ID的生成是一个非常重要的问题。为了保证分布式环境下ID的唯一性和无序性,我们可以采用雪花算法、UUID、数据库自增等方式生成唯一ID。

其中,雪花算法是目前应用最广泛的分布式ID生成算法之一。它通过使用时间戳、机器ID和序列号来生成64位的唯一ID,可以在多个节点上生成ID而不会重复。

除了ID的生成算法外,还需要考虑时钟回拨问题、ID的长度、ID的可读性等问题。为了解决时钟回拨问题,我们可以采取物理时钟、NTP同步、预留ID范围等方式;

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/332175.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

leetcode 013二维区域和检索---矩阵不可变

给定一个二维矩阵 matrix&#xff0c;以下类型的多个请求&#xff1a; 计算其子矩形范围内元素的总和&#xff0c;该子矩阵的左上角为 (row1, col1) &#xff0c;右下角为 (row2, col2) 。 实现 NumMatrix 类&#xff1a; NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进…

Python数据分析案例36——基于神经网络的AQI多步预测(空气质量预测)

案例背景 不知道大家发现了没&#xff0c;现在的神经网络做时间序列的预测都是单步预测&#xff0c;即(需要使用X的t-n期到X的t-1期的数据去预测X的t期的数据)&#xff0c;这种预测只能预测一个点&#xff0c;我需要预测X的t1期的数据就没办法了&#xff0c;有的同学说可以把预…

部署Sqli-labs靶场:一篇文章解析全过程

部署Sqli-labs靶场&#xff1a;一篇文章解析全过程 0x01 前言 Sqli-labs是一个在线的SQL注入练习平台&#xff0c;提供了一系列关卡供用户练习SQL注入的技巧和防范方法。在这个平台上&#xff0c;用户可以尝试注入攻击&#xff0c;并测试自己的技能和工具&#xff0c;同时也可…

无心剑七绝《腊八粥香》

七绝腊八粥香 欣逢腊八粥浓香 五谷丰登聚宝庄 祈福心诚情不尽 佳肴共品待春芳 2024年1月18日 平水韵七阳平韵 这首七言绝句《腊八粥香》以腊八节为背景&#xff0c;描绘了人们欢庆腊八、祈福迎新的情景。 首句“欣逢腊八粥浓香”&#xff0c;开门见山地点明了主题——腊八节&a…

【笔记】《WebGL 编程指南》第 2 章 WebGL 入门

第一个 WebGL 程序 【P42】 默认情况下&#xff0c;<canvas>是透明的 【P44】 它不直接提供绘图方法&#xff0c;而是提供一种叫上下文&#xff08;context&#xff09;的机制来进行绘图。 【P45】 计算机系统通常使用红、绿、蓝这三原色组合来表示颜色&#xff0c;这种…

IMX6LL|时钟控制

一.时钟控制模块 4个层次配置芯片时钟 晶振时钟PLL与PFD时钟PLL选择时钟根时钟/外设时钟 1.1晶振时钟 系统时钟来源 RTC时钟源&#xff1a;32.768KHz&#xff0c;连接RTC模块&#xff0c;进行时间计算。系统时钟&#xff1a;24MHz&#xff0c;芯片主晶振 1.2PLL和PFD倍频时钟…

十一、常用API——正则表达式

目录 练习1&#xff1a; 正则表达式的作用 正则表达式 字符类&#xff08;只匹配一个字符&#xff09; 预定义字符&#xff08;只匹配一个字符&#xff09; 数量词 类 Pattern 正则表达式的构造摘要 反斜线、转义和引用 字符类 行结束符 组和捕获 Unicode 支持 与…

leetcode234. 回文链表

题目 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;hea…

关于KT6368A双模蓝牙芯片的BLE在ios的lightblue大数量数据测试

测试简介 关于KT6368A双模蓝牙芯片的BLE在ios的lightblue app大数量数据测试 测试环境&#xff1a;iphone7 。KT6368A双模程序96B6 App&#xff1a;lightblue ios端 可以打开log日志查看通讯流程 测试数据&#xff1a;长度是1224个字节&#xff0c;单次直接发给KT6368A&a…

ELK之Filebeat输出日志格式设置及输出字段过滤和修改

一、Filebeat输出日志格式设置 1.1 编辑vim filebeat.yml文件,修改输出格式设置 # output to console output.console:codec.format: string: %{[@timestamp]} %{[message]}pretty: true### 1.2 测试 执行 ./filebeat -e 可以看到/tmp/access.log(目前文件里只有140.77.188…

【Java 设计模式】结构型之桥接模式

文章目录 1. 定义2. 应用场景3. 代码实现结语 桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;它将抽象部分与实现部分分离&#xff0c;使它们可以独立变化&#xff0c;从而降低它们之间的耦合。桥接模式通过将抽象部分和实现部分分离&#x…

【PyTorch】PyTorch之Tensors操作篇

文章目录 前言一、Tensor创建1、TENSOR2、SPARSE_COO_TENSOR3、SPARSE_CSR_TENSOR4、ASARRAY5、AS_TENSOR6、FROM_NUMPY7、FROMBUFFER8、ZEROS和ZEROS_LIKE9、ONES和ONES_LIKE10、ARANGE11、LINSPACE12、LOGSPACE13、EYE14、EMPTY和EMPTY_LIKE15、FULL和FULL_LIKE 前言 介绍Te…

Docker搭建MySQL主从数据库-亲测有效

1、测试环境概述 1、使用MySQL5.7.35版本 2、使用Centos7操作系统 3、使用Docker20版本 案例中描述了整个测试的详细过程 2、安装Docker 2.1、如果已经安装docker,可以先卸载 yum remove -y docker \ docker-client \ docker-client-latest \ docker-common \ docker-l…

Nginx重写功能location与rewrite

1. location 从功能看 rewrite 和 location 似乎有点像&#xff0c;都能实现跳转&#xff0c;主要区别在于 rewrite 是在同一域名内更改获取资源的路径&#xff0c;而 location 是对一类路径做控制访问或反向代理&#xff0c;还可以proxy_pass 到其他机器。 rewrite 对访问的…

java数据结构与算法刷题-----LeetCode977. 有序数组的平方

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 时间复杂度 空间复杂度 O(n * l o g 2 n log_2{n} log2​…

Qt通用属性工具:随心定义,随时可见(三)

传送门: 《Qt通用属性工具&#xff1a;随心定义&#xff0c;随时可见&#xff08;一&#xff09;》 《Qt通用属性工具&#xff1a;随心定义&#xff0c;随时可见&#xff08;二&#xff09;》 《Qt通用属性工具&#xff1a;随心定义&#xff0c;随时可见&#xff08;三&#xf…

MFC 绘图

目录 MFC中绘图 CPaintDC&#xff0c;封装了在WM_PAINT消息中绘图的绘图设备 CClientDC类&#xff0c;封装了在客户区绘图的绘图设备 CGdiObject类(绘图对象类)&#xff0c;封装了各种绘图对象相关的操作 MFC中绘图 Windows绘图需要绘图设备&#xff0c;Win32&#xff1a;…

jvm -Djava.library.path 无法打开共享对象文件:

项目代码修改 java -jar -Xms1024m -Xmx1024m -Dloader.path/data/encrypt/lib -Djava.library.path/data/encrypt/libVtExtAPI.so server-1.0.0-SNAPSHOT.jar 重新启动

JavaScript基础语法

速通回顾一遍 引入方式 一般会把<script>标签置于<body>元素底部&#xff0c;改善显示速度&#xff1a; 内部脚本&#xff1a;<script></script>标签内外部脚本&#xff1a;<script src""></script>配置src 外部js文件中&…