SpringBoot分布式生成递增单号

方法一:Redis实现

前言

利用Redis特有的原子性的特性,在Redis中实现自增。
在Redis中设置数据的自动递增,同时设置数据的到期时间,在业务流程中,单号是每天自动递增的,同时加上一些特有的前缀组成。
本文设计的单号的格式为:YSD20221111000066

准备

在项目中集成Redis,此处不再做过多叙述,直接进入正题!!!!!

步骤

  • 获取次日凌晨时间戳
 public long getNextDay() {
        Calendar c = Calendar.getInstance();
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);
        //今天凌晨
        return c.getTimeInMillis() / 1000 * (24 * 60 * 60);
    }
  • 生成单号的完整代码
@Component
public class RedisCode {


    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 依赖Redis的原子性,设置数据中的自动递增,有效期到12点
     *
     * @param key
     */
    public String getCode(String key) {
        StringBuilder orderNo = new StringBuilder();
        String format = new SimpleDateFormat("yyyyMMdd").format(new Date());
        orderNo.append("YSD").append(format);
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, Objects.requireNonNull(redisTemplate.getConnectionFactory()));
        //当缓存中没有时,默认时0
        long increment = entityIdCounter.getAndIncrement();
        if (increment == 0) {
            //当数据为0的时候,设置有效期,10秒后过期
            //entityIdCounter.expire(3600, TimeUnit.SECONDS);
            //设置到什么时间过期.例:次日凌晨过期
            entityIdCounter.expireAt(new Date(getNextDay()));
        }
        //当日返回的id最低六位
        increment = increment + 1;
        String count = increment <= 9 ? "00000" : increment <= 99 ? "0000" : increment <= 999 ? "000" : increment <= 9999 ? "00" : increment <= 99999 ? "0" : "";
        orderNo.append(count).append(increment);
        return orderNo.toString();
    }

    /**
     * 获取次日凌晨的时间戳
     *
     * @return
     */
    public long getNextDay() {
        Calendar c = Calendar.getInstance();
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);
        //今天凌晨
        return c.getTimeInMillis() / 1000 * (24 * 60 * 60);
    }
}

测试

    @Test
    public void d() {
        long a = System.currentTimeMillis();
        for (int i = 0; i < 20; i++) {
            System.out.println(redisCode.getCode("code"));
        }
        long b = System.currentTimeMillis();
        System.out.println("耗时:" + (b - a) / 1000);
    }

测试结果

在这里插入图片描述

方法二: 雪花算法

雪花算法简介

SnowFlake 雪花算法
SnowFlake 中文意思为雪花,故称为雪花算法。最早是 Twitter 公司在其内部用于分布式环境下生成唯一 ID。在2014年开源 scala 语言版本。
如需了解更多可以自行百度,不再赘述,直接上代码。

完整代码

public class SnowflakeIdWorker {

    //开始时间截 (2015-01-01)
    private final long START_TIME_STAMP = 1420041600000L;

    /**
     * 每一部分占用的位数
     */
    //序列号占用的位数
    private final long SEQUENCE_BIT = 12L;
    //机器标识占用的位数
    private final long MACHINE_BIT = 5L;
    //数据中心占用的位数
    private final long DATACENTER_BIT = 5L;


