首页
关于
Search
1
sql的注入原因和解决办法
145 阅读
2
SpringBoot整合腾讯云存储COS及基本使用,增删改查......
129 阅读
3
深究contains方法
100 阅读
4
多线程概述
74 阅读
5
学习的第一个注解@WebServlet - JavaWeb
73 阅读
默认分类
Java
C/C++
Mysql
JavaWeb
SpringBoot
算法
前端
Linux
Search
标签搜索
Spring
HTTP
Java
JavaWeb
IOC
mybatis
腾讯云
COS
云存储
CDN
redis
分布式
id
全局唯一id
Typecho
累计撰写
26
篇文章
累计收到
3
条评论
首页
栏目
默认分类
Java
C/C++
Mysql
JavaWeb
SpringBoot
算法
前端
Linux
页面
关于
搜索到
1
篇与
分布式
的结果
2023-07-28
基于Redis实现的分布式全局唯一ID
分布式id的要求唯一性:可以在多个系统之间保持唯一性。高性能:redis是基于内存的数据库,性能极高。高可用:redis支持集群。递增性:具有单调递增的特性安全性:id拼接了其他信息,递增的规律性不会太明显。基于redis的id的组成id由三个部分组成,符号位,时间戳,序列号。使用long类型存储,长整型占八个字节,1个字节等于8个比特位,即64位。第1位表示符号位,永远是0;第2~32位表示时间戳,以秒为单位,可以使用69年;剩下的33~64位表示序列号,秒内的计数器,支持每秒产生2^32个不同的id;代码实现1、生成时间戳需要事先准备一个固定的起始时间戳,先运行main方法里面的代码获得。每次生成id时,获取当前时间戳。通过“起始时间戳”减去“当前时间戳”,获得组成id的时间戳。// 1、 private static final long BEGIN_TIMESTAMP = 1672531200L; public static void main(String[] args){ // 获得一个时间戳,时间为2023年1月1日0点0分0秒 LocalDateTime time = LocalDateTime.of(2023, 1, 1, 0, 0, 0); long timestamp = time.toEpochSecond(ZoneOffset.UTC); System.out.println(timestamp); // 1672531200 } // 2、 LocalDateTime now = LocalDateTime.now(); long nowSecond = now.toEpochSecond(ZoneOffset.UTC); long timestamp = nowSecond = BEGIN_TIMESTAMP; 2、生成序列号其实就是在redis里设置一个key,通过对key自增,每次获得id使用自增后的值,组成序列号部分。需要特别注意key的组成,因为redis中key的自增上限是2^64次方,虽然已经很大了,但毕竟还是有上限。所以解决办法就是“key+日期”。这里将key拼接上生成id时的年月日,这样做的好处是,key会随着年月日变化;还有利于统计,某天的key自增的值就代表当前生成的id的数量。/** * “icr”:自增key的标识,可改 * “keyPrefix”:业务id * “date”:当前年月日 * * 例如:icr:order:2023:07:28 */ String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd")); long count = stringRedisTemplate.opsForValue().increment("icr:"+keyPrefix+":"+date);3、组成idlong类型是占64个bit,此时将其左移32位,原来的32个位置上会自动补0。然后再将结果和序列号进行“或运算”,就得到了完整id。long id = timestamp << 32 | count; return id;4、图解:原始时间戳 左移32位的时间戳 或运算之后的时间戳此时id的前32位是符号位和时间戳组成,后32位是存储在redis里的那个自增key的值。id会随时间戳而变化,而时间戳是精确到秒,所以理论上,就算在极高的并发下,只要一秒内并发不超过2^32次方,id就是唯一的。完整代码@Component public class RedisIdWorker { // 开始时间戳 private static final long BEGIN_TIMESTAMP = 1672531200L; private StringRedisTemplate stringRedisTemplate; public RedisIdWorker(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } public long nextID(String keyPrefix){ // 1、生成时间戳 LocalDateTime now = LocalDateTime.now(); long nowSecond = now.toEpochSecond(ZoneOffset.UTC); long timestamp = nowSecond = BEGIN_TIMESTAMP; // 2、生成序列号 // 获取当前日期,精确到天。用以解决redis中,单key自增上限问题,还有利于统计。redis单key自增上限为2^64次方 String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd")); // 自增长 long count = stringRedisTemplate.opsForValue().increment("icr:"+keyPrefix+":"+date); // 3、拼接并返回 //生成的时间戳左移32位,然后或上自增长的值 long id = timestamp << 32 | count; return id; } }性能测试用线程池模拟并发,for循环提交任务。 private ExecutorService es = Executors.newFixedThreadPool(500); @Test void testIdWorker() throws InterruptedException { CountDownLatch cdl = new CountDownLatch(500); Runnable task = () -> { for (int i = 0; i < 100; i++) { long id = redisIdWorker.nextID("order"); System.out.println(id); } cdl.countDown(); }; StopWatch stopWatch = new StopWatch(); stopWatch.start(); for (int i = 0; i < 500; i++) { es.submit(task); } cdl.await(); stopWatch.stop(); // time = 3961 System.out.println("time = " + stopWatch.getTotalTimeMillis()); }本地电脑测试结果:5万个id,用时3秒多。
2023年07月28日
4 阅读
0 评论
0 点赞