本文紧接着上文高并发系统-分布式唯一ID生成(二)-号段模式及应用
2. 生成方案
2.5 Redis ID
Redis可以作为一个可靠的存储和生成分布式ID的方案之一。 Redis分布式ID实现主要是通过提供像 INCR
和 INCRBY
这样的自增原子命令,由于Redis单线程的特点,可以保证ID的唯一性和有序性。
1. 优点
性能不错、每秒10万并发量,性能是优于数据库。
生成的 ID 是有序递增的。
2. 缺点
redis 宕机后不可用,RDB重启数据丢失会重复ID。
自增,数据量易暴露。
引入新的组件,增加系统复杂度,并且需要占用网络资源,性能要比本地生成慢
3. 应用场景
适用于TPS要求不高场景或者可通过活动区分开,比如发放优惠券券码,可以通过批次作为前缀,每个批次使用不同REDIS自增序列
尤其比较较高并发生成唯一ID且ID长度序列一致场景,比如券码,京东E卡的卡号之类的
4. 实践
场景:生成一个10位长度券码,要求是唯一的,并且长度都为10位。
思路:
通过REDIS自增ID实现编码,序列值转换成35进制,在后面加一位Z,剩余位数用随机数填充保证固定长度唯一编码 比如序列为100
,生成的最终编码为2TZB47AIPX
通过上述方式,保证生成值唯一,且能够达到35^10个
代码实现:
核心逻辑
java
@Service
@Slf4j
public class RedisIdSeq {
private final static char[] CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y'};
private final int SCALE = 35;
private final static char SPLIT = 'Z';
@Autowired
private RedisService redisService;
public List<String> createCode(int maxNum, int size, String priFix, String key, int maxLength) {
int curIndex = redisService.incrAndGetIntValue(key, size);
if (curIndex > maxNum) {
log.error("createCode exceed max num ={}", maxNum);
return Collections.emptyList();
}
List<String> resultList = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
resultList.add(generateCode(priFix, curIndex--, maxLength));
}
return resultList;
}
private String generateCode(String preFix, int num, int maxLength) {
int temp = num;
int index = 0;
StringBuilder stringBuilder = new StringBuilder(maxLength);
stringBuilder.append(preFix);
while (temp > 0) {
index = num % SCALE;
stringBuilder.append(CHARS[index - 1]);
temp = temp / SCALE;
}
if (stringBuilder.length() >= maxLength) {
return stringBuilder.toString();
}
stringBuilder.append(SPLIT);
int randomSize = maxLength - stringBuilder.length();
for (int i = 0; i < randomSize; i++) {
stringBuilder.append(CHARS[SecureRandomUtil.getInstance().nextInt(SCALE)]);
}
return stringBuilder.toString();
}
}
使用redission取值
java
public int incrAndGetIntValue(String key, Integer addNum) {
RAtomicLong rAtomicLong = redissonClient.getAtomicLong(key);
return (int) rAtomicLong.addAndGet(addNum);
}
最终的test用例如下
java
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@Slf4j
public class RedisIdSeqTest {
@Resource
private RedisIdSeq redisIdSeq;
@Test
public void testCreateCode() {
List<String> prefixList = redisIdSeq.createCode(10000, 1, "", "ABC", 10);
log.info("prefixList={}", prefixList);
List<String> codeList = redisIdSeq.createCode(10000, 10, prefixList.get(0), prefixList.get(0), 20);
log.info("codeList={}", codeList);
}
}
最终生成结果如下:
xml
2023-12-24 21:47:28.978|INFO |test|1|127.0.0.1|8173c001efdc4303bab4aaf23464a2b8|prefixList=[1Z6PEOSSA3]|com.toby.dynamic.data.source.redis.id.RedisIdSeqTest
2023-12-24 21:47:31.552|INFO |test|1|127.0.0.1|8173c001efdc4303bab4aaf23464a2b8|codeList=[1Z6PEOSSA39Z1VC68J5P, 1Z6PEOSSA38Z468YSILR, 1Z6PEOSSA37ZAJQTERNX, 1Z6PEOSSA36Z64KRUV6A, 1Z6PEOSSA35Z9K7F06K2, 1Z6PEOSSA34ZULBTKOJ4, 1Z6PEOSSA33Z733FXM2R, 1Z6PEOSSA32ZD52WWEOC, 1Z6PEOSSA31ZAMBGCBW2, 1Z6PEOSSA30Z7BTE9SN9]|com.toby.dynamic.data.source.redis.id.RedisIdSeqTest
参考
分布式 ID 详解