    /**
     * 每一部分的最大值
     */
    //最大数据中心数量,结果是31
    private final long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);

    //最大机器数量,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
    private final long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);

    //最大序列,这里为4095 (0b111111111111=0xfff=4095)
    private final long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);


    /**
     * 每一部分向左的位移
     */
    //机器ID向左移12位
    private final long MACHINE_ID_LEFT = SEQUENCE_BIT;

    //数据中心id向左移17位(12+5)
    private final long DATACENTER_ID_LEFT = SEQUENCE_BIT + MACHINE_BIT;

    //时间截向左移22位(5+5+12)
    private final long TIME_STAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT + DATACENTER_BIT;


    //数据中心ID(0~31)
    private long datacenterId;

    //机器ID(0~31)
    private long machineId;

    //序列号 { 毫秒内序列(0~4095)}
    private long sequence = 0L;

    //上一次时间戳
    private long lastTimestamp = -1L;


    /**
     * 构造函数
     *
     * @param machineId    工作ID (0~31)
     * @param datacenterId 数据中心ID (0~31)
     */
    public SnowflakeIdWorker(long machineId, long datacenterId) {
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", MAX_MACHINE_NUM));
        }
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", MAX_DATACENTER_NUM));
        }
        this.machineId = machineId;
        this.datacenterId = datacenterId;
    }


    /**
     * 获得下一个ID (该方法是线程安全的)
     *
     * @return SnowflakeId
     */
    public synchronized long nextId() {

        long currentTimeStamp = getCurrentTimeStamp();
        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (currentTimeStamp < lastTimestamp) {
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - currentTimeStamp));
        }
        //如果是同一时间生成的,则进行毫秒内序列
        if (currentTimeStamp == lastTimestamp) {
            //相同毫秒内,序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //毫秒内序列溢出
            if (sequence == 0) {
                //阻塞到下一个毫秒,获得新的时间戳
                currentTimeStamp = getNewTimeStamp(lastTimestamp);
            }
        }
        //时间戳改变,毫秒内序列重置
        else {
            sequence = 0L;
        }
        //上次生成ID的时间截
        lastTimestamp = currentTimeStamp;
        //移位并通过或运算拼到一起组成64位的ID
        return ((currentTimeStamp - START_TIME_STAMP) << TIME_STAMP_LEFT) //时间戳部分
                | (datacenterId << DATACENTER_ID_LEFT) //数据中心部分
                | (machineId << MACHINE_ID_LEFT) //机器标识部分
                | sequence;  //序列号部分
    }


    /**
     * 返回以毫秒为单位的当前时间
     */
    protected long getCurrentTimeStamp() {
        return System.currentTimeMillis();
    }

    /**
     * 获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     */
    protected long getNewTimeStamp(long lastTimestamp) {
        long timestamp = getCurrentTimeStamp();
        while (timestamp <= lastTimestamp) {
            timestamp = getCurrentTimeStamp();
        }
        return timestamp;
    }

}

测试

@Test
    public void c() {
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(1, 2);
        for (int i = 0; i < 10; i++) {
            long id = idWorker.nextId();
            System.out.println(id);
        }

    }

测试结果

1040665789449113606
1040665789453307904
1040665789453307905
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot是一个用于创建基于Spring的独立应用程序的框架,而分布式爬虫是指将爬虫任务分配给多个节点进行处理,以提高爬取效率和速度。因此,Spring Boot可以作为分布式爬虫的框架之一来使用。 下面是使用Spring Boot实现分布式爬虫的步骤: 1.使用Spring Boot创建一个Web应用程序,用于接收爬虫任务和返回爬取结果。 2.使用Redis作为分布式任务队列,将待爬取的URL放入队列中。 3.使用多线程或者分布式计算框架(如Hadoop、Spark等)来处理爬虫任务,从Redis队列中获取URL进行爬取,并将爬取结果存储到数据库中。 4.使用定时任务或者消息队列来监控爬虫任务的执行情况,以及处理异常情况。 下面是一个使用Spring Boot和Redis实现分布式爬虫的示例代码: ```java @RestController public class SpiderController { @Autowired private RedisTemplate<String, String> redisTemplate; @RequestMapping("/start") public String startSpider() { // 将待爬取的URL放入Redis队列中 redisTemplate.opsForList().leftPush("spider:start_urls", "http://www.example.com"); return "Spider started!"; } @RequestMapping("/result") public List<String> getSpiderResult() { // 从数据库中获取爬取结果 List<String> result = new ArrayList<>(); // ... return result; } } @Component public class SpiderTask { @Autowired private RedisTemplate<String, String> redisTemplate; @Scheduled(fixedDelay = 1000) public void processSpiderTask() { // 从Redis队列中获取待爬取的URL String url = redisTemplate.opsForList().rightPop("spider:start_urls"); if (url != null) { // 爬取URL并将结果存储到数据库中 // ... } } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值