基于Redis生成全局ID

基于Redis生成全局ID

方法一:预先生成订单号方案

该方案主要步骤如下:

预生成订单号并存入Redis

  • 首先,可统计当天的订单数据量基于系统当前时间戳生成一批订单号。
  • 将这些订单号按顺序推入Redis列表中。
  • 每次需要生成订单号时,从Redis列表中弹出一个。
import redis.clients.jedis.Jedis;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
 * @author wstv
 */
public class OrderNumberGenerator {

    private Jedis jedis;

    public OrderNumberGenerator(Jedis jedis) {
        this.jedis = jedis;
    }

    /**
     * 提前生成一批订单号并放入Redis
     * @param batchSize 批量生成的数量
     */
    public void preGenerateOrderNumbers(int batchSize) {
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
        String currentDateStr = now.format(formatter);

        for (int i = 0; i < batchSize; i++) {
            String orderId = currentDateStr + String.format("%05d", i); // 前面加上系统时间戳,后面补足零
            jedis.rpush("order_numbers", orderId);
        }
    }

    /**
     * 从Redis中取出一个订单号
     * @return 订单号,若无则返回null
     */
    public String getNextOrderNumber() {
        String orderId = jedis.lpop("order_numbers");
        if (orderId != null) {
            return orderId;
        } else {
            // 当订单号不足时,重新生成一批
            preGenerateOrderNumbers(1000); // 示例批量生成1000个
            return getNextOrderNumber(); // 再次尝试获取
        }
    }
}

// 使用示例:
Jedis jedis = new Jedis("localhost"); // 初始化Jedis连接
OrderNumberGenerator generator = new OrderNumberGenerator(jedis);
generator.preGenerateOrderNumbers(1000); // 预先生成一批订单号
String orderNumber = generator.getNextOrderNumber(); // 获取一个订单号
System.out.println(orderNumber);

方法二:利用Redis自增特性

因为Redis是单线的,天生保证原子性,可以使用Redis的原子操作 INCR和INCRBY来实现
但是这种方式存在局限性,如果服务重启,如果没有持久化配置,可能会导致计数器从 0 开始,所以通常我们会设置 Redis 数据库持久化或者定期备份计数值。

优缺点

  • 优点

    • 不依赖数据库,效率高,性能优越。
    • 数字ID自然排序,有利于分页查询和排序操作。
  • 缺点

    • 如果系统尚未集成Redis,则需额外引入组件,增加系统复杂度。
    • 需要编码和配置工作较多。

加入每天从0开始的订单编号需求,我们将为每一天创建一个独立的Redis键,并使用INCR原子操作来递增订单号。下面是一个基础实现:

import redis.clients.jedis.Jedis;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class RedisBasedOrderIdGenerator {

    private static final String KEY_PREFIX = "daily_order_id_";

    private final Jedis jedis;
    private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    public RedisBasedOrderIdGenerator(Jedis jedis) {
        this.jedis = jedis;
    }

    /**
     * 生成一个新的订单号,格式为:日期(年月日)+ 当日自增ID(左补零至10位)
     * @return 新的订单号
     */
    public String generateNewOrderId() {
        LocalDate today = LocalDate.now();
        String key = KEY_PREFIX + today.format(dateFormatter);

        // 设置Redis键的过期时间,比如让其在当天结束时自动删除,以便第二天从0开始
        jedis.expire(key, calculateExpireSecondsForToday());

        // 自增并获取新值
        long autoIncrementId = jedis.incr(key);

        // 将自增ID转换为字符串,并补足前导零
        String orderId = today.format(dateFormatter) + String.format("%010d", autoIncrementId);

        return orderId;
    }

    /**
     * 根据当前日期计算Redis键的有效期秒数,使其在每天结束时过期
     * @return 当天剩余的秒数
     */
    private int calculateExpireSecondsForToday() {
        LocalDate tomorrow = LocalDate.now().plusDays(1);
        LocalDateTime endOfToday = tomorrow.atStartOfDay();
        return (int) Duration.between(LocalDateTime.now(), endOfToday).getSeconds();
    }

    // 使用示例:
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");
        RedisBasedOrderIdGenerator generator = new RedisBasedOrderIdGenerator(jedis);
        String orderId = generator.generateNewOrderId();
        System.out.println(orderId);
    }
}

注意:在Redis集群情况下,同样和Redis一样需要设置不同的增长步长,同时key一定要设置有效期
可以使用Redis集群来获取更高的吞吐量。

  • 示例:假设有一个包含 5 台 Redis 的集群,初始值和步长分别为 1,2,3,4,5 和 5,各节点生成的 ID 如下:

  • A:1,6,11,16,21

  • B:2,7,12,17,22

  • C:3,8,13,18,23

  • D:4,9,14,19,24

  • E:5,10,15,20,25

比较适合使用Redis来生成每天从0开始的流水号。比如订单号=日期+当日自增长号。可以每天在Redis中生成一个Key,使用INCR进行累加。
如果生成的订单号超过自增增长的话,可以采用前缀+自增+并且设置有效期

方法三:Snowflake 算法(雪花算法)

Snowflake 算法是一种 Twitter 开源的分布式 ID 生成算法,它能生成一个64位的全局唯一ID,且具备良好的趋势递增和可读性。
通过 Redis 作为存储媒介,确保其中的部分序列号部分(sequence)在多节点下有序递增。

// 在 Redis 中维护序列号部分
long sequence = redis.incr("sequence");
long timestampBits = getCurrentTimestampInSnowflakeFormat();
long workerIdBits = getWorkerIdInSnowflakeFormat();

// 合成全局ID
long globalId = (timestampBits << timestampShift) | (workerIdBits << workerIdShift) | sequence;

方法四、分布式锁+自增ID

  • 在多节点服务架构中,为避免多个服务实例同时修改同一计数器引发冲突,可使用 Redis 分布式锁(例如 RedLock 或 SETNXEXPIRE)来确保同一时间仅有一个实例生成 ID:
try {
    // 获取分布式锁
    if (redis.setnx(lockKey, currentThreadId, expireTime)) {
        long currentId = redis.incr("global_id_counter");
        // 释放锁
        redis.del(lockKey);
        return currentId;
    }
} catch (Exception e) {
    // 锁获取失败处理
}
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值