Edits Sequence Number Generator for out-of-box, no more code to write, you deserve it

Keywords: Java Redis Spring github SpringBoot

First look at the overall effect

 


One of the goals of software development is to "fool" simple things.Look at the following picture:

 


On the left are three generators configured in application.yml, and on the right they can be injected directly into the code, note that you don't have to write any code.It's sour.

Here's how it works:

Above is the first sequence number generated by three generators.Ouch, that's good.


Learn to analyze slowly

 


Serial numbers are familiar to everyone, except for an initial value, a step, and sometimes a maximum value.This is just the most basic information, and you can add others as needed.

It is easy to abstract an interface, such as the following code:

/** * Sequence Number Generator * @author lixinjie * @since 2019-04-04 */public interface SnGenerator {   /**Name, used as appropriate*/  String getName();  /**bean name registered in container*/  String getBeanName();  /**initial value*/  long getInitNum();  /**step*/  long getStep();  /**Get the next serial number*/  long nextNum();  /**Maximum*/  long getMaxNum();}

 

All that remains is the following three questions:

 

  • Once the interface is present, it is naturally a Redis-based implementation, which is the first problem encountered.

  • You also need to dynamically register the bean definitions with the container based on the configuration, which is the second issue.

  • Naturally, you need to read out this configuration information from the configuration file for use in the previous step, which is the third question.



Learn to Realize Again

 


The interface is implemented primarily with Redis's INCRBY command, which is an atomic command.Even if you have multiple nodes, each node has multiple threads calling at the same time, it is OK.

Also, if the key does not exist, it will be set to 0 on the first call.The benefit of this is that the program does not need to consider whether the key exists or not, but simply calls self-incrementing.

The only upset thing is that the key is only set to 0. If the initial value doesn't start at 0, it's a little upset. The easiest way to do this is to add the initial value as an offset in the program.

Another option, of course, is to actively set the initial value of the key.Because of concurrency, it is natural to use the SETNX command.It's OK, but there are still some people who feel psychologically unsafe.That's when the command is executed during the application startup phase, at which point there must be no calls.See the following source code:

 

/** * Redis-based implementation * @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; }
}


To dynamically register a bean definition, the Spring framework provides a dedicated interface, 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(BeanDefinitionRegistry registry) throws BeansException;
}

It is a bean factory postprocessor, and the framework automatically detects and applies this type of implementation class when it is applied at startup.

To use the content in the configuration file, the Spring framework provides a specialized interface, EnvironmentAware:

public interface EnvironmentAware extends Aware {
/** * Set the {@code Environment} that this component runs in. */ void setEnvironment(Environment environment);
}

It is an Aware interface, and the framework automatically detects this type of interface when the application starts and set s you in what you need.

There's a bit more of this source, so let's just look at two parts.The following reads the configuration information from the configuration file:

  private SnGeneInfo[] parseSnGeneInfos() {    String prefix = "sngenerator";    String[] generators = environment.getProperty(prefix + ".generators", String[].class);    SnGeneInfo[] infos = new SnGeneInfo[generators.length];    for (int i = 0; i < generators.length; i++) {      infos[i] = buildSnGeneInfo(prefix, generators[i]);    }    return infos;  }   private SnGeneInfo buildSnGeneInfo(String prefix, String generator) {    return new SnGeneInfo(        prefix + ":" + generator,        environment.getProperty(prefix + "." + generator + ".bean-name"),        environment.getProperty(prefix + "." + generator + ".init-num", long.class),        environment.getProperty(prefix + "." + generator + ".step", long.class),        environment.getProperty(prefix + "." + generator + ".max-num", long.class)      );  }


The following are the registered bean s definitions:

  @Override  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {    SnGeneInfo[] geneInfos = parseSnGeneInfos();    System.out.println(logInfo(geneInfos));    for (SnGeneInfo geneInfo : geneInfos) {      BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(RedisSnGenerator.class);      bdb.addConstructorArgValue(geneInfo.getKeyName());      bdb.addConstructorArgValue(geneInfo.getBeanName());      bdb.addConstructorArgValue(geneInfo.getInitNum());      bdb.addConstructorArgValue(geneInfo.getStep());      bdb.addConstructorArgValue(geneInfo.getMaxNum());      registry.registerBeanDefinition(geneInfo.getBeanName(), bdb.getBeanDefinition());    }  }

 

 

Need a lot of sequences?

 


The above method is not preferable if you need a very large number of sequence generators.The Grouped Sequence Generator can be used, and there can be enough sequences within each group without interacting with each other.

Here's how to use it, or it's as simple as that:

 

 

Here's how it works:

 

Each set of internal key s, such as f1/f2/f3 above, can be passed in directly on demand without configuration.

The method to get the next serial number at this point takes an argument, which is used to pass the key.It is based on Redis Hash.

/** * Group Sequence Number Generator * @author lixinjie * @since 2019-04-04 */public interface GroupSnGenerator {   /**Name, used as appropriate*/  String getName();  /**bean name registered in container*/  String getBeanName();  /**initial value*/  long getInitNum();  /**step*/  long getStep();  /**Get the next serial number*/  long nextNum(String identifier);  /**Maximum*/  long getMaxNum();}

You will also encounter the problem of setting the initial value for hashkey, which is certainly different from the above, please read the source code.

/** * Redis-based implementation * @author lixinjie * @since 2019-04-04 */public class RedisGroupSnGenerator implements GroupSnGenerator {
private Map<String, Boolean> existHashKeys = new ConcurrentHashMap<>(); @Autowired private StringRedisTemplate stringRedisTemplate; private String name; private String beanName; private long initNum; private long step; private long maxNum; public RedisGroupSnGenerator(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.opsForHash().putIfAbsent(getName(), "flag", 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(String identifier) { if (!existHashKeys.containsKey(identifier)) { stringRedisTemplate.opsForHash().putIfAbsent(getName(), identifier, String.valueOf(getInitNum())); existHashKeys.putIfAbsent(identifier, Boolean.TRUE); } return stringRedisTemplate.opsForHash().increment(getName(), identifier, getStep()); }
@Override public long getMaxNum() { return maxNum; }
}

Source address: https://github.com/coding-new-talk/cnt-springboot.git

 

 

 

(END)

 

New programming saying, this number has been working for 10 years

Architect maintenance, insight into the nature of technology,

Vivid and funny, welcome to your attention!

Posted by mryno on Sun, 19 May 2019 12:18:40 -0700