遇到了几个需要生产唯一键的场景
1:仓储系统,库内各类单号需要唯一并带有一定的业务意义
要求:AAAA20151212000000001 AAAA为舱内业务编码,中间8位为对应的年月日,后8位为唯一键保障。
2:电商订单系统,订单号需要唯一
要求:28710080047686 后四位为买家Id(for分库分表),前面数据为唯一键保障位。
3:聊天app系统注册用户Id与评论Id唯一键需求。
要求:etx32s 用户Id唯一值
三个需求中,都要求有一个能保障序列号唯一的键值。分布式系统中,保障键值唯一的方案较多。比如:简单粗暴的DB自增序列,MD5(基本唯一),以及在场景一中,当时用的tair(阿里中间件)做的分布式锁保障自增唯一。本次介绍一个sequence生成中间件产品的设计方案。
整体方案:
ps:
整体方案思路:
应用集群每次获取一个id端,本地缓存。每次使用时,本地获取。当本地id段使用完之后,再进行一次远程调用,更新。
服务端,DB做主从配置,采用MHA异常主从切换。
容量分析:
集群10wqps,500台服务器。单机id段设置1w长度,对服务器端的请求压力不到1qps,服务端无压力,且绝大部分为本地获取,无rpc消耗。
example:
1:客户端初始化
//初始化客户端Util
public static IdGenerator instance(String... password) {
IdGenerator ig = generators.get(key);
if (ig == null) {
synchronized(generators) {
ig = generators.get(key);
if (ig == null) {
ig = new IdRangeIncrementGenerator(tmpPassword, idRangeDispatcher);
generators.putIfAbsent(key, ig);
}
}
}
return ig;
}
//初始化客户端序列化段与本地Queue
public IdRangeIncrementGenerator(final String schema, final String table, final String password,
IdRangeDispatcher dispatcher) throws NoSuchGeneratorException {
super(dispatcher);
checkArgument(isNotBlank(password), "The password is blank");
this.password = password;
//异步获取idRange
backThread = new Thread(new Runnable() {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
IdRange idRange = retryNext(password);//调用服务端、走DB获取Id端
backQueue.put(new IdRangeRound(idRange));
} catch (InterruptedException ie) {
logger.error("interrupted.");
Thread.currentThread().interrupt();
break;
} catch (Throwable e) {
logger.error("backThread retryNext() error", e);
}
}
}
});
backThread.setName("Raptor-IdGenerator-backThread");
backThread.setDaemon(true);
backThread.start();
IdRangeRound idRange = reload(idRangeRound);
String msg = "Can't get idRange by password[" + password + "]";
checkNotNull(idRange, msg);
}
2 客户端获取Id段使用
//直接本地方法获取
@Override
public long next(int num) throws NoMoreIdException {
checkArgument(num > 0, "The num[" + num + "] must be > 0 ");
long[] ids = new long[num];
for (int i = 0; i < num; i++) {
long id;
IdRangeRound ir = idRangeRound;
while ((id = ir.next(1)) == -1) {
//当本地id段不够使用时,重新远程获取更新id段
ir = reload(ir);
}
ids[i] = id;
}
return ids;
}
//阻塞线程更新,重新获取Id端
private synchronized IdRangeRound reload(IdRangeRound ir) {
if (ir != idRangeRound) {
return idRangeRound;
}
try {
return idRangeRound = backQueue.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
3 服务端DB容灾方案:MHA