1. 全局ID生成器

redis自增Id作为主键的问题_redis自增Id作为主键的问题

id的规律性明显造成某些信息的泄露;
使用自增ID作为主键会导致一些问题。首先,由于自增ID必须是唯一的,因此当达到最大值时,无法再向表中插入新的数据,这限制了表的数据量。例如:订单如果一直自增,id值达到数亿级别时,单表就很难插入了;
其次,由于每次插入新数据都需要更新自增ID的值,当表中存在大量数据时,这个过程会变得越来越耗时;

redis自增Id作为主键的问题_数据库_02

  1. 全局唯一性:生成的ID在整个分布式系统中必须是唯一的,避免ID冲突的发生。这是保证数据一致性和避免数据丢失的关键。
  2. 高性能:生成ID的速度应该足够快,以满足高并发的请求。高性能的ID生成器能够处理大量的请求,而不会成为系统的瓶颈。
  3. 递增性:生成的ID应该具备趋势递增的特性,即后续生成的ID值应该大于前面生成的ID值。这可以提高数据库索引的性能,减少IO的次数。
  4. 高可用:ID生成器应该是可靠可用的,即保证在系统故障或网络中断的情况下,依然能够正常生成唯一ID。
  5. 安全性:生成的ID应该是难以被猜测到且具有权限控制机制,还有不包括敏感信息。

系列号就是Redis自增的数值

redis自增Id作为主键的问题_自增_03

redis自增Id作为主键的问题_数据库_04

2. Redis实现全局ID

要点:
在对序列号进行自增长时,为防止自增值过大采用拼接时间戳date;
最后得到全局ID,时间戳左移与序列号做或运算得到全局ID,思路值得学习;

@Component
public class RedisIdWorker {

    private StringRedisTemplate stringRedisTemplate;

    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 开始时间戳
     */
    private static final long BEGIN_TIMESTAMP = 1640995200L;

    /**
     * 序列号位数
     */
    private static final int COUNT_BITS = 32;

    public long nextId(String keyPrefix){
        //1. 生成时间戳
        LocalDateTime now = LocalDateTime.now();
        long nowEpochSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timeStamp = nowEpochSecond - BEGIN_TIMESTAMP;
        //2. 生成序列号
        //2.1 获取当前日期,精确到天
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        //2.2 自增长
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
        //3. 拼接并返回(时间戳左移与序列号做或运算得到全局ID)
        return timeStamp << COUNT_BITS | count;
    }
}

对生成ID策略进行测试;
要点:
lambda表达式的应用; 多线程运用以及线程池的应用,关于计算多线程的运算时间的方法;
new CountDownLatch(300),设置初始计数值为300;
调用await()方法进行等待,直到计数器值为0,才会继续执行后续代码,从而得出运算的时间;
通过使用CountDownLatch来等待所有任务执行完毕,并使用线程池来并发执行任务,评估redisIdWorker的性能;

Runnable task = new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            long id = redisIdWorker.nextId("order");
            System.out.println("id= " + id);
        }
    }
};
@Resource
private RedisIdWorker redisIdWorker;
/**
 * 创建线程池
 */
private ExecutorService ex = Executors.newFixedThreadPool(500);
@Test
void testIdWorker() throws InterruptedException {
    CountDownLatch countDownLatch = new CountDownLatch(300);
    //lambda表达式
    Runnable task = () -> {
        for (int i = 0; i < 100; i++) {
            long id = redisIdWorker.nextId("order");
            System.out.println("id= " + id);
        }
        countDownLatch.countDown();
    };
    
    long begin = System.currentTimeMillis();
    for (int i = 0; i < 300; i++) {
        ex.submit(task);
    }
    countDownLatch.await();
    long end = System.currentTimeMillis();
    System.out.println("time = " + (end - begin));
}