adoquery 如何判断当前是最后一条记录_如何产生一个全局唯一的流水号(附demo)...

2faff29afcfbe15eb0bcdc8857d12494.png

本文介绍如何使用最简单的方法产生一个全局唯一的流水号,支持集群,性能可靠,并且经过实际的应用

唯一流水号的格式为当前系统时间+当前服务器编号+并发序列号,长度最短可为17位,每毫秒支持生成多个并且支持集群部署

废话不多说,直接上demo,以下demo只需要把连接数据库的工具类Dbutil换成你自己的就可以直接使用了,demo运行成功后需要注意下文中的注意事项

package com.helianxiaowu.utils;

import com.newcapec.dao.SysClusterNodeDao;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Map;
import java.util.Random;

/**
 * @title 唯一流水号生成器
 * @desc
 * @author helianxiaowu
 * @date 2020/1/18 下午 11:48
 */
public class PrimaryGenerator {

    private static final Logger logger = LoggerFactory.getLogger(PrimaryGenerator.class);

    // 单例模式
    private static PrimaryGenerator primaryGenerator = new PrimaryGenerator();
    private PrimaryGenerator() {}
    /**
     * 对外提供单例对象
     * @return
     */
    public static PrimaryGenerator getInstance() {
        return primaryGenerator;
    }

    /**
     * @title 生成一个唯一编号
     * @desc
     * @author W.jw
     * @date 2018/5/9 14:23
     */
    public synchronized String make() {

        // 获取当前系统的时间戳
        Date now = new Date();
        long timestamp = now.getTime();

        /**
         * 判断当前时间戳和上一次产生流水号的时间是否相等
         *   如果不相等,可以使用当前时间戳
         *   如果相等,判断并发数是否达到最大值
         *     如果达到最大值,需要等待下一个毫秒作为新的时间戳
         *     如果没有达到最大值,则可以使用当前序列值
         */
        if (this.lastTime == timestamp) {
            serial = serial + 1 ;
            if (serial == maxSerial) {
                timestamp = this.tilNextMillis(this.lastTime);
            }
        } else {
            serial = 0;
        }

        // 将当前系统时间赋值给lastTime,方便下一次做判断
        this.lastTime = timestamp;

        // 保证序列号是3位,产生的订单号长度一样。也可以不补位,订单号长度会短点
        String.format("%03d", serial);

        // 把当前系统时间格式化为yyMMddHHmmssSSS
        String time = DateUtil.dateToString(now, "yyMMddHHmmssSSS");

        // 唯一流水号 = yyMMddHHmmssSSS+当前服务器唯一编号+序列号
        StringBuilder sb = new StringBuilder(time).append(getMachineId()).append(serial);
        return sb.toString();
    }

    /**
     * 等待下一个毫秒
     * @param currentTime 当前系统时间
     * @return
     */
    private long tilNextMillis(long currentTime) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= currentTime) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

    /**
     * 获取当前服务器的编号,固定长度为2位
     *   从数据库中获取当前服务器的ip在服务器中对应的唯一id
     *   如果id存在,则使用id作为唯一编号
     *   如果id不存在,获取当前服务器的ip并插入数据库得到id
     *
     * @return
     */
    private String getMachineId() {

        if (this.machineId != null) {
            return this.machineId;
        }

        /**
         * 获取当前服务器的ip,如果没有获取到ip则随机返回一个两位数
         */
        String ip = this.getIp();
        if (ip == null) {
            // 随机返回两位数并且加上100,保证随机返回的数值和数据库中的id值不冲突
            return String.format("%02d", new Random().nextInt(100) + 100);
        }

        /**
         * 从数据库中获取当前服务器的ip对应的id值
         *   如果没有则插入一条记录并获取id值
         *   如果有则直接使用
         */
        Map<String, Object> nodeMap = DbUtil.getByIp(ip);
        if (nodeMap == null || nodeMap.size() == 0) {
            try {
                DbUtil.save(ip);
                nodeMap = DbUtil.getByIp(ip);
            } catch (Exception e) {
                logger.error("插入集群节点信息失败:{}" , e);
                return String.format("%02d", new Random().nextInt(100) + 100);
            }
        }
        this.machineId = String.format("%02d", nodeMap.get("id"));
        return this.machineId;
    }

    /**
     * 获取当前服务器ip
     * @return
     */
    private static String getIp() {
        InetAddress address = null;
        try {
            address = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            e.printStackTrace();
            return null;
        }
        return address.getHostAddress();
    }

    /**
     * 最后一次生成订单号的时间,格式yyMMddHHmmssSSS
     */
    private long lastTime = 0;
    /**
     * 当前服务器在集群内的编号
     */
    private String machineId;
    /**
     * 并发序列号
     */
    private long serial = 0L;
    /**
     * 允许并发的最大值
     */
    private final int maxSerial = 999;
}

注意事项

1.本demo需要保证单例运行,否则会产生重复的流水号。保证单例模式的方法有很多种,如:使用单例模式或把demo交由spring管理,demo中使用的是单例模式

2.序列号的最大值不可以无限大,需要考虑服务器的配置。建议使用999,每毫秒产生1000个流水号,一秒就可以产生60*1000个流水号,并发性已经很好了

3.本demo中机器编号限制为两位,最大支持99个服务器,如果你的集群中服务器数量超过99个,则需要把编号的位数扩大。当然,服务器的数量不足10个的话,也可以调小,流水号的长度也会短点


微信搜索赫连小伍关注我的公众号,文章不定期推送

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值