【Redis】解决全局唯一 id 问题
创始人
2024-03-20 04:14:20
0

永远要记得坚持的意义

一、全局唯一 id 场景

概念: 以订单表的 id 为例

使用自增 id 会产生的问题:

  • id 的规律性太明显,容易让用户猜测到一些信息
  • 受表单数据量的限制 —— 分布式存储时,会产生问题 (自增长,容易产生 id 重复)

因此,我们需要定义全局 id

二、全局 id 生成器

全局 id 生成器的特性:

分布式系统下用来生成全局唯一 id 的工具,其特性有:

  • 唯一性
  • 高可用
  • 高性能
  • 递增性
  • 安全性

全局唯一 id 生成的实现:

  • UUID
    16进制字符串,不单调递增,jdk自带的
  • Redis 自增
  • snowflake 算法
    对时钟依赖非常高
  • 数据库自增策略
    数据库单独设置一个自增表,获取自增,实现唯一效果 (单调自增的数据库版,性能没有 Redis 自增好)

我们本文主要介绍 使用 Redis 作为全局唯一 id 生成器

我们不直接使用 redis 自增的数值,而是拼接一些其他信息,主要拼接的信息如下:

符号位 + 时间戳 + 序列号

在这里插入图片描述

三、技术实现

封装好的 Redis 生成唯一 id 的工具类如下:

@Component
public class RedisIdWorker {private static final long BEGIN_TIME = 1670198400;private StringRedisTemplate stringRedisTemplate;private RedisIdWorker(StringRedisTemplate stringRedisTemplate){this.stringRedisTemplate = stringRedisTemplate;}public long nextId(String keyPrefix){// 1. 生成时间戳LocalDateTime localDateTime = LocalDateTime.now();long nowSecond = localDateTime.toEpochSecond(ZoneOffset.UTC);long timestamp = nowSecond - BEGIN_TIME;// 2. 生成序列号 —— 防止超过上限(Redis 自增的上限 2 的64 次幂) 解决方式:再拼接一个日期字符串// 2.1 获取当前日期String day = localDateTime.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + day);// 3. 拼接并返回return timestamp<<32 | count; // 前 32 位为时间戳 ,后 32 位为 序列号(即 count)}//   生成时间戳开始时间 —— 我这里是 2022 / 12/ 5 日开始
//    public static void main(String[] args) {
//        LocalDateTime time = LocalDateTime.of(2022, 12, 5, 0, 0, 0);
//        long second = time.toEpochSecond(ZoneOffset.UTC);
//        System.out.println(second);
//    }
}

测试类代码:

@Slf4j
@SpringBootTest
class HmDianPingApplicationTests {@Autowiredprivate RedisIdWorker redisIdWorker;// 500 线程的线程池子private ExecutorService es = Executors.newFixedThreadPool(500);@Testpublic void testIdWorker() throws InterruptedException {CountDownLatch latch = new CountDownLatch(200);// 每个线程生成 100 个 idRunnable task = () -> {for (int i=0; i<5; i++) {long id = redisIdWorker.nextId("order");log.info("id=" + id);}latch.countDown();};long begin = System.currentTimeMillis();for (int i=0; i<200; i++){es.submit(task);}latch.await();long end = System.currentTimeMillis();log.info("time=" + (end - begin));}}

使用全局唯一 id 解决秒杀业务

    @Resourceprivate RedisIdWorker redisIdWorker;@Resourceprivate ISeckillVoucherService seckillVoucherService;@Override@Transactionalpublic Result seckillVoucher(Long voucherId) {// 1. 查询优惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);// 2. 判断秒杀是否开始if(voucher.getBeginTime().isAfter(LocalDateTime.now())){return Result.fail("秒杀还未开始");}// 3. 判断秒杀是否已经结束if(voucher.getEndTime().isBefore(LocalDateTime.now())){return Result.fail("秒杀已经结束");}// 4. 判断库存是否充足if(voucher.getStock() < 1){return Result.fail("库存不足");}// 5. 扣除库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).update();if (!success){return Result.fail("库存不足");}// 6. 创建订单VoucherOrder voucherOrder = new VoucherOrder();// 订单的参数 —— 订单 id \ 代金券 id \ 用户 id// 生成唯一 idlong id = redisIdWorker.nextId("order");voucherOrder.setId(id);voucherOrder.setVoucherId(voucherId);voucherOrder.setUserId(1L);save(voucherOrder);return Result.ok();}

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...