La génération d'identifiants uniques dans un environnement distribué nécessite une approche spécifique. Pour répondre à un besoin de création d'identifiants de commande selon une règle basée sur la date (année, mois, jour) combinée à une chaîne aléatoire et une séquence incrémentale, une soluiton basée sur Redis peut être mise en œuvre.
@Slf4j
@Service
public class DistributedIdService {
@Autowired
private RedisTemplate redisTemplate;
private final Object monitor = new Object();
private static final Cache<String, Optional<SequenceRange>> sequenceCache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.DAYS)
.build();
@SneakyThrows
public String generateIdentifier() {
String dateKey = DateUtils.formatCurrentDateShort();
StringBuilder identifier = new StringBuilder(dateKey);
synchronized (monitor) {
Optional<SequenceRange> range = getCachedSequence(dateKey, 10000, DateUtils.getTomorrow());
if (!range.isPresent()) {
for (int i = 0; i < 10; i++) {
identifier.append((int)(Math.random() * 10));
}
return identifier.toString();
}
SequenceRange seqRange = range.get();
if (seqRange.getCurrent().get() >= seqRange.getUpperBound().get()) {
log.info("Cache invalidation triggered - bounds: {}-{}", seqRange.getCurrent(), seqRange.getUpperBound());
sequenceCache.invalidate(dateKey);
return generateIdentifier();
}
seqRange.getCurrent().incrementAndGet();
sequenceCache.put(dateKey, Optional.of(seqRange));
String formattedSeq = String.format("%07d", seqRange.getCurrent().get());
for (int i = 0; i < 3; i++) {
identifier.append((int)(Math.random() * 10));
}
identifier.append(formattedSeq);
}
log.info("Generated identifier: {}", identifier);
return identifier.toString();
}
private Optional<SequenceRange> getCachedSequence(String key, int batchSize, Date expiration) {
try {
return sequenceCache.get(key, () -> {
RedisAtomicLong redisCounter = new RedisAtomicLong("seq:" + key, redisTemplate.getConnectionFactory());
redisCounter.expireAt(expiration);
long endVal = redisCounter.addAndGet(batchSize);
SequenceRange newRange = new SequenceRange(
new AtomicLong(endVal - batchSize),
new AtomicLong(endVal)
);
return Optional.of(newRange);
});
} catch (Exception e) {
log.error("Error retrieving sequence for key: {}", key, e);
return Optional.empty();
}
}
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SequenceRange implements Serializable {
private AtomicLong current;
private AtomicLong upperBound;
}
public class DateUtils {
public static String formatCurrentDateShort() {
return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
}
public static Date getTomorrow() {
return Date.from(LocalDate.now().plusDays(1)
.atStartOfDay(ZoneId.systemDefault()).toInstant());
}
}
La classe principale DistributedIdService utilise un cache local pour stocker des plages de séquences obtenues à partir de Redis. Le mécanisme fonctinone par lots de 10 000 séquences, réduisant ainsi les appels à Redis. La génération finale combine la date courante, une chaîne aléatoire de 3 chiffres et le numéro de séquence sur 7 chiffres, produisant des identifiants uniques adaptés aux systèmes distribués.