开箱即用(out-of-box)的Redis序列号生成器,不用再写任何代码,你值得拥有

本文介绍了一个基于Redis的序列号生成器,无需编写任何代码即可使用。通过配置文件设定生成器,利用Redis的原子命令INCRBY确保线程安全。文章详细讲解了实现过程,包括从配置文件读取信息、动态注册bean和初始化key的方法。对于大量序列需求,文章提出了分组序列生成器的解决方案,利用Redis的Hash功能。提供了源码地址供读者参考。
摘要由CSDN通过智能技术生成

先看整体效果

 


把简单的东西“傻瓜化”是软件开发追求的目标之一。请看下图:

 


左边是在 application.yml 里配置了3个生成器,右边可以直接注入到代码中使用,注意,不用写任何代码。这酸爽。

下面请看效果:

上面是3个生成器生成的第一个序号。哎吆,还不错哦。

 

慢慢学会分析

 


序列号大家都非常熟悉,无非就是一个初始值、步长,有时还有最大值。这只是最基本的信息,还可以按需添加其他的。

很容易抽象出一个接口,如下代码:

/** * 序列号生成器 * @author lixinjie * @since 2019-04-04 */public interface SnGenerator {
    /**名称,根据实际情况使用*/  String getName();  /**注册到容器中的bean名称*/  String getBeanName();  /**初始值*/  long getInitNum();  /**步长*/  long getStep();  /**获取下一个序列号*/  long nextNum();  /**最大值*/  long getMaxNum();}

 

剩下的就是下面这三个问题了:

 

  • 接口有了之后,自然就是基于Redis的实现了,这是遇到第一个问题。

  • 还需要根据配置动态向容器中注册bean定义,这是第二个问题。

  • 自然需要从配置文件中读出这些配置信息,供上一步使用,这是第三个问题。


 

再来学会实现

 


接口实现时主要用到Redis的INCRBY命令,这是个原子命令。即使你有多个节点,每个节点有多个线程同时来调用,也是OK的。

而且key不存在时,首次调用时还会将key设置为0。这样带来的好处就是,程序不用考虑key是否存在,直接调用自增就可以了。

唯一不爽的就是key只会被设置为0,如果初始值不是从0开始的,就真有些不爽,最简单的办法就是在程序中把初始值作为偏移量叠加上去。

当然还有另一个办法,就是主动设置key的初始值。因为存在并发,自然要使用SETNX命令。这样已经完全OK,但还是会有人在心理上觉得不安全。那就在应用启动阶段执行该命令,此时肯定不会有调用的。请看下面源码:

 

/** * 基于Redis的实现 * @author lixinjie * @since 2019-04-04 */public class RedisSnGenerator implements SnGenerator {
  
  @Autowired  private StringRedisTemplate stringRedisTemplate;  private String name;  private String beanName;  private long initNum;  private long step;  private long maxNum;  public RedisSnGenerator(String name, String beanName, long initNum, long step, long maxNum) {
      this.name = name;    this.beanName = beanName;    this.initNum = initNum;    this.step = step;    this.maxNum = maxNum;  }  @PostConstruct  public void init() {
      if (!stringRedisTemplate.hasKey(getName())) {
        stringRedisTemplate.opsForValue().setIfAbsent(getName(), String.valueOf(getInitNum()));    }  }
  @Override  public String getName() {
      return name;  }
  public String getBeanName() {
      return beanName;  }
  @Override  public long getInitNum() {
      return initNum;  }
  @Override  public long getStep() {
      return step;  }
  @Override  public long nextNum() {
      return stringRedisTemplate.opsForValue().increment(getName(), getStep());  }
  @Override  public long getMaxNum() {
      return maxNum;  }
}


要动态注册bean定义,Spring框架提供了专用接口,BeanDefinitionRegistryPostProcessor:

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
  
  /**   * @param registry the bean definition registry used by the application context   * @throws org.springframework.beans.BeansException in case of errors   */  void postProcessBeanDefinitionRegistry(BeanDefinitionRe
public synchronized String nextId() { long timestamp = timeGen(); //获取当前毫秒数 //如果服务器时间有问题(时钟后退) 报错。 if (timestamp < lastTimestamp) { throw new RuntimeException(String.format( "Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } //如果上次生成时间和当前时间相同,在同一毫秒内 if (lastTimestamp == timestamp) { //sequence自增,因为sequence只有12bit,所以和sequenceMask相与一下,去掉高位 sequence = (sequence + 1) & sequenceMask; //判断是否溢出,也就是每毫秒内超过4095,当为4096时,与sequenceMask相与,sequence就等于0 if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); //自旋等待到下一毫秒 } } else { sequence = 0L; //如果和上次生成时间不同,重置sequence,就是下一毫秒开始,sequence计数重新从0开始累加 } lastTimestamp = timestamp; long suffix = (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence; String datePrefix = DateFormatUtils.format(timestamp, "yyyyMMddHHMMssSSS"); return datePrefix + suffix; } protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } protected long timeGen() { return System.currentTimeMillis(); } private byte getLastIP(){ byte lastip = 0; try{ InetAddress ip = InetAddress.getLocalHost(); byte[] ipByte = ip.getAddress(); lastip = ipByte[ipByte.length - 1]; } catch (UnknownHostException e) { e.printStackTrace(); } return lastip; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值