当前位置: 首页 > news >正文

杭州建站模板系统广州商城型网站

杭州建站模板系统,广州商城型网站,注册网站乱填邮箱,玛沁县网站建设公司全局唯一id生成器 Redis 生成 前提知识~~ 时间戳 时间戳这个东西我们老是听到,却可能不是特别了解 首先,时间戳是从1970年1月1号0点0分开始的秒数,我查了蛮多资料,理论上来说,时间戳是没有上限的,而我们一般用位数来限制这里的上限,比如32位 我们来实际计算一下 32位的二进…全局唯一id生成器 Redis 生成 前提知识~~ 时间戳 时间戳这个东西我们老是听到,却可能不是特别了解 首先,时间戳是从1970年1月1号0点0分开始的秒数,我查了蛮多资料,理论上来说,时间戳是没有上限的,而我们一般用位数来限制这里的上限,比如32位 我们来实际计算一下 32位的二进制, 2的32次方 - 1 4294967296 - 1 4294967295 因为时间戳表示的是秒数,所以这里就是32位下,最大的秒数 一天的秒数为 86400 365天的秒数为31536000 那么32位的时间戳是 4294967295 / 31536000 136年 像现在是2024年,已经过了54年了,那么还有82年就要过期了 搞清楚这里的计算,我们后面就不会突然觉得,诶这里会不会超出上限 如何实现Redis全局id 首先我们要搞清楚为什么要全局id,全局id的作用是什么 第一: 唯一性,我们不能一套系统很多种全局id的生成器把,不能都用mysql自动生成id把,那样会混论,尤其是分布式系统 第二: 安全性,为了不让黑客知道我们生成id的规律,我们要加点佐料进去,例如时间戳 第三: 高可用 高性能 递增性 高可用就是,一个单点故障了,另外的一个服务器可以顶上,高性能就是生成的快,递增性就是为了我们业务的正常递增 所以我们就有redis生成全局id 这上面都符合,特别是高可用,可以用redis集群来保证,但是安全性,就要用不同的方法来实现了 这里是一种设计方法 设计的详解 时间戳31位,序列号32位 这里的全局id的意思就是,每一秒内的序列号作为全局id 这里的设计就很不错,这样很大程度上解决了问题,你可能会想要是1s内,超出了2的32次方怎么办,好办,就多写几位,压缩时间戳的位数 我们再来讲讲这里的时间戳的上限,如果是31位的化,那么最大就是2的31次方- 1 2147483648 - 1 2147483647 一年的秒数(365天) 2147483647 2147483647 / 31536000 68 年 约等于68年, 现在是2024年 离1970年已经54年了,所以按道理来说14年后就过期了 也就是2038年 这里的序列号,就用redis的自增来实现 实际代码 /*** 全局唯一id生成器 Redis实现* author jjking* date 2024-02-07 20:27*/ Component public class RedisIdWorker {Autowiredprivate RedisTemplate redisTemplate;/*** 生成id* param keyPrefix 业务的前缀key* return*/public long nextId(String keyPrefix) {//生成时间戳LocalDateTime now LocalDateTime.now();long timestamp now.toEpochSecond(ZoneOffset.UTC);//生成序列号String date now.format(DateTimeFormatter.ofPattern(yyyy:MM:dd));long count redisTemplate.opsForValue().increment(icr: keyPrefix : date);return timestamp 32 | count;} } 这里比较有意思的点是两个点 第一: 是这里的redis生成序列号的点,特别要加入业务代码的前缀,不然全都用一个不就乱了套了,还有就是redis的value是有位数上限的,好像是2的64次方,所以这里还是会超出上限的,那么为了解决这个问题,就用了这里的时间来做区别,这样基本就不会有问题了 第二: ,这里的返回结果的计算也蛮有意思,首先是时间的位数向左边移动了32位,这里的意思就是腾出32位给序列号,然后再用位运算 或,来加上这里的序列号 特别要注意这里的或,很有意思,0 | 0 还是0 0 | 1 那么就是1,所以这里可以直接加上,这个得想一想才能想明白 UUID生成 UUID就比较耳熟能祥了,我这里写一个生成的范例 Test public void test1() {String uuid UUID.randomUUID().toString();System.out.println(uuid); }可以看出来,他的位数分布是8-4-4-4-12位,一共是32位16进制数 我们来计算一下,总共多少字节,我们先转为为二进制,一位16进制,是4位二进制,那么 总共有32 * 4 128位二进制 一个字节是8位二进制 128 / 8 16字节 我们上面的redis生成的id是64位的,他的一半8个字节 所以,他的第一个缺点就是太大了,占内存 而且,这个uuid,也不太安全 但是他的优点就是性能还算蛮高的,还没有网络消耗 雪花算法 (重中之重) 先来了解雪花算法生成的id组成 最高位 固定为 0 ,符号位,因为生成的id都为正数,固定为041位 时间戳 单位 毫秒 经过计算最多可以使用69年10 位机器码 5位 数据中心id 5位 工作机器id12 位序列号 这个样子有点类似于我们redis生成的id,不过序列号少了,并且是毫秒级的,还有一个机器码 我这里摘的是糊涂工具包中的雪花算法id,并且简略了一些无关辅助代码 代码 package com.hmdp.utils;import cn.hutool.core.date.SystemClock; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil;import java.io.Serializable; import java.util.Date;/*** Twitter的Snowflake 算法br* 分布式系统中有一些需要使用全局唯一ID的场景有些时候我们希望能使用一种简单一些的ID并且希望ID能够按照时间有序生成。** p* snowflake的结构如下(每部分用-分开):br** pre* 符号位1bit- 时间戳相对值41bit- 数据中心标志5bit- 机器标志5bit- 递增序号12bit* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000* /pre* p* 第一位为未使用(符号位表示正数)接下来的41位为毫秒级时间(41位的长度可以使用69年)br* 然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点br* 最后12位是毫秒内的计数12位的计数顺序号支持每个节点每毫秒产生4096个ID序号* p* 并且可以通过生成的id反推出生成时间,datacenterId和workerId* p* 参考http://www.cnblogs.com/relucent/p/4955340.htmlbr* 关于长度是18还是19的问题见https://blog.csdn.net/unifirst/article/details/80408050** author Looly* since 3.0.1*/ public class Snowflake implements Serializable {private static final long serialVersionUID 1L;/*** 默认的起始时间为Thu, 04 Nov 2010 01:42:54 GMT*/public static long DEFAULT_TWEPOCH 1288834974657L;/*** 默认回拨时间2S*/public static long DEFAULT_TIME_OFFSET 2000L;private static final long WORKER_ID_BITS 5L;// 最大支持机器节点数0~31一共32个SuppressWarnings({PointlessBitwiseExpression, FieldCanBeLocal})//-1L 为 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 (1L的补码)//左移5为 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1110 0000//-1L为 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111// ^ 异或是不同为1,相同为0// 结果为 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1111private static final long MAX_WORKER_ID -1L ^ (-1L WORKER_ID_BITS);private static final long DATA_CENTER_ID_BITS 5L;// 最大支持数据中心节点数0~31一共32个SuppressWarnings({PointlessBitwiseExpression, FieldCanBeLocal})//和上面的最大工作id一样的道理private static final long MAX_DATA_CENTER_ID -1L ^ (-1L DATA_CENTER_ID_BITS);// 序列号12位表示只允许workId的范围为0-4095private static final long SEQUENCE_BITS 12L;// 机器节点左移12位private static final long WORKER_ID_SHIFT SEQUENCE_BITS;// 数据中心节点左移17位private static final long DATA_CENTER_ID_SHIFT SEQUENCE_BITS WORKER_ID_BITS;// 时间毫秒数左移22位private static final long TIMESTAMP_LEFT_SHIFT SEQUENCE_BITS WORKER_ID_BITS DATA_CENTER_ID_BITS;// 序列掩码用于限定序列最大值不能超过4095//计算机的负数是用补码表示的//1L 0000000000000000000000000000000000000000000000000000000000000001//1L 反码 1111111111111111111111111111111111111111111111111111111111111110//1L 补码 1111111111111111111111111111111111111111111111111111111111111111 补码 反码 1//这里是 -1L(64位1) 往左移动12位 111111111111111111111111111111111111111111111111 0000 0000 0000 0000// ~ 取反 000000000000000000000000000000000000000000000000 1111 1111 1111 1111//结果为 2的12次方 - 1 4095SuppressWarnings(FieldCanBeLocal)private static final long SEQUENCE_MASK ~(-1L SEQUENCE_BITS);// 4095//起始时间private final long twepoch;private final long workerId;private final long dataCenterId;private final boolean useSystemClock;// 允许的时钟回拨数private final long timeOffset;private long sequence 0L;private long lastTimestamp -1L;/*** param epochDate 初始化时间起点null表示默认起始日期,后期修改会导致id重复,如果要修改连workerId dataCenterId慎用* param workerId 工作机器节点id* param dataCenterId 数据中心id* param isUseSystemClock 是否使用{link SystemClock} 获取当前时间戳* param timeOffset 允许时间回拨的毫秒数* since 5.7.3*/public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock, long timeOffset) {//如果没有给起始的时间就用默认的起始时间if (null ! epochDate) {this.twepoch epochDate.getTime();} else{// Thu, 04 Nov 2010 01:42:54 GMTthis.twepoch DEFAULT_TWEPOCH;}//工作机器id 31if (workerId MAX_WORKER_ID || workerId 0) {throw new IllegalArgumentException(StrUtil.format(worker Id cant be greater than {} or less than 0, MAX_WORKER_ID));}if (dataCenterId MAX_DATA_CENTER_ID || dataCenterId 0) {throw new IllegalArgumentException(StrUtil.format(datacenter Id cant be greater than {} or less than 0, MAX_DATA_CENTER_ID));}this.workerId workerId;this.dataCenterId dataCenterId;this.useSystemClock isUseSystemClock;this.timeOffset timeOffset;}/*** 根据Snowflake的ID获取机器id** param id snowflake算法生成的id* return 所属机器的id*/public long getWorkerId(long id) {return id WORKER_ID_SHIFT ~(-1L WORKER_ID_BITS);}/*** 根据Snowflake的ID获取数据中心id** param id snowflake算法生成的id* return 所属数据中心*/public long getDataCenterId(long id) {return id DATA_CENTER_ID_SHIFT ~(-1L DATA_CENTER_ID_BITS);}/*** 根据Snowflake的ID获取生成时间** param id snowflake算法生成的id* return 生成的时间*/public long getGenerateDateTime(long id) {return (id TIMESTAMP_LEFT_SHIFT ~(-1L 41L)) twepoch;}/*** 下一个ID** return ID*/public synchronized long nextId() {//获取当前时间戳long timestamp genTime();//如果小于上次的时间,这里有问题,时间回拨!if (timestamp this.lastTimestamp) {if(this.lastTimestamp - timestamp timeOffset){// 容忍指定的回拨避免NTP校时造成的异常timestamp lastTimestamp;} else{// 如果服务器时间有问题(时钟后退) 报错。throw new IllegalStateException(StrUtil.format(Clock moved backwards. Refusing to generate id for {}ms, lastTimestamp - timestamp));}}//如果等于上次的时间,说明,此时是同一毫秒,递增序列号if (timestamp this.lastTimestamp) {//SEQUENCE_MASK为4095,这里的运算看上面的解释,这个相当于最大值//SEQUENCE_MASK 为 00000000000000000000000000000000000000000000 0000 1111 1111 1111 1111//假设此时的序列号为4095(sequence) 那么前面是4096 00000000000000000000000000000000000000000000 0001 0000 0000 0000 0000//这样子与,0 0 0 ----- 0 1 0 ----- 1 1 1//所以最后结果为 00000000000000000000000000000000000000000000 0000 0000 0000 0000 0000final long sequence (this.sequence 1) SEQUENCE_MASK;//如果此时为0说明,已经到了4095了,到达上限,应该等待下一个毫秒if (sequence 0) {timestamp tilNextMillis(lastTimestamp);}this.sequence sequence;} else {sequence 0L;}//赋值此时的上一次时间戳(毫秒)lastTimestamp timestamp;return ((timestamp - twepoch) TIMESTAMP_LEFT_SHIFT)| (dataCenterId DATA_CENTER_ID_SHIFT)| (workerId WORKER_ID_SHIFT)| sequence;}/*** 循环等待下一个时间** param lastTimestamp 上次记录的时间* return 下一个时间*/private long tilNextMillis(long lastTimestamp) {long timestamp genTime();// 循环直到操作系统时间戳变化while (timestamp lastTimestamp) {timestamp genTime();}if (timestamp lastTimestamp) {// 如果发现新的时间戳比上次记录的时间戳数值小说明操作系统时间发生了倒退报错throw new IllegalStateException(StrUtil.format(Clock moved backwards. Refusing to generate id for {}ms, lastTimestamp - timestamp));}return timestamp;}/*** 生成时间戳** return 时间戳*/private long genTime() {return this.useSystemClock ? SystemClock.now() : System.currentTimeMillis();}// ------------------------------------------------------------------------------------------------------------------------------------ Private method end } 会有点长,但是核心的东西就一段 我们直接来看这一段 /*** 下一个ID** return ID*/public synchronized long nextId() {//获取当前时间戳long timestamp genTime();//如果小于上次的时间,这里有问题,时间回拨!if (timestamp this.lastTimestamp) {if(this.lastTimestamp - timestamp timeOffset){// 容忍指定的回拨避免NTP校时造成的异常timestamp lastTimestamp;} else{// 如果服务器时间有问题(时钟后退) 报错。throw new IllegalStateException(StrUtil.format(Clock moved backwards. Refusing to generate id for {}ms, lastTimestamp - timestamp));}}//如果等于上次的时间,说明,此时是同一毫秒,递增序列号if (timestamp this.lastTimestamp) {//SEQUENCE_MASK为4095,这里的运算看上面的解释,这个相当于最大值//SEQUENCE_MASK 为 00000000000000000000000000000000000000000000 0000 1111 1111 1111 1111//假设此时的序列号为4095(sequence) 那么前面是4096 00000000000000000000000000000000000000000000 0001 0000 0000 0000 0000//这样子与,0 0 0 ----- 0 1 0 ----- 1 1 1//所以最后结果为 00000000000000000000000000000000000000000000 0000 0000 0000 0000 0000final long sequence (this.sequence 1) SEQUENCE_MASK;//如果此时为0说明,已经到了4095了,到达上限,应该等待下一个毫秒if (sequence 0) {timestamp tilNextMillis(lastTimestamp);}this.sequence sequence;} else {sequence 0L;}//赋值此时的上一次时间戳(毫秒)lastTimestamp timestamp;return ((timestamp - twepoch) TIMESTAMP_LEFT_SHIFT)| (dataCenterId DATA_CENTER_ID_SHIFT)| (workerId WORKER_ID_SHIFT)| sequence;} 我们来总结一下,这个核心代码的代码逻辑 我们要生成id的化,需要几部分 时间戳 机器码 序列号 机器码也就是我们服务器的标识,一般是我们字节写的,所以不用考虑这个 重点在于时间戳 序列号 时间戳的生成: 当前时间戳,并且是毫秒级的 时间戳的生成,代码很简单,所以也不要终点考虑 序列号的生成(重点): 第一: 我们需要校验这里的时间戳,是否有问题,也就是当前时间比上一次的时间还早,出现时间回拨问题 第二: 我们得校验此时的序列号是否超过上限,如果超过上限,那么置此时的序列号为0,并且等待下一毫秒,将此时的时间戳更新 最重要的问题就是这两,相比较,比较简单的问题是这里的超过上限问题,这里也很简单,就是循环等待下一毫秒,到达下一毫秒更新此时的时间戳,序列号也已经设置好了为0 最难也是最重要的问题,时间回拨问题,这里的位运算问题,还是很好理解的,只要会位运算,都能解决 但是我这里特别不能搞懂,为啥这里要用位运算 类似于如下代码 // 序列掩码用于限定序列最大值不能超过4095//计算机的负数是用补码表示的//1L 0000000000000000000000000000000000000000000000000000000000000001//1L 反码 1111111111111111111111111111111111111111111111111111111111111110//1L 补码 1111111111111111111111111111111111111111111111111111111111111111 补码 反码 1//这里是 -1L(64位1) 往左移动12位 111111111111111111111111111111111111111111111111 0000 0000 0000 0000// ~ 取反 000000000000000000000000000000000000000000000000 1111 1111 1111 1111//结果为 2的12次方 - 1 4095SuppressWarnings(FieldCanBeLocal)private static final long SEQUENCE_MASK ~(-1L SEQUENCE_BITS);// 4095 这里的mask就是,相当于最大值,我不能明白的是,为什么不直接写4095L 或者写2的12次方 - 1,这里的12次方的12 一样也可以写成这里的 SEQUENCE_BITS 为啥要搞这个位运算???,我查了一下,都没有这方面的问题,如果你懂的化,可以私信我,谢谢了 时钟回拨问题 我也是看别人说,会有这个时钟回拨问题,问题的出现在于,有可能运维人员手动的更改了服务器的时间,或者两个服务器时间不同,需要同步时间,就会导致这里的时钟回拨问题 解决方案: 第一种方案: 是如果是时间回拨只是一两次,并且时间跨度不是很大的化,例如1 到 3秒,那么就直接等,那么几秒,这样子相当于有冗余,但是影响也不是很大,但这种操作,不能再并发量很高的时候操作,不然肯定出问题 第二种方案: 就是美团 和百度的方案 这两的方案我就先不研究了,到时候我真的懂了,就来更新这里的博客,我估计我也看不懂
http://www.zqtcl.cn/news/743034/

相关文章:

  • 南同网站建设hr系统管理软件排名
  • 水果网店网站建设策划书做企业形象网站
  • 小清新博客网站软件公司有哪些部门
  • 企业网站托管一年多少钱想学电商运营在哪里学
  • 网站建设自评报告手机电商平台怎么做的
  • 安阳网站建设优化免费的免抠图素材网站
  • 网站主机有什么用seo网站课程
  • 网站关键词优化软件网站的二次开发
  • 网站建设技术服务费怎么入账杭州网站推广与优化
  • 咨询类网站建设方案书重庆360网络推广
  • 简单网站模板下载wordpress调用数据库字段
  • 万网空间最多放几个网站好的网站首页的特点
  • .net做网站安全吗wordpress取消邮件验证
  • 沈阳做网站推广唐山网站怎么做seo
  • 网站备案说主体已注销刷关键词指数
  • 学做网站教学百度网盘动软代码生成器 做网站
  • 长辛店网站建设手机评测网站
  • 网站建设公司选哪个好软件开发
  • 隐形眼镜网站开发的经济效益莘县网站开发
  • 开创集团网站建设如何在学校网站上做链接
  • 上海优秀网站设计百度投诉中心人工电话号码
  • 卖建材的网站有哪些跨境电商工具类产品的网站
  • 做毕业网站的周记网站开发项目书
  • 门户网站价格仿站工具下载后咋做网站
  • 国外优秀ui设计网站常州网站建设电话
  • 大连手机网站建设做外贸无网站如何做
  • 做旅游门票网站需要什么材料人工智能培训机构哪个好
  • 免费的网站程序个人网站可以做论坛么
  • ps中网站页面做多大的wordpress cdn 阿里
  • 深圳整站创意设计方法有哪些