In distributed system design, global unique ID is a basic and critical component. With the expansion of business scale and the evolution of system architecture to microservices, traditional stand-alone self-increasing IDs can no longer meet the needs. Highly concurrency and high availability distributed ID generation solutions have become a necessary condition for building a reliable distributed system.
Redis has high performance, atomic operation and simple and easy-to-use features, so we can realize the generation of global unique IDs based on Redis.
The core requirements of distributed ID
An excellent distributed ID generation solution should meet the following requirements
- Global uniqueness: Ensure that the ID is not duplicated in the entire distributed system
- high performance: Can quickly generate IDs and support high concurrency scenarios
- Highly available: Avoid single point of failure and ensure continuous service availability
- Trends are increasing: The generated ID is roughly increasing, which is convenient for database indexing and sharding
- Security (optional): It does not contain sensitive information and is not easy to be speculated and forged
1. Simple self-increment ID based on INCR command
principle
This is the most direct way to implement Redis distributed ID, using RedisINCR
Command atomically increments a counter to ensure unique ID in a distributed environment.
Code implementation
import ; import ; @Component public class RedisSimpleIdGenerator { private final RedisTemplate<String, String> redisTemplate; private final String ID_KEY; public RedisSimpleIdGenerator(RedisTemplate<String, String> redisTemplate) { = redisTemplate; this.ID_KEY = "distributed:id:generator"; } /** * Generate the next ID * @return Unique ID */ public long nextId() { Long id = ().increment(ID_KEY); if (id == null) { throw new RuntimeException("Failed to generate id"); } return id; } /** * Generate ID for the specified service * @param bizTag Business tags * @return Unique ID */ public long nextId(String bizTag) { String key = ID_KEY + ":" + bizTag; Long id = ().increment(key); if (id == null) { throw new RuntimeException("Failed to generate id for " + bizTag); } return id; } /** * Get the current ID value but not increment * @param bizTag Business tags * @return Current ID value */ public long currentId(String bizTag) { String key = ID_KEY + ":" + bizTag; String value = ().get(key); return value != null ? (value) : 0; } }
Pros and cons
advantage
- The implementation is extremely simple, only one Redis operation is required
- ID is strictly incremented, suitable as the database primary key
- Support multi-service ID isolation
shortcoming
- Redis single point of failure will cause ID generation service to be unavailable
- Master-slave switching may cause ID duplication
- Cannot include business meaning
Applicable scenarios
- Auto-increment primary key generation for small and medium-sized systems
- Business scenarios that require ID continuity
- Applications for single data center deployment
2. Bulk ID generation based on Lua scripts
principle
A batch of IDs can be obtained at one time through Lua scripts to reduce the number of network round trips. Clients can allocate IDs in memory in sequence, significantly improving performance.
Code implementation
import ; import ; import ; import ; import ; import ; import ; import ; @Component public class RedisBatchIdGenerator { private final RedisTemplate<String, String> redisTemplate; private final String ID_KEY = "distributed:batch:id"; private final DefaultRedisScript<Long> batchIncrScript; // Batch size private final int BATCH_SIZE = 1000; // Local counter and lock private AtomicLong currentId = new AtomicLong(0); private AtomicLong endId = new AtomicLong(0); private final Lock lock = new ReentrantLock(); public RedisBatchIdGenerator(RedisTemplate<String, String> redisTemplate) { = redisTemplate; // Create Lua script String scriptText = "local key = KEYS[1] " + "local step = tonumber(ARGV[1]) " + "local currentValue = ('incrby', key, step) " + "return currentValue"; = new DefaultRedisScript<>(); (scriptText); (); } /** * Get the next ID */ public long nextId() { // If the current ID exceeds the allocation range, re-acquire a batch if (() >= ()) { (); try { // Double check to prevent repeated acquisition of multi-threads if (() >= ()) { // Execute Lua script to get a batch of IDs Long newEndId = ( batchIncrScript, (ID_KEY), (BATCH_SIZE) ); if (newEndId == null) { throw new RuntimeException("Failed to generate batch ids"); } // Set a new ID range (newEndId); (newEndId - BATCH_SIZE); } } finally { (); } } // Assign the next ID return (); } /** * Generate ID for the specified service */ public long nextId(String bizTag) { // In actual projects, independent counters and ranges should be maintained for each business tag // This is simplified, using only different Redis keys String key = ID_KEY + ":" + bizTag; Long newEndId = ( batchIncrScript, (key), (1) ); return newEndId != null ? newEndId : -1; } }
Pros and cons
advantage
- Significantly reduce the number of Redis network requests
- Client caches ID segments, greatly improving performance
- Reduce Redis Server Pressure
- Support burst traffic processing
shortcoming
- Implementation complexity increases
- Service restart may lead to waste of ID segments
Applicable scenarios
- High concurrency system, scenarios that require extremely high ID generation performance
- Businesses with less stringent requirements for ID continuity
- Scenarios where a small number of ID waste can tolerate
3. Redis-based segmented ID allocation (segment mode)
principle
The number segment mode is an optimized batch ID generation solution, which reduces competition among services by pre-allocating number segments (ID ranges), and at the same time introduces a dual-buffer mechanism to improve availability.
Code implementation
import ; import ; import ; import ; import ; import ; import ; import ; import ; @Component public class RedisSegmentIdGenerator { private final RedisTemplate<String, String> redisTemplate; private final String SEGMENT_KEY = "distributed:segment:id"; private final DefaultRedisScript<Long> segmentScript; // Size private final int SEGMENT_STEP = 1000; // Loading factor, when the current number segment is used to load the next number segment asynchronously when using this percentage private final double LOAD_FACTOR = 0.7; // Map that stores business segment information private final Map<String, SegmentBuffer> businessSegmentMap = new ConcurrentHashMap<>(); public RedisSegmentIdGenerator(RedisTemplate<String, String> redisTemplate) { = redisTemplate; // Create Lua script String scriptText = "local key = KEYS[1] " + "local step = tonumber(ARGV[1]) " + "local value = ('incrby', key, step) " + "return value"; = new DefaultRedisScript<>(); (scriptText); (); } /** * Get the next ID * @param bizTag Business tags * @return Unique ID */ public long nextId(String bizTag) { // Get or create a number segment buffer SegmentBuffer buffer = ( bizTag, k -> new SegmentBuffer(bizTag)); return (); } /** * Internal number segment buffer class, implementing dual-buffer mechanism */ private class SegmentBuffer { private String bizTag; private Segment[] segments = new Segment[2]; // Double Buffers private volatile int currentPos = 0; // The current segment location private Lock lock = new ReentrantLock(); private volatile boolean isLoadingNext = false; // Is the next number segment being loaded asynchronously public SegmentBuffer(String bizTag) { = bizTag; segments[0] = new Segment(0, 0); segments[1] = new Segment(0, 0); } /** * Get the next ID */ public long nextId() { // Get the current number segment Segment segment = segments[currentPos]; // If the current segment is empty or used up, switch to another segment if (!() || () > ()) { (); try { // Double check the current segment status segment = segments[currentPos]; if (!() || () > ()) { // Switch to another segment currentPos = (currentPos + 1) % 2; segment = segments[currentPos]; // If another segment is not initialized or used up, then load synchronously if (!() || () > ()) { loadSegmentFromRedis(segment); } } } finally { (); } } // Check whether the next segment needs to be loaded asynchronously long value = (); if (value > () + (() - ()) * LOAD_FACTOR && !isLoadingNext) { isLoadingNext = true; // Asynchronously load the next segment new Thread(() -> { Segment nextSegment = segments[(currentPos + 1) % 2]; loadSegmentFromRedis(nextSegment); isLoadingNext = false; }).start(); } return value; } /** * Loading segment from Redis */ private void loadSegmentFromRedis(Segment segment) { String key = SEGMENT_KEY + ":" + bizTag; // Execute Lua script to get the maximum number segment value Long max = ( segmentScript, (key), (SEGMENT_STEP) ); if (max == null) { throw new RuntimeException("Failed to load segment from Redis"); } // Set number range long min = max - SEGMENT_STEP + 1; (max); (min); (min - 1); // Set to min-1, the first incrementAndGet returns min (true); } } /** * Internal number segment class, storing range information of number segments */ private class Segment { private long min; // Minimum value private long max; // Maximum value private AtomicLong value; // Current value private volatile boolean initialized; // Is it initialized public Segment(long min, long max) { = min; = max; = new AtomicLong(min); = false; } public long getValue() { return (); } public void setValue(long value) { (value); } public long incrementAndGet() { return (); } public long getMin() { return min; } public void setMin(long min) { = min; } public long getMax() { return max; } public void setMax(long max) { = max; } public boolean isInitialized() { return initialized; } public void setInitialized(boolean initialized) { = initialized; } } }
Pros and cons
advantage
- Dual Buffer design, high availability
- Asynchronously load the next number segment, with higher performance
- Redis access frequency significantly
- Even if Redis is temporarily unavailable, an ID of a period of time can still be assigned.
shortcoming
- Complex implementation and large code volume
- When deploying multiple instances, the number segments obtained by each instance are not continuous.
- The ID in the number segment may be wasted when restarting the service
- Need to maintain state in memory
Applicable scenarios
- Services with high requirements for ID generation availability
- Distributed systems requiring high-performance and multi-server deployment
4. Performance comparison and selection suggestions
Strategy | performance | Availability | ID length | Implement complexity | Monotonously increasing |
---|---|---|---|---|---|
INCR Command | ★★★☆☆ | ★★☆☆☆ | Increment integer | Low | Strictly increment |
Lua batch generation | ★★★★★ | ★★★☆☆ | Increment integer | middle | In-batch increment |
Segmented ID | ★★★★★ | ★★★★☆ | Increment integer | high | In-segment increment |
5. Practical optimization skills
1. Redis high availability configuration
// Configure Redis Sentinel mode to improve usability@Bean public RedisConnectionFactory redisConnectionFactory() { RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration() .master("mymaster") .sentinel("127.0.0.1", 26379) .sentinel("127.0.0.1", 26380) .sentinel("127.0.0.1", 26381); return new LettuceConnectionFactory(sentinelConfig); }
2. ID warm-up strategy
// Preheat ID generator when system starts@PostConstruct public void preWarmIdGenerator() { // Obtain a batch of IDs in advance to ensure that the system is ready to use immediately after starting for (int i = 0; i < 10; i++) { try { ("order"); ("user"); ("payment"); } catch (Exception e) { ("Failed to pre-warm ID generator", e); } } }
3. Downgrade strategy
// Downgrade policy when Redis is unavailablepublic long nextIdWithFallback(String bizTag) { try { return (bizTag); } catch (Exception e) { ("Failed to get ID from Redis, using local fallback", e); // Use local UUID or other alternatives return (().getMostSignificantBits()); } }
6. Conclusion
When choosing a suitable distributed ID generation strategy, it is necessary to comprehensively consider the system scale, performance requirements, reliability requirements and implementation complexity. No matter which solution you choose, you should pay attention to high availability design, add monitoring and early warning mechanisms to ensure the stable operation of ID generation services.
In practice, these solutions can be combined and optimized based on business needs, such as selecting different strategies for different services, or embeding business identifiers in IDs, etc., to create a distributed ID generation solution that is more suitable for your own system.
This is the article about three strategies for generating distributed global unique IDs based on Redis. For more related content related to Redis generation distributed global unique IDs, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!