阅读了这篇文章而后总结了下我们系统中全局唯一ID的生成方法。
http://www.cnblogs.com/heyuquan/archive/2013/08/16/global-guid-identity-maxId.html
主键ID采用UUID,只作为唯一性标识。
部分业务表需要有业务No的,我们采用以下方法生成唯一ID:
从redis中用lpop指令取指定key值的数据。(lpop:移除并返回列表的头元素)
如果将指定key值的数据取完了,会触发初始化。
初次初始化:
1)用for update锁表,存储最小值1和最大值50到数据库中。
2)将这50个数字放入redis中。
下次初始化:
1)用for update锁表,存储最小值51和最大值100到数据库中。
2)将这50个数字放入redis中。
将返回的值组装成全局唯一性ID。
数据库有脚本每天清理表数据,避免最大值与最小值过大。
这里的指定key是根据业务编号+日期组成的,确保每天不同。
附代码:
// 设置no
String applyNo = cityCodeDto.getCityCode() + DateUtils.getCurrentTimeNum();
applyReceptionDto.setApplyLoanNo(generatorService.getNextVal(applyNo, 5).getData());
@Override
public ServiceResult<String> getNextVal(String sequenceName, Integer bit) {
Long nextValue = getNextValFromRedis(sequenceName);
if (nextValue != null) {
return ServiceResult.newSuccess(formatBitValue(sequenceName, nextValue, bit));
}
MyphLogger.debug("getNextVal start init sequenceName ,sequenceName=", sequenceName);
initSequence(sequenceName);
MyphLogger.debug("getNextVal end init sequenceName,sequenceName=" + sequenceName);
//重新尝试从redis获取
Long id = getNextValFromRedis(sequenceName);
if (id == null) {
return ServiceResult.newFailure("id生成器非预期异常");
}
return ServiceResult.newSuccess(formatBitValue(sequenceName, id, bit));
}
private Long getNextValFromRedis(String sequenceName) {
Long nextVal = CacheService.ListKey.lpop(SequenceHelper.getKey(sequenceName), Long.class);
MyphLogger.debug(sequenceName + "value" + nextVal);
if (nextVal == null) {
return null;
}
try {
return nextVal;
} catch (Exception e) {
throw new RuntimeException("seqkey放入非法字符,key=" + SequenceHelper.getKey(sequenceName));
}
}
/**
* 执行redis数据队列的预生成流程,生成批量一批数据到redis队列供服务使用
*
* @param sequenceName
*/
private void initSequence(String sequenceName) {
String minValue = SequenceHelper.getSeqMinValue(sequenceName);
generatorService.addSeqToCacheByName(sequenceName, minValue);
}
@Service
public class GeneratorServiceImpl implements GeneratorService {
private static final Long startNo = 1l;
private static final Integer initialCapacity = 50;
@Resource
private MyphSequenceDao myphSequenceDao;
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void addSeqToCacheByName(String seqName, String minValue) {
// 使用for update 锁住记录 防止并发
MyphSequence seq = myphSequenceDao.queryAndLockSeq(seqName);
if (null == seq) {
MyphSequence sequence = new MyphSequence();
sequence.setMinValue(startNo);
sequence.setEnable(true);
sequence.setInitialCapacity(initialCapacity);
sequence.setSequenceName(seqName);
myphSequenceDao.add(sequence);
seq = myphSequenceDao.queryAndLockSeq(seqName);
}
String nowMinValue = SequenceHelper.getSeqMinValue(seqName);
MyphLogger.debug("IdGeneratorService1 " + minValue + " " + nowMinValue + "");
// 如果nowMinValue为null,可以执行初始化
// 如果 nowMinValue 和 minValue 不等,说明之前有人改动过这个值,此次不需要操作了
if (StringUtils.isNotEmpty(nowMinValue) && !StringUtils.equals(nowMinValue, minValue)) {
MyphLogger.debug("IdGeneratorService :normal return :nowMinValue=" + nowMinValue + ",preminValue=" + minValue + ",seqname=" + seqName);
return;
}
// 查询结果入redis 全部结果一次插入
long currentMinValue = seq.getMinValue();
long nextMinValue = currentMinValue + seq.getInitialCapacity() * 1;
long maxValue = (seq.getMaxValue() == null) ? Long.MAX_VALUE : seq
.getMaxValue();
nextMinValue = nextMinValue > maxValue ? maxValue : nextMinValue;
MyphLogger.debug("IdGeneratorService addSeqToCache start1--:seq[name=" + seq.getSequenceName() + ",min=" + seq.getMinValue() + ",max=" + seq.getMaxValue() + ".nextMin=" + nextMinValue + ",updateTime=" + seq.getUpdateTime());
seq.setMinValue(nextMinValue);
myphSequenceDao.updateSeqInfo(seq.getId(), seq.getMinValue());
MyphLogger.debug("IdGeneratorService addSeqToCache start2--: update db minavlue:nextMinValue=" + nextMinValue + ",currentMinValue=" + currentMinValue + ",seqname=" + seqName);
//redis set min value
SequenceHelper.setSeqMinValue(seqName, String.valueOf(nextMinValue));
List<Long> values = new ArrayList<Long>();
for (long i = currentMinValue; i < nextMinValue; i += 1) {
values.add(i);
}
CacheService.ListKey.rpush(SequenceHelper.getKey(seq.getSequenceName()), values);
}