Génération d'identifiants distribués via Redis

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.

Étiquettes: Redis identifiant distribué séquence Cache Spring

Publié le 14 juin à 02h